Googletest export

Detect when C++ parametric tests (TEST_P) are not instantiated.

When an un-instantiated TEST_P is found, a new test will be inserted that will emit a warning message.

This can be made to error with minor code edits.
In the future, that is intended to be the default.

PiperOrigin-RevId: 284901666
This commit is contained in:
Abseil Team 2019-12-10 22:43:25 -05:00 committed by Matt Calabrese
parent 88ba008c23
commit d442089d53
5 changed files with 96 additions and 4 deletions

View File

@ -42,12 +42,14 @@
#include <memory> #include <memory>
#include <set> #include <set>
#include <tuple> #include <tuple>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "gtest/internal/gtest-internal.h" #include "gtest/internal/gtest-internal.h"
#include "gtest/internal/gtest-port.h" #include "gtest/internal/gtest-port.h"
#include "gtest/gtest-printers.h" #include "gtest/gtest-printers.h"
#include "gtest/gtest-test-part.h"
namespace testing { namespace testing {
// Input to a parameterized test name generator, describing a test parameter. // Input to a parameterized test name generator, describing a test parameter.
@ -472,6 +474,8 @@ class ParameterizedTestSuiteInfoBase {
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfoBase); GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfoBase);
}; };
void InsertSyntheticTestCase(const std::string &name, CodeLocation location);
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. // INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
// //
// ParameterizedTestSuiteInfo accumulates tests obtained from TEST_P // ParameterizedTestSuiteInfo accumulates tests obtained from TEST_P
@ -522,11 +526,13 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
return 0; // Return value used only to run this method in namespace scope. return 0; // Return value used only to run this method in namespace scope.
} }
// UnitTest class invokes this method to register tests in this test suite // UnitTest class invokes this method to register tests in this test suite
// test suites right before running tests in RUN_ALL_TESTS macro. // right before running tests in RUN_ALL_TESTS macro.
// This method should not be called more than once on any single // This method should not be called more than once on any single
// instance of a ParameterizedTestSuiteInfoBase derived class. // instance of a ParameterizedTestSuiteInfoBase derived class.
// UnitTest has a guard to prevent from calling this method more than once. // UnitTest has a guard to prevent from calling this method more than once.
void RegisterTests() override { void RegisterTests() override {
bool generated_instantiations = false;
for (typename TestInfoContainer::iterator test_it = tests_.begin(); for (typename TestInfoContainer::iterator test_it = tests_.begin();
test_it != tests_.end(); ++test_it) { test_it != tests_.end(); ++test_it) {
std::shared_ptr<TestInfo> test_info = *test_it; std::shared_ptr<TestInfo> test_info = *test_it;
@ -549,6 +555,8 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
for (typename ParamGenerator<ParamType>::iterator param_it = for (typename ParamGenerator<ParamType>::iterator param_it =
generator.begin(); generator.begin();
param_it != generator.end(); ++param_it, ++i) { param_it != generator.end(); ++param_it, ++i) {
generated_instantiations = true;
Message test_name_stream; Message test_name_stream;
std::string param_name = name_func( std::string param_name = name_func(
@ -577,6 +585,11 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
} // for param_it } // for param_it
} // for gen_it } // for gen_it
} // for test_it } // for test_it
if (!generated_instantiations) {
// There are no generaotrs, or they all generate nothing ...
InsertSyntheticTestCase(GetTestSuiteName(), code_location_);
}
} // RegisterTests } // RegisterTests
private: private:

View File

@ -407,6 +407,66 @@ void AssertHelper::operator=(const Message& message) const {
); // NOLINT ); // NOLINT
} }
namespace {
// When TEST_P is found without a matching INSTANTIATE_TEST_SUITE_P
// to creates test cases for it, a syntetic test case is
// inserted to report ether an error or a log message.
//
// This configuration bit will likely be removed at some point.
constexpr bool kErrorOnUninstantiatedParameterizedTest = false;
// A test that fails at a given file/line location with a given message.
class FailureTest : public Test {
public:
explicit FailureTest(const CodeLocation& loc, std::string error_message,
bool as_error)
: loc_(loc),
error_message_(std::move(error_message)),
as_error_(as_error) {}
void TestBody() override {
if (as_error_) {
AssertHelper(TestPartResult::kNonFatalFailure, loc_.file.c_str(),
loc_.line, "") = Message() << error_message_;
} else {
std::cout << error_message_ << std::endl;
}
}
private:
const CodeLocation loc_;
const std::string error_message_;
const bool as_error_;
};
} // namespace
// If this parameterized test suite has no instantiations (and that
// has not been marked as okay), emit a test case reporting that.
void InsertSyntheticTestCase(const std::string &name, CodeLocation location) {
std::string message =
"Paramaterized test suite " + name +
" is defined via TEST_P, but never instantiated. None of the test cases "
"will run. Either no INSTANTIATE_TEST_SUITE_P is provided or the only "
"ones provided expand to nothing."
"\n\n"
"Ideally, TEST_P definitions should only ever be included as part of "
"binaries that intend to use them. (As opposed to, for example, being "
"placed in a library that may be linked in to get other utilities.)";
std::string full_name = "UninstantiatedParamaterizedTestSuite<" + name + ">";
RegisterTest( //
"GoogleTestVerification", full_name.c_str(),
nullptr, // No type parameter.
nullptr, // No value parameter.
location.file.c_str(), location.line, [message, location] {
return new FailureTest(location, message,
kErrorOnUninstantiatedParameterizedTest);
});
}
// A copy of all command line arguments. Set by InitGoogleTest(). // A copy of all command line arguments. Set by InitGoogleTest().
static ::std::vector<std::string> g_argvs; static ::std::vector<std::string> g_argvs;

View File

@ -12,7 +12,7 @@ Expected equality of these values:
3 3
Stack trace: (omitted) Stack trace: (omitted)
[==========] Running 84 tests from 39 test suites. [==========] Running 85 tests from 40 test suites.
[----------] Global test environment set-up. [----------] Global test environment set-up.
FooEnvironment::SetUp() called. FooEnvironment::SetUp() called.
BarEnvironment::SetUp() called. BarEnvironment::SetUp() called.
@ -979,6 +979,12 @@ Expected failure
Stack trace: (omitted) Stack trace: (omitted)
[ FAILED ] PrintingStrings/ParamTest.Failure/a, where GetParam() = "a" [ FAILED ] PrintingStrings/ParamTest.Failure/a, where GetParam() = "a"
[----------] 1 test from GoogleTestVerification
[ RUN ] GoogleTestVerification.UninstantiatedParamaterizedTestSuite<DetectNotInstantiatedTest>
Paramaterized test suite DetectNotInstantiatedTest is defined via TEST_P, but never instantiated. None of the test cases will run. Either no INSTANTIATE_TEST_SUITE_P is provided or the only ones provided expand to nothing.
Ideally, TEST_P definitions should only ever be included as part of binaries that intend to use them. (As opposed to, for example, being placed in a library that may be linked in to get other utilities.)
[ OK ] GoogleTestVerification.UninstantiatedParamaterizedTestSuite<DetectNotInstantiatedTest>
[----------] Global test environment tear-down [----------] Global test environment tear-down
BarEnvironment::TearDown() called. BarEnvironment::TearDown() called.
googletest-output-test_.cc:#: Failure googletest-output-test_.cc:#: Failure
@ -992,8 +998,8 @@ Failed
Expected fatal failure. Expected fatal failure.
Stack trace: (omitted) Stack trace: (omitted)
[==========] 84 tests from 39 test suites ran. [==========] 85 tests from 40 test suites ran.
[ PASSED ] 30 tests. [ PASSED ] 31 tests.
[ FAILED ] 54 tests, listed below: [ FAILED ] 54 tests, listed below:
[ FAILED ] NonfatalFailureTest.EscapesStringOperands [ FAILED ] NonfatalFailureTest.EscapesStringOperands
[ FAILED ] NonfatalFailureTest.DiffForLongStrings [ FAILED ] NonfatalFailureTest.DiffForLongStrings

View File

@ -782,6 +782,13 @@ INSTANTIATE_TEST_SUITE_P(PrintingStrings,
testing::Values(std::string("a")), testing::Values(std::string("a")),
ParamNameFunc); ParamNameFunc);
// fails under kErrorOnUninstantiatedParameterizedTest=true
class DetectNotInstantiatedTest : public testing::TestWithParam<int> {};
TEST_P(DetectNotInstantiatedTest, Used) { }
// This would make the test failure from the above go away.
// INSTANTIATE_TEST_SUITE_P(Fix, DetectNotInstantiatedTest, testing::Values(1));
// This #ifdef block tests the output of typed tests. // This #ifdef block tests the output of typed tests.
#if GTEST_HAS_TYPED_TEST #if GTEST_HAS_TYPED_TEST

View File

@ -1068,6 +1068,12 @@ TEST_P(MyEnumTest, ChecksParamMoreThanZero) { EXPECT_GE(10, GetParam()); }
INSTANTIATE_TEST_SUITE_P(MyEnumTests, MyEnumTest, INSTANTIATE_TEST_SUITE_P(MyEnumTests, MyEnumTest,
::testing::Values(ENUM1, ENUM2, 0)); ::testing::Values(ENUM1, ENUM2, 0));
namespace works_here {
// Never used not instantiated, this should work.
class NotUsedTest : public testing::TestWithParam<int> {};
} // namespace works_here
int main(int argc, char **argv) { int main(int argc, char **argv) {
// Used in TestGenerationTest test suite. // Used in TestGenerationTest test suite.
AddGlobalTestEnvironment(TestGenerationTest::Environment::Instance()); AddGlobalTestEnvironment(TestGenerationTest::Environment::Instance());