This package contains a collection of utilities around Google's Protocolbuffer. The functions offered in this packages are widely used across Google's C++ code base and have saved tens of thousands of engineering hours.
-
rule:
@com_helly25_proto//mbo/proto:parse_text_proto_cc
-
namespace:
mbo::proto
-
ParseTextProtoOrDie
(text_proto
[,std::source_location
])text_proto
is a text proto best identified as a raw-string with markerpb
orproto
.- If
text_proto
cannot be parsed into the receiving proto type, then the function will fail. - Prefer this function over template function
ParseTextOrDie
.
-
ParseTextOrDie
<Proto
>(text_proto
[,std::source_location
])text_proto
is a text proto best identified as a raw-string with markerpb
orproto
.Proto
is the type to produce.- If
text_proto
cannot be parsed as aProto
, then the function will fail. - Use this only if the
Proto
type cannot be inferred byParserTextProtoOrDie
.
-
ParseTest
<Proto
>(text_proto
[,std::source_location
]) ->absl::StatusOr
<Proto
>text_proto
is a text proto best identified as a raw-string with markerpb
orproto
.Proto
is the type to produce.- If
text_proto
cannot be parse as aProto
, then the function returns a non-absl::OkStatus
. - Use this function in cases where errors are expected.
BUILD.bazel:
cc_test(
name = "test",
srcs = ["test.cc"],
deps = ["@com_helly25_proto//mbo/proto:parse_text_proto",]
)
Source test.cc:
#include "mbo/proto/parse_text_proto.h"
using ::mbo::proto::ParseTextProtoOrDie;
TEST(Foo, Test) {
MyProto msg = ParseTextProtoOrDie(R"pb(
field: "name"
what: "Just dump the plain text-proto as C++ raw-string."
)pb");
// ...
}
Note:
- In the above example the proto is not manually constructed field by field.
- Instead the text-proto output is directly used as a C++ raw-string.
- Further the C++ raw-string is enclosed in
pb
markers which some tidy tools identify and use to correctly format the text-proto. - One of the biggest advantages of these parse function is that their result can be assigned into a const variable.
The ParseTextProtoOrDie
function dies if the input text-proto is not valid. That is done because the function emulates type safety this way. That is the author will likely only have to fix this once while many people will read the code. Further, this is test input that is supposed to be correct as is. If the input is of dynamic nature, then ParseText<ProtoType>(std::string_view)
has to be used.
-
rule:
@com_helly25_proto//mbo/proto:matchers_cc
-
namespace:
mbo::proto
-
EqualsProto
(msg
)msg
: protocolbuffer Message or string- Checks whether
msg
and the argument are the same proto. - If a string is used it is advisable to format the string as a raw-string with 'pb' marker as demonstrated above.
-
EqualsProto
()- 2-tuple polymorphic matcher that can be used for container comparisons.
-
EquivToProto
(msg
)msg
: protocolbuffer Message or string- Similar to
EqualsProto
but checks whethermsg
and the argument are equivalent. - Equivalent means that if one side sets a field to the default value and the other side does not have that field specified, then they are equivalent.
-
EquivToProto
()- 2-tuple polymorphic matcher that can be used for container comparisons.
-
Approximately
(matcher
[,margin
[,fraction
]])matcher
wrapper that allows to comparedouble
andfloat
values with a margin of error.- optional
margin
of error and a relativefraction
of error which will make values match if satisfied.
-
TreatingNaNsAsEqual
(matcher
)matcher
wrapper that compares floating-point fields such that NaNs are equal
-
IgnoringFields
(ignore_fields
,matcher
)matcher
wrapper that allows to ignore fields with different values.ignore_fields
list of fields to ignore. Fields are specified by their fully qualified names, i.e., the names corresponding to FieldDescriptor.full_name(). (e.g. testing.internal.FooProto2.member).
-
IgnoringFieldPaths
(ignore_field_paths
,matcher
)matcher
wrapper that allows to ignore fields with different values by their paths.ignore_field_paths
list of paths to ignore (e.g. 'field.sub_field.terminal_field').
-
IgnoringRepeatedFieldOrdering
(matcher
)matcher
wrapper that allows to ignore the order in which repeated fields are presented.- E.g.:
IgnoringRepeatedFieldOrdering(EqualsProto(R"pb(x: 1 x: 2)pb"))
: While the provided proto has the repeated fieldx
specified in the order[1, 2]
, the matcher will also match if the argument proto has the order reversed.
-
Partially
(matcher
)matcher
wrapper that compares only fields present in the expected protobuf. For example,Partially(EqualsProto(p))
will ignore any field that is not set in p when comparing the protobufs.
-
WhenDeserialized
(matcher
)matcher
wrapper that matches a string that can be deserialized as a protobuf that matchesmatcher
.
-
WhenDeserializedAs
<Proto
>(matcher
)matcher
wrapper that matches a string that can be deserialized as a protobuf of typeProto
that matchesmatcher
.
BUILD.bazel:
cc_test(
name = "test",
srcs = ["test.cc"],
deps = ["@com_helly25_proto//mbo/proto:matchers"],
)
Source test.cc:
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "mbo/proto/matchers.h"
using ::mbo::proto::EqualsProto;
using ::mbo::proto::IgnoringRepeatedFieldOrdering;
TEST(Foo, EqualsProto) {
MyProto msg;
msg.set_field("name");
EXPECT_THAT(msg, EqualsProto(R"pb(
field: "name"
)pb"));
}
In the above example EqualsProto
takes the text-proto as a C++ raw-string.
The matchers can of course be combined with the parse functions. The below shows how a FunctionUnderTest
can be tested. It receives the proto input directly from the parse function and the matcher compares it directly to the expected golden result text-proto. Note how there is no field-by-field processing anywhere. No dstraction from what is being tested and what the expectations are. Or in other words the test avoids misleading and error prone in-test logic. And becasue the function-under-test is called inside the EXPECT_THAT macro the gtest failure messages will show what actually failed (and not something like "Input: temp_var").
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "mbo/proto/matchers.h"
#include "mbo/proto/parse_text_proto.h"
using ::mbo::proto::EqualsProto;
using ::mbo::proto::IgnoringRepeatedFieldOrdering;
using ::mbo::proto::ParseTextProtoOrDie;
MyProto FunctionUnderTest(const MyProto& proto) {
return proto;
}
TEST(Foo, Wrapper) {
const MyProto input = ParseTextProtoOrDie(R"pb(
number: 1
number: 2
number: 3
)pb");
EXPECT_THAT(
FunctionUnderTest(input),
IgnoringRepeatedFieldOrdering(EqualsProto(R"pb(
number: 1
number: 2
number: 3
)pb")));
}
This repository requires a C++20 compiler (in case of MacOS XCode 15 is needed). The project's CI test a combination of Clang and GCC compilers on Linux/Ubuntu and MacOS.
The reliance on a C++20 compiler is because it uses std::source_location
since Google's Abseil's SourceLocation
has not been open sourced.
The project only comes with a Bazel BUILD.bazel file and can be added to other Bazel projects.
The project is formatted with specific clang-format settings which require clang 16+ (in case of MacOs LLVM 16+ can be installed using brew). For simplicity in dev mode the project pulls the appropriate clang tools and can be compiled with those tools using bazel [build|test] --config=clang ...
.
Checkout Releases or use head ref as follows:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "com_helly25_proto",
url = "https://github.com/helly25/proto/archive/refs/heads/main.tar.gz",
# See https://github.com/helly25/proto/releases for releases.
)
Check Releases for details. All that is needed is a bazel_dep
instruction with the correct version.
bazel_dep(name = "helly25_proto", version = "0.0.0", repo_name = "com_helly25_proto")
The clone was made from Google's CPP-proto-builder, of which the project lead is the original author and lead for over a decade. That includes in particular the parse_proto components which were invented in their original form around 2012 and used widely throughout Google.
The following files were cloned:
cp ../cpp-proto-builder/proto_builder/oss/parse_proto_text.* proto/mbo/proto
cp ../cpp-proto-builder/proto_builder/oss/parse_proto_text_test.cc proto/mbo/proto
cp ../cpp-proto-builder/proto_builder/oss/tests/simple_message.proto proto/mbo/proto
patch <proto/mbo/proto/parse_proto_text.diff
The diff files are available in the repository history.
The matchers are part of Google's CPP-proto-builder, (see above). Alternatively this could have been done from:
The FHIR sources are stripped and the nucleus sources are older and finally inazarenko's clone was modified to remove other Google dependencies which creates the issue that the GoogleTest docs do not apply as for instance the regular expression library is different.
The following files were cloned:
cp ../cpp-proto-builder/proto_builder/oss/testing/proto_test_util.* proto/mbo/testing/proto
patch <proto/mbo/testing/proto_test_util.diff
The diff files are available in the repository history.
The include guards were updated and the namespace changed to testing::proto
which allows to
import the whole namespace easily. Further logging was switched directly to
Abseil logging (this was not an option when I wrote
the proto Builder or when it was open sourced).
This clone was established 2023.07.15. The source has since been moved and modified but remains as close to the original source as possible.