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
|
// The variables defined in the type-parameterized test macros are
|
||||||
// static as typically these macros are used in a .h file that can be
|
// static as typically these macros are used in a .h file that can be
|
||||||
// #included in multiple translation units linked together.
|
// #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) \
|
#define TYPED_TEST_SUITE_P(SuiteName) \
|
||||||
static ::testing::internal::TypedTestSuitePState \
|
[[maybe_unused]] static ::testing::internal::TypedTestSuitePState \
|
||||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName)
|
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName)
|
||||||
|
|
||||||
// Legacy API is deprecated but still available
|
// Legacy API is deprecated but still available
|
||||||
@ -276,10 +279,12 @@ INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes);
|
|||||||
typedef gtest_TypeParam_ TypeParam; \
|
typedef gtest_TypeParam_ TypeParam; \
|
||||||
void TestBody() override; \
|
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_ = \
|
[[maybe_unused]] static bool gtest_##TestName##_defined_ = \
|
||||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName( \
|
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTest( \
|
||||||
__FILE__, __LINE__, GTEST_STRINGIFY_(SuiteName), \
|
>est_##TestName##_registered_test_, GTEST_STRINGIFY_(SuiteName)); \
|
||||||
GTEST_STRINGIFY_(TestName)); \
|
|
||||||
} \
|
} \
|
||||||
template <typename gtest_TypeParam_> \
|
template <typename gtest_TypeParam_> \
|
||||||
void GTEST_SUITE_NAMESPACE_( \
|
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 */)
|
/* 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.
|
// 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 {
|
class GTEST_API_ [[nodiscard]] TypedTestSuitePState {
|
||||||
public:
|
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
|
constexpr TypedTestSuitePState() = default;
|
||||||
// if the test suite hasn't been registered; otherwise aborts the
|
|
||||||
// program.
|
// Adds the given test to registered_tests_ and returns true if the test
|
||||||
bool AddTestName(const char* file, int line, const char* case_name,
|
// suite hasn't been registered; otherwise aborts the program.
|
||||||
const char* test_name) {
|
bool AddTest(RegisteredTest* test, const char* case_name) {
|
||||||
if (registered_) {
|
if (registered_) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"%s Test %s must be defined before "
|
"%s Test %s must be defined before "
|
||||||
"REGISTER_TYPED_TEST_SUITE_P(%s, ...).\n",
|
"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);
|
fflush(stderr);
|
||||||
posix::Abort();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestExists(const std::string& test_name) const {
|
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 {
|
CodeLocation GetCodeLocation(const std::string& test_name) const {
|
||||||
RegisteredTestsMap::const_iterator it = registered_tests_.find(test_name);
|
const RegisteredTest* test = FindTest(test_name);
|
||||||
GTEST_CHECK_(it != registered_tests_.end());
|
GTEST_CHECK_(test != nullptr);
|
||||||
return it->second;
|
return CodeLocation(test->file, test->line);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifies that registered_tests match the test names in
|
// 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.
|
// aborts the program otherwise.
|
||||||
const char* VerifyRegisteredTestNames(const char* test_suite_name,
|
const char* VerifyRegisteredTestNames(const char* test_suite_name,
|
||||||
const char* file, int line,
|
const char* file, int line,
|
||||||
const char* registered_tests);
|
const char* registered_tests);
|
||||||
|
|
||||||
private:
|
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_;
|
bool registered_ = false;
|
||||||
RegisteredTestsMap registered_tests_;
|
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
|
// Legacy API is deprecated but still available
|
||||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||||
using TypedTestCasePState = TypedTestSuitePState;
|
using TypedTestCasePState = TypedTestSuitePState;
|
||||||
|
|||||||
@ -62,7 +62,6 @@ const char* TypedTestSuitePState::VerifyRegisteredTestNames(
|
|||||||
const char* registered_tests) {
|
const char* registered_tests) {
|
||||||
RegisterTypeParameterizedTestSuite(test_suite_name, CodeLocation(file, line));
|
RegisterTypeParameterizedTestSuite(test_suite_name, CodeLocation(file, line));
|
||||||
|
|
||||||
typedef RegisteredTestsMap::const_iterator RegisteredTestIter;
|
|
||||||
registered_ = true;
|
registered_ = true;
|
||||||
|
|
||||||
std::vector<std::string> name_vec = SplitIntoTestNames(registered_tests);
|
std::vector<std::string> name_vec = SplitIntoTestNames(registered_tests);
|
||||||
@ -78,7 +77,7 @@ const char* TypedTestSuitePState::VerifyRegisteredTestNames(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registered_tests_.count(name) != 0) {
|
if (TestExists(name)) {
|
||||||
tests.insert(name);
|
tests.insert(name);
|
||||||
} else {
|
} else {
|
||||||
errors << "No test named " << name
|
errors << "No test named " << name
|
||||||
@ -86,10 +85,10 @@ const char* TypedTestSuitePState::VerifyRegisteredTestNames(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (RegisteredTestIter it = registered_tests_.begin();
|
for (const RegisteredTest* it = registered_tests_; it != nullptr;
|
||||||
it != registered_tests_.end(); ++it) {
|
it = it->next) {
|
||||||
if (tests.count(it->first) == 0) {
|
if (tests.count(it->name) == 0) {
|
||||||
errors << "You forgot to list test " << it->first << ".\n";
|
errors << "You forgot to list test " << it->name << ".\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -200,12 +200,15 @@ using testing::internal::TypedTestSuitePState;
|
|||||||
class TypedTestSuitePStateTest : public Test {
|
class TypedTestSuitePStateTest : public Test {
|
||||||
protected:
|
protected:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
state_.AddTestName("foo.cc", 0, "FooTest", "A");
|
state_.AddTest(&test_a_, "FooTest");
|
||||||
state_.AddTestName("foo.cc", 0, "FooTest", "B");
|
state_.AddTest(&test_b_, "FooTest");
|
||||||
state_.AddTestName("foo.cc", 0, "FooTest", "C");
|
state_.AddTest(&test_c_, "FooTest");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedTestSuitePState state_;
|
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) {
|
TEST_F(TypedTestSuitePStateTest, SucceedsForMatchingList) {
|
||||||
@ -246,8 +249,9 @@ TEST_F(TypedTestSuitePStateDeathTest, DetectsMissedTest) {
|
|||||||
// a run-time error if the test case has been registered.
|
// a run-time error if the test case has been registered.
|
||||||
TEST_F(TypedTestSuitePStateDeathTest, DetectsTestAfterRegistration) {
|
TEST_F(TypedTestSuitePStateDeathTest, DetectsTestAfterRegistration) {
|
||||||
state_.VerifyRegisteredTestNames("Suite", "foo.cc", 1, "A, B, C");
|
state_.VerifyRegisteredTestNames("Suite", "foo.cc", 1, "A, B, C");
|
||||||
|
TypedTestSuitePState::RegisteredTest test_d = {"D", "foo.cc", 2, nullptr};
|
||||||
EXPECT_DEATH_IF_SUPPORTED(
|
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"
|
"foo\\.cc.2.?: Test D must be defined before REGISTER_TYPED_TEST_SUITE_P"
|
||||||
"\\(FooTest, \\.\\.\\.\\)\\.");
|
"\\(FooTest, \\.\\.\\.\\)\\.");
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user