mirror of
https://github.com/google/googletest.git
synced 2026-10-28 18:07:18 +08:00
Eliminate exit-time destructor in TypedTestSuitePState.
`TYPED_TEST_SUITE_P` defines a static `TypedTestSuitePState` variable at namespace scope. Storing registered tests in a `std::map<std::string, CodeLocation>` required dynamic initialization, heap allocations, and an exit-time destructor, triggering `-Wexit-time-destructors` warnings in builds that enforce trivial exit. Store registered tests in an intrusive sorted singly-linked list of static POD `RegisteredTest` nodes defined by `TYPED_TEST_P`. `TypedTestSuitePState` is now `constexpr`-constructible and trivially destructible without heap allocations. PiperOrigin-RevId: 998634744 Change-Id: I0c833ffeebdf21b155038b7e95e497637e68564a
This commit is contained in:
parent
988ea2c179
commit
b66cffcd5f
@ -256,8 +256,11 @@ INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes);
|
||||
// The variables defined in the type-parameterized test macros are
|
||||
// static as typically these macros are used in a .h file that can be
|
||||
// #included in multiple translation units linked together.
|
||||
// Marked [[maybe_unused]] because TypedTestSuitePState is
|
||||
// constexpr-constructible and trivially destructible, so an uninstantiated
|
||||
// suite would otherwise trigger -Wunused-variable.
|
||||
#define TYPED_TEST_SUITE_P(SuiteName) \
|
||||
static ::testing::internal::TypedTestSuitePState \
|
||||
[[maybe_unused]] static ::testing::internal::TypedTestSuitePState \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName)
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
@ -276,10 +279,12 @@ INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes);
|
||||
typedef gtest_TypeParam_ TypeParam; \
|
||||
void TestBody() override; \
|
||||
}; \
|
||||
static ::testing::internal::TypedTestSuitePState::RegisteredTest \
|
||||
gtest_##TestName##_registered_test_ = {GTEST_STRINGIFY_(TestName), \
|
||||
__FILE__, __LINE__, nullptr}; \
|
||||
[[maybe_unused]] static bool gtest_##TestName##_defined_ = \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName( \
|
||||
__FILE__, __LINE__, GTEST_STRINGIFY_(SuiteName), \
|
||||
GTEST_STRINGIFY_(TestName)); \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTest( \
|
||||
>est_##TestName##_registered_test_, GTEST_STRINGIFY_(SuiteName)); \
|
||||
} \
|
||||
template <typename gtest_TypeParam_> \
|
||||
void GTEST_SUITE_NAMESPACE_( \
|
||||
|
||||
@ -577,51 +577,87 @@ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
// State of the definition of a type-parameterized test suite.
|
||||
// Uses an intrusive linked list of caller-allocated POD RegisteredTest nodes
|
||||
// rather than an STL container.
|
||||
// REQUIRES: TypedTestSuitePState must be constexpr-constructible and trivially
|
||||
// destructible so that namespace-scope static instances defined by
|
||||
// TYPED_TEST_SUITE_P avoid -Wglobal-constructors and -Wexit-time-destructors.
|
||||
class GTEST_API_ [[nodiscard]] TypedTestSuitePState {
|
||||
public:
|
||||
TypedTestSuitePState() : registered_(false) {}
|
||||
// Caller-owned POD node linked into registered_tests_; must outlive this
|
||||
// TypedTestSuitePState instance.
|
||||
struct RegisteredTest {
|
||||
const char* name;
|
||||
const char* file;
|
||||
int line;
|
||||
RegisteredTest* next;
|
||||
};
|
||||
|
||||
// Adds the given test name to defined_test_names_ and return true
|
||||
// if the test suite hasn't been registered; otherwise aborts the
|
||||
// program.
|
||||
bool AddTestName(const char* file, int line, const char* case_name,
|
||||
const char* test_name) {
|
||||
constexpr TypedTestSuitePState() = default;
|
||||
|
||||
// Adds the given test to registered_tests_ and returns true if the test
|
||||
// suite hasn't been registered; otherwise aborts the program.
|
||||
bool AddTest(RegisteredTest* test, const char* case_name) {
|
||||
if (registered_) {
|
||||
fprintf(stderr,
|
||||
"%s Test %s must be defined before "
|
||||
"REGISTER_TYPED_TEST_SUITE_P(%s, ...).\n",
|
||||
FormatFileLocation(file, line).c_str(), test_name, case_name);
|
||||
FormatFileLocation(test->file, test->line).c_str(), test->name,
|
||||
case_name);
|
||||
fflush(stderr);
|
||||
posix::Abort();
|
||||
}
|
||||
registered_tests_.emplace(test_name, CodeLocation(file, line));
|
||||
// Insert into registered_tests_ in lexicographical order by test name
|
||||
// (ignoring duplicates) so VerifyRegisteredTestNames reports missing tests
|
||||
// in deterministic sorted order.
|
||||
RegisteredTest** p = ®istered_tests_;
|
||||
while (*p != nullptr && strcmp((*p)->name, test->name) < 0) {
|
||||
p = &(*p)->next;
|
||||
}
|
||||
if (*p == nullptr || strcmp((*p)->name, test->name) != 0) {
|
||||
test->next = *p;
|
||||
*p = test;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestExists(const std::string& test_name) const {
|
||||
return registered_tests_.count(test_name) > 0;
|
||||
return FindTest(test_name) != nullptr;
|
||||
}
|
||||
|
||||
const CodeLocation& GetCodeLocation(const std::string& test_name) const {
|
||||
RegisteredTestsMap::const_iterator it = registered_tests_.find(test_name);
|
||||
GTEST_CHECK_(it != registered_tests_.end());
|
||||
return it->second;
|
||||
CodeLocation GetCodeLocation(const std::string& test_name) const {
|
||||
const RegisteredTest* test = FindTest(test_name);
|
||||
GTEST_CHECK_(test != nullptr);
|
||||
return CodeLocation(test->file, test->line);
|
||||
}
|
||||
|
||||
// Verifies that registered_tests match the test names in
|
||||
// defined_test_names_; returns registered_tests if successful, or
|
||||
// registered_tests_; returns registered_tests if successful, or
|
||||
// aborts the program otherwise.
|
||||
const char* VerifyRegisteredTestNames(const char* test_suite_name,
|
||||
const char* file, int line,
|
||||
const char* registered_tests);
|
||||
|
||||
private:
|
||||
typedef ::std::map<std::string, CodeLocation, std::less<>> RegisteredTestsMap;
|
||||
const RegisteredTest* FindTest(const std::string& test_name) const {
|
||||
for (const RegisteredTest* it = registered_tests_; it != nullptr;
|
||||
it = it->next) {
|
||||
if (test_name == it->name) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool registered_;
|
||||
RegisteredTestsMap registered_tests_;
|
||||
bool registered_ = false;
|
||||
RegisteredTest* registered_tests_ = nullptr;
|
||||
};
|
||||
|
||||
static_assert((static_cast<void>(TypedTestSuitePState{}), true),
|
||||
"TypedTestSuitePState must be constexpr-constructible");
|
||||
static_assert(std::is_trivially_destructible_v<TypedTestSuitePState>,
|
||||
"TypedTestSuitePState must be trivially destructible");
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
using TypedTestCasePState = TypedTestSuitePState;
|
||||
|
||||
@ -62,7 +62,6 @@ const char* TypedTestSuitePState::VerifyRegisteredTestNames(
|
||||
const char* registered_tests) {
|
||||
RegisterTypeParameterizedTestSuite(test_suite_name, CodeLocation(file, line));
|
||||
|
||||
typedef RegisteredTestsMap::const_iterator RegisteredTestIter;
|
||||
registered_ = true;
|
||||
|
||||
std::vector<std::string> name_vec = SplitIntoTestNames(registered_tests);
|
||||
@ -78,7 +77,7 @@ const char* TypedTestSuitePState::VerifyRegisteredTestNames(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (registered_tests_.count(name) != 0) {
|
||||
if (TestExists(name)) {
|
||||
tests.insert(name);
|
||||
} else {
|
||||
errors << "No test named " << name
|
||||
@ -86,10 +85,10 @@ const char* TypedTestSuitePState::VerifyRegisteredTestNames(
|
||||
}
|
||||
}
|
||||
|
||||
for (RegisteredTestIter it = registered_tests_.begin();
|
||||
it != registered_tests_.end(); ++it) {
|
||||
if (tests.count(it->first) == 0) {
|
||||
errors << "You forgot to list test " << it->first << ".\n";
|
||||
for (const RegisteredTest* it = registered_tests_; it != nullptr;
|
||||
it = it->next) {
|
||||
if (tests.count(it->name) == 0) {
|
||||
errors << "You forgot to list test " << it->name << ".\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -200,12 +200,15 @@ using testing::internal::TypedTestSuitePState;
|
||||
class TypedTestSuitePStateTest : public Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
state_.AddTestName("foo.cc", 0, "FooTest", "A");
|
||||
state_.AddTestName("foo.cc", 0, "FooTest", "B");
|
||||
state_.AddTestName("foo.cc", 0, "FooTest", "C");
|
||||
state_.AddTest(&test_a_, "FooTest");
|
||||
state_.AddTest(&test_b_, "FooTest");
|
||||
state_.AddTest(&test_c_, "FooTest");
|
||||
}
|
||||
|
||||
TypedTestSuitePState state_;
|
||||
TypedTestSuitePState::RegisteredTest test_a_ = {"A", "foo.cc", 0, nullptr};
|
||||
TypedTestSuitePState::RegisteredTest test_b_ = {"B", "foo.cc", 0, nullptr};
|
||||
TypedTestSuitePState::RegisteredTest test_c_ = {"C", "foo.cc", 0, nullptr};
|
||||
};
|
||||
|
||||
TEST_F(TypedTestSuitePStateTest, SucceedsForMatchingList) {
|
||||
@ -246,8 +249,9 @@ TEST_F(TypedTestSuitePStateDeathTest, DetectsMissedTest) {
|
||||
// a run-time error if the test case has been registered.
|
||||
TEST_F(TypedTestSuitePStateDeathTest, DetectsTestAfterRegistration) {
|
||||
state_.VerifyRegisteredTestNames("Suite", "foo.cc", 1, "A, B, C");
|
||||
TypedTestSuitePState::RegisteredTest test_d = {"D", "foo.cc", 2, nullptr};
|
||||
EXPECT_DEATH_IF_SUPPORTED(
|
||||
state_.AddTestName("foo.cc", 2, "FooTest", "D"),
|
||||
state_.AddTest(&test_d, "FooTest"),
|
||||
"foo\\.cc.2.?: Test D must be defined before REGISTER_TYPED_TEST_SUITE_P"
|
||||
"\\(FooTest, \\.\\.\\.\\)\\.");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user