#ifndef GOOGLE_PROTOBUF_CONFORMANCE_TESTEE_H__ #define GOOGLE_PROTOBUF_CONFORMANCE_TESTEE_H__ #include #include #include "absl/container/flat_hash_set.h" #include "absl/strings/string_view.h" #include "binary_wireformat.h" #include "conformance/conformance.pb.h" #include "test_runner.h" #include "google/protobuf/descriptor.h" // This file defines the APIs used by conformance tests to interact with // testees. The structure of these APIs are intentionally decoupled from the // runner/testee protocol (which are used to implement them), in order to // maximize their flexibility in tests. // // Tests should not ever need to name any of these types directly, but will // obtain a Test object pointing to the global testee and pass the final // TestResult to one of our matchers. // // Example: // // EXPECT_THAT(RequiredTest() // .ParseBinary(Wire(LengthPrefixedField(1, "foo")) // .SerializeText({.print_unknown_fields = true}), // ParsedPayload(EqualsProto("pb(1: "foo")pb"))); // TODO Possible future APIs to expand conformance coverage: // - Add ClearUnknownFields() to InMemoryMessage // - Add MergeFrom() method to InMemoryMessage to merge raw binary // - Remove && qualifiers on Parse* and add InMemoryMessage::Merge that merges // two parsed messages // - Add ConstructEmpty methods on Test // - Add reflection methods to InMemoryMessage (e.g. Get/Set/Add, and a Has that // returns TestResult) // - Add a SerializeIntoMemory method that allows further action on the results // of serialization instead of immediately returning it namespace google { namespace protobuf { namespace conformance { namespace internal { // The strictness of a test. Required tests will fail the test suite if they // fail. Recommended tests will not fail the test suite if they fail, but will // be reported as a warning. enum class TestStrictness { kRequired = 0, kRecommended = 1, }; // The final result of a conformance test, to be processed by a matcher. class TestResult { public: // The name of the test that was run, useful for failure matching and // reporting. absl::string_view name() const { return test_name_; } // The strictness of the test. TestStrictness strictness() const { return strictness_; } // The type of the message that was tested, needed for parsing. const Descriptor* type() const { return type_; } // The format of the output that was requested. ::conformance::WireFormat format() const { return format_; } // The conformance response that was returned from the testee. This will // contain either the resulting payload or an error message. const ::conformance::ConformanceResponse& response() const { return response_; } private: TestResult(absl::string_view test_name, TestStrictness strictness, const Descriptor* type, ::conformance::WireFormat format, ::conformance::ConformanceResponse response) : test_name_(test_name), strictness_(strictness), type_(type), format_(format), response_(std::move(response)) {} friend class InMemoryMessage; std::string test_name_; TestStrictness strictness_; const Descriptor* type_; ::conformance::WireFormat format_; ::conformance::ConformanceResponse response_; }; // Options for serializing text format. struct TextSerializationOptions { bool print_unknown_fields = false; }; // This class represents a message held in memory by the testee that can be // manipulated in various ways. class InMemoryMessage { public: ~InMemoryMessage() = default; // Serialize the message back in any of our supported formats. These all // consume the message. TestResult SerializeBinary() &&; TestResult SerializeText(TextSerializationOptions options = {}) &&; TestResult SerializeJson() &&; private: InMemoryMessage(class Testee* testee, absl::string_view name, TestStrictness strictness, const Descriptor* type, ::conformance::ConformanceRequest request) : testee_(testee), name_(name), strictness_(strictness), type_(type), request_(std::move(request)) {} friend class Test; TestResult SerializeImpl(::conformance::WireFormat format); class Testee* testee_; std::string name_; TestStrictness strictness_; const Descriptor* type_; ::conformance::ConformanceRequest request_; }; // Options for parsing JSON. struct JsonParseOptions { bool ignore_unknown_fields = false; }; // This class represents a single test case representing some interaction with // the testee. The end result of a test should be a single TestResult. class Test { public: ~Test() = default; // Parse the message from one of our supported formats into an in-memory // message for further processing. InMemoryMessage ParseBinary(const Descriptor* type, Wire input) &&; InMemoryMessage ParseText(const Descriptor* type, absl::string_view input) &&; InMemoryMessage ParseJson(const Descriptor* type, absl::string_view input, JsonParseOptions options = {}) &&; private: Test(class Testee* testee, absl::string_view name, TestStrictness strictness) : testee_(testee), name_(name), strictness_(strictness) {} friend class Testee; class Testee* testee_; std::string name_; TestStrictness strictness_; }; // This class represents an abstraction of the testee. It is used to // create Test objects that can be used to interact further for testing. class Testee { public: explicit Testee(ConformanceTestRunner* runner) : runner_(runner) {} Test CreateTest(absl::string_view name, TestStrictness strictness) { return Test(this, name, strictness); } private: ::conformance::ConformanceResponse Run( absl::string_view test_name, const ::conformance::ConformanceRequest& request); friend class InMemoryMessage; ConformanceTestRunner* runner_; absl::flat_hash_set test_names_ran_; }; } // namespace internal } // namespace conformance } // namespace protobuf } // namespace google #endif // GOOGLE_PROTOBUF_CONFORMANCE_TESTEE_H__