blob: 2757d3667f8d80a4f1f4e470cba21414cfbf41ec [file]
#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 "conformance/binary_wireformat.h"
#include "conformance/conformance.pb.h"
#include "conformance/test_runner.h"
#include "google/protobuf/descriptor.h"
// The APIs conformance tests use to interact with a testee. They are
// deliberately decoupled from the runner/testee protocol that implements
// them. That keeps them flexible for tests.
//
// Tests should never need to name any of these types directly. A test
// obtains a Test object for the global testee from Testee() (see
// test_fixture.h), chains operations on it and passes the final
// TestResult to Yields() (see matchers.h):
//
// EXPECT_THAT(Testee()
// .ParseBinary(TestAllTypesProto2::descriptor(), input)
// .SerializeBinary(),
// Yields(WhenParsed(EqualsBinaryProto(input))));
// 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 {
// How important it is that an implementation passes a test. kP0 is the
// baseline every implementation must pass. Every priority is enforced
// unless the enforcement level (see below) leaves some out. A failing kP1
// test is then counted but not failed, unless it is in the failure list (see
// matchers.h). Test names still spell kP0 as "Required" and kP1 as
// "Recommended" (see PriorityLevelName()).
//
// A suite declares its priority with ConformanceTest::DefaultPriority(). A
// single test overrides it with Testee(priority); see test_fixture.h.
// TODO: b/564550230 - rename the levels in test names to P0/P1 once every
// suite has been triaged.
enum class TestPriority { kP0 = 0, kP1 = 1 };
// The priorities, spelled the way suites write them: Testee(kP1).
inline constexpr TestPriority kP0 = TestPriority::kP0;
inline constexpr TestPriority kP1 = TestPriority::kP1;
// The lowest priority there is. An enforcement level (see TestManager in
// test_manager.h) is the lowest priority whose unlisted failures fail the run;
// kLowestPriority, the default, enforces every priority.
inline constexpr TestPriority kLowestPriority = kP1;
// The name of a priority: "P0" or "P1".
absl::string_view PriorityName(TestPriority priority);
// The level a priority is named with in test names, until the rename (see
// TestPriority): "Required" for kP0, "Recommended" for kP1.
absl::string_view PriorityLevelName(TestPriority priority);
namespace internal {
// 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 priority of the test (see TestPriority). Yields() tolerates a
// failing kP1 test only if the enforcement level leaves kP1 out and the
// test isn't listed.
TestPriority priority() const { return priority_; }
// 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, TestPriority priority,
const Descriptor* type, ::conformance::WireFormat format,
::conformance::ConformanceResponse response)
: test_name_(test_name),
priority_(priority),
type_(type),
format_(format),
response_(std::move(response)) {}
friend class InMemoryMessage;
std::string test_name_;
TestPriority priority_;
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,
TestPriority priority, const Descriptor* type,
::conformance::ConformanceRequest request)
: testee_(testee),
name_(name),
priority_(priority),
type_(type),
request_(std::move(request)) {}
friend class Test;
TestResult SerializeImpl(::conformance::WireFormat format);
class Testee* testee_;
std::string name_;
TestPriority priority_;
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, TestPriority priority)
: testee_(testee), name_(name), priority_(priority) {}
friend class Testee;
class Testee* testee_;
std::string name_;
TestPriority priority_;
};
// 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, TestPriority priority) {
return Test(this, name, priority);
}
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__