blob: 3bff3c5c48c3acfe651cb8a93a645b27f715f284 [file] [log] [blame]
#ifndef GOOGLE_PROTOBUF_CONFORMANCE_TESTEE_H__
#define GOOGLE_PROTOBUF_CONFORMANCE_TESTEE_H__
#include <string>
#include <utility>
#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<std::string> test_names_ran_;
};
} // namespace internal
} // namespace conformance
} // namespace protobuf
} // namespace google
#endif // GOOGLE_PROTOBUF_CONFORMANCE_TESTEE_H__