| // 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_fixture.h" | |
| #include <string> | |
| #include "google/protobuf/descriptor.pb.h" | |
| #include <gtest/gtest.h> | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/log/absl_check.h" | |
| #include "absl/strings/string_view.h" | |
| #include "conformance/test_environment.h" | |
| #include "conformance/testee.h" | |
| #include "google/protobuf/descriptor.h" | |
| #include "google/protobuf/descriptor_legacy.h" | |
| namespace google { | |
| namespace protobuf { | |
| namespace conformance { | |
| namespace { | |
| using ::google::protobuf::conformance::internal::ConformanceEnvironment; | |
| using ::google::protobuf::conformance::internal::Statistics; | |
| // Whether tests for `descriptor`'s message type run under `maximum_edition`: | |
| // the file's edition must not be newer than it. | |
| bool IsSupported(const Descriptor& descriptor, Edition maximum_edition) { | |
| Edition edition = FileDescriptorLegacy(descriptor.file()).edition(); | |
| if (edition == EDITION_UNSTABLE) { | |
| // `maximum_edition` is the maximum *stable* edition. Like the legacy | |
| // runner, run the unstable tests alongside any editions tests. | |
| // TODO: this runs the (newer-than-anything) unstable tests | |
| // at --maximum_edition=2023 while a stable 2024 message would be skipped; | |
| // gate EDITION_UNSTABLE on its own flag once legacy parity no longer | |
| // matters. | |
| return maximum_edition >= EDITION_2023; | |
| } | |
| return edition <= maximum_edition; | |
| } | |
| // Statistics accumulated by ConformanceTest::TearDown() for each test suite, | |
| // keyed by suite name, until TearDownTestSuite() reports them. | |
| absl::flat_hash_map<std::string, Statistics>& SuiteStatistics() { | |
| static auto* suite_statistics = | |
| new absl::flat_hash_map<std::string, Statistics>(); | |
| return *suite_statistics; | |
| } | |
| std::string CurrentTestName() { | |
| const testing::TestInfo* test_info = | |
| testing::UnitTest::GetInstance()->current_test_info(); | |
| ABSL_CHECK(test_info != nullptr) | |
| << "Testee() can only infer the test name from inside a test body; " | |
| "pass a name explicitly otherwise."; | |
| return test_info->name(); | |
| } | |
| std::string CurrentTestSuiteName() { | |
| const testing::TestSuite* test_suite = | |
| testing::UnitTest::GetInstance()->current_test_suite(); | |
| ABSL_CHECK(test_suite != nullptr) | |
| << "ConformanceTest's TearDown hooks must run inside a test suite."; | |
| return test_suite->name(); | |
| } | |
| } // namespace | |
| void ConformanceTest::SetUp() { | |
| ConformanceEnvironment& environment = ConformanceEnvironment::Get(); | |
| initial_statistics_ = Statistics::From(environment.test_manager()); | |
| // TODO: Transitional. Goes away with the in-binary | |
| // performance mode, see ConformanceEnvironmentOptions::performance. | |
| if (IsPerformanceTest() && !environment.options().performance) { | |
| GTEST_SKIP() << "Performance tests only run with --performance."; | |
| } | |
| if (!IsPerformanceTest() && environment.options().performance) { | |
| GTEST_SKIP() << "Only performance tests run with --performance."; | |
| } | |
| if (const Descriptor* message = MessageUnderTest(); | |
| message != nullptr && | |
| !IsSupported(*message, environment.options().maximum_edition)) { | |
| GTEST_SKIP() << "Skipping " << message->full_name() | |
| << " because its edition is newer than --maximum_edition."; | |
| } | |
| } | |
| void ConformanceTest::TearDown() { | |
| Statistics delta = | |
| Statistics::From(ConformanceEnvironment::Get().test_manager()) - | |
| initial_statistics_; | |
| delta.RecordProperties(); | |
| SuiteStatistics()[CurrentTestSuiteName()] += delta; | |
| } | |
| void ConformanceTest::TearDownTestSuite() { | |
| auto node = SuiteStatistics().extract(CurrentTestSuiteName()); | |
| if (!node) { | |
| // gtest only runs TearDownTestSuite() for suites with at least one test to | |
| // run, and TearDown() runs even for tests skipped in SetUp(), so this only | |
| // happens when a fatal failure in a SetUpTestSuite() skipped every test | |
| // outright (which is already reported), or when a subclass's TearDown() | |
| // doesn't call ConformanceTest::TearDown(). | |
| return; | |
| } | |
| node.mapped().RecordProperties(); | |
| } | |
| internal::Test ConformanceTest::Testee() { return Testee(CurrentTestName()); } | |
| internal::Test ConformanceTest::Testee(absl::string_view name) { | |
| return Testee(DefaultPriority(), name); | |
| } | |
| internal::Test ConformanceTest::Testee(TestPriority priority) { | |
| return Testee(priority, CurrentTestName()); | |
| } | |
| internal::Test ConformanceTest::Testee(TestPriority priority, | |
| absl::string_view name) { | |
| return ConformanceEnvironment::Get().testee().CreateTest(name, priority); | |
| } | |
| } // namespace conformance | |
| } // namespace protobuf | |
| } // namespace google |