diff --git a/include/gmock/internal/gmock-port.h b/include/gmock/internal/gmock-port.h index cad195ad..d242c8e4 100644 --- a/include/gmock/internal/gmock-port.h +++ b/include/gmock/internal/gmock-port.h @@ -166,16 +166,16 @@ inline To down_cast(From* f) { // so we only accept pointers return static_cast(f); } -// The GMOCK_COMPILE_ASSERT macro can be used to verify that a compile time +// The GMOCK_COMPILE_ASSERT_ macro can be used to verify that a compile time // expression is true. For example, you could use it to verify the // size of a static array: // -// GMOCK_COMPILE_ASSERT(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, -// content_type_names_incorrect_size); +// GMOCK_COMPILE_ASSERT_(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); // // or to make sure a struct is smaller than a certain size: // -// GMOCK_COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); +// GMOCK_COMPILE_ASSERT_(sizeof(foo) < 128, foo_too_large); // // The second argument to the macro is the name of the variable. If // the expression is false, most compilers will issue a warning/error diff --git a/src/gmock-spec-builders.cc b/src/gmock-spec-builders.cc index 2bb72954..65a74b81 100644 --- a/src/gmock-spec-builders.cc +++ b/src/gmock-spec-builders.cc @@ -168,6 +168,8 @@ struct MockObjectState { // invoked on this mock object. const char* first_used_file; int first_used_line; + ::std::string first_used_test_case; + ::std::string first_used_test; bool leakable; // true iff it's OK to leak the object. FunctionMockers function_mockers; // All registered methods of the object. }; @@ -203,8 +205,13 @@ class MockObjectRegistry { const MockObjectState& state = it->second; internal::FormatFileLocation( state.first_used_file, state.first_used_line, &cout); - cout << " ERROR: this mock object should be deleted but never is. " - << "Its address is @" << it->first << "."; + cout << " ERROR: this mock object"; + if (state.first_used_test != "") { + cout << " (used in test " << state.first_used_test_case << "." + << state.first_used_test << ")"; + } + cout << " should be deleted but never is. Its address is @" + << it->first << "."; leaked_count++; } if (leaked_count > 0) { @@ -357,6 +364,15 @@ void Mock::RegisterUseByOnCallOrExpectCall( if (state.first_used_file == NULL) { state.first_used_file = file; state.first_used_line = line; + const TestInfo* const test_info = + UnitTest::GetInstance()->current_test_info(); + if (test_info != NULL) { + // TODO(wan@google.com): record the test case name when the + // ON_CALL or EXPECT_CALL is invoked from SetUpTestCase() or + // TearDownTestCase(). + state.first_used_test_case = test_info->test_case_name(); + state.first_used_test = test_info->name(); + } } } diff --git a/test/gmock_output_test.py b/test/gmock_output_test.py index 2e992190..f43f7074 100755 --- a/test/gmock_output_test.py +++ b/test/gmock_output_test.py @@ -64,6 +64,7 @@ GOLDEN_NAME = 'gmock_output_test_golden.txt' GOLDEN_PATH = os.path.join(gmock_test_utils.GetSourceDir(), GOLDEN_NAME) + def ToUnixLineEnding(s): """Changes all Windows/Mac line endings in s to UNIX line endings.""" @@ -109,15 +110,38 @@ def RemoveMemoryAddresses(output): return re.sub(r'@\w+', '@0x#', output) -def NormalizeOutput(output): - """Normalizes output (the output of gmock_output_test_.exe).""" +def RemoveTestNamesOfLeakedMocks(output): + """Removes the test names of leaked mock objects from the test output.""" + + return re.sub(r'\(used in test .+\) ', '', output) + + +def GetLeakyTests(output): + """Returns a list of test names that leak mock objects.""" + + # findall() returns a list of all matches of the regex in output. + # For example, if '(used in test FooTest.Bar)' is in output, the + # list will contain 'FooTest.Bar'. + return re.findall(r'\(used in test (.+)\)', output) + + +def GetNormalizedOutputAndLeakyTests(output): + """Normalizes the output of gmock_output_test_. + + Args: + output: The test output. + + Returns: + A tuple (the normalized test output, the list of test names that have + leaked mocks). + """ output = ToUnixLineEnding(output) output = RemoveReportHeaderAndFooter(output) output = NormalizeErrorMarker(output) output = RemoveLocations(output) output = RemoveMemoryAddresses(output) - return output + return (RemoveTestNamesOfLeakedMocks(output), GetLeakyTests(output)) def IterShellCommandOutput(cmd, stdin_string=None): @@ -167,9 +191,8 @@ def GetShellCommandOutput(cmd, stdin_string=None): return string.join(lines, '') -def GetCommandOutput(cmd): - """Runs a command and returns its output with all file location - info stripped off. +def GetNormalizedCommandOutputAndLeakyTests(cmd): + """Runs a command and returns its normalized output and a list of leaky tests. Args: cmd: the shell command. @@ -177,22 +200,29 @@ def GetCommandOutput(cmd): # Disables exception pop-ups on Windows. os.environ['GTEST_CATCH_EXCEPTIONS'] = '1' - return NormalizeOutput(GetShellCommandOutput(cmd, '')) + return GetNormalizedOutputAndLeakyTests(GetShellCommandOutput(cmd, '')) class GMockOutputTest(unittest.TestCase): def testOutput(self): - output = GetCommandOutput(COMMAND) + (output, leaky_tests) = GetNormalizedCommandOutputAndLeakyTests(COMMAND) golden_file = open(GOLDEN_PATH, 'rb') golden = golden_file.read() golden_file.close() + # The normalized output should match the golden file. self.assertEquals(golden, output) + # The raw output should contain 2 leaked mock object errors for + # test GMockOutputTest.CatchesLeakedMocks. + self.assertEquals(['GMockOutputTest.CatchesLeakedMocks', + 'GMockOutputTest.CatchesLeakedMocks'], + leaky_tests) + if __name__ == '__main__': if sys.argv[1:] == [GENGOLDEN_FLAG]: - output = GetCommandOutput(COMMAND) + (output, _) = GetNormalizedCommandOutputAndLeakyTests(COMMAND) golden_file = open(GOLDEN_PATH, 'wb') golden_file.write(output) golden_file.close() diff --git a/test/gmock_output_test_.cc b/test/gmock_output_test_.cc index c97bc78c..97619af1 100644 --- a/test/gmock_output_test_.cc +++ b/test/gmock_output_test_.cc @@ -258,6 +258,16 @@ TEST_F(GMockOutputTest, CatchesLeakedMocks) { // Both foo1 and foo2 are deliberately leaked. } +void TestCatchesLeakedMocksInAdHocTests() { + MockFoo* foo = new MockFoo; + + // Invokes EXPECT_CALL on foo. + EXPECT_CALL(*foo, Bar2(_, _)); + foo->Bar2(2, 1); + + // foo is deliberately leaked. +} + int main(int argc, char **argv) { testing::InitGoogleMock(&argc, argv); @@ -266,5 +276,6 @@ int main(int argc, char **argv) { testing::GMOCK_FLAG(catch_leaked_mocks) = true; testing::GMOCK_FLAG(verbose) = "warning"; + TestCatchesLeakedMocksInAdHocTests(); return RUN_ALL_TESTS(); } diff --git a/test/gmock_output_test_golden.txt b/test/gmock_output_test_golden.txt index 50ef7b75..887b7be7 100644 --- a/test/gmock_output_test_golden.txt +++ b/test/gmock_output_test_golden.txt @@ -298,4 +298,5 @@ Stack trace: FILE:#: ERROR: this mock object should be deleted but never is. Its address is @0x#. FILE:#: ERROR: this mock object should be deleted but never is. Its address is @0x#. -ERROR: 2 leaked mock objects found at program exit. +FILE:#: ERROR: this mock object should be deleted but never is. Its address is @0x#. +ERROR: 3 leaked mock objects found at program exit.