Skip to content

Commit

Permalink
Googletest export
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Abseil Team authored and mattcalabrese-google committed Dec 13, 2019
1 parent 88ba008 commit d442089
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 4 deletions.
15 changes: 14 additions & 1 deletion googletest/include/gtest/internal/gtest-param-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@
#include <memory>
#include <set>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

#include "gtest/internal/gtest-internal.h"
#include "gtest/internal/gtest-port.h"
#include "gtest/gtest-printers.h"
#include "gtest/gtest-test-part.h"

namespace testing {
// Input to a parameterized test name generator, describing a test parameter.
Expand Down Expand Up @@ -472,6 +474,8 @@ class ParameterizedTestSuiteInfoBase {
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfoBase);
};

void InsertSyntheticTestCase(const std::string &name, CodeLocation location);

// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
//
// ParameterizedTestSuiteInfo accumulates tests obtained from TEST_P
Expand Down Expand Up @@ -522,11 +526,13 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
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
// 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
// instance of a ParameterizedTestSuiteInfoBase derived class.
// UnitTest has a guard to prevent from calling this method more than once.
void RegisterTests() override {
bool generated_instantiations = false;

for (typename TestInfoContainer::iterator test_it = tests_.begin();
test_it != tests_.end(); ++test_it) {
std::shared_ptr<TestInfo> test_info = *test_it;
Expand All @@ -549,6 +555,8 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
for (typename ParamGenerator<ParamType>::iterator param_it =
generator.begin();
param_it != generator.end(); ++param_it, ++i) {
generated_instantiations = true;

Message test_name_stream;

std::string param_name = name_func(
Expand Down Expand Up @@ -577,6 +585,11 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
} // for param_it
} // for gen_it
} // for test_it

if (!generated_instantiations) {
// There are no generaotrs, or they all generate nothing ...
InsertSyntheticTestCase(GetTestSuiteName(), code_location_);
}
} // RegisterTests

private:
Expand Down
60 changes: 60 additions & 0 deletions googletest/src/gtest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,66 @@ void AssertHelper::operator=(const Message& message) const {
); // 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().
static ::std::vector<std::string> g_argvs;

Expand Down
12 changes: 9 additions & 3 deletions googletest/test/googletest-output-test-golden-lin.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Expected equality of these values:
3
Stack trace: (omitted)

[==========] Running 84 tests from 39 test suites.
[==========] Running 85 tests from 40 test suites.
[----------] Global test environment set-up.
FooEnvironment::SetUp() called.
BarEnvironment::SetUp() called.
Expand Down Expand Up @@ -979,6 +979,12 @@ Expected failure
Stack trace: (omitted)

[ 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
BarEnvironment::TearDown() called.
googletest-output-test_.cc:#: Failure
Expand All @@ -992,8 +998,8 @@ Failed
Expected fatal failure.
Stack trace: (omitted)

[==========] 84 tests from 39 test suites ran.
[ PASSED ] 30 tests.
[==========] 85 tests from 40 test suites ran.
[ PASSED ] 31 tests.
[ FAILED ] 54 tests, listed below:
[ FAILED ] NonfatalFailureTest.EscapesStringOperands
[ FAILED ] NonfatalFailureTest.DiffForLongStrings
Expand Down
7 changes: 7 additions & 0 deletions googletest/test/googletest-output-test_.cc
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,13 @@ INSTANTIATE_TEST_SUITE_P(PrintingStrings,
testing::Values(std::string("a")),
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.
#if GTEST_HAS_TYPED_TEST

Expand Down
6 changes: 6 additions & 0 deletions googletest/test/googletest-param-test-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,12 @@ TEST_P(MyEnumTest, ChecksParamMoreThanZero) { EXPECT_GE(10, GetParam()); }
INSTANTIATE_TEST_SUITE_P(MyEnumTests, MyEnumTest,
::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) {
// Used in TestGenerationTest test suite.
AddGlobalTestEnvironment(TestGenerationTest::Environment::Instance());
Expand Down

0 comments on commit d442089

Please sign in to comment.