blob: 91280e206de6aec3959c3273c854f4ff320b68ca [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/test_environment.h"
#ifdef _WIN32
#include <direct.h> // for _chdir, _getcwd
#else
#include <unistd.h> // for chdir, getcwd
#endif
#include <cstdlib>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "google/protobuf/testing/file.h"
#include "google/protobuf/testing/file.h"
#include "google/protobuf/testing/file.h"
#include "google/protobuf/descriptor.pb.h"
#include <gmock/gmock.h>
#include "google/protobuf/testing/googletest.h"
#include <gtest/gtest-spi.h>
#include <gtest/gtest.h>
#include "absl/log/absl_check.h"
#include "absl/status/status_matchers.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/matchers.h"
#include "conformance/mock_test_runner.h"
#include "conformance/test_environment_testing.h"
#include "conformance/test_manager.h"
#include "conformance/testee.h"
#include "google/protobuf/test_messages_proto3.pb.h"
#include "google/protobuf/text_format.h"
namespace google {
namespace protobuf {
namespace conformance {
namespace {
using ::absl_testing::IsOk;
using ::google::protobuf::conformance::internal::ConformanceEnvironment;
using ::google::protobuf::conformance::internal::ConformanceEnvironmentOptions;
using ::protobuf_test_messages::proto3::TestAllTypesProto3;
using ::testing::_;
using ::testing::HasSubstr;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Return;
// The name the testee sees for a kP0 test named "Foo" that parses
// TestAllTypesProto3 from binary and serializes it back to binary.
constexpr absl::string_view kFooTestName =
"Required.Proto3.ProtobufInput.Foo.ProtobufOutput";
constexpr absl::string_view kBarTestName =
"Required.Proto3.ProtobufInput.Bar.ProtobufOutput";
std::string SerializedResponse(absl::string_view textproto) {
::conformance::ConformanceResponse response;
ABSL_CHECK(TextFormat::ParseFromString(textproto, &response));
return response.SerializeAsString();
}
// A testee that successfully round-trips `optional_int32: 99` for every test
// (by default: being a mock, a test can still set expectations on it).
class FakeTestRunner : public NiceMock<MockTestRunner> {
public:
FakeTestRunner() {
ON_CALL(*this, RunTest)
.WillByDefault(
Return(SerializedResponse(R"pb(protobuf_payload: "\010c")pb")));
}
};
// A FakeTestRunner that reports its destruction.
class FlaggingTestRunner : public FakeTestRunner {
public:
explicit FlaggingTestRunner(bool* destroyed) : destroyed_(destroyed) {}
~FlaggingTestRunner() override { *destroyed_ = true; }
private:
bool* destroyed_;
};
// Sets an environment variable for the lifetime of this object.
class ScopedEnvironmentVariable {
public:
ScopedEnvironmentVariable(absl::string_view name,
const absl::optional<std::string>& value)
: name_(name) {
if (const char* old = std::getenv(name_.c_str()); old != nullptr) {
old_value_ = old;
}
Set(value);
}
~ScopedEnvironmentVariable() { Set(old_value_); }
private:
void Set(const absl::optional<std::string>& value) {
#ifdef _WIN32
// The MSVC CRT has no setenv()/unsetenv(); _putenv_s() with an empty
// value removes the variable, so std::getenv() then returns null again.
ABSL_CHECK_EQ(
_putenv_s(name_.c_str(), value.has_value() ? value->c_str() : ""), 0);
#else
if (value.has_value()) {
ABSL_CHECK_EQ(setenv(name_.c_str(), value->c_str(), /*overwrite=*/1), 0);
} else {
ABSL_CHECK_EQ(unsetenv(name_.c_str()), 0);
}
#endif
}
std::string name_;
absl::optional<std::string> old_value_;
};
// Changes the working directory for the lifetime of this object.
class ScopedWorkingDirectory {
public:
explicit ScopedWorkingDirectory(const std::string& directory) {
char* cwd = CurrentDirectory();
ABSL_CHECK(cwd != nullptr);
old_directory_ = cwd;
std::free(cwd);
ABSL_CHECK_EQ(ChangeDirectory(directory), 0) << directory;
}
~ScopedWorkingDirectory() {
ABSL_CHECK_EQ(ChangeDirectory(old_directory_), 0) << old_directory_;
}
private:
// Both glibc/macOS (as an extension) and the MSVC CRT malloc() a buffer of
// the right size when given a null one.
static char* CurrentDirectory() {
#ifdef _WIN32
return _getcwd(nullptr, 0);
#else
return getcwd(nullptr, 0);
#endif
}
static int ChangeDirectory(const std::string& directory) {
#ifdef _WIN32
return _chdir(directory.c_str());
#else
return chdir(directory.c_str());
#endif
}
std::string old_directory_;
};
// Tears down `env` in a full run, expecting it to report unseen expected
// failures with a message containing `expected_message`.
void TearDownExpectingUnseenFailures(ConformanceEnvironment& env,
absl::string_view expected_message) {
internal::ScopedPartialRunOverride full_run(false);
EXPECT_NONFATAL_FAILURE(env.TearDown(), std::string(expected_message));
}
class ConformanceEnvironmentTest : public ::testing::Test {
protected:
ConformanceEnvironmentTest()
: tmp_dir_(absl::StrCat(
TestTempDir(), "/test_environment_test/",
testing::UnitTest::GetInstance()->current_test_info()->name())) {
ABSL_CHECK_OK(
File::RecursivelyCreateDir(tmp_dir_, 0777));
}
~ConformanceEnvironmentTest() override {
File::DeleteRecursively(tmp_dir_, NULL, NULL);
}
// Writes `content` to a file named `name` in this test's temp directory and
// returns its path.
std::string WriteFile(absl::string_view name, absl::string_view content) {
std::string path = TmpPath(name);
ABSL_CHECK_OK(File::SetContents(path, content, true));
return path;
}
// `path` is a std::string (not a string_view) for the open source
// File::GetContents() shim.
std::string ReadFile(const std::string& path) {
std::string content;
ABSL_CHECK_OK(File::GetContents(path, &content, true));
return content;
}
std::string TmpPath(absl::string_view name) {
return absl::StrCat(tmp_dir_, "/", name);
}
std::string CreateTmpDir(absl::string_view name) {
std::string path = TmpPath(name);
ABSL_CHECK_OK(File::RecursivelyCreateDir(path, 0777));
return path;
}
// Runs a kP0 test named `name` against the global testee.
internal::TestResult RunP0(absl::string_view name) {
return ConformanceEnvironment::Get()
.testee()
.CreateTest(name, kP0)
.ParseBinary(TestAllTypesProto3::descriptor(), VarintField(1, 99))
.SerializeBinary();
}
internal::TestResult RunP1(absl::string_view name) {
return ConformanceEnvironment::Get()
.testee()
.CreateTest(name, kP1)
.ParseBinary(TestAllTypesProto3::descriptor(), VarintField(1, 99))
.SerializeBinary();
}
FakeTestRunner fake_runner_;
private:
std::string tmp_dir_;
};
TEST_F(ConformanceEnvironmentTest, ScopedEnvironmentIsTheGlobalOne) {
internal::ScopedGlobalConformanceEnvironment env({/*runner=*/&fake_runner_});
EXPECT_EQ(&internal::GetGlobalTestManager(), &env->test_manager());
}
TEST_F(ConformanceEnvironmentTest, EnforcementLevelIsPassedToTestManager) {
{
// Every priority is enforced by default.
internal::ScopedGlobalConformanceEnvironment env(
{/*runner=*/&fake_runner_});
EXPECT_THAT(env->test_manager().ReportFailure("P1.x", kP1, "boom"),
Not(IsOk()));
}
{
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.enforcement_level = kP0;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
EXPECT_THAT(env->test_manager().ReportFailure("P1.x", kP1, "boom"), IsOk());
}
}
TEST_F(ConformanceEnvironmentTest, TesteeRoutesToRunner) {
MockTestRunner mock;
internal::ScopedGlobalConformanceEnvironment env({/*runner=*/&mock});
env->SetUp();
EXPECT_CALL(mock, RunTest(kFooTestName, _))
.WillOnce(Return(SerializedResponse(R"pb(protobuf_payload: "\010c")pb")));
EXPECT_THAT(RunP0("Foo"),
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 99)pb"))));
EXPECT_EQ(env->test_manager().expected_successes(), 1);
EXPECT_EQ(env->test_manager().unexpected_failures(), 0);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, UnexpectedFailureFailsTheTest) {
internal::ScopedGlobalConformanceEnvironment env({/*runner=*/&fake_runner_});
env->SetUp();
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(RunP0("Foo"), Yields(IsParseError())),
"Should have failed to parse, but didn't.");
EXPECT_EQ(env->test_manager().unexpected_failures(), 1);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, ExpectedFailureFromFailureListPasses) {
std::string failure_list =
WriteFile("failures.txt",
absl::StrCat(kFooTestName, " # Should have failed to parse\n"));
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
EXPECT_THAT(RunP0("Foo"), Yields(IsParseError()));
EXPECT_EQ(env->test_manager().expected_failures(), 1);
EXPECT_EQ(env->test_manager().unexpected_failures(), 0);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, LoadsMultipleFailureLists) {
std::string first = WriteFile("first.txt", absl::StrCat(kFooTestName, "\n"));
std::string second =
WriteFile("second.txt", absl::StrCat(kBarTestName, "\n"));
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {first, second};
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
EXPECT_THAT(RunP0("Foo"), Yields(IsParseError()));
EXPECT_THAT(RunP0("Bar"), Yields(IsParseError()));
EXPECT_EQ(env->test_manager().expected_failures(), 2);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, OverlappingFailureListsAreFatalInSetUp) {
std::string first = WriteFile("first.txt", absl::StrCat(kFooTestName, "\n"));
std::string second =
WriteFile("second.txt", absl::StrCat(kFooTestName, "\n"));
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {first, second};
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
testing::TestPartResultArray failures;
{
testing::ScopedFakeTestPartResultReporter reporter(
testing::ScopedFakeTestPartResultReporter::
INTERCEPT_ONLY_CURRENT_THREAD,
&failures);
env->SetUp();
}
ASSERT_EQ(failures.size(), 1);
EXPECT_TRUE(failures.GetTestPartResult(0).fatally_failed());
EXPECT_THAT(failures.GetTestPartResult(0).message(),
HasSubstr(absl::StrCat("Failed to load failure list ", second)));
EXPECT_THAT(failures.GetTestPartResult(0).message(),
HasSubstr("already exists"));
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, P1FailureIsToleratedWhenNotEnforced) {
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.enforcement_level = kP0;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
EXPECT_THAT(RunP1("Foo"), Yields(IsParseError()));
EXPECT_EQ(env->test_manager().tolerated_failures(), 1);
EXPECT_EQ(env->test_manager().skipped(), 0);
EXPECT_EQ(env->test_manager().unexpected_failures(), 0);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest,
ListedP1FailureIsExpectedEvenWhenNotEnforced) {
std::string failure_list = WriteFile(
"failures.txt",
"Recommended.Proto3.ProtobufInput.Foo.ProtobufOutput # Should have "
"failed to parse\n");
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
options.enforcement_level = kP0;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
EXPECT_THAT(RunP1("Foo"), Yields(IsParseError()));
EXPECT_EQ(env->test_manager().expected_failures(), 1);
EXPECT_EQ(env->test_manager().tolerated_failures(), 0);
// ...and so it isn't unseen either.
internal::ScopedPartialRunOverride full_run(false);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, MissingFailureListIsFatalInSetUp) {
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {TmpPath("does_not_exist.txt")};
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
testing::TestPartResultArray failures;
{
testing::ScopedFakeTestPartResultReporter reporter(
testing::ScopedFakeTestPartResultReporter::
INTERCEPT_ONLY_CURRENT_THREAD,
&failures);
env->SetUp();
}
ASSERT_EQ(failures.size(), 1);
EXPECT_TRUE(failures.GetTestPartResult(0).fatally_failed());
EXPECT_THAT(failures.GetTestPartResult(0).message(),
HasSubstr("Failed to load failure list"));
// TearDown must not pile on more failures after a failed SetUp.
internal::ScopedPartialRunOverride full_run(false);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, TearDownFailsOnUnseenExpectedFailures) {
std::string failure_list =
WriteFile("failures.txt", absl::StrCat(kFooTestName, "\n"));
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
TearDownExpectingUnseenFailures(
env.environment(),
absl::StrCat("expected failures were not seen: ", kFooTestName,
"\nRemove them from the failure list, or rerun with --fix"));
}
TEST_F(ConformanceEnvironmentTest, UnseenExpectedFailuresAreFineInPartialRun) {
std::string failure_list =
WriteFile("failures.txt", absl::StrCat(kFooTestName, "\n"));
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
internal::ScopedPartialRunOverride partial_run(true);
env->TearDown();
EXPECT_THAT(env->test_manager().Finalize().message(),
HasSubstr(absl::StrCat("were not seen: ", kFooTestName)));
}
TEST_F(ConformanceEnvironmentTest, UnseenCheckCanBeDisabled) {
std::string failure_list =
WriteFile("failures.txt", absl::StrCat(kFooTestName, "\n"));
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
options.check_unseen_expected_failures = false;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
internal::ScopedPartialRunOverride full_run(false);
env->TearDown();
EXPECT_THAT(env->test_manager().Finalize().message(),
HasSubstr(absl::StrCat("were not seen: ", kFooTestName)));
}
TEST_F(ConformanceEnvironmentTest, FixWritesToTheOutputFile) {
std::string failure_list =
WriteFile("failures.txt", absl::StrCat(kFooTestName, " # stale\n"));
std::string fixed = TmpPath("fixed.txt");
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
options.fix = true;
options.fix_output_file = fixed;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
// Foo (listed) is never run; Bar (unlisted) fails.
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(RunP0("Bar"), Yields(IsParseError())),
"Should have failed to parse, but didn't.");
// The unseen entry is still reported, but the message points at the
// rewritten list instead of suggesting --fix.
TearDownExpectingUnseenFailures(
env.environment(),
absl::StrCat("expected failures were not seen: ", kFooTestName,
"\nThey have been dropped from the updated failure list "
"written to ",
fixed));
std::string content = ReadFile(fixed);
EXPECT_THAT(content, HasSubstr(absl::StrCat(
kBarTestName,
" # Should have failed to parse, but didn't.")));
EXPECT_THAT(content, Not(HasSubstr(kFooTestName)));
// The input is left alone when an explicit output file is given.
EXPECT_THAT(ReadFile(failure_list), HasSubstr("stale"));
}
TEST_F(ConformanceEnvironmentTest, FixDefaultsToTheSingleAbsoluteFailureList) {
std::string failure_list =
WriteFile("failures.txt", absl::StrCat(kFooTestName, " # stale\n"));
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
options.fix = true;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(RunP0("Bar"), Yields(IsParseError())),
"Should have failed to parse, but didn't.");
TearDownExpectingUnseenFailures(
env.environment(),
absl::StrCat("updated failure list written to ", failure_list));
std::string content = ReadFile(failure_list);
EXPECT_THAT(content, HasSubstr(kBarTestName));
EXPECT_THAT(content, Not(HasSubstr(kFooTestName)));
}
TEST_F(ConformanceEnvironmentTest,
FixResolvesRelativeFailureListUnderBuildWorkspaceDirectory) {
// The relative path is loaded from the working directory (the runfiles, under
// bazel) but rewritten under $BUILD_WORKSPACE_DIRECTORY (the source tree).
std::string runfiles = CreateTmpDir("runfiles");
std::string workspace = CreateTmpDir("workspace");
std::string stale = absl::StrCat(kFooTestName, " # stale\n");
WriteFile("runfiles/failures.txt", stale);
WriteFile("workspace/failures.txt", stale);
ScopedWorkingDirectory cwd(runfiles);
ScopedEnvironmentVariable build_workspace_directory(
"BUILD_WORKSPACE_DIRECTORY", workspace);
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {"failures.txt"};
options.fix = true;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(RunP0("Bar"), Yields(IsParseError())),
"Should have failed to parse, but didn't.");
TearDownExpectingUnseenFailures(
env.environment(), absl::StrCat("updated failure list written to ",
workspace, "/failures.txt"));
EXPECT_THAT(ReadFile(TmpPath("workspace/failures.txt")),
HasSubstr(kBarTestName));
EXPECT_EQ(ReadFile(TmpPath("runfiles/failures.txt")), stale);
}
TEST_F(ConformanceEnvironmentTest,
FixWithRelativeFailureListNeedsBuildWorkspaceDirectory) {
std::string runfiles = CreateTmpDir("runfiles");
WriteFile("runfiles/failures.txt", "");
ScopedWorkingDirectory cwd(runfiles);
ScopedEnvironmentVariable build_workspace_directory(
"BUILD_WORKSPACE_DIRECTORY", absl::nullopt);
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {"failures.txt"};
options.fix = true;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
internal::ScopedPartialRunOverride full_run(false);
EXPECT_NONFATAL_FAILURE(
env->TearDown(),
"--fix needs either --fix_output_file or $BUILD_WORKSPACE_DIRECTORY");
EXPECT_EQ(ReadFile(TmpPath("runfiles/failures.txt")), "");
}
TEST_F(ConformanceEnvironmentTest, FixWithMultipleListsNeedsOutputFile) {
std::string first = WriteFile("first.txt", "");
std::string second = WriteFile("second.txt", "");
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {first, second};
options.fix = true;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
internal::ScopedPartialRunOverride full_run(false);
EXPECT_NONFATAL_FAILURE(
env->TearDown(),
"--fix needs --fix_output_file when more than one --failure_list");
}
TEST_F(ConformanceEnvironmentTest, FixWithNoListsNeedsOutputFile) {
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.fix = true;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
internal::ScopedPartialRunOverride full_run(false);
EXPECT_NONFATAL_FAILURE(
env->TearDown(), "--fix needs --fix_output_file when no --failure_list");
}
TEST_F(ConformanceEnvironmentTest, FixIsRefusedInPartialRun) {
std::string failure_list =
WriteFile("failures.txt", absl::StrCat(kFooTestName, " # stale\n"));
std::string fixed = TmpPath("fixed.txt");
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
options.fix = true;
options.fix_output_file = fixed;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(RunP0("Bar"), Yields(IsParseError())),
"Should have failed to parse, but didn't.");
// Rewriting would drop Foo, whose test simply wasn't selected. The unseen
// check itself stays quiet in a partial run, so this is the only failure.
internal::ScopedPartialRunOverride partial_run(true);
EXPECT_NONFATAL_FAILURE(env->TearDown(), "--fix ignored");
EXPECT_FALSE(File::Exists(fixed));
EXPECT_THAT(ReadFile(failure_list), HasSubstr("stale"));
}
TEST_F(ConformanceEnvironmentTest, FixReportsWriteFailures) {
std::string failure_list = WriteFile("failures.txt", "");
std::string not_a_file = CreateTmpDir("directory");
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.failure_list_files = {failure_list};
options.fix = true;
options.fix_output_file = not_a_file;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(RunP0("Bar"), Yields(IsParseError())),
"Should have failed to parse, but didn't.");
internal::ScopedPartialRunOverride full_run(false);
EXPECT_NONFATAL_FAILURE(
env->TearDown(),
absl::StrCat("Failed to write updated failure list to ", not_a_file));
}
TEST_F(ConformanceEnvironmentTest, StatisticsSnapshotAndDelta) {
internal::ScopedGlobalConformanceEnvironment env({/*runner=*/&fake_runner_});
env->SetUp();
internal::Statistics before = internal::Statistics::From(env->test_manager());
EXPECT_THAT(RunP0("Foo"),
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 99)pb"))));
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(RunP0("Bar"), Yields(IsParseError())),
"Should have failed to parse");
internal::Statistics delta =
internal::Statistics::From(env->test_manager()) - before;
EXPECT_EQ(delta.expected_successes, 1);
EXPECT_EQ(delta.unexpected_failures, 1);
EXPECT_EQ(delta.expected_failures, 0);
EXPECT_EQ(delta.unexpected_successes, 0);
EXPECT_EQ(delta.skipped_tests, 0);
EXPECT_EQ(delta.listed_skips, 0);
EXPECT_EQ(delta.tolerated_failures, 0);
internal::Statistics total;
total += delta;
total += delta;
EXPECT_EQ(total.expected_successes, 2);
EXPECT_EQ(total.unexpected_failures, 2);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, StatisticsCountListedSkips) {
std::string failure_list =
WriteFile("failures.txt", absl::StrCat(kFooTestName, " # listed\n"));
MockTestRunner mock;
ConformanceEnvironmentOptions options;
options.runner = &mock;
options.failure_list_files = {failure_list};
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
internal::Statistics before = internal::Statistics::From(env->test_manager());
// A listed test the testee skips fails (see matchers.h) and is both a skip
// and a listed skip, but neither an expected nor an unexpected anything.
EXPECT_CALL(mock, RunTest(kFooTestName, _))
.WillOnce(Return(SerializedResponse(R"pb(skipped: "not supported")pb")));
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(RunP0("Foo"), Yields(IsParseError())),
"is in the failure list but was skipped by the testee");
internal::Statistics delta =
internal::Statistics::From(env->test_manager()) - before;
EXPECT_EQ(delta.skipped_tests, 1);
EXPECT_EQ(delta.listed_skips, 1);
EXPECT_EQ(delta.expected_failures, 0);
EXPECT_EQ(delta.unexpected_failures, 0);
EXPECT_EQ(delta.expected_successes, 0);
EXPECT_EQ(delta.unexpected_successes, 0);
internal::Statistics total;
total += delta;
total += delta;
EXPECT_EQ(total.skipped_tests, 2);
EXPECT_EQ(total.listed_skips, 2);
// The skip counts as having seen the entry, so this doesn't report it.
internal::ScopedPartialRunOverride full_run(false);
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, MaximumEditionIsClampedToProto3) {
// proto2 and proto3 tests always run, so anything older than proto3
// (including the legacy runner's EDITION_UNKNOWN default) behaves like
// proto3 rather than disabling everything.
for (Edition edition : {EDITION_UNKNOWN, EDITION_LEGACY, EDITION_PROTO2}) {
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.maximum_edition = edition;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
EXPECT_EQ(env->options().maximum_edition, EDITION_PROTO3);
}
ConformanceEnvironmentOptions options;
options.runner = &fake_runner_;
options.maximum_edition = EDITION_2023;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
EXPECT_EQ(env->options().maximum_edition, EDITION_2023);
}
TEST_F(ConformanceEnvironmentTest, SpawnsForkPipeRunnerLazily) {
// The testee binary doesn't exist, but ForkPipeRunner only spawns it on
// first use, so constructing the environment must succeed.
ConformanceEnvironmentOptions options;
options.testee_binary = TmpPath("no_such_testee");
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
env->SetUp();
env->TearDown();
}
TEST_F(ConformanceEnvironmentTest, TearDownShutsDownTheOwnedRunner) {
bool destroyed = false;
internal::ScopedGlobalConformanceEnvironment env(
{/*runner=*/nullptr,
/*owned_runner=*/std::make_unique<FlaggingTestRunner>(&destroyed)});
env->SetUp();
EXPECT_THAT(RunP0("Foo"),
Yields(WhenParsed(EqualsTextProto(R"pb(optional_int32: 99)pb"))));
EXPECT_FALSE(destroyed);
env->TearDown();
EXPECT_TRUE(destroyed);
// The statistics survive the testee.
EXPECT_EQ(env->test_manager().expected_successes(), 1);
}
TEST_F(ConformanceEnvironmentTest, TearDownWithoutSetUpOnlyReleasesTheTestee) {
// gtest skips an environment's SetUp() and TearDown() when it selects no
// test to run; the merged runner (conformance_test_main.cc) then calls
// TearDown() itself so that the environment lets go of its runner. Nothing
// else may happen: no --fix rewrite and no unseen-entry failure, as the
// failure list was never loaded.
std::string failure_list =
WriteFile("failures.txt", absl::StrCat(kFooTestName, " # never seen\n"));
std::string fixed = TmpPath("fixed.txt");
bool destroyed = false;
ConformanceEnvironmentOptions options;
options.owned_runner = std::make_unique<FlaggingTestRunner>(&destroyed);
options.failure_list_files = {failure_list};
options.fix = true;
options.fix_output_file = fixed;
internal::ScopedGlobalConformanceEnvironment env(std::move(options));
internal::ScopedPartialRunOverride full_run(false);
env->TearDown();
EXPECT_TRUE(destroyed);
EXPECT_FALSE(File::Exists(fixed));
EXPECT_THAT(ReadFile(failure_list), HasSubstr("never seen"));
EXPECT_EQ(env->test_manager().expected_failures(), 0);
}
TEST_F(ConformanceEnvironmentTest, OwnedRunnerDiesWithTheEnvironment) {
bool destroyed = false;
{
internal::ScopedGlobalConformanceEnvironment env(
{/*runner=*/nullptr,
/*owned_runner=*/std::make_unique<FlaggingTestRunner>(&destroyed)});
EXPECT_FALSE(destroyed);
}
EXPECT_TRUE(destroyed);
}
TEST(ConformanceEnvironmentDeathTest, GetWithoutInstall) {
EXPECT_DEATH(ConformanceEnvironment::Get(), "Install");
}
TEST(ConformanceEnvironmentDeathTest, RequiresExactlyOneTestee) {
FakeTestRunner runner;
EXPECT_DEATH(internal::ScopedGlobalConformanceEnvironment env({}),
"Exactly one");
EXPECT_DEATH(internal::ScopedGlobalConformanceEnvironment env(
{/*runner=*/&runner, /*owned_runner=*/nullptr,
/*testee_binary=*/"/bin/true"}),
"Exactly one");
EXPECT_DEATH(internal::ScopedGlobalConformanceEnvironment env(
{/*runner=*/&runner,
/*owned_runner=*/std::make_unique<FakeTestRunner>()}),
"Exactly one");
}
TEST(ConformanceEnvironmentDeathTest, OnlyOneGlobalEnvironmentAtATime) {
FakeTestRunner runner;
internal::ScopedGlobalConformanceEnvironment env({/*runner=*/&runner});
EXPECT_DEATH(
internal::ScopedGlobalConformanceEnvironment nested({/*runner=*/&runner}),
"already set");
EXPECT_DEATH(ConformanceEnvironment::Install({/*runner=*/&runner}),
"only be called once");
}
TEST(ConformanceEnvironmentDeathTest, OnlyOnePartialRunOverrideAtATime) {
FakeTestRunner runner;
internal::ScopedGlobalConformanceEnvironment env({/*runner=*/&runner});
internal::ScopedPartialRunOverride partial_run(true);
EXPECT_DEATH(internal::ScopedPartialRunOverride nested(false),
"already active");
}
TEST(ConformanceEnvironmentDeathTest, TesteeIsGoneAfterTearDown) {
FakeTestRunner runner;
internal::ScopedGlobalConformanceEnvironment env({/*runner=*/&runner});
env->SetUp();
env->TearDown();
EXPECT_DEATH(env->testee(), "already been shut down");
}
} // namespace
} // namespace conformance
} // namespace protobuf
} // namespace google