blob: ea4ca9c6fb221df939d737a77c424791229a9953 [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
// End-to-end checks of test_environment_main and ConformanceEnvironment,
// running the deliberately failing sample_conformance_test against the real
// C++ testee. Only what needs a real test process is checked here: the
// command line handling of the main, what gtest itself contributes to a run
// (partial-run detection, the XML report) and the exit code. Everything else
// is covered in-process by test_environment_test.
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <initializer_list>
#include <string>
#include <vector>
#ifndef _WIN32
#include <sys/wait.h> // for WEXITSTATUS
#endif
#include "tools/cpp/runfiles/runfiles.h"
#include "google/protobuf/testing/file.h"
#include "google/protobuf/testing/file.h"
#include <gmock/gmock.h>
#include "google/protobuf/testing/googletest.h"
#include <gtest/gtest.h>
#include "absl/log/absl_check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
namespace google {
namespace protobuf {
namespace conformance {
namespace {
using ::testing::AllOf;
using ::testing::HasSubstr;
using ::testing::Not;
// The names of the sample tests, as they appear in failure lists.
constexpr absl::string_view kDeliberateFailure =
"Required.Proto3.ProtobufInput.SampleDeliberateFailure.ProtobufOutput";
constexpr absl::string_view kP1Failure =
"Recommended.Proto3.ProtobufInput.SampleP1Failure.ProtobufOutput";
constexpr absl::string_view kNeverRuns =
"Required.Proto3.ProtobufInput.SampleNeverRuns.ProtobufOutput";
constexpr absl::string_view kFailureMessage =
"Should have failed to parse, but didn't.";
// gtest spells its flags --gtest_*, Google's internal build --gunit_*.
constexpr absl::string_view kGtestFlag = "--" GTEST_FLAG_PREFIX_;
std::string Rlocation(const std::string& path) {
static const bazel::tools::cpp::runfiles::Runfiles* const runfiles = [] {
std::string error;
bazel::tools::cpp::runfiles::Runfiles* runfiles =
bazel::tools::cpp::runfiles::Runfiles::CreateForTest(&error);
ABSL_CHECK(runfiles != nullptr) << error;
return runfiles;
}();
return runfiles->Rlocation(path);
}
// The BUILD rule passes the sample test and the testee as $(rlocationpath)
// in these variables.
std::string RunfilesPathFromEnvironment(const char* name) {
const char* path = std::getenv(name);
ABSL_CHECK(path != nullptr) << name << " is not set";
return path;
}
const std::string& SampleTest() {
static const std::string* const path =
new std::string(Rlocation(RunfilesPathFromEnvironment("SAMPLE_TEST")));
return *path;
}
const std::string& TesteeBinary() {
static const std::string* const path =
new std::string(Rlocation(RunfilesPathFromEnvironment("TESTEE")));
return *path;
}
// The --failure_list flag for sample_failure_list_<name>.txt, a data
// dependency from the sample test's package.
std::string FailureListFlag(absl::string_view name) {
std::string package = RunfilesPathFromEnvironment("SAMPLE_TEST");
package.erase(package.rfind('/'));
return absl::StrCat(
"--failure_list=",
Rlocation(absl::StrCat(package, "/sample_failure_list_", name, ".txt")));
}
std::string TmpPath(absl::string_view name) {
return absl::StrCat(TestTempDir(), "/", name);
}
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;
}
// Quotes `arg` for the shell that popen() runs commands through.
std::string ShellQuote(absl::string_view arg) {
#ifdef _WIN32
return absl::StrCat("\"", arg, "\"");
#else
return absl::StrCat("'", absl::StrReplaceAll(arg, {{"'", "'\\''"}}), "'");
#endif
}
struct RunResult {
int exit_code;
std::string output; // stdout and stderr
};
// Runs `binary` with `args` and returns its exit code and output.
RunResult RunBinary(const std::string& binary,
absl::Span<const std::string> args) {
std::string command = ShellQuote(binary);
for (const std::string& arg : args) {
absl::StrAppend(&command, " ", ShellQuote(arg));
}
absl::StrAppend(&command, " 2>&1");
#ifdef _WIN32
// cmd.exe strips the first and last quote of a command line that starts
// with one, so give it a pair to strip.
FILE* pipe = _popen(absl::StrCat("\"", command, "\"").c_str(), "r");
#else
FILE* pipe = popen(command.c_str(), "r");
#endif
ABSL_CHECK(pipe != nullptr) << "Failed to run " << command;
RunResult result;
char buffer[4096];
size_t n;
while ((n = fread(buffer, 1, sizeof(buffer), pipe)) > 0) {
result.output.append(buffer, n);
}
#ifdef _WIN32
result.exit_code = _pclose(pipe);
#else
int status = pclose(pipe);
result.exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
#endif
return result;
}
// Runs the sample test against the C++ testee with `args`.
RunResult RunSample(std::vector<std::string> args) {
args.insert(args.begin(), absl::StrCat("--testee_binary=", TesteeBinary()));
return RunBinary(SampleTest(), args);
}
// Returns the part of the gtest XML `report` from the element opening with
// `open` up to the first of `ends`. gtest writes an element's properties
// before its children, so the right `ends` isolate one element's properties.
std::string XmlScope(const std::string& report, absl::string_view open,
std::initializer_list<absl::string_view> ends) {
size_t begin = report.find(open);
if (begin == std::string::npos) return "";
size_t end = report.size();
for (absl::string_view terminator : ends) {
end = std::min(end, report.find(terminator, begin + open.size()));
}
return report.substr(begin, end - begin);
}
std::string XmlTestCase(const std::string& report, absl::string_view name) {
return XmlScope(report, absl::StrCat("<testcase name=\"", name, "\""),
{"<testcase", "</testsuite>"});
}
std::string XmlTestSuite(const std::string& report, absl::string_view name) {
return XmlScope(report, absl::StrCat("<testsuite name=\"", name, "\""),
{"<testcase", "</testsuite>"});
}
std::string XmlTestRun(const std::string& report) {
return XmlScope(report, "<testsuites", {"<testsuite "});
}
class TestEnvironmentIntegrationTest : public testing::Test {
protected:
static void SetUpTestSuite() {
// The test runner configures this binary's gtest through these variables
// (its filter, sharding, XML report, ...). gtest has read them by now,
// and they must not reach the binaries the tests run.
for (const char* name :
{"TESTBRIDGE_TEST_ONLY", "TESTBRIDGE_TEST_RUNNER_FAIL_FAST",
"XML_OUTPUT_FILE", "TEST_PREMATURE_EXIT_FILE",
"TEST_WARNINGS_OUTPUT_FILE", "TEST_TOTAL_SHARDS", "TEST_SHARD_INDEX",
"TEST_SHARD_STATUS_FILE", "GTEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX",
"GTEST_SHARD_STATUS_FILE"}) {
#ifdef _WIN32
_putenv_s(name, ""); // An empty value removes the variable.
#else
unsetenv(name);
#endif
}
}
};
TEST_F(TestEnvironmentIntegrationTest, PassesWithAMatchingFailureList) {
RunResult run = RunSample({FailureListFlag("ok")});
EXPECT_EQ(run.exit_code, 0) << run.output;
EXPECT_THAT(run.output,
AllOf(HasSubstr("[ OK ] SampleConformanceTest.SamplePass"),
HasSubstr("[ OK ] "
"SampleConformanceTest.SampleDeliberateFailure"),
HasSubstr("[ OK ] "
"SampleConformanceTest.SampleP1Failure")));
}
TEST_F(TestEnvironmentIntegrationTest, FailsWithAnEmptyFailureList) {
RunResult run = RunSample({FailureListFlag("empty")});
EXPECT_NE(run.exit_code, 0);
EXPECT_THAT(run.output,
AllOf(HasSubstr("[ FAILED ] "
"SampleConformanceTest.SampleDeliberateFailure"),
HasSubstr(kDeliberateFailure), HasSubstr(kFailureMessage)));
}
TEST_F(TestEnvironmentIntegrationTest, ListsTestsWithoutATestee) {
RunResult run =
RunBinary(SampleTest(), {absl::StrCat(kGtestFlag, "list_tests")});
EXPECT_EQ(run.exit_code, 0) << run.output;
EXPECT_THAT(run.output, HasSubstr("SampleDeliberateFailure"));
}
TEST_F(TestEnvironmentIntegrationTest, PassesPositionalArgumentsToTheTestee) {
// The testee ignores them; this checks that flag parsing lets them through.
RunResult run =
RunSample({FailureListFlag("ok"), "--", "--ignored_by_testee"});
EXPECT_EQ(run.exit_code, 0) << run.output;
}
// Partial runs are detected from gtest's own test counts and state (see
// IsPartialRun() in test_environment.cc), which only a real run sets up.
TEST_F(TestEnvironmentIntegrationTest, UnseenExpectedFailuresFailAFullRun) {
// The DISABLED_ sample test doesn't run either; that mustn't make the run
// count as partial.
RunResult run = RunSample({FailureListFlag("unseen")});
EXPECT_NE(run.exit_code, 0);
EXPECT_THAT(
run.output,
HasSubstr(absl::StrCat("expected failures were not seen: ", kNeverRuns)));
}
TEST_F(TestEnvironmentIntegrationTest, UnseenExpectedFailuresPassAFilteredRun) {
RunResult run =
RunSample({FailureListFlag("unseen"),
absl::StrCat(kGtestFlag,
"filter=SampleConformanceTest.Sample*Failure")});
EXPECT_EQ(run.exit_code, 0) << run.output;
EXPECT_THAT(run.output, Not(HasSubstr("were not seen")));
}
TEST_F(TestEnvironmentIntegrationTest, AnEarlyStopUnderFailFastIsAPartialRun) {
// With a list that only names the never-run test, the deliberate failure
// stops the run before the other tests, and the unseen entry isn't reported
// on top of it.
std::string list =
WriteFile("never_runs_only.txt", absl::StrCat(kNeverRuns, "\n"));
RunResult run = RunSample({absl::StrCat("--failure_list=", list),
absl::StrCat(kGtestFlag, "fail_fast")});
EXPECT_NE(run.exit_code, 0);
EXPECT_THAT(run.output,
AllOf(HasSubstr("[ FAILED ] "
"SampleConformanceTest.SampleDeliberateFailure"),
Not(HasSubstr("were not seen"))));
}
TEST_F(TestEnvironmentIntegrationTest, FixRewritesTheFailureListInPlace) {
std::string list = WriteFile("failure_list.txt", "");
RunResult run = RunSample({absl::StrCat("--failure_list=", list), "--fix"});
// The failures were unexpected in this run; the rewritten list expects them.
EXPECT_NE(run.exit_code, 0);
EXPECT_THAT(ReadFile(list),
AllOf(HasSubstr(kDeliberateFailure), HasSubstr(kP1Failure),
HasSubstr(absl::StrCat("# ", kFailureMessage))));
run = RunSample({absl::StrCat("--failure_list=", list)});
EXPECT_EQ(run.exit_code, 0) << run.output;
}
TEST_F(TestEnvironmentIntegrationTest, RecordsStatisticsInTheXmlReport) {
std::string xml = TmpPath("report.xml");
RunResult run = RunSample(
{FailureListFlag("ok"), absl::StrCat(kGtestFlag, "output=xml:", xml)});
ASSERT_EQ(run.exit_code, 0) << run.output;
std::string report = ReadFile(xml);
// Each level gets its own numbers: the P0 and P1 failures are one expected
// failure each, two on their suite and on the run.
EXPECT_THAT(XmlTestCase(report, "SampleDeliberateFailure"),
HasSubstr("name=\"expected_failures\" value=\"1\""));
EXPECT_THAT(XmlTestCase(report, "SamplePass"),
HasSubstr("name=\"expected_successes\" value=\"1\""));
EXPECT_THAT(XmlTestSuite(report, "SampleConformanceTest"),
HasSubstr("name=\"expected_failures\" value=\"2\""));
EXPECT_THAT(XmlTestRun(report),
AllOf(HasSubstr("name=\"expected_failures\" value=\"2\""),
HasSubstr("name=\"unexpected_failures\" value=\"0\"")));
}
} // namespace
} // namespace conformance
} // namespace protobuf
} // namespace google