From 54501746a6cfd366b6e7bab3c902e0f8d5826521 Mon Sep 17 00:00:00 2001 From: Zhanyong Wan Date: Mon, 24 Feb 2025 13:17:47 -0800 Subject: [PATCH] Adds support for a --gtest_fail_if_no_test_linked flag to fail the test program if no test case is linked in (a common programmer mistake). PiperOrigin-RevId: 730571311 Change-Id: I1dab04adfe35581274d0b4ec79a017014d50e1ea --- docs/advanced.md | 14 +++ googletest/src/gtest.cc | 15 +++ googletest/test/BUILD.bazel | 67 +++++++--- ...no-test-linked-test-with-disabled-test_.cc | 38 ++++++ ...-no-test-linked-test-with-enabled-test_.cc | 38 ++++++ .../googletest-fail-if-no-test-linked-test.py | 114 ++++++++++++++++++ 6 files changed, 270 insertions(+), 16 deletions(-) create mode 100644 googletest/test/googletest-fail-if-no-test-linked-test-with-disabled-test_.cc create mode 100644 googletest/test/googletest-fail-if-no-test-linked-test-with-enabled-test_.cc create mode 100755 googletest/test/googletest-fail-if-no-test-linked-test.py diff --git a/docs/advanced.md b/docs/advanced.md index 6b7715dc..9b1220a1 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -1929,6 +1929,20 @@ the `--gtest_also_run_disabled_tests` flag or set the You can combine this with the `--gtest_filter` flag to further select which disabled tests to run. +### Enforcing Having At Least One Test Case + +A not uncommon programmer mistake is to write a test program that has no test +case linked in. This can happen, for example, when you put test case definitions +in a library and the library is not marked as "always link". + +To catch such mistakes, run the test program with the +`--gtest_fail_if_no_test_linked` flag or set the `GTEST_FAIL_IF_NO_TEST_LINKED` +environment variable to a value other than `0`. Now the program will fail if no +test case is linked in. + +Note that *any* test case linked in makes the program valid for the purpose of +this check. In particular, even a disabled test case suffices. + ### Repeating the Tests Once in a while you'll run into a test whose result is hit-or-miss. Perhaps it diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index 7ff82546..81096ae2 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -258,6 +258,12 @@ GTEST_DEFINE_bool_( testing::GetDefaultFailFast()), "True if and only if a test failure should stop further test execution."); +GTEST_DEFINE_bool_( + fail_if_no_test_linked, + testing::internal::BoolFromGTestEnv("fail_if_no_test_linked", false), + "True if and only if the test should fail if no test case (including " + "disabled test cases) is linked."); + GTEST_DEFINE_bool_( also_run_disabled_tests, testing::internal::BoolFromGTestEnv("also_run_disabled_tests", false), @@ -5890,6 +5896,14 @@ bool UnitTestImpl::RunAllTests() { // user didn't call InitGoogleTest. PostFlagParsingInit(); + if (GTEST_FLAG_GET(fail_if_no_test_linked) && total_test_count() == 0) { + ColoredPrintf( + GTestColor::kRed, + "This test program does NOT link in any test case. This is INVALID. " + "Please make sure to link in at least one test case.\n"); + return false; + } + #if GTEST_HAS_FILE_SYSTEM // Even if sharding is not on, test runners may want to use the // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding @@ -6677,6 +6691,7 @@ static bool ParseGoogleTestFlag(const char* const arg) { GTEST_INTERNAL_PARSE_FLAG(death_test_style); GTEST_INTERNAL_PARSE_FLAG(death_test_use_fork); GTEST_INTERNAL_PARSE_FLAG(fail_fast); + GTEST_INTERNAL_PARSE_FLAG(fail_if_no_test_linked); GTEST_INTERNAL_PARSE_FLAG(filter); GTEST_INTERNAL_PARSE_FLAG(internal_run_death_test); GTEST_INTERNAL_PARSE_FLAG(list_tests); diff --git a/googletest/test/BUILD.bazel b/googletest/test/BUILD.bazel index 234b1a8f..c561ef8b 100644 --- a/googletest/test/BUILD.bazel +++ b/googletest/test/BUILD.bazel @@ -47,33 +47,36 @@ cc_test( "*.h", ], exclude = [ - "gtest-unittest-api_test.cc", - "googletest/src/gtest-all.cc", - "gtest_all_test.cc", - "gtest-death-test_ex_test.cc", - "gtest-listener_test.cc", - "gtest-unittest-api_test.cc", - "googletest-param-test-test.cc", - "googletest-param-test2-test.cc", + # go/keep-sorted start + "googletest-break-on-failure-unittest_.cc", "googletest-catch-exceptions-test_.cc", "googletest-color-test_.cc", + "googletest-death-test_ex_test.cc", "googletest-env-var-test_.cc", + "googletest-fail-if-no-test-linked-test-with-disabled-test_.cc", + "googletest-fail-if-no-test-linked-test-with-enabled-test_.cc", "googletest-failfast-unittest_.cc", "googletest-filter-unittest_.cc", "googletest-global-environment-unittest_.cc", - "googletest-break-on-failure-unittest_.cc", + "googletest-list-tests-unittest_.cc", "googletest-listener-test.cc", "googletest-message-test.cc", "googletest-output-test_.cc", - "googletest-list-tests-unittest_.cc", - "googletest-shuffle-test_.cc", - "googletest-setuptestsuite-test_.cc", - "googletest-uninitialized-test_.cc", - "googletest-death-test_ex_test.cc", - "googletest-param-test-test", - "googletest-throw-on-failure-test_.cc", "googletest-param-test-invalid-name1-test_.cc", "googletest-param-test-invalid-name2-test_.cc", + "googletest-param-test-test", + "googletest-param-test-test.cc", + "googletest-param-test2-test.cc", + "googletest-setuptestsuite-test_.cc", + "googletest-shuffle-test_.cc", + "googletest-throw-on-failure-test_.cc", + "googletest-uninitialized-test_.cc", + "googletest/src/gtest-all.cc", + "gtest-death-test_ex_test.cc", + "gtest-listener_test.cc", + "gtest-unittest-api_test.cc", + "gtest_all_test.cc", + # go/keep-sorted end ], ) + select({ "//:windows": [], @@ -323,6 +326,26 @@ cc_binary( deps = ["//:gtest"], ) +cc_binary( + name = "googletest-fail-if-no-test-linked-test-without-test_", + testonly = 1, + deps = ["//:gtest_main"], +) + +cc_binary( + name = "googletest-fail-if-no-test-linked-test-with-disabled-test_", + testonly = 1, + srcs = ["googletest-fail-if-no-test-linked-test-with-disabled-test_.cc"], + deps = ["//:gtest_main"], +) + +cc_binary( + name = "googletest-fail-if-no-test-linked-test-with-enabled-test_", + testonly = 1, + srcs = ["googletest-fail-if-no-test-linked-test-with-enabled-test_.cc"], + deps = ["//:gtest_main"], +) + cc_test( name = "gtest_skip_test", size = "small", @@ -363,6 +386,18 @@ py_test( deps = [":gtest_test_utils"], ) +py_test( + name = "googletest-fail-if-no-test-linked-test", + size = "small", + srcs = ["googletest-fail-if-no-test-linked-test.py"], + data = [ + ":googletest-fail-if-no-test-linked-test-with-disabled-test_", + ":googletest-fail-if-no-test-linked-test-with-enabled-test_", + ":googletest-fail-if-no-test-linked-test-without-test_", + ], + deps = [":gtest_test_utils"], +) + cc_binary( name = "googletest-shuffle-test_", srcs = ["googletest-shuffle-test_.cc"], diff --git a/googletest/test/googletest-fail-if-no-test-linked-test-with-disabled-test_.cc b/googletest/test/googletest-fail-if-no-test-linked-test-with-disabled-test_.cc new file mode 100644 index 00000000..56d26901 --- /dev/null +++ b/googletest/test/googletest-fail-if-no-test-linked-test-with-disabled-test_.cc @@ -0,0 +1,38 @@ +// Copyright 2025, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Unit test for Google Test's --gtest_fail_if_no_test_linked flag. +// +// This program will be invoked from a Python test. +// Don't run it directly. + +#include "gtest/gtest.h" + +// A dummy test that is disabled. +TEST(SomeTest, DISABLED_Test1) {} diff --git a/googletest/test/googletest-fail-if-no-test-linked-test-with-enabled-test_.cc b/googletest/test/googletest-fail-if-no-test-linked-test-with-enabled-test_.cc new file mode 100644 index 00000000..1ed20343 --- /dev/null +++ b/googletest/test/googletest-fail-if-no-test-linked-test-with-enabled-test_.cc @@ -0,0 +1,38 @@ +// Copyright 2025, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Unit test for Google Test's --gtest_fail_if_no_test_linked flag. +// +// This program will be invoked from a Python test. +// Don't run it directly. + +#include "gtest/gtest.h" + +// A dummy test that is enabled. +TEST(SomeTest, Test1) {} diff --git a/googletest/test/googletest-fail-if-no-test-linked-test.py b/googletest/test/googletest-fail-if-no-test-linked-test.py new file mode 100755 index 00000000..3ecf2084 --- /dev/null +++ b/googletest/test/googletest-fail-if-no-test-linked-test.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 # pylint: disable=g-interpreter-mismatch +# +# Copyright 2025, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Tests for Google Test's --gtest_fail_if_no_test_linked flag.""" + +from googletest.test import gtest_test_utils + +# The command line flag for enabling the fail-if-no-test-linked behavior. +FAIL_IF_NO_TEST_LINKED_FLAG = "gtest_fail_if_no_test_linked" + + +class GTestFailIfNoTestLinkedTest(gtest_test_utils.TestCase): + """Tests the --gtest_fail_if_no_test_linked flag.""" + + def Run(self, program_name, flag=None): + """Run the given program with the given flag. + + Args: + program_name: Name of the program to run. + flag: The command line flag to pass to the program, or None. + + Returns: + True if the program exits with code 0, false otherwise. + """ + + exe_path = gtest_test_utils.GetTestExecutablePath(program_name) + args = [exe_path] + if flag is not None: + args += [flag] + process = gtest_test_utils.Subprocess(args, capture_stderr=False) + return process.exited and process.exit_code == 0 + + def testSucceedsIfNoTestLinkedAndFlagNotSpecified(self): + """Tests the behavior of no test linked and flag not specified.""" + + self.assertTrue( + self.Run("googletest-fail-if-no-test-linked-test-without-test_") + ) + + def testFailsIfNoTestLinkedAndFlagSpecified(self): + """Tests the behavior of no test linked and flag specified.""" + + self.assertFalse( + self.Run( + "googletest-fail-if-no-test-linked-test-without-test_", + f"--{FAIL_IF_NO_TEST_LINKED_FLAG}", + ) + ) + + def testSucceedsIfEnabledTestLinkedAndFlagNotSpecified(self): + """Tests the behavior of enabled test linked and flag not specified.""" + + self.assertTrue( + self.Run("googletest-fail-if-no-test-linked-test-with-enabled-test_") + ) + + def testSucceedsIfEnabledTestLinkedAndFlagSpecified(self): + """Tests the behavior of enabled test linked and flag specified.""" + + self.assertTrue( + self.Run( + "googletest-fail-if-no-test-linked-test-with-enabled-test_", + f"--{FAIL_IF_NO_TEST_LINKED_FLAG}", + ) + ) + + def testSucceedsIfDisabledTestLinkedAndFlagNotSpecified(self): + """Tests the behavior of disabled test linked and flag not specified.""" + + self.assertTrue( + self.Run("googletest-fail-if-no-test-linked-test-with-disabled-test_") + ) + + def testSucceedsIfDisabledTestLinkedAndFlagSpecified(self): + """Tests the behavior of disabled test linked and flag specified.""" + + self.assertTrue( + self.Run( + "googletest-fail-if-no-test-linked-test-with-disabled-test_", + f"--{FAIL_IF_NO_TEST_LINKED_FLAG}", + ) + ) + + +if __name__ == "__main__": + gtest_test_utils.Main()