From e5669fdffc94e901bcad20a69b38547012f81882 Mon Sep 17 00:00:00 2001 From: Derek Mauro Date: Wed, 26 Feb 2025 11:56:21 -0800 Subject: [PATCH] Output to the test warning file if no tests are linked. Bazel sets the environment variable TEST_WARNINGS_OUTPUT_FILE https://bazel.build/reference/test-encyclopedia#initial-conditions If no tests are linked and the new flag --gtest_fail_if_no_test_linked is not true (which is the current default), we can still warn the user as this may be a programming error without failing the test (which would break existing users). PiperOrigin-RevId: 731402363 Change-Id: Ia481689efd4bd18889feaaa38bc56049a3f651cd --- googletest/src/gtest.cc | 46 +++++++++++-- .../googletest-fail-if-no-test-linked-test.py | 69 +++++++++++++++++-- 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index 81096ae28..26959dfc0 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -192,12 +192,17 @@ static const char kDefaultOutputFormat[] = "xml"; // The default output file. static const char kDefaultOutputFile[] = "test_detail"; +// These environment variables are set by Bazel. +// https://bazel.build/reference/test-encyclopedia#initial-conditions +// // The environment variable name for the test shard index. static const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; // The environment variable name for the total number of test shards. static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS"; // The environment variable name for the test shard status file. static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE"; +// The environment variable name for the test output warnings file. +static const char kTestWarningsOutputFile[] = "TEST_WARNINGS_OUTPUT_FILE"; namespace internal { @@ -5875,6 +5880,23 @@ TestSuite* UnitTestImpl::GetTestSuite( static void SetUpEnvironment(Environment* env) { env->SetUp(); } static void TearDownEnvironment(Environment* env) { env->TearDown(); } +// If the environment variable TEST_WARNINGS_OUTPUT_FILE was provided, appends +// `str` to the file, creating the file if necessary. +#if GTEST_HAS_FILE_SYSTEM +static void AppendToTestWarningsOutputFile(const std::string& str) { + const char* const filename = posix::GetEnv(kTestWarningsOutputFile); + if (filename == nullptr) { + return; + } + auto* const file = posix::FOpen(filename, "a"); + if (file == nullptr) { + return; + } + GTEST_CHECK_(fwrite(str.data(), 1, str.size(), file) == str.size()); + GTEST_CHECK_(posix::FClose(file) == 0); +} +#endif // GTEST_HAS_FILE_SYSTEM + // Runs all tests in this UnitTest object, prints the result, and // returns true if all tests are successful. If any exception is // thrown during a test, the test is considered to be failed, but the @@ -5896,12 +5918,26 @@ bool UnitTestImpl::RunAllTests() { // user didn't call InitGoogleTest. PostFlagParsingInit(); - if (GTEST_FLAG_GET(fail_if_no_test_linked) && total_test_count() == 0) { + // Handle the case where the program has no tests linked. + // Sometimes this is a programmer mistake, but sometimes it is intended. + if (total_test_count() == 0) { + constexpr char kNoTestLinkedMessage[] = + "This test program does NOT link in any test case."; + constexpr char kNoTestLinkedFatal[] = + "This is INVALID. Please make sure to link in at least one test case."; + constexpr char kNoTestLinkedWarning[] = + "Please make sure this is intended."; + const bool fail_if_no_test_linked = GTEST_FLAG_GET(fail_if_no_test_linked); 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; + GTestColor::kRed, "%s %s\n", kNoTestLinkedMessage, + fail_if_no_test_linked ? kNoTestLinkedFatal : kNoTestLinkedWarning); + if (fail_if_no_test_linked) { + return false; + } +#if GTEST_HAS_FILE_SYSTEM + AppendToTestWarningsOutputFile(std::string(kNoTestLinkedMessage) + ' ' + + kNoTestLinkedWarning + '\n'); +#endif // GTEST_HAS_FILE_SYSTEM } #if GTEST_HAS_FILE_SYSTEM diff --git a/googletest/test/googletest-fail-if-no-test-linked-test.py b/googletest/test/googletest-fail-if-no-test-linked-test.py index 3ecf20840..f5854ba9b 100755 --- a/googletest/test/googletest-fail-if-no-test-linked-test.py +++ b/googletest/test/googletest-fail-if-no-test-linked-test.py @@ -31,21 +31,26 @@ """Tests for Google Test's --gtest_fail_if_no_test_linked flag.""" +import os 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" +# The environment variable for the test output warnings file. +TEST_WARNINGS_OUTPUT_FILE = "TEST_WARNINGS_OUTPUT_FILE" + class GTestFailIfNoTestLinkedTest(gtest_test_utils.TestCase): """Tests the --gtest_fail_if_no_test_linked flag.""" - def Run(self, program_name, flag=None): + def Run(self, program_name, flag=None, env=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. + env: Dictionary with environment to pass to the subprocess. Returns: True if the program exits with code 0, false otherwise. @@ -55,59 +60,109 @@ class GTestFailIfNoTestLinkedTest(gtest_test_utils.TestCase): args = [exe_path] if flag is not None: args += [flag] - process = gtest_test_utils.Subprocess(args, capture_stderr=False) + process = gtest_test_utils.Subprocess(args, capture_stderr=False, env=env) 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 testSucceedsIfNoTestLinkedAndFlagNotSpecifiedWithWarningFile(self): + """Tests that no test linked results in warning file output.""" + + warning_file = os.path.join(gtest_test_utils.GetTempDir(), "NO_TEST_LINKED") + self.assertTrue( + self.Run( + "googletest-fail-if-no-test-linked-test-without-test_", + env={TEST_WARNINGS_OUTPUT_FILE: warning_file}, + ) + ) + warning_file_contents = open(warning_file, "r").read() + self.assertEqual( + warning_file_contents, + "This test program does NOT link in any test case. Please make sure" + " this is intended.\n", + ) + def testFailsIfNoTestLinkedAndFlagSpecified(self): """Tests the behavior of no test linked and flag specified.""" + warning_file = os.path.join( + gtest_test_utils.GetTempDir(), "SHOULD_NOT_EXIST" + ) self.assertFalse( self.Run( "googletest-fail-if-no-test-linked-test-without-test_", f"--{FAIL_IF_NO_TEST_LINKED_FLAG}", + env={TEST_WARNINGS_OUTPUT_FILE: warning_file}, ) ) + with self.assertRaises(FileNotFoundError): + open(warning_file, "r") 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_") + warning_file = os.path.join( + gtest_test_utils.GetTempDir(), "SHOULD_NOT_EXIST" ) + self.assertTrue( + self.Run( + "googletest-fail-if-no-test-linked-test-with-enabled-test_", + env={TEST_WARNINGS_OUTPUT_FILE: warning_file}, + ) + ) + with self.assertRaises(FileNotFoundError): + open(warning_file, "r") def testSucceedsIfEnabledTestLinkedAndFlagSpecified(self): """Tests the behavior of enabled test linked and flag specified.""" + warning_file = os.path.join( + gtest_test_utils.GetTempDir(), "SHOULD_NOT_EXIST" + ) self.assertTrue( self.Run( "googletest-fail-if-no-test-linked-test-with-enabled-test_", f"--{FAIL_IF_NO_TEST_LINKED_FLAG}", + env={TEST_WARNINGS_OUTPUT_FILE: warning_file}, ) ) + with self.assertRaises(FileNotFoundError): + open(warning_file, "r") 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_") + warning_file = os.path.join( + gtest_test_utils.GetTempDir(), "SHOULD_NOT_EXIST" ) + self.assertTrue( + self.Run( + "googletest-fail-if-no-test-linked-test-with-disabled-test_", + env={TEST_WARNINGS_OUTPUT_FILE: warning_file}, + ) + ) + with self.assertRaises(FileNotFoundError): + open(warning_file, "r") def testSucceedsIfDisabledTestLinkedAndFlagSpecified(self): """Tests the behavior of disabled test linked and flag specified.""" + warning_file = os.path.join( + gtest_test_utils.GetTempDir(), "SHOULD_NOT_EXIST" + ) self.assertTrue( self.Run( "googletest-fail-if-no-test-linked-test-with-disabled-test_", f"--{FAIL_IF_NO_TEST_LINKED_FLAG}", + env={TEST_WARNINGS_OUTPUT_FILE: warning_file}, ) ) + with self.assertRaises(FileNotFoundError): + open(warning_file, "r") if __name__ == "__main__":