blob: 9fbad60388b10802595e553c835dcaa51f5cbef1 [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "conformance/matchers.h"
#include <cmath>
#include <fstream>
#include <iterator>
#include <ostream>
#include <string>
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest-spi.h>
#include <gtest/gtest.h>
#include "absl/base/log_severity.h"
#include "absl/log/absl_check.h"
#include "absl/log/scoped_mock_log.h"
#include "absl/status/status_matchers.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "conformance/binary_wireformat.h"
#include "conformance/conformance.pb.h"
#include "conformance/global_test_environment.h"
#include "conformance/mock_test_runner.h"
#include "conformance/result_ledger.h"
#include "conformance/testee.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/message.h"
#include "google/protobuf/test_messages_proto2.pb.h"
#include "google/protobuf/text_format.h"
namespace google {
namespace protobuf {
namespace conformance {
namespace internal {
namespace {
// Backing state for the mocked-out global environment below. In the real
// conformance binary this would be populated from CLI flags and the failure
// list on disk, but we don't want to drag that in for a unit test.
ResultLedger* global_ledger = nullptr;
} // namespace
ResultLedger& GetGlobalResultLedger() {
ABSL_CHECK(global_ledger != nullptr);
return *global_ledger;
}
} // namespace internal
namespace {
using ::absl_testing::IsOk;
using ::conformance::ConformanceResponse;
using ::conformance::WireFormat;
using ::google::protobuf::conformance::TestPriority;
using ::google::protobuf::conformance::internal::ResultLedger;
using ::google::protobuf::conformance::internal::TestResult;
using ::protobuf_test_messages::proto2::TestAllTypesProto2;
using ::protobuf_test_messages::proto2::UnknownToTestAllTypes;
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::FieldsAre;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Optional;
using ::testing::Pair;
using ::testing::PrintToString;
using ::testing::Return;
using ::testing::TestParamInfo;
using ::testing::Truly;
using ::testing::Value;
using ::testing::Values;
// Creates a result for the leaf matcher tests by running a kP0 test with the
// given input format and requested output format (and, for text output,
// whether unknown fields are to be printed) against a testee that answers
// `response`. The matchers never look at the input payload itself, only at
// its format, so the input is empty.
//
// The leaf matchers never touch the ResultLedger; the tests make sure of it by
// not installing one at all while they run. Only YieldsTest below installs
// one.
TestResult CreateResult(absl::string_view test_name, WireFormat input_format,
WireFormat output_format,
ConformanceResponse response) {
NiceMock<MockTestRunner> runner;
ON_CALL(runner, RunTest).WillByDefault(Return(response.SerializeAsString()));
internal::Testee testee(&runner);
internal::Test test = testee.CreateTest(test_name, TestPriority::kP0);
internal::InMemoryMessage message = [&] {
switch (input_format) {
case ::conformance::TEXT_FORMAT:
return std::move(test).ParseText(TestAllTypesProto2::descriptor(), "");
case ::conformance::JSON:
return std::move(test).ParseJson(TestAllTypesProto2::descriptor(), "");
default:
return std::move(test).ParseBinary(TestAllTypesProto2::descriptor(),
Wire());
}
}();
TestResult result = [&] {
switch (output_format) {
case ::conformance::TEXT_FORMAT:
return std::move(message).SerializeText();
case ::conformance::JSON:
return std::move(message).SerializeJson();
default:
return std::move(message).SerializeBinary();
}
}();
return result;
}
// Most tests only care about the requested output format; they use binary
// input.
TestResult CreateResult(absl::string_view test_name, WireFormat output_format,
ConformanceResponse response) {
return CreateResult(test_name, ::conformance::PROTOBUF, output_format,
std::move(response));
}
ConformanceResponse ProtobufPayload(Wire payload) {
ConformanceResponse response;
response.set_protobuf_payload(std::move(payload).str());
return response;
}
// Note: we deliberately don't use test_textproto.h here, whose EqualsProto()
// matcher ADL would find alongside our matchers via google::protobuf::Message.
ConformanceResponse ResponseFromText(absl::string_view textproto) {
ConformanceResponse response;
ABSL_CHECK(TextFormat::ParseFromString(textproto, &response)) << textproto;
return response;
}
template <class M>
std::string Describe(const M& m) {
return testing::DescribeMatcher<const TestResult&>(m, /*negation=*/false);
}
template <class M>
std::string DescribeNegation(const M& m) {
return testing::DescribeMatcher<const TestResult&>(m, /*negation=*/true);
}
// Runs `m` on `arg` once. The returned AssertionResult succeeds iff `m`
// matched and its message is the matcher's explanation; check both with
// Accepts()/Rejects() below, so that a wrong verdict is reported at the caller.
template <class M, class T>
testing::AssertionResult Explain(const M& m, const T& arg) {
testing::StringMatchResultListener listener;
const bool matched = testing::ExplainMatchResult(m, arg, &listener);
return (matched ? testing::AssertionSuccess() : testing::AssertionFailure())
<< listener.str();
}
// Matches an Explain() result whose matcher accepted the value and whose
// explanation matches `explanation` (a string or a matcher on std::string).
MATCHER_P(Accepts, explanation,
absl::StrCat(negation ? "isn't accepted, or is" : "is",
" accepted with an explanation that ",
testing::DescribeMatcher<std::string>(explanation))) {
if (!arg) {
*result_listener << "the matcher rejected the value: " << arg.message();
return false;
}
return testing::ExplainMatchResult(explanation, std::string(arg.message()),
result_listener);
}
// Matches an Explain() result whose matcher rejected the value and whose
// explanation matches `explanation` (a string or a matcher on std::string).
MATCHER_P(Rejects, explanation,
absl::StrCat(negation ? "isn't rejected, or is" : "is",
" rejected with an explanation that ",
testing::DescribeMatcher<std::string>(explanation))) {
if (arg) {
*result_listener << "the matcher accepted the value: " << arg.message();
return false;
}
return testing::ExplainMatchResult(explanation, std::string(arg.message()),
result_listener);
}
// ---------------------------------------------------------------------------
// PrintTo(TestResult)
// ---------------------------------------------------------------------------
// Runs the binary-to-binary test "foo" of the given priority against a testee
// that answers `response_textproto`.
TestResult ResultWithResponse(absl::string_view response_textproto,
TestPriority priority = TestPriority::kP0) {
NiceMock<MockTestRunner> runner;
ON_CALL(runner, RunTest)
.WillByDefault(
Return(ResponseFromText(response_textproto).SerializeAsString()));
internal::Testee testee(&runner);
return testee.CreateTest("foo", priority)
.ParseBinary(TestAllTypesProto2::descriptor(), Wire("wire"))
.SerializeBinary();
}
constexpr absl::string_view kPrintedRequiredTest =
R"(Required test "Required.Proto2.ProtobufInput.foo.ProtobufOutput")";
TEST(PrintTestResultTest, ParseError) {
EXPECT_EQ(PrintToString(
ResultWithResponse(R"pb(parse_error: "failed to parse")pb")),
absl::StrCat(kPrintedRequiredTest,
R"( with response {parse_error: "failed to parse"})"));
}
TEST(PrintTestResultTest, Skipped) {
EXPECT_EQ(
PrintToString(ResultWithResponse(R"pb(skipped: "skipped message")pb",
TestPriority::kP1)),
R"(Recommended test "Recommended.Proto2.ProtobufInput.foo.ProtobufOutput" )"
R"(with response {skipped: "skipped message"})");
}
TEST(PrintTestResultTest, EmptyResponse) {
EXPECT_EQ(PrintToString(ResultWithResponse("")),
absl::StrCat(kPrintedRequiredTest, " with response {}"));
}
TEST(PrintTestResultTest, ProtobufPayloadIsDecoded) {
EXPECT_EQ(
PrintToString(ResultWithResponse(R"pb(protobuf_payload: "\010\t")pb")),
absl::StrCat(kPrintedRequiredTest,
R"( with response {protobuf_payload: "\010\t"} )"
R"((decoded: {optional_int32: 9}))"));
}
TEST(PrintTestResultTest, UnparseableProtobufPayload) {
EXPECT_EQ(
PrintToString(ResultWithResponse(R"pb(protobuf_payload: "\001")pb")),
absl::StrCat(
kPrintedRequiredTest,
R"( with response {protobuf_payload: "\001"} (unparseable))"));
}
TEST(PrintTestResultTest, TruncatedProtobufPayloadIsDecodedInFull) {
// The raw bytes are cut after 200 bytes, but the message they decode to is
// printed whole.
TestAllTypesProto2 message;
message.set_optional_string(std::string(300, 'a'));
const std::string payload = message.SerializeAsString();
EXPECT_EQ(
PrintToString(ResultWithResponse(
absl::StrCat("protobuf_payload: \"", absl::CEscape(payload), "\""))),
absl::StrCat(kPrintedRequiredTest, " with response {protobuf_payload: \"",
absl::CEscape(payload.substr(0, 200)),
"...(truncated)\"} (decoded: {optional_string: \"",
std::string(300, 'a'), "\"})"));
}
TEST(PrintTestResultTest, LargePayloadsAreTruncated) {
std::string large_payload(300, 'a');
EXPECT_EQ(
PrintToString(ResultWithResponse(
absl::StrCat("text_payload: \"", large_payload, "\""))),
absl::StrCat(kPrintedRequiredTest, " with response {text_payload: \"",
std::string(200, 'a'), "...(truncated)\"}"));
}
// ---------------------------------------------------------------------------
// Descriptions
// ---------------------------------------------------------------------------
TEST(MatcherDescriptionTest, WhenParsed) {
EXPECT_EQ(Describe(WhenParsed(EqualsTextProto(R"pb(optional_int32: 9)pb"))),
R"(when parsed, equals text proto "optional_int32: 9")");
EXPECT_EQ(
DescribeNegation(WhenParsed(EqualsTextProto(R"pb(optional_int32: 9)pb"))),
R"(when parsed, doesn't equal text proto "optional_int32: 9")");
}
TEST(MatcherDescriptionTest, WhenParsedWithGenericMatcher) {
EXPECT_EQ(Describe(WhenParsed(_)), "when parsed, is anything");
EXPECT_EQ(
Describe(WhenParsed(Not(EqualsTextProto(R"pb(optional_int32: 9)pb")))),
R"(when parsed, doesn't equal text proto "optional_int32: 9")");
}
TEST(MatcherDescriptionTest, RawPayload) {
EXPECT_EQ(Describe(RawPayload(Wire("foo"))), R"(payload is equal to "foo")");
EXPECT_EQ(DescribeNegation(RawPayload(Wire("foo"))),
R"(payload isn't equal to "foo")");
EXPECT_EQ(Describe(RawPayload(VarintField(1, 2))),
R"(payload is equal to "\010\002")");
EXPECT_EQ(DescribeNegation(RawPayload(VarintField(1, 2))),
R"(payload isn't equal to "\010\002")");
}
TEST(MatcherDescriptionTest, EqualsTextProto) {
EXPECT_EQ(testing::DescribeMatcher<const Message&>(
EqualsTextProto(R"pb(optional_int32: 9)pb")),
"equals text proto \"optional_int32: 9\"");
EXPECT_EQ(testing::DescribeMatcher<const Message&>(
EqualsTextProto(R"pb(optional_int32: 9)pb"), /*negation=*/true),
"doesn't equal text proto \"optional_int32: 9\"");
}
TEST(MatcherDescriptionTest, EqualsBinaryProto) {
EXPECT_EQ(testing::DescribeMatcher<const Message&>(
EqualsBinaryProto(VarintField(1, 9))),
"equals binary proto \"\\010\\t\"");
EXPECT_EQ(testing::DescribeMatcher<const Message&>(
EqualsBinaryProto(Wire("\010\t")), /*negation=*/true),
"doesn't equal binary proto \"\\010\\t\"");
}
TEST(MatcherDescriptionTest, FailureMatchers) {
EXPECT_EQ(Describe(IsParseError()), "is a parse error");
EXPECT_EQ(DescribeNegation(IsParseError()), "is not a parse error");
EXPECT_EQ(Describe(IsSerializeError()), "is a serialize error");
EXPECT_EQ(DescribeNegation(IsSerializeError()), "is not a serialize error");
}
TEST(MatcherDescriptionTest, Yields) {
EXPECT_EQ(
Describe(Yields(IsParseError())),
"yields a result that is a parse error (or is an expected failure)");
EXPECT_EQ(DescribeNegation(Yields(IsParseError())),
"doesn't yield a result that is a parse error, nor an expected "
"failure");
EXPECT_EQ(Describe(Yields(AnyOf(IsParseError(), WhenParsed(_)))),
"yields a result that (is a parse error) or (when parsed, is "
"anything) (or is an expected failure)");
}
// ---------------------------------------------------------------------------
// Response handling shared by WhenParsed() and RawPayload()
// ---------------------------------------------------------------------------
// These responses fail before any payload is compared, so the expected payload
// is irrelevant.
TEST(PayloadMatcherTest, EmptyResponse) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF, ConformanceResponse());
EXPECT_THAT(Explain(WhenParsed(_), result),
Rejects("Response didn't have any field in the Response."));
EXPECT_THAT(Explain(RawPayload(Wire()), result),
Rejects("Response didn't have any field in the Response."));
}
struct ErrorResponseCase {
absl::string_view name;
absl::string_view response;
};
class PayloadMatcherErrorResponseTest
: public testing::TestWithParam<ErrorResponseCase> {};
TEST_P(PayloadMatcherErrorResponseTest, IsAFailureForWhenParsed) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(GetParam().response));
EXPECT_THAT(Explain(WhenParsed(_), result),
Rejects("Failed to parse input or produce output."));
}
TEST_P(PayloadMatcherErrorResponseTest, IsAFailureForPayload) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(GetParam().response));
EXPECT_THAT(Explain(RawPayload(Wire()), result),
Rejects("Failed to parse input or produce output."));
}
INSTANTIATE_TEST_SUITE_P(
ErrorResponses, PayloadMatcherErrorResponseTest,
Values(ErrorResponseCase{"ParseError", R"pb(parse_error: "foo")pb"},
ErrorResponseCase{"SerializeError", R"pb(serialize_error: "foo")pb"},
ErrorResponseCase{"RuntimeError", R"pb(runtime_error: "foo")pb"},
ErrorResponseCase{"TimeoutError", R"pb(timeout_error: "foo")pb"}),
[](const TestParamInfo<ErrorResponseCase>& info) {
return std::string(info.param.name);
});
TEST(PayloadMatcherTest, Skipped) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(skipped: "skipped message")pb"));
EXPECT_THAT(Explain(WhenParsed(_), result),
Rejects("the testee skipped the test: skipped message"));
EXPECT_THAT(Explain(RawPayload(Wire()), result),
Rejects("the testee skipped the test: skipped message"));
}
TEST(PayloadMatcherTest, WrongOutputFormat) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(json_payload: "{}")pb"));
EXPECT_THAT(
Explain(WhenParsed(_), result),
Rejects("Test was asked for PROTOBUF output but provided JSON instead."));
EXPECT_THAT(
Explain(RawPayload(Wire()), result),
Rejects("Test was asked for PROTOBUF output but provided JSON instead."));
}
TEST(PayloadMatcherTest, WrongOutputFormatText) {
TestResult result = CreateResult("foo", ::conformance::TEXT_FORMAT,
ProtobufPayload(VarintField(1, 2)));
EXPECT_THAT(
Explain(WhenParsed(EqualsTextProto(R"pb(optional_int32: 2)pb")), result),
Rejects("Test was asked for TEXT_FORMAT output but provided PROTOBUF "
"instead."));
}
TEST(PayloadMatcherTest, WrongOutputFormatJspb) {
TestResult result =
CreateResult("foo", ::conformance::JSON,
ResponseFromText(R"pb(jspb_payload: "[]")pb"));
EXPECT_THAT(
Explain(RawPayload(Wire()), result),
Rejects("Test was asked for JSON output but provided JSPB instead."));
}
// ---------------------------------------------------------------------------
// WhenParsed()
// ---------------------------------------------------------------------------
TEST(WhenParsedTest, Matches) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_THAT(
Explain(WhenParsed(EqualsTextProto(R"pb(optional_int32: 2)pb")), result),
Accepts(IsEmpty()));
}
TEST(WhenParsedTest, UnparseableProtobufPayload) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(Wire("\001")));
EXPECT_THAT(
Explain(WhenParsed(EqualsTextProto("")), result),
Rejects("Protobuf output we received from test was unparseable."));
}
TEST(WhenParsedTest, UnparseableTextPayload) {
TestResult result =
CreateResult("foo", ::conformance::TEXT_FORMAT,
ResponseFromText(R"pb(text_payload: "nonsense: 1")pb"));
EXPECT_THAT(
Explain(WhenParsed(EqualsTextProto("")), result),
Rejects("TEXT_FORMAT output we received from test was unparseable."));
}
TEST(WhenParsedTest, UnparseablePayloadFailsEvenForAnything) {
// The payload has to be decodable before any inner matcher gets a say.
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(Wire("\001")));
EXPECT_THAT(
Explain(WhenParsed(_), result),
Rejects("Protobuf output we received from test was unparseable."));
}
TEST(WhenParsedTest, JsonOutputIsNotSupported) {
TestResult result =
CreateResult("foo", ::conformance::JSON,
ResponseFromText(R"pb(json_payload: "{}")pb"));
EXPECT_THAT(
Explain(WhenParsed(_), result),
Rejects("WhenParsed is not supported for JSON output; use "
"RawPayload() to match the raw JSON text until JSON matching is "
"migrated to gtest (b/410122158)."));
}
TEST(WhenParsedTest, TextOutput) {
TestResult result = CreateResult(
"foo", ::conformance::TEXT_FORMAT,
ResponseFromText(R"pb(text_payload: "optional_int32: 9")pb"));
EXPECT_TRUE(
Value(result, WhenParsed(EqualsTextProto(R"pb(optional_int32: 9)pb"))));
}
TEST(WhenParsedTest, TextOutputFieldNumbersAccepted) {
// Testees asked to print unknown fields emit them by number, so field
// numbers are always accepted.
TestResult result =
CreateResult("foo", ::conformance::TEXT_FORMAT,
ResponseFromText(R"pb(text_payload: "1: 9")pb"));
EXPECT_TRUE(
Value(result, WhenParsed(EqualsTextProto(R"pb(optional_int32: 9)pb"))));
}
TEST(WhenParsedTest, EqualsTextProtoMismatch) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_THAT(
Explain(WhenParsed(EqualsTextProto(R"pb(optional_int32: 1)pb")), result),
Rejects("Output was not equivalent to reference message: "
"modified: optional_int32: 1 -> 2\n"));
}
TEST(WhenParsedTest, EqualsTextProtoMatchesNan) {
TestAllTypesProto2 message;
message.set_optional_float(std::nanf(""));
message.set_optional_double(std::nan(""));
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(Wire(message.SerializeAsString())));
EXPECT_TRUE(Value(result, WhenParsed(EqualsTextProto(R"pb(
optional_float: nan
optional_double: nan
)pb"))));
}
TEST(WhenParsedDeathTest, EqualsTextProtoUnparseableExpected) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_DEATH(
(void)Explain(WhenParsed(EqualsTextProto(R"pb(unknown: 1)pb")), result),
"Failed to parse expected text proto.*unknown: 1");
}
TEST(WhenParsedTest, EqualsBinaryProto) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
// Equivalent, but not byte-identical (over-long varint encoding).
EXPECT_TRUE(
Value(result, WhenParsed(EqualsBinaryProto(LongVarintField(1, 2, 1)))));
}
TEST(WhenParsedTest, EqualsBinaryProtoWithTextOutput) {
TestResult result = CreateResult(
"foo", ::conformance::TEXT_FORMAT,
ResponseFromText(R"pb(text_payload: "optional_int32: 9")pb"));
EXPECT_TRUE(Value(result, WhenParsed(EqualsBinaryProto(VarintField(1, 9)))));
}
TEST(WhenParsedTest, EqualsBinaryProtoMismatch) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_THAT(Explain(WhenParsed(EqualsBinaryProto(VarintField(1, 1))), result),
Rejects("Output was not equivalent to reference message: "
"modified: optional_int32: 1 -> 2\n"));
}
TEST(WhenParsedDeathTest, EqualsBinaryProtoUnparseableExpected) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_DEATH(
(void)Explain(WhenParsed(EqualsBinaryProto(Wire("\001"))), result),
"Failed to parse expected wire data");
}
TEST(WhenParsedTest, AcceptsAnyMessageMatcher) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_TRUE(Value(result, WhenParsed(_)));
EXPECT_TRUE(Value(
result,
WhenParsed(AllOf(EqualsTextProto(R"pb(optional_int32: 2)pb"),
Not(EqualsTextProto(R"pb(optional_int32: 3)pb"))))));
}
TEST(WhenParsedTest, InnerMatcherWithoutExplanation) {
// Inner matchers that don't explain themselves still get a useful failure
// message, including the decoded payload.
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_THAT(
Explain(WhenParsed(Not(EqualsTextProto(R"pb(optional_int32: 2)pb"))),
result),
Rejects("Expect: when parsed, doesn't equal text proto "
R"("optional_int32: 2", but got: {optional_int32: 2})"));
}
TEST(WhenParsedTest, ComposesWithGMock) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_TRUE(Value(result, Not(WhenParsed(EqualsTextProto("")))));
EXPECT_TRUE(Value(
result, AnyOf(IsParseError(),
WhenParsed(EqualsTextProto(R"pb(optional_int32: 2)pb")))));
EXPECT_TRUE(Value(result, AllOf(Not(IsParseError()), WhenParsed(_))));
}
// ---------------------------------------------------------------------------
// WhenParsedAs()
// ---------------------------------------------------------------------------
TEST(WhenParsedAsTest, DecodesBinaryPayloadAsTheGivenType) {
// Field 1001 is unknown to the test's type (TestAllTypesProto2) but is
// optional_int32 of the shadow type UnknownToTestAllTypes.
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1001, 7)));
EXPECT_THAT(Explain(WhenParsedAs<UnknownToTestAllTypes>(
EqualsTextProto(R"pb(optional_int32: 7)pb")),
result),
Accepts(IsEmpty()));
// Decoded as the test's own type the field stays unknown.
EXPECT_THAT(
Explain(WhenParsed(EqualsTextProto(R"pb(optional_int32: 7)pb")), result),
Rejects("Output was not equivalent to reference message: "
"added: 1001[0]: 7\n"
"deleted: optional_int32: 7\n"));
}
TEST(WhenParsedAsTest, TextOutputFieldNumbersAccepted) {
// A testee asked to print unknown fields emits them by number. The
// shadow type gives them names again.
TestResult result =
CreateResult("foo", ::conformance::TEXT_FORMAT,
ResponseFromText(R"pb(text_payload: "1001: 7")pb"));
EXPECT_THAT(Explain(WhenParsedAs<UnknownToTestAllTypes>(
EqualsTextProto(R"pb(optional_int32: 7)pb")),
result),
Accepts(IsEmpty()));
}
TEST(WhenParsedAsTest, FailureMessagesMatchWhenParsed) {
// With the test's own type as the override, WhenParsedAs() must behave
// exactly like WhenParsed(), failure messages included.
{
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(Wire("\001")));
EXPECT_THAT(
Explain(WhenParsedAs<TestAllTypesProto2>(EqualsTextProto("")), result),
Rejects("Protobuf output we received from test was unparseable."));
}
{
TestResult result =
CreateResult("foo", ::conformance::TEXT_FORMAT,
ResponseFromText(R"pb(text_payload: "nonsense: 1")pb"));
EXPECT_THAT(
Explain(WhenParsedAs<TestAllTypesProto2>(EqualsTextProto("")), result),
Rejects("TEXT_FORMAT output we received from test was unparseable."));
}
{
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_THAT(Explain(WhenParsedAs<TestAllTypesProto2>(
EqualsTextProto(R"pb(optional_int32: 1)pb")),
result),
Rejects("Output was not equivalent to reference message: "
"modified: optional_int32: 1 -> 2\n"));
EXPECT_THAT(
Explain(WhenParsedAs<TestAllTypesProto2>(
Not(EqualsTextProto(R"pb(optional_int32: 2)pb"))),
result),
Rejects("Expect: when parsed, doesn't equal text proto "
R"("optional_int32: 2", but got: {optional_int32: 2})"));
}
{
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(parse_error: "foo")pb"));
EXPECT_THAT(Explain(WhenParsedAs<TestAllTypesProto2>(_), result),
Rejects("Failed to parse input or produce output."));
}
}
TEST(MatcherDescriptionTest, WhenParsedAs) {
// The description (gtest's "Expected:" line, never a failure-list message)
// names the type the payload is decoded as.
EXPECT_EQ(Describe(WhenParsedAs<UnknownToTestAllTypes>(
EqualsTextProto(R"pb(optional_int32: 9)pb"))),
"when parsed as "
"protobuf_test_messages.proto2.UnknownToTestAllTypes, equals text "
R"(proto "optional_int32: 9")");
EXPECT_EQ(
DescribeNegation(WhenParsedAs<UnknownToTestAllTypes>(
EqualsTextProto(R"pb(optional_int32: 9)pb"))),
"when parsed as protobuf_test_messages.proto2.UnknownToTestAllTypes, "
R"(doesn't equal text proto "optional_int32: 9")");
}
// ---------------------------------------------------------------------------
// RawPayload()
// ---------------------------------------------------------------------------
TEST(RawPayloadTest, MatchesBytes) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
EXPECT_TRUE(Value(result, RawPayload(VarintField(1, 2))));
EXPECT_THAT(Explain(RawPayload(VarintField(1, 2)), result),
Accepts(IsEmpty()));
}
TEST(RawPayloadTest, BytesMismatch) {
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(VarintField(1, 2)));
// Equivalent messages, but not byte-identical. The failure message is the
// legacy one.
EXPECT_THAT(Explain(RawPayload(LongVarintField(1, 2, 1)), result),
Rejects("Output was not equivalent to reference message: "
"Expect: \\010\\202\\000, but got: \\010\\002"));
}
TEST(RawPayloadTest, UnparseableProtobufOutputFailsEvenForTheExactBytes) {
// Like the legacy runner's require_same_wire_format, binary output must
// decode as the test's message type.
TestResult result = CreateResult("foo", ::conformance::PROTOBUF,
ProtobufPayload(Wire("\001")));
EXPECT_THAT(
Explain(RawPayload(Wire("\001")), result),
Rejects("Protobuf output we received from test was unparseable."));
}
TEST(RawPayloadTest, TextOutput) {
TestResult result = CreateResult(
"foo", ::conformance::TEXT_FORMAT,
ResponseFromText(R"pb(text_payload: "optional_int32: 9")pb"));
EXPECT_TRUE(Value(result, RawPayload(Wire("optional_int32: 9"))));
// Text output isn't required to be parseable.
EXPECT_TRUE(Value(
CreateResult("bar", ::conformance::TEXT_FORMAT,
ResponseFromText(R"pb(text_payload: "nonsense: 1")pb")),
RawPayload(Wire("nonsense: 1"))));
}
TEST(RawPayloadTest, JsonOutput) {
// Raw payloads don't need to be decoded, so JSON works already.
TestResult result =
CreateResult("foo", ::conformance::JSON,
ResponseFromText(R"pb(json_payload: "{}")pb"));
EXPECT_TRUE(Value(result, RawPayload(Wire("{}"))));
}
// ---------------------------------------------------------------------------
// EqualsTextProto() / EqualsBinaryProto() used directly on messages
// ---------------------------------------------------------------------------
TEST(EqualsTextProtoTest, Success) {
TestAllTypesProto2 message;
message.set_optional_int32(9);
EXPECT_THAT(message, EqualsTextProto(R"pb(optional_int32: 9)pb"));
}
TEST(EqualsTextProtoTest, MatchesNan) {
TestAllTypesProto2 message;
message.set_optional_float(std::nanf(""));
EXPECT_THAT(message, EqualsTextProto(R"pb(optional_float: nan)pb"));
}
TEST(EqualsTextProtoTest, Failure) {
TestAllTypesProto2 message;
message.set_optional_float(std::nanf(""));
auto matcher = EqualsTextProto(R"pb(optional_float: 1.0)pb");
EXPECT_THAT(Explain(matcher, message),
Rejects("Output was not equivalent to reference message: "
"modified: optional_float: 1 -> nan\n"));
EXPECT_THAT(message, Not(matcher));
}
TEST(EqualsTextProtoTest, WorksOnBaseMessage) {
TestAllTypesProto2 message;
message.set_optional_int32(9);
const Message& base = message;
EXPECT_THAT(base, EqualsTextProto(R"pb(optional_int32: 9)pb"));
EXPECT_THAT(base, Not(EqualsTextProto(R"pb(optional_int32: 8)pb")));
}
TEST(EqualsTextProtoDeathTest, ParseFailure) {
TestAllTypesProto2 message;
EXPECT_DEATH((void)Explain(EqualsTextProto(R"pb(unknown: 1.0)pb"), message),
"Failed to parse expected text proto.*unknown: 1.0");
}
TEST(EqualsBinaryProtoTest, Success) {
TestAllTypesProto2 message;
message.set_optional_int32(9);
EXPECT_THAT(message, EqualsBinaryProto(VarintField(1, 9)));
EXPECT_THAT(message, EqualsBinaryProto(Wire("\010\t")));
}
TEST(EqualsBinaryProtoTest, Failure) {
TestAllTypesProto2 message;
message.set_optional_int32(9);
auto matcher = EqualsBinaryProto(VarintField(1, 8));
EXPECT_THAT(Explain(matcher, message),
Rejects("Output was not equivalent to reference message: "
"modified: optional_int32: 8 -> 9\n"));
EXPECT_THAT(message, Not(matcher));
}
TEST(EqualsBinaryProtoDeathTest, ParseFailure) {
TestAllTypesProto2 message;
EXPECT_DEATH((void)Explain(EqualsBinaryProto(Wire("\001")), message),
"Failed to parse expected wire data");
}
// ---------------------------------------------------------------------------
// Failure matchers
// ---------------------------------------------------------------------------
TEST(FailureMatcherTest, ParseErrorMatches) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(parse_error: "failed to parse")pb"));
EXPECT_THAT(Explain(IsParseError(), result), Accepts(IsEmpty()));
EXPECT_FALSE(Value(result, IsSerializeError()));
}
TEST(FailureMatcherTest, ParseErrorWithPayload) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF, ProtobufPayload(Wire()));
EXPECT_THAT(Explain(IsParseError(), result),
Rejects("Should have failed to parse, but didn't."));
}
TEST(FailureMatcherTest, ParseErrorEmptyResponse) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF, ConformanceResponse());
EXPECT_THAT(Explain(IsParseError(), result),
Rejects("Should have failed to parse, but didn't."));
}
TEST(FailureMatcherTest, ParseErrorSerializeError) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(serialize_error: "x")pb"));
EXPECT_THAT(Explain(IsParseError(), result),
Rejects("Should have failed to parse, but didn't."));
}
TEST(FailureMatcherTest, ParseErrorRuntimeError) {
TestResult result =
CreateResult("foo", /*input_format=*/::conformance::PROTOBUF,
/*output_format=*/::conformance::PROTOBUF,
ResponseFromText(R"pb(runtime_error: "x")pb"));
EXPECT_THAT(
Explain(IsParseError(), result),
Rejects("Should have failed to parse, but raised an error instead."));
}
TEST(FailureMatcherTest, ParseErrorSkipped) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(skipped: "skipped message")pb"));
EXPECT_THAT(Explain(IsParseError(), result),
Rejects("the testee skipped the test: skipped message"));
}
TEST(FailureMatcherTest, SerializeErrorMatches) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(serialize_error: "failed")pb"));
EXPECT_THAT(Explain(IsSerializeError(), result), Accepts(IsEmpty()));
EXPECT_FALSE(Value(result, IsParseError()));
}
TEST(FailureMatcherTest, SerializeErrorWithPayload) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF, ProtobufPayload(Wire()));
EXPECT_THAT(Explain(IsSerializeError(), result),
Rejects("Should have failed to serialize, but didn't."));
}
TEST(FailureMatcherTest, SerializeErrorRuntimeError) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(runtime_error: "x")pb"));
EXPECT_THAT(
Explain(IsSerializeError(), result),
Rejects("Should have failed to serialize, but raised an error instead."));
}
TEST(FailureMatcherTest, ComposesWithGMock) {
TestResult result =
CreateResult("foo", ::conformance::PROTOBUF,
ResponseFromText(R"pb(parse_error: "failed to parse")pb"));
EXPECT_TRUE(Value(result, Not(IsSerializeError())));
EXPECT_TRUE(Value(result, AnyOf(IsSerializeError(), IsParseError())));
EXPECT_FALSE(Value(result, AllOf(IsSerializeError(), IsParseError())));
}
// ---------------------------------------------------------------------------
// Yields()
// ---------------------------------------------------------------------------
// A snapshot of the ResultLedger counters, for concise assertions.
struct Counts {
int expected_successes = 0;
int expected_failures = 0;
int unexpected_successes = 0;
int unexpected_failures = 0;
int skipped = 0;
int listed_skips = 0;
int tolerated_failures = 0;
bool operator==(const Counts& other) const {
return expected_successes == other.expected_successes &&
expected_failures == other.expected_failures &&
unexpected_successes == other.unexpected_successes &&
unexpected_failures == other.unexpected_failures &&
skipped == other.skipped && listed_skips == other.listed_skips &&
tolerated_failures == other.tolerated_failures;
}
friend void PrintTo(const Counts& counts, std::ostream* os) {
*os << "{expected_successes: " << counts.expected_successes
<< ", expected_failures: " << counts.expected_failures
<< ", unexpected_successes: " << counts.unexpected_successes
<< ", unexpected_failures: " << counts.unexpected_failures
<< ", skipped: " << counts.skipped
<< ", listed_skips: " << counts.listed_skips
<< ", tolerated_failures: " << counts.tolerated_failures << "}";
}
};
Counts GetCounts(const ResultLedger& ledger) {
return Counts{
/*expected_successes=*/ledger.expected_successes(),
/*expected_failures=*/ledger.expected_failures(),
/*unexpected_successes=*/ledger.unexpected_successes(),
/*unexpected_failures=*/ledger.unexpected_failures(),
/*skipped=*/ledger.skipped(),
/*listed_skips=*/ledger.listed_skips(),
/*tolerated_failures=*/
ledger.tolerated_failures(),
};
}
// The names Run() below produces for the test named "foo".
constexpr absl::string_view kP0Foo =
"Required.Proto2.ProtobufInput.foo.ProtobufOutput";
constexpr absl::string_view kP1Foo =
"Recommended.Proto2.ProtobufInput.foo.ProtobufOutput";
// Canned testee responses.
constexpr absl::string_view kPayload2 = R"pb(protobuf_payload: "\010\002")pb";
constexpr absl::string_view kParseError = R"pb(parse_error: "bad input")pb";
constexpr absl::string_view kSerializeError =
R"pb(serialize_error: "can't serialize")pb";
constexpr absl::string_view kRuntimeError = R"pb(runtime_error: "crashed")pb";
constexpr absl::string_view kSkipped = R"pb(skipped: "not supported")pb";
// The legacy failure messages the canned responses lead to.
constexpr absl::string_view kMismatch1 =
"Output was not equivalent to reference message: "
"modified: optional_int32: 1 -> 2\n";
constexpr absl::string_view kNotAParseError =
"Should have failed to parse, but didn't.";
// Writes `content` to a fresh file under the test's temporary directory and
// returns its path.
std::string WriteTempFile(absl::string_view name, absl::string_view content) {
const std::string path = absl::StrCat(
testing::TempDir(), "/",
testing::UnitTest::GetInstance()->current_test_info()->name(), ".", name);
std::ofstream file(path);
file << content;
file.close();
ABSL_CHECK(file.good()) << path;
return path;
}
std::string ReadFile(absl::string_view path) {
std::ifstream file{std::string(path)};
ABSL_CHECK(file.is_open()) << path;
return std::string(std::istreambuf_iterator<char>(file), {});
}
class YieldsTest : public testing::Test {
protected:
YieldsTest() { internal::global_ledger = &ledger_; }
~YieldsTest() override {
ledger_.Finalize().IgnoreError();
internal::global_ledger = nullptr;
}
// Makes the testee answer every test with `response`.
void RespondWith(absl::string_view response) {
ON_CALL(runner_, RunTest)
.WillByDefault(Return(ResponseFromText(response).SerializeAsString()));
}
// Runs the binary-to-binary test `name` (of the given priority) against a
// testee answering `response`.
TestResult Run(absl::string_view response,
TestPriority priority = TestPriority::kP0,
absl::string_view name = "foo") {
RespondWith(response);
return testee_.CreateTest(name, priority)
.ParseBinary(TestAllTypesProto2::descriptor(), Wire("wire"))
.SerializeBinary();
}
// Like Run(), but asks for text format output.
TestResult RunText(absl::string_view response,
TestPriority priority = TestPriority::kP0,
absl::string_view name = "foo") {
RespondWith(response);
return testee_.CreateTest(name, priority)
.ParseBinary(TestAllTypesProto2::descriptor(), Wire("wire"))
.SerializeText();
}
// Lists `test_name` in the failure list with `message`, as a line of a
// failure list file would.
void AddToFailureList(absl::string_view test_name,
absl::string_view message) {
ASSERT_THAT(ledger_.LoadFailureList(WriteTempFile(
absl::StrCat("failure_list_", ++failure_lists_, ".txt"),
absl::StrCat(test_name, " # ", message, "\n"))),
IsOk());
}
ResultLedger ledger_;
NiceMock<MockTestRunner> runner_;
internal::Testee testee_{&runner_};
int failure_lists_ = 0;
};
TEST_F(YieldsTest, UnlistedSuccessPasses) {
auto matcher =
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 2)pb")));
EXPECT_THAT(Explain(matcher, Run(kPayload2)), Accepts(IsEmpty()));
EXPECT_THAT(Run(kPayload2, TestPriority::kP0, "other"), matcher);
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/2}));
EXPECT_THAT(ledger_.Finalize(), IsOk());
}
TEST_F(YieldsTest, UnlistedFailureFails) {
auto matcher =
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 1)pb")));
TestResult result = Run(kPayload2);
EXPECT_THAT(Explain(matcher, result),
Rejects(absl::StrCat(kMismatch1,
"\nUnexpected failure for test: ", kP0Foo)));
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, matcher),
"modified: optional_int32: 1 -> 2");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
// The recorded message is the legacy one, as the failure list would get it.
EXPECT_THAT(ledger_.UnexpectedFailures(),
ElementsAre(FieldsAre(kP0Foo,
"Output was not equivalent to reference "
"message: modified: optional_int32: 1 -> 2",
absl::nullopt)));
}
TEST_F(YieldsTest, UnlistedFailureIsCountedOnceDespiteGtestRetrying) {
// gtest evaluates a matcher a second time to explain a failed assertion.
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(Run(kPayload2), Yields(IsParseError())),
"Should have failed to parse, but didn't.");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
EXPECT_THAT(ledger_.UnexpectedFailures(),
ElementsAre(FieldsAre(kP0Foo, kNotAParseError, absl::nullopt)));
}
TEST_F(YieldsTest, StaleVerdictIsNeverReplayedForAnotherResult) {
// A long-lived matcher that checked a result which has since been destroyed
// must not replay that verdict for a different result, even one that reuses
// the same address: the replay is keyed to the result's name as well.
auto first_matcher = Yields(IsParseError());
{
TestResult first = Run(kPayload2, TestPriority::kP0, "first");
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(first, first_matcher),
"Should have failed to parse, but didn't.");
}
TestResult second = Run(kParseError, TestPriority::kP0, "second");
EXPECT_THAT(second, Yields(IsParseError()));
EXPECT_THAT(Explain(first_matcher, second),
Rejects(HasSubstr("was already checked")));
EXPECT_EQ(GetCounts(ledger_),
(Counts{/*expected_successes=*/1, /*expected_failures=*/0,
/*unexpected_successes=*/0, /*unexpected_failures=*/1}));
}
TEST_F(YieldsTest, PayloadBytesMismatchRecordsTheLegacyOctalMessage) {
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(Run(kPayload2), Yields(RawPayload(VarintField(1, 1)))),
"Output was not equivalent to reference message: "
"Expect: \\010\\001, but got: \\010\\002");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
EXPECT_THAT(
ledger_.UnexpectedFailures(),
ElementsAre(FieldsAre(kP0Foo,
"Output was not equivalent to reference message: "
"Expect: \\010\\001, but got: \\010\\002",
absl::nullopt)));
}
TEST_F(YieldsTest, TextFormatOutput) {
constexpr absl::string_view kText2 =
R"pb(text_payload: "optional_int32: 2")pb";
EXPECT_THAT(RunText(kText2),
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 2)pb"))));
EXPECT_THAT(RunText(kText2, TestPriority::kP0, "raw"),
Yields(RawPayload(Wire("optional_int32: 2"))));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/2}));
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(
RunText(kText2, TestPriority::kP0, "mismatch"),
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 1)pb")))),
"modified: optional_int32: 1 -> 2");
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(RunText(R"pb(text_payload: "nonsense: 1")pb",
TestPriority::kP0, "unparseable"),
Yields(WhenParsed(_))),
"TEXT_FORMAT output we received from test was unparseable.");
EXPECT_EQ(GetCounts(ledger_),
(Counts{/*expected_successes=*/2, /*expected_failures=*/0,
/*unexpected_successes=*/0, /*unexpected_failures=*/2}));
EXPECT_THAT(
ledger_.UnexpectedFailures(),
ElementsAre(
FieldsAre("Required.Proto2.ProtobufInput.mismatch.TextFormatOutput",
"Output was not equivalent to reference message: "
"modified: optional_int32: 1 -> 2",
absl::nullopt),
FieldsAre(
"Required.Proto2.ProtobufInput.unparseable.TextFormatOutput",
"TEXT_FORMAT output we received from test was unparseable.",
absl::nullopt)));
}
TEST_F(YieldsTest, ListedFailureWithMatchingMessagePasses) {
AddToFailureList(kP0Foo, kMismatch1);
absl::ScopedMockLog log;
EXPECT_CALL(log, Log).Times(AnyNumber());
// (The message is logged without its trailing newline.)
EXPECT_CALL(
log,
Log(absl::LogSeverity::kInfo, _,
Eq(absl::StrCat("Ignoring expected failure for test ", kP0Foo, ": ",
absl::StripTrailingAsciiWhitespace(kMismatch1)))))
.Times(1);
log.StartCapturingLogs();
auto matcher =
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 1)pb")));
TestResult result = Run(kPayload2);
EXPECT_THAT(Explain(matcher, result), Accepts(IsEmpty()));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/1}));
EXPECT_THAT(ledger_.UnexpectedFailures(), IsEmpty());
EXPECT_THAT(ledger_.Finalize(), IsOk());
}
TEST_F(YieldsTest, ListedFailureWithMessagePrefixPasses) {
AddToFailureList(kP0Foo, "Output was not equivalent to reference");
EXPECT_THAT(Run(kPayload2),
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 1)pb"))));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/1}));
EXPECT_THAT(ledger_.Finalize(), IsOk());
}
TEST_F(YieldsTest, ListedFailureViaWildcardPasses) {
AddToFailureList("Required.*.ProtobufInput.foo.ProtobufOutput",
kNotAParseError);
EXPECT_THAT(Run(kPayload2), Yields(IsParseError()));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/1}));
EXPECT_THAT(ledger_.Finalize(), IsOk());
}
TEST_F(YieldsTest, ListedFailureWithDifferentMessageFails) {
AddToFailureList(kP0Foo, "Some other message");
auto matcher = Yields(IsParseError());
TestResult result = Run(kPayload2);
EXPECT_THAT(
Explain(matcher, result),
Rejects(absl::StrCat(
kNotAParseError, "\nUnexpected failure message for test: ", kP0Foo,
" expected: Some other message actual: ", kNotAParseError)));
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, matcher),
"Unexpected failure message for test");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
EXPECT_THAT(ledger_.UnexpectedFailures(),
ElementsAre(FieldsAre(kP0Foo, kNotAParseError, absl::nullopt)));
}
TEST_F(YieldsTest, ListedSuccessFails) {
AddToFailureList(kP0Foo, kNotAParseError);
auto matcher =
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 2)pb")));
TestResult result = Run(kPayload2);
// The legacy runner's wording, including the matched entry.
EXPECT_THAT(
Explain(matcher, result),
Rejects(absl::StrCat("test ", kP0Foo, " (matched to ", kP0Foo,
") is in the failure list, but test succeeded. "
"Remove its match from the failure list.")));
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, matcher),
"is in the failure list, but test succeeded");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/1}));
EXPECT_THAT(
ledger_.UnexpectedSuccesses(),
ElementsAre(FieldsAre(kP0Foo, kNotAParseError, Optional(kP0Foo))));
}
TEST_F(YieldsTest, ListedSuccessViaWildcardNamesTheWildcard) {
AddToFailureList("Required.*.ProtobufInput.foo.ProtobufOutput", "");
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(Run(kParseError), Yields(IsParseError())),
"test Required.Proto2.ProtobufInput.foo.ProtobufOutput (matched to "
"Required.*.ProtobufInput.foo.ProtobufOutput) is in the failure list, "
"but test succeeded. Remove its match from the failure list.");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/1}));
}
TEST_F(YieldsTest, P1FailureIsToleratedWhenNotEnforced) {
ledger_.set_enforcement_level(kP0);
absl::ScopedMockLog log;
EXPECT_CALL(log, Log).Times(AnyNumber());
EXPECT_CALL(
log,
Log(absl::LogSeverity::kWarning, _,
Eq(absl::StrCat("WARNING, test=", kP1Foo, ": ", kNotAParseError))))
.Times(1);
log.StartCapturingLogs();
auto matcher = Yields(IsParseError());
TestResult result = Run(kPayload2, TestPriority::kP1);
EXPECT_THAT(Explain(matcher, result), Accepts(IsEmpty()));
// It's neither a skip nor a failure.
EXPECT_EQ(GetCounts(ledger_),
(Counts{/*expected_successes=*/0, /*expected_failures=*/0,
/*unexpected_successes=*/0, /*unexpected_failures=*/0,
/*skipped=*/0, /*listed_skips=*/0,
/*tolerated_failures=*/1}));
EXPECT_THAT(ledger_.UnexpectedFailures(), IsEmpty());
EXPECT_THAT(ledger_.Finalize(), IsOk());
}
TEST_F(YieldsTest, P1FailureFailsWhenEnforced) {
ledger_.set_enforcement_level(kP1);
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(Run(kPayload2, TestPriority::kP1), Yields(IsParseError())),
"Should have failed to parse, but didn't.");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
EXPECT_THAT(ledger_.UnexpectedFailures(),
ElementsAre(FieldsAre(kP1Foo, kNotAParseError, absl::nullopt)));
}
TEST_F(YieldsTest, ListedP1FailureIsTrackedEvenWhenNotEnforced) {
// A kP1 test that's already in the failure list keeps being tracked
// there, so that the list can't go stale unnoticed.
ledger_.set_enforcement_level(kP0);
AddToFailureList(kP1Foo, kNotAParseError);
EXPECT_THAT(Run(kPayload2, TestPriority::kP1), Yields(IsParseError()));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/1}));
EXPECT_THAT(ledger_.Finalize(), IsOk());
}
TEST_F(YieldsTest,
ListedP1FailureWithDifferentMessageFailsEvenWhenNotEnforced) {
ledger_.set_enforcement_level(kP0);
AddToFailureList(kP1Foo, "Some other message");
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(Run(kPayload2, TestPriority::kP1), Yields(IsParseError())),
"Unexpected failure message for test");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
}
TEST_F(YieldsTest, P1SuccessPasses) {
ledger_.set_enforcement_level(kP0);
EXPECT_THAT(Run(kParseError, TestPriority::kP1), Yields(IsParseError()));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/1}));
}
TEST_F(YieldsTest, UnlistedSkipPasses) {
absl::ScopedMockLog log;
EXPECT_CALL(log, Log).Times(AnyNumber());
EXPECT_CALL(
log, Log(absl::LogSeverity::kInfo, _,
Eq(absl::StrCat("Skipping test ", kP0Foo, ": not supported"))))
.Times(1);
log.StartCapturingLogs();
// The inner matcher never gets to see a skipped result.
bool inner_called = false;
auto matcher = Yields(Truly([&](const TestResult&) {
inner_called = true;
return false;
}));
TestResult result = Run(kSkipped);
EXPECT_THAT(Explain(matcher, result), Accepts(IsEmpty()));
EXPECT_FALSE(inner_called);
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/0,
/*skipped=*/1}));
EXPECT_THAT(ledger_.Finalize(), IsOk());
}
TEST_F(YieldsTest, ListedSkipFailsOnce) {
// Deliberately stricter than the legacy runner, which passed this case: a
// listed test the testee skips fails, but only here. The entry counts as
// seen (a skip says nothing about whether it is still needed), so Finalize()
// doesn't report it a second time and --fix keeps it.
ASSERT_THAT(ledger_.LoadFailureList(WriteTempFile(
"failure_list.txt", absl::StrCat(kP0Foo, " # abc\n"))),
IsOk());
auto matcher = Yields(IsParseError());
TestResult result = Run(kSkipped);
// The failure names the matched entry and the remedy, like an unexpected
// success does.
EXPECT_THAT(Explain(matcher, result),
Rejects(absl::StrCat(
"test ", kP0Foo, " (matched to ", kP0Foo,
") is in the failure list but was skipped by the testee: "
"not supported. Remove its match from the failure list.")));
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, matcher),
"is in the failure list but was skipped");
// The ledger records it as a listed skip, not as an unexpected failure.
EXPECT_EQ(GetCounts(ledger_),
(Counts{/*expected_successes=*/0, /*expected_failures=*/0,
/*unexpected_successes=*/0, /*unexpected_failures=*/0,
/*skipped=*/1, /*listed_skips=*/1}));
EXPECT_THAT(ledger_.ListedSkips(), ElementsAre(Pair(kP0Foo, kP0Foo)));
EXPECT_THAT(ledger_.UnexpectedFailures(), IsEmpty());
EXPECT_THAT(ledger_.Finalize(), IsOk());
const std::string fixed = WriteTempFile("fixed.txt", "");
ASSERT_THAT(ledger_.SaveFailureList(fixed), IsOk());
EXPECT_EQ(ReadFile(fixed), absl::StrCat(kP0Foo, " # abc\n"));
}
TEST_F(YieldsTest, RuntimeErrorKeepsTheInnerMatchersFailureMessage) {
// The legacy runner reports a runtime error where a parse error was expected
// with a dedicated message, which the failure lists rely on.
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(Run(kRuntimeError), Yields(IsParseError())),
"Should have failed to parse, but raised an error instead.");
EXPECT_THAT(
ledger_.UnexpectedFailures(),
ElementsAre(FieldsAre(
kP0Foo, "Should have failed to parse, but raised an error instead.",
absl::nullopt)));
}
TEST_F(YieldsTest, ListedRuntimeErrorIsAnExpectedFailure) {
AddToFailureList(kP0Foo,
"Should have failed to parse, but raised an error instead.");
EXPECT_THAT(Run(kRuntimeError), Yields(IsParseError()));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/1}));
EXPECT_THAT(ledger_.Finalize(), IsOk());
}
TEST_F(YieldsTest, AnyOfMatchesEitherLeg) {
auto matcher = Yields(
AnyOf(IsParseError(), WhenParsed(EqualsBinaryProto(VarintField(1, 2)))));
EXPECT_THAT(Run(kParseError, TestPriority::kP0, "first"), matcher);
EXPECT_THAT(Run(kPayload2, TestPriority::kP0, "second"), matcher);
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/2}));
}
TEST_F(YieldsTest, AnyOfFailsWhenNoLegMatches) {
auto matcher = Yields(
AnyOf(IsParseError(), WhenParsed(EqualsBinaryProto(VarintField(1, 2)))));
TestResult result = Run(kSerializeError);
EXPECT_THAT(
Explain(matcher, result),
Rejects(AllOf(
HasSubstr("Should have failed to parse, but didn't."),
HasSubstr("Failed to parse input or produce output."),
HasSubstr(absl::StrCat("Unexpected failure for test: ", kP0Foo)))));
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, matcher), "Unexpected failure");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
}
TEST_F(YieldsTest, NotComposes) {
EXPECT_THAT(Run(kPayload2, TestPriority::kP0, "first"),
Yields(Not(IsParseError())));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/1}));
auto matcher = Yields(Not(IsParseError()));
TestResult result = Run(kParseError, TestPriority::kP0, "second");
// Not() has no explanation of its own, so the description is used.
EXPECT_THAT(Explain(matcher, result),
Rejects(absl::StrCat(
"which doesn't match (is not a parse error)",
"\nUnexpected failure for test: ",
"Required.Proto2.ProtobufInput.second.ProtobufOutput")));
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, matcher),
"which doesn't match (is not a parse error)");
EXPECT_EQ(GetCounts(ledger_),
(Counts{/*expected_successes=*/1, /*expected_failures=*/0,
/*unexpected_successes=*/0, /*unexpected_failures=*/1}));
}
TEST_F(YieldsTest, CheckingAPassedResultAgainFails) {
// Whatever the second matcher is, including the very same object.
auto matcher = Yields(IsParseError());
TestResult result = Run(kParseError);
EXPECT_THAT(result, matcher);
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(result, matcher),
"TestResult for Required.Proto2.ProtobufInput.foo.ProtobufOutput was "
"already checked; each result may be checked exactly once");
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, Yields(IsParseError())),
"was already checked");
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, Yields(Not(IsParseError()))),
"was already checked");
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/1}));
}
TEST_F(YieldsTest, CheckingAFailedResultAgainReplaysTheFailure) {
// gtest re-evaluates a failing matcher, through the same matcher object, to
// explain the failure. The matcher replays its own failed verdict, so the
// ledger hears about the test only once. Any other matcher finds the
// result already checked.
auto matcher = Yields(IsParseError());
TestResult result = Run(kPayload2);
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(result, matcher),
"Should have failed to parse, but didn't.");
EXPECT_THAT(Explain(matcher, result),
Rejects(absl::StrCat(kNotAParseError,
"\nUnexpected failure for test: ", kP0Foo)));
EXPECT_THAT(Explain(Yields(IsParseError()), result),
Rejects(HasSubstr("was already checked")));
EXPECT_THAT(Explain(Yields(WhenParsed(_)), result),
Rejects(HasSubstr("was already checked")));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
EXPECT_THAT(ledger_.UnexpectedFailures(),
ElementsAre(FieldsAre(kP0Foo, kNotAParseError, absl::nullopt)));
}
TEST_F(YieldsTest, ACopyOfACheckedResultIsAlreadyChecked) {
TestResult original = Run(kPayload2);
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(original, Yields(IsParseError())),
"Should have failed to parse, but didn't.");
// The ledger knows the test by name, so a copy of the result is checked
// too and the ledger hears nothing more.
TestResult copy = original;
EXPECT_THAT(Explain(Yields(IsParseError()), copy),
Rejects(HasSubstr("was already checked")));
EXPECT_EQ(GetCounts(ledger_), (Counts{/*expected_successes=*/0,
/*expected_failures=*/0,
/*unexpected_successes=*/0,
/*unexpected_failures=*/1}));
}
TEST_F(YieldsTest, SameMatcherCanCheckSeveralResults) {
auto matcher = Yields(IsParseError());
EXPECT_THAT(Run(kParseError, TestPriority::kP0, "first"), matcher);
EXPECT_THAT(Run(kParseError, TestPriority::kP0, "second"), matcher);
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(Run(kPayload2, TestPriority::kP0, "third"), matcher),
"Should have failed to parse, but didn't.");
EXPECT_EQ(GetCounts(ledger_),
(Counts{/*expected_successes=*/2, /*expected_failures=*/0,
/*unexpected_successes=*/0, /*unexpected_failures=*/1}));
}
} // namespace
} // namespace conformance
} // namespace protobuf
} // namespace google