/*
* Copyright 2018 Google LLC.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// gMock matchers used to validate protocol buffer arguments.
// WHAT THIS IS
// ============
//
// This library defines the following matchers in the ::nucleus namespace:
//
// EqualsProto(pb) The argument equals pb.
// EqualsInitializedProto(pb) The argument is initialized and equals pb.
// EquivToProto(pb) The argument is equivalent to pb.
// EquivToInitializedProto(pb) The argument is initialized and equivalent
// to pb.
// IsInitializedProto() The argument is an initialized protobuf.
//
// where:
//
// - pb can be either a protobuf value or a human-readable string
// representation of it.
// - When pb is a string, the matcher can optionally accept a
// template argument for the type of the protobuf,
// e.g. EqualsProto<Foo>("foo: 1").
// - "equals" is defined as the argument's Equals(pb) method returns true.
// - "equivalent to" is defined as the argument's Equivalent(pb) method
// returns true.
// - "initialized" means that the argument's IsInitialized() method returns
// true.
//
// These matchers can match either a protobuf value or a pointer to
// it. They make a copy of pb, and thus can out-live pb. When the
// match fails, the matchers print a detailed message (the value of
// the actual protobuf, the value of the expected protobuf, and which
// fields are different).
//
// This library also defines the following matcher transformer
// functions in the ::nucleus::proto namespace:
//
// Approximately(m, margin, fraction)
// The same as m, except that it compares
// floating-point fields approximately (using
// google::protobuf::util::MessageDifferencer's APPROXIMATE
// comparison option). m can be any of the
// Equals* and EquivTo* protobuf matchers above. If margin
// is specified, floats and doubles will be considered
// approximately equal if they are within that margin, i.e.
// abs(expected - actual) <= margin. If fraction is
// specified, floats and doubles will be considered
// approximately equal if they are within a fraction of
// their magnitude, i.e. abs(expected - actual) <=
// fraction * max(abs(expected), abs(actual)). Two fields
// will be considered equal if they're within the fraction
// _or_ within the margin, so omitting or setting the
// fraction to 0.0 will only check against the margin.
// Similarly, setting the margin to 0.0 will only check
// using the fraction. If margin and fraction are omitted,
// MathLimits<T>::kStdError for that type (T=float or
// T=double) is used for both the margin and fraction.
// TreatingNaNsAsEqual(m)
// The same as m, except that treats floating-point fields
// that are NaN as equal. m can be any of the Equals* and
// EquivTo* protobuf matchers above.
// IgnoringFields(fields, m)
// The same as m, except the specified fields will be
// ignored when matching (using
// google::protobuf::util::MessageDifferencer::IgnoreField). fields is
// represented as a container or an initializer list of
// strings and each element is specified by their fully
// qualified names, i.e., the names corresponding to
// FieldDescriptor.full_name(). m can be
// any of the Equals* and EquivTo* protobuf matchers above.
// It can also be any of the transformer matchers listed
// here (e.g. Approximately, TreatingNaNsAsEqual) as long as
// the intent of the each concatenated matcher is mutually
// exclusive (e.g. using IgnoringFields in conjunction with
// Partially can have different results depending on whether
// the fields specified in IgnoringFields is part of the
// fields covered by Partially).
// IgnoringFieldPaths(field_paths, m)
// The same as m, except the specified fields will be
// ignored when matching. field_paths is represented as a
// container or an initializer list of strings and
// each element is specified by their path relative to the
// proto being matched by m. Paths can contain indices
// and/or extensions. Examples:
// Ignores field singular_field/repeated_field:
// singular_field
// repeated_field
// Ignores just the third repeated_field instance:
// repeated_field[2]
// Ignores some_field in singular_nested/repeated_nested:
// singular_nested.some_field
// repeated_nested.some_field
// Ignores some_field in instance 2 of repeated_nested:
// repeated_nested[2].some_field
// Ignores extension SomeExtension.msg of repeated_nested:
// repeated_nested.(package.SomeExtension.msg)
// Ignores subfield of extension:
// repeated_nested.(package.SomeExtension.msg).subfield
// The same restrictions as for IgnoringFields apply.
// IgnoringRepeatedFieldOrdering(m)
// The same as m, except that it ignores the relative
// ordering of elements within each repeated field in m.
// See google::protobuf::util::MessageDifferencer::TreatAsSet() for
// more details.
// Partially(m)
// The same as m, except that only fields present in
// the expected protobuf are considered (using
// google::protobuf::util::MessageDifferencer's PARTIAL
// comparison option). m can be any of the
// Equals* and EquivTo* protobuf matchers above.
// WhenDeserialized(typed_pb_matcher)
// The string argument is a serialization of a
// protobuf that matches typed_pb_matcher.
// typed_pb_matcher can be an Equals* or EquivTo*
// protobuf matcher (possibly with Approximately()
// or Partially() modifiers) where the type of the
// protobuf is known at run time (e.g. it cannot
// be EqualsProto("...") as it's unclear what type
// the string represents).
// WhenDeserializedAs<PB>(pb_matcher)
// Like WhenDeserialized(), except that the type
// of the deserialized protobuf must be PB. Since
// the protobuf type is known, pb_matcher can be *any*
// valid protobuf matcher, including EqualsProto("...").
//
// Approximately(), TreatingNaNsAsEqual(), Partially(), IgnoringFields(), and
// IgnoringRepeatedFieldOrdering() can be combined (nested)
// and the composition order is irrelevant:
//
// Approximately(Partially(EquivToProto(pb)))
// and
// Partially(Approximately(EquivToProto(pb)))
// are the same thing.
//
// EXAMPLES
// ========
//
// using ::nucleus::EqualsProto;
// using ::nucleus::EquivToProto;
// using ::nucleus::proto::Approximately;
// using ::nucleus::proto::Partially;
// using ::nucleus::proto::WhenDeserialized;
//
// // my_pb.Equals(expected_pb).
// EXPECT_THAT(my_pb, EqualsProto(expected_pb));
//
// // my_pb is equivalent to a protobuf whose foo field is 1 and
// // whose bar field is "x".
// EXPECT_THAT(my_pb, EquivToProto("foo: 1 "
// "bar: 'x'"));
//
// // my_pb is equal to expected_pb, comparing all floating-point
// // fields approximately.
// EXPECT_THAT(my_pb, Approximately(EqualsProto(expected_pb)));
//
// // my_pb is equivalent to expected_pb. A field is ignored in the
// // comparison if it's present in my_pb but not in expected_pb.
// EXPECT_THAT(my_pb, Partially(EquivToProto(expected_pb)));
//
// string data;
// my_pb.SerializeToString(&data);
// // data can be deserialized to a protobuf that equals expected_pb.
// EXPECT_THAT(data, WhenDeserialized(EqualsProto(expected_pb)));
// // The following line doesn't compile, as the matcher doesn't know
// // the type of the protobuf.
// // EXPECT_THAT(data, WhenDeserialized(EqualsProto("foo: 1")));
#ifndef THIRD_PARTY_NUCLEUS_TESTING_PROTOCOL_BUFFER_MATCHERS_H_
#define THIRD_PARTY_NUCLEUS_TESTING_PROTOCOL_BUFFER_MATCHERS_H_
#include <initializer_list>
#include <iostream> // NOLINT
#include <memory>
#include <sstream> // NOLINT
#include <string> // NOLINT
#include <vector> // NOLINT
#include <gmock/gmock-generated-matchers.h>
#include <gmock/gmock-matchers.h>
#include <gmock/gmock-more-matchers.h>
#include "absl/log/check.h"
#include "absl/strings/string_view.h"
#include "third_party/nucleus/platform/types.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "google/protobuf/message.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/util/field_comparator.h"
#include "google/protobuf/util/message_differencer.h"
namespace nucleus {
namespace internal {
// Utilities.
// How to compare two fields (equal vs. equivalent).
typedef google::protobuf::util::MessageDifferencer::MessageFieldComparison
ProtoFieldComparison;
// How to compare two floating-points (exact vs. approximate).
typedef google::protobuf::util::DefaultFieldComparator::FloatComparison
ProtoFloatComparison;
// How to compare repeated fields (whether the order of elements matters).
typedef google::protobuf::util::MessageDifferencer::RepeatedFieldComparison
RepeatedFieldComparison;
// Whether to compare all fields (full) or only fields present in the
// expected protobuf (partial).
typedef google::protobuf::util::MessageDifferencer::Scope ProtoComparisonScope;
const ProtoFieldComparison kProtoEqual =
google::protobuf::util::MessageDifferencer::EQUAL;
const ProtoFieldComparison kProtoEquiv =
google::protobuf::util::MessageDifferencer::EQUIVALENT;
const ProtoFloatComparison kProtoExact =
google::protobuf::util::DefaultFieldComparator::EXACT;
const ProtoFloatComparison kProtoApproximate =
google::protobuf::util::DefaultFieldComparator::APPROXIMATE;
const RepeatedFieldComparison kProtoCompareRepeatedFieldsRespectOrdering =
google::protobuf::util::MessageDifferencer::AS_LIST;
const RepeatedFieldComparison kProtoCompareRepeatedFieldsIgnoringOrdering =
google::protobuf::util::MessageDifferencer::AS_SET;
const ProtoComparisonScope kProtoFull = google::protobuf::util::MessageDifferencer::FULL;
const ProtoComparisonScope kProtoPartial =
google::protobuf::util::MessageDifferencer::PARTIAL;
// Options for comparing two protobufs.
struct ProtoComparison {
ProtoComparison()
: field_comp(kProtoEqual),
float_comp(kProtoExact),
treating_nan_as_equal(false),
has_custom_margin(false),
has_custom_fraction(false),
repeated_field_comp(kProtoCompareRepeatedFieldsRespectOrdering),
scope(kProtoFull),
float_margin(0.0),
float_fraction(0.0) {}
ProtoFieldComparison field_comp;
ProtoFloatComparison float_comp;
bool treating_nan_as_equal;
bool has_custom_margin; // only used when float_comp = APPROXIMATE
bool has_custom_fraction; // only used when float_comp = APPROXIMATE
RepeatedFieldComparison repeated_field_comp;
ProtoComparisonScope scope;
double float_margin; // only used when has_custom_margin is set.
double float_fraction; // only used when has_custom_fraction is set.
std::vector<string> ignore_fields;
std::vector<string> ignore_field_paths;
};
// Whether the protobuf must be initialized.
const bool kMustBeInitialized = true;
const bool kMayBeUninitialized = false;
// Parses the TextFormat representation of a protobuf, allowing required fields
// to be missing. Returns true iff successful.
bool ParsePartialFromAscii(const string& pb_ascii, google::protobuf::Message* proto,
string* error_text);
// Returns a protobuf of type Proto by parsing the given TextFormat
// representation of it. Required fields can be missing, in which case the
// returned protobuf will not be fully initialized.
template <class Proto>
Proto MakePartialProtoFromAscii(const string& str) {
Proto proto;
string error_text;
CHECK(ParsePartialFromAscii(str, &proto, &error_text))
<< "Failed to parse \"" << str << "\" as a "
<< proto.GetDescriptor()->full_name() << ":\n" << error_text;
return proto;
}
// Returns true iff p and q can be compared (i.e. have the same descriptor).
bool ProtoComparable(const google::protobuf::Message& p, const google::protobuf::Message& q);
// Returns true iff actual and expected are comparable and match. The
// comp argument specifies how the two are compared.
bool ProtoCompare(const ProtoComparison& comp,
const google::protobuf::Message& actual,
const google::protobuf::Message& expected);
// Overload for ProtoCompare where the expected message is specified as a text
// proto. If the text cannot be parsed as a message of the same type as the
// actual message, a CHECK failure will cause the test to fail and no subsequent
// tests will be run.
template <typename Proto>
inline bool ProtoCompare(const ProtoComparison& comp,
const Proto& actual,
const string& expected) {
return ProtoCompare(comp, actual, MakePartialProtoFromAscii<Proto>(expected));
}
// Describes the types of the expected and the actual protocol buffer.
string DescribeTypes(const google::protobuf::Message& expected,
const google::protobuf::Message& actual);
// Prints the protocol buffer pointed to by proto.
string PrintProtoPointee(const google::protobuf::Message* proto);
// Describes the differences between the two protocol buffers.
string DescribeDiff(const ProtoComparison& comp,
const google::protobuf::Message& actual,
const google::protobuf::Message& expected);
// Common code for implementing EqualsProto, EquivToProto,
// EqualsInitializedProto, and EquivToInitializedProto.
class ProtoMatcherBase {
public:
ProtoMatcherBase(
bool must_be_initialized, // Must the argument be fully initialized?
const ProtoComparison& comp) // How to compare the two protobufs.
: must_be_initialized_(must_be_initialized),
comp_(new auto(comp)) {}
ProtoMatcherBase(const ProtoMatcherBase& other)
: must_be_initialized_(other.must_be_initialized_),
comp_(new auto(*other.comp_)) {}
ProtoMatcherBase(ProtoMatcherBase&& other) = default;
virtual ~ProtoMatcherBase() {}
// Prints the expected protocol buffer.
virtual void PrintExpectedTo(::std::ostream* os) const = 0;
// Returns the expected value as a protobuf object; if the object
// cannot be created (e.g. in ProtoStringMatcher), explains why to
// 'listener' and returns NULL. The caller must call
// DeleteExpectedProto() on the returned value later.
virtual const google::protobuf::Message* CreateExpectedProto(
const google::protobuf::Message& arg, // For determining the type of the
// expected protobuf.
::testing::MatchResultListener* listener) const = 0;
// Deletes the given expected protobuf, which must be obtained from
// a call to CreateExpectedProto() earlier.
virtual void DeleteExpectedProto(const google::protobuf::Message* expected) const = 0;
// Makes this matcher compare floating-points approximately.
void SetCompareApproximately() { comp_->float_comp = kProtoApproximate; }
// Makes this matcher treating NaNs as equal when comparing floating-points.
void SetCompareTreatingNaNsAsEqual() { comp_->treating_nan_as_equal = true; }
// Makes this matcher ignore string elements specified by their fully
// qualified names, i.e., names corresponding to FieldDescriptor.full_name().
template <class Iterator>
void AddCompareIgnoringFields(Iterator first, Iterator last) {
comp_->ignore_fields.insert(comp_->ignore_fields.end(), first, last);
}
// Makes this matcher ignore string elements specified by their relative
// FieldPath.
template <class Iterator>
void AddCompareIgnoringFieldPaths(Iterator first, Iterator last) {
comp_->ignore_field_paths.insert(comp_->ignore_field_paths.end(), first,
last);
}
// Makes this matcher compare repeated fields ignoring ordering of elements.
void SetCompareRepeatedFieldsIgnoringOrdering() {
comp_->repeated_field_comp = kProtoCompareRepeatedFieldsIgnoringOrdering;
}
// Sets the margin of error for approximate floating point comparison.
void SetMargin(double margin) {
CHECK_GE(margin, 0.0) << "Using a negative margin for Approximately";
comp_->has_custom_margin = true;
comp_->float_margin = margin;
}
// Sets the relative fraction of error for approximate floating point
// comparison.
void SetFraction(double fraction) {
CHECK(0.0 <= fraction && fraction < 1.0) <<
"Fraction for Approximately must be >= 0.0 and < 1.0";
comp_->has_custom_fraction = true;
comp_->float_fraction = fraction;
}
// Makes this matcher compare protobufs partially.
void SetComparePartially() { comp_->scope = kProtoPartial; }
bool MatchAndExplain(const google::protobuf::Message& arg,
::testing::MatchResultListener* listener) const {
return MatchAndExplain(arg, false, listener);
}
bool MatchAndExplain(const google::protobuf::Message* arg,
::testing::MatchResultListener* listener) const {
return (arg != NULL) && MatchAndExplain(*arg, true, listener);
}
// Describes the expected relation between the actual protobuf and
// the expected one.
void DescribeRelationToExpectedProto(::std::ostream* os) const {
if (comp_->repeated_field_comp ==
kProtoCompareRepeatedFieldsIgnoringOrdering) {
*os << "(ignoring repeated field ordering) ";
}
if (!comp_->ignore_fields.empty()) {
*os << "(ignoring fields: ";
const char *sep = "";
for (size_t i = 0; i < comp_->ignore_fields.size(); ++i, sep = ", ")
*os << sep << comp_->ignore_fields[i];
*os << ") ";
}
if (comp_->float_comp == kProtoApproximate) {
*os << "approximately ";
if (comp_->has_custom_margin || comp_->has_custom_fraction) {
*os << "(";
if (comp_->has_custom_margin) {
std::stringstream ss;
ss << std::setprecision(std::numeric_limits<double>::digits10 + 2)
<< comp_->float_margin;
*os << "absolute error of float or double fields <= " << ss.str();
}
if (comp_->has_custom_margin && comp_->has_custom_fraction) {
*os << " or ";
}
if (comp_->has_custom_fraction) {
std::stringstream ss;
ss << std::setprecision(std::numeric_limits<double>::digits10 + 2)
<< comp_->float_fraction;
*os << "relative error of float or double fields <= " << ss.str();
}
*os << ") ";
}
}
*os << (comp_->scope == kProtoPartial ? "partially " : "")
<< (comp_->field_comp == kProtoEqual ? "equal" : "equivalent")
<< (comp_->treating_nan_as_equal ? " (treating NaNs as equal)" : "")
<< " to ";
PrintExpectedTo(os);
}
void DescribeTo(::std::ostream* os) const {
*os << "is " << (must_be_initialized_ ? "fully initialized and " : "");
DescribeRelationToExpectedProto(os);
}
void DescribeNegationTo(::std::ostream* os) const {
*os << "is " << (must_be_initialized_ ? "not fully initialized or " : "")
<< "not ";
DescribeRelationToExpectedProto(os);
}
bool must_be_initialized() const { return must_be_initialized_; }
const ProtoComparison& comp() const { return *comp_; }
private:
bool MatchAndExplain(const google::protobuf::Message& arg, bool is_matcher_for_pointer,
::testing::MatchResultListener* listener) const;
const bool must_be_initialized_;
std::unique_ptr<ProtoComparison> comp_;
};
// Returns a copy of the given proto2 message.
inline google::protobuf::Message* CloneProto2(const google::protobuf::Message& src) {
google::protobuf::Message* clone = src.New();
clone->CopyFrom(src);
return clone;
}
// Implements EqualsProto, EquivToProto, EqualsInitializedProto, and
// EquivToInitializedProto, where the matcher parameter is a protobuf.
class ProtoMatcher : public ProtoMatcherBase {
public:
ProtoMatcher(
const google::protobuf::Message& expected, // The expected protobuf.
bool must_be_initialized, // Must the argument be fully initialized?
const ProtoComparison& comp) // How to compare the two protobufs.
: ProtoMatcherBase(must_be_initialized, comp),
expected_(CloneProto2(expected)) {
if (must_be_initialized) {
CHECK(expected.IsInitialized())
<< "The protocol buffer given to *InitializedProto() "
<< "must itself be initialized, but the following required fields "
<< "are missing: " << expected.InitializationErrorString() << ".";
}
}
virtual void PrintExpectedTo(::std::ostream* os) const {
*os << expected_->GetDescriptor()->full_name() << " ";
::testing::internal::UniversalPrint(*expected_, os);
}
virtual const google::protobuf::Message* CreateExpectedProto(
const google::protobuf::Message& /* arg */,
::testing::MatchResultListener* /* listener */) const {
return expected_.get();
}
virtual void DeleteExpectedProto(const google::protobuf::Message* expected) const {}
const std::shared_ptr<const google::protobuf::Message>& expected()
const {
return expected_;
}
private:
const std::shared_ptr<const google::protobuf::Message> expected_;
};
// Implements EqualsProto, EquivToProto, EqualsInitializedProto, and
// EquivToInitializedProto, where the matcher parameter is a string.
class ProtoStringMatcher : public ProtoMatcherBase {
public:
ProtoStringMatcher(
const string& expected, // The text representing the expected protobuf.
bool must_be_initialized, // Must the argument be fully initialized?
const ProtoComparison comp) // How to compare the two protobufs.
: ProtoMatcherBase(must_be_initialized, comp),
expected_(expected) {}
// Parses the expected string as a protobuf of the same type as arg,
// and returns the parsed protobuf (or NULL when the parse fails).
// The caller must call DeleteExpectedProto() on the return value
// later.
virtual const google::protobuf::Message* CreateExpectedProto(
const google::protobuf::Message& arg,
::testing::MatchResultListener* listener) const {
google::protobuf::Message* expected_proto = arg.New();
// We don't insist that the expected string parses as an
// *initialized* protobuf. Otherwise EqualsProto("...") may
// wrongfully fail when the actual protobuf is not fully
// initialized. If the user wants to ensure that the actual
// protobuf is initialized, they should use
// EqualsInitializedProto("...") instead of EqualsProto("..."),
// and the MatchAndExplain() function in ProtoMatcherBase will
// enforce it.
string error_text;
if (ParsePartialFromAscii(expected_, expected_proto, &error_text)) {
return expected_proto;
} else {
delete expected_proto;
if (listener->IsInterested()) {
*listener << "where ";
PrintExpectedTo(listener->stream());
*listener << " doesn't parse as a " << arg.GetDescriptor()->full_name()
<< ":\n" << error_text;
}
return NULL;
}
}
virtual void DeleteExpectedProto(const google::protobuf::Message* expected) const {
delete expected;
}
virtual void PrintExpectedTo(::std::ostream* os) const {
*os << "<" << expected_ << ">";
}
private:
const string expected_;
};
typedef ::testing::PolymorphicMatcher<ProtoMatcher> PolymorphicProtoMatcher;
// Common code for implementing WhenDeserialized(proto_matcher) and
// WhenDeserializedAs<PB>(proto_matcher).
template <class Proto>
class WhenDeserializedMatcherBase {
public:
typedef ::testing::Matcher<const Proto&> InnerMatcher;
explicit WhenDeserializedMatcherBase(const InnerMatcher& proto_matcher)
: proto_matcher_(proto_matcher) {}
virtual ~WhenDeserializedMatcherBase() {}
// Creates an empty protobuf with the expected type.
virtual Proto* MakeEmptyProto() const = 0;
// Type name of the expected protobuf.
virtual string ExpectedTypeName() const = 0;
// Name of the type argument given to WhenDeserializedAs<>(), or
// "protobuf" for WhenDeserialized().
virtual string TypeArgName() const = 0;
// Deserializes the string as a protobuf of the same type as the expected
// protobuf.
Proto* Deserialize(google::protobuf::io::ZeroCopyInputStream* input) const {
Proto* proto = MakeEmptyProto();
// ParsePartialFromString() parses a serialized representation of a
// protobuf, allowing required fields to be missing. This means
// that we don't insist on the parsed protobuf being fully
// initialized. This allows the user to choose whether it should
// be initialized using EqualsProto vs EqualsInitializedProto, for
// example.
if (proto->ParsePartialFromZeroCopyStream(input)) {
return proto;
} else {
delete proto;
return NULL;
}
}
void DescribeTo(::std::ostream* os) const {
*os << "can be deserialized as a " << TypeArgName() << " that ";
proto_matcher_.DescribeTo(os);
}
void DescribeNegationTo(::std::ostream* os) const {
*os << "cannot be deserialized as a " << TypeArgName() << " that ";
proto_matcher_.DescribeTo(os);
}
bool MatchAndExplain(google::protobuf::io::ZeroCopyInputStream* arg,
::testing::MatchResultListener* listener) const {
// Deserializes the string arg as a protobuf of the same type as the
// expected protobuf.
::std::unique_ptr<const Proto> deserialized_arg(Deserialize(arg));
if (!listener->IsInterested()) {
// No need to explain the match result.
return (deserialized_arg != NULL) &&
proto_matcher_.Matches(*deserialized_arg);
}
::std::ostream* const os = listener->stream();
if (deserialized_arg == NULL) {
*os << "which cannot be deserialized as a " << ExpectedTypeName();
return false;
}
*os << "which deserializes to ";
UniversalPrint(*deserialized_arg, os);
::testing::StringMatchResultListener inner_listener;
const bool match =
proto_matcher_.MatchAndExplain(*deserialized_arg, &inner_listener);
const string explain = inner_listener.str();
if (explain != "") {
*os << ",\n" << explain;
}
return match;
}
bool MatchAndExplain(const string& str,
::testing::MatchResultListener* listener) const {
google::protobuf::io::ArrayInputStream input(str.data(), str.size());
return MatchAndExplain(&input, listener);
}
bool MatchAndExplain(absl::string_view sp,
::testing::MatchResultListener* listener) const {
google::protobuf::io::ArrayInputStream input(sp.data(), sp.size());
return MatchAndExplain(&input, listener);
}
bool MatchAndExplain(const char* str,
::testing::MatchResultListener* listener) const {
google::protobuf::io::ArrayInputStream input(str, strlen(str));
return MatchAndExplain(&input, listener);
}
private:
const InnerMatcher proto_matcher_;
};
// Implements WhenDeserialized(proto_matcher).
class WhenDeserializedMatcher
: public WhenDeserializedMatcherBase<google::protobuf::Message> {
public:
explicit WhenDeserializedMatcher(const PolymorphicProtoMatcher& proto_matcher)
: WhenDeserializedMatcherBase<google::protobuf::Message>(proto_matcher),
expected_proto_(proto_matcher.impl().expected()) {}
virtual google::protobuf::Message* MakeEmptyProto() const {
return expected_proto_->New();
}
virtual string ExpectedTypeName() const {
return expected_proto_->GetDescriptor()->full_name();
}
virtual string TypeArgName() const { return "protobuf"; }
private:
// The expected protobuf specified in the inner matcher
// (proto_matcher_). We only need a std::shared_ptr to it instead of
// making a copy, as the expected protobuf will never be changed
// once created.
const std::shared_ptr<const google::protobuf::Message> expected_proto_;
};
// Implements WhenDeserializedAs<Proto>(proto_matcher).
template <class Proto>
class WhenDeserializedAsMatcher : public WhenDeserializedMatcherBase<Proto> {
public:
typedef ::testing::Matcher<const Proto&> InnerMatcher;
explicit WhenDeserializedAsMatcher(const InnerMatcher& inner_matcher)
: WhenDeserializedMatcherBase<Proto>(inner_matcher) {}
virtual Proto* MakeEmptyProto() const { return new Proto; }
virtual string ExpectedTypeName() const {
return Proto().GetDescriptor()->full_name();
}
virtual string TypeArgName() const { return ExpectedTypeName(); }
};
// Implements the IsInitializedProto matcher, which is used to verify that a
// protocol buffer is valid using the IsInitialized method.
class IsInitializedProtoMatcher {
public:
void DescribeTo(::std::ostream* os) const {
*os << "is a fully initialized protocol buffer";
}
void DescribeNegationTo(::std::ostream* os) const {
*os << "is not a fully initialized protocol buffer";
}
template <typename T>
bool MatchAndExplain(T& arg, // NOLINT
::testing::MatchResultListener* listener) const {
if (!arg.IsInitialized()) {
*listener << "which is missing the following required fields: "
<< arg.InitializationErrorString();
return false;
}
return true;
}
// It's critical for this overload to take a T* instead of a const
// T*. Otherwise the other version would be a better match when arg
// is a pointer to a non-const value.
template <typename T>
bool MatchAndExplain(T* arg, ::testing::MatchResultListener* listener) const {
if (listener->IsInterested() && arg != NULL) {
*listener << PrintProtoPointee(arg);
}
if (arg == NULL) {
*listener << "which is null";
return false;
} else if (!arg->IsInitialized()) {
*listener << ", which is missing the following required fields: "
<< arg->InitializationErrorString();
return false;
} else {
return true;
}
}
};
// Implements EqualsProto and EquivToProto for 2-tuple matchers.
class TupleProtoMatcher {
public:
explicit TupleProtoMatcher(const ProtoComparison& comp)
: comp_(new auto(comp)) {}
TupleProtoMatcher(const TupleProtoMatcher& other)
: comp_(new auto(*other.comp_)) {}
TupleProtoMatcher(TupleProtoMatcher&& other) = default;
template <typename T1, typename T2>
operator ::testing::Matcher< ::testing::tuple<T1, T2> >() const {
return MakeMatcher(new Impl< ::testing::tuple<T1, T2> >(*comp_));
}
template <typename T1, typename T2>
operator ::testing::Matcher<const ::testing::tuple<T1, T2>&>() const {
return MakeMatcher(new Impl<const ::testing::tuple<T1, T2>&>(*comp_));
}
// Allows matcher transformers, e.g., Approximately(), Partially(), etc. to
// change the behavior of this 2-tuple matcher.
TupleProtoMatcher& mutable_impl() { return *this; }
// Makes this matcher compare floating-points approximately.
void SetCompareApproximately() { comp_->float_comp = kProtoApproximate; }
// Makes this matcher treating NaNs as equal when comparing floating-points.
void SetCompareTreatingNaNsAsEqual() { comp_->treating_nan_as_equal = true; }
// Makes this matcher ignore string elements specified by their fully
// qualified names, i.e., names corresponding to FieldDescriptor.full_name().
template <class Iterator>
void AddCompareIgnoringFields(Iterator first, Iterator last) {
comp_->ignore_fields.insert(comp_->ignore_fields.end(), first, last);
}
// Makes this matcher ignore string elements specified by their relative
// FieldPath.
template <class Iterator>
void AddCompareIgnoringFieldPaths(Iterator first, Iterator last) {
comp_->ignore_field_paths.insert(comp_->ignore_field_paths.end(), first,
last);
}
// Makes this matcher compare repeated fields ignoring ordering of elements.
void SetCompareRepeatedFieldsIgnoringOrdering() {
comp_->repeated_field_comp = kProtoCompareRepeatedFieldsIgnoringOrdering;
}
// Sets the margin of error for approximate floating point comparison.
void SetMargin(double margin) {
CHECK_GE(margin, 0.0) << "Using a negative margin for Approximately";
comp_->has_custom_margin = true;
comp_->float_margin = margin;
}
// Sets the relative fraction of error for approximate floating point
// comparison.
void SetFraction(double fraction) {
CHECK(0.0 <= fraction && fraction <= 1.0) <<
"Fraction for Relatively must be >= 0.0 and < 1.0";
comp_->has_custom_fraction = true;
comp_->float_fraction = fraction;
}
// Makes this matcher compares protobufs partially.
void SetComparePartially() { comp_->scope = kProtoPartial; }
private:
template <typename Tuple>
class Impl : public ::testing::MatcherInterface<Tuple> {
public:
explicit Impl(const ProtoComparison& comp) : comp_(comp) {}
virtual bool MatchAndExplain(
Tuple args, ::testing::MatchResultListener* /* listener */) const {
using ::testing::get;
return ProtoCompare(comp_, get<0>(args), get<1>(args));
}
virtual void DescribeTo(::std::ostream* os) const {
*os << (comp_.field_comp == kProtoEqual ? "are equal"
: "are equivalent");
}
virtual void DescribeNegationTo(::std::ostream* os) const {
*os << (comp_.field_comp == kProtoEqual ? "are not equal"
: "are not equivalent");
}
private:
const ProtoComparison comp_;
};
std::unique_ptr<ProtoComparison> comp_;
};
} // namespace internal
// Creates a polymorphic matcher that matches a 2-tuple where
// first.Equals(second) is true.
inline internal::TupleProtoMatcher EqualsProto() {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEqual;
return internal::TupleProtoMatcher(comp);
}
// Creates a polymorphic matcher that matches a 2-tuple where
// first.Equivalent(second) is true.
inline internal::TupleProtoMatcher EquivToProto() {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEquiv;
return internal::TupleProtoMatcher(comp);
}
// Constructs a matcher that matches the argument if
// argument.Equals(x) or argument->Equals(x) returns true.
inline internal::PolymorphicProtoMatcher EqualsProto(const google::protobuf::Message& x) {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEqual;
return ::testing::MakePolymorphicMatcher(
internal::ProtoMatcher(x, internal::kMayBeUninitialized, comp));
}
inline ::testing::PolymorphicMatcher<internal::ProtoStringMatcher> EqualsProto(
const string& x) {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEqual;
return ::testing::MakePolymorphicMatcher(
internal::ProtoStringMatcher(x, internal::kMayBeUninitialized, comp));
}
template <class Proto>
inline internal::PolymorphicProtoMatcher EqualsProto(const string& str) {
return EqualsProto(internal::MakePartialProtoFromAscii<Proto>(str));
}
// Constructs a matcher that matches the argument if
// argument.Equivalent(x) or argument->Equivalent(x) returns true.
inline internal::PolymorphicProtoMatcher
EquivToProto(const google::protobuf::Message& x) {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEquiv;
return ::testing::MakePolymorphicMatcher(
internal::ProtoMatcher(x, internal::kMayBeUninitialized, comp));
}
inline ::testing::PolymorphicMatcher<internal::ProtoStringMatcher> EquivToProto(
const string& x) {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEquiv;
return ::testing::MakePolymorphicMatcher(
internal::ProtoStringMatcher(x, internal::kMayBeUninitialized, comp));
}
template <class Proto>
inline internal::PolymorphicProtoMatcher EquivToProto(const string& str) {
return EquivToProto(internal::MakePartialProtoFromAscii<Proto>(str));
}
// Constructs a matcher that matches the argument if
// argument.IsInitialized() or argument->IsInitialized() returns true.
inline ::testing::PolymorphicMatcher<internal::IsInitializedProtoMatcher>
IsInitializedProto() {
return ::testing::MakePolymorphicMatcher(
internal::IsInitializedProtoMatcher());
}
// Constructs a matcher that matches an argument whose IsInitialized()
// and Equals(x) methods both return true. The argument can be either
// a protocol buffer or a pointer to it.
inline internal::PolymorphicProtoMatcher
EqualsInitializedProto(const google::protobuf::Message& x) {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEqual;
return ::testing::MakePolymorphicMatcher(
internal::ProtoMatcher(x, internal::kMustBeInitialized, comp));
}
inline ::testing::PolymorphicMatcher<internal::ProtoStringMatcher>
EqualsInitializedProto(const string& x) {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEqual;
return ::testing::MakePolymorphicMatcher(
internal::ProtoStringMatcher(x, internal::kMustBeInitialized, comp));
}
template <class Proto>
inline internal::PolymorphicProtoMatcher EqualsInitializedProto(
const string& str) {
return EqualsInitializedProto(
internal::MakePartialProtoFromAscii<Proto>(str));
}
// Constructs a matcher that matches an argument whose IsInitialized()
// and Equivalent(x) methods both return true. The argument can be
// either a protocol buffer or a pointer to it.
inline internal::PolymorphicProtoMatcher
EquivToInitializedProto(const google::protobuf::Message& x) {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEquiv;
return ::testing::MakePolymorphicMatcher(
internal::ProtoMatcher(x, internal::kMustBeInitialized, comp));
}
inline ::testing::PolymorphicMatcher<internal::ProtoStringMatcher>
EquivToInitializedProto(const string& x) {
internal::ProtoComparison comp;
comp.field_comp = internal::kProtoEquiv;
return ::testing::MakePolymorphicMatcher(
internal::ProtoStringMatcher(x, internal::kMustBeInitialized, comp));
}
template <class Proto>
inline internal::PolymorphicProtoMatcher EquivToInitializedProto(
const string& str) {
return EquivToInitializedProto(
internal::MakePartialProtoFromAscii<Proto>(str));
}
namespace proto {
// Approximately(m) returns a matcher that is the same as m, except
// that it compares floating-point fields approximately (using
// google::protobuf::util::MessageDifferencer's APPROXIMATE comparison option).
// The inner matcher m can be any of the Equals* and EquivTo* protobuf
// matchers above.
template <class InnerProtoMatcher>
inline InnerProtoMatcher Approximately(InnerProtoMatcher inner_proto_matcher) {
inner_proto_matcher.mutable_impl().SetCompareApproximately();
return inner_proto_matcher;
}
// Alternative version of Approximately which takes an explicit margin of error.
template <class InnerProtoMatcher>
inline InnerProtoMatcher Approximately(InnerProtoMatcher inner_proto_matcher,
double margin) {
inner_proto_matcher.mutable_impl().SetCompareApproximately();
inner_proto_matcher.mutable_impl().SetMargin(margin);
return inner_proto_matcher;
}
// Alternative version of Approximately which takes an explicit margin of error
// and a relative fraction of error and will match if either is satisfied.
template <class InnerProtoMatcher>
inline InnerProtoMatcher Approximately(InnerProtoMatcher inner_proto_matcher,
double margin, double fraction) {
inner_proto_matcher.mutable_impl().SetCompareApproximately();
inner_proto_matcher.mutable_impl().SetMargin(margin);
inner_proto_matcher.mutable_impl().SetFraction(fraction);
return inner_proto_matcher;
}
// TreatingNaNsAsEqual(m) returns a matcher that is the same as m, except that
// it compares floating-point fields such that NaNs are equal.
// The inner matcher m can be any of the Equals* and EquivTo* protobuf matchers
// above.
template <class InnerProtoMatcher>
inline InnerProtoMatcher TreatingNaNsAsEqual(
InnerProtoMatcher inner_proto_matcher) {
inner_proto_matcher.mutable_impl().SetCompareTreatingNaNsAsEqual();
return inner_proto_matcher;
}
// IgnoringFields(fields, m) returns a matcher that is the same as m, except the
// specified fields will be ignored when matching
// (using google::protobuf::util::MessageDifferencer::IgnoreField). Each element in fields
// are specified by their fully qualified names, i.e., the names corresponding
// to FieldDescriptor.full_name(). (e.g. testing.internal.FooProto2.member).
// m can be any of the Equals* and EquivTo* protobuf matchers above.
// It can also be any of the transformer matchers listed here (e.g.
// Approximately, TreatingNaNsAsEqual) as long as the intent of the each
// concatenated matcher is mutually exclusive (e.g. using IgnoringFields in
// conjunction with Partially can have different results depending on whether
// the fields specified in IgnoringFields is part of the fields covered by
// Partially).
template <class InnerProtoMatcher, class Container>
inline InnerProtoMatcher IgnoringFields(const Container& ignore_fields,
InnerProtoMatcher inner_proto_matcher) {
inner_proto_matcher.mutable_impl().AddCompareIgnoringFields(
ignore_fields.begin(), ignore_fields.end());
return inner_proto_matcher;
}
// See top comment.
template <class InnerProtoMatcher, class Container>
inline InnerProtoMatcher IgnoringFieldPaths(
const Container& ignore_field_paths,
InnerProtoMatcher inner_proto_matcher) {
inner_proto_matcher.mutable_impl().AddCompareIgnoringFieldPaths(
ignore_field_paths.begin(), ignore_field_paths.end());
return inner_proto_matcher;
}
#ifdef LANG_CXX11
template <class InnerProtoMatcher, class T>
inline InnerProtoMatcher IgnoringFields(std::initializer_list<T> il,
InnerProtoMatcher inner_proto_matcher) {
inner_proto_matcher.mutable_impl().AddCompareIgnoringFields(
il.begin(), il.end());
return inner_proto_matcher;
}
template <class InnerProtoMatcher, class T>
inline InnerProtoMatcher IgnoringFieldPaths(
std::initializer_list<T> il, InnerProtoMatcher inner_proto_matcher) {
inner_proto_matcher.mutable_impl().AddCompareIgnoringFieldPaths(il.begin(),
il.end());
return inner_proto_matcher;
}
#endif // LANG_CXX11
// IgnoringRepeatedFieldOrdering(m) returns a matcher that is the same as m,
// except that it ignores the relative ordering of elements within each repeated
// field in m. See google::protobuf::MessageDifferencer::TreatAsSet() for more details.
template <class InnerProtoMatcher>
inline InnerProtoMatcher IgnoringRepeatedFieldOrdering(
InnerProtoMatcher inner_proto_matcher) {
inner_proto_matcher.mutable_impl().SetCompareRepeatedFieldsIgnoringOrdering();
return inner_proto_matcher;
}
// Partially(m) returns a matcher that is the same as m, except that
// only fields present in the expected protobuf are considered (using
// google::protobuf::util::MessageDifferencer's PARTIAL comparison option). For
// example, Partially(EqualsProto(p)) will ignore any field that's
// not set in p when comparing the protobufs. The inner matcher m can
// be any of the Equals* and EquivTo* protobuf matchers above.
template <class InnerProtoMatcher>
inline InnerProtoMatcher Partially(InnerProtoMatcher inner_proto_matcher) {
inner_proto_matcher.mutable_impl().SetComparePartially();
return inner_proto_matcher;
}
// WhenDeserialized(m) is a matcher that matches a string that can be
// deserialized as a protobuf that matches m. m must be a protobuf
// matcher where the expected protobuf type is known at run time.
inline ::testing::PolymorphicMatcher<internal::WhenDeserializedMatcher>
WhenDeserialized(const internal::PolymorphicProtoMatcher& proto_matcher) {
return ::testing::MakePolymorphicMatcher(
internal::WhenDeserializedMatcher(proto_matcher));
}
// WhenDeserializedAs<Proto>(m) is a matcher that matches a string
// that can be deserialized as a protobuf of type Proto that matches
// m, which can be any valid protobuf matcher.
template <class Proto, class InnerMatcher>
inline ::testing::PolymorphicMatcher<
internal::WhenDeserializedAsMatcher<Proto> >
WhenDeserializedAs(const InnerMatcher& inner_matcher) {
return MakePolymorphicMatcher(internal::WhenDeserializedAsMatcher<Proto>(
::testing::SafeMatcherCast<const Proto&>(inner_matcher)));
}
} // namespace proto
} // namespace nucleus
#endif // THIRD_PARTY_NUCLEUS_TESTING_PROTOCOL_BUFFER_MATCHERS_H_