diff --git a/docs/gmock_cheat_sheet.md b/docs/gmock_cheat_sheet.md index 62159998b..56b74ba0a 100644 --- a/docs/gmock_cheat_sheet.md +++ b/docs/gmock_cheat_sheet.md @@ -219,6 +219,13 @@ verified: Mock::AllowLeak(&mock_obj); ``` +Furthermore you can perform instant leak checks. This may help tracing down leaking +mock objects when running a larger test suite: + +```cpp +Mock::CheckLeakInstant(); +``` + ## Mock Classes gMock defines a convenient mock class template diff --git a/googlemock/include/gmock/gmock-spec-builders.h b/googlemock/include/gmock/gmock-spec-builders.h index e861817f4..8935f800f 100644 --- a/googlemock/include/gmock/gmock-spec-builders.h +++ b/googlemock/include/gmock/gmock-spec-builders.h @@ -370,6 +370,11 @@ class GTEST_API_ Mock { static void AllowLeak(const void* mock_obj) GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + // Tells Google Mock to instantly check for leftover mock objects and report + // them if there are. + static void CheckLeakInstant(void) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + // Verifies and clears all expectations on the given mock object. // If the expectations aren't satisfied, generates one or more // Google Test non-fatal failures and returns false. diff --git a/googlemock/src/gmock-spec-builders.cc b/googlemock/src/gmock-spec-builders.cc index 603ad7aa0..f4210b2a7 100644 --- a/googlemock/src/gmock-spec-builders.cc +++ b/googlemock/src/gmock-spec-builders.cc @@ -456,6 +456,10 @@ static CallReaction intToCallReaction(int mock_behavior) { namespace { +class MockObjectRegistry; +bool ReportMockObjectRegistryLeaks(MockObjectRegistry const& reg, + std::string& msg); + typedef std::set FunctionMockers; // The current state of a mock object. Such information is needed for @@ -485,49 +489,19 @@ class MockObjectRegistry { typedef std::map StateMap; // This destructor will be called when a program exits, after all - // tests in it have been run. By then, there should be no mock - // object alive. Therefore we report any living object as test + // tests in it have been run. By then, there should be no mock + // object alive. Therefore we report any living object as test // failure, unless the user explicitly asked us to ignore it. ~MockObjectRegistry() { - if (!GMOCK_FLAG_GET(catch_leaked_mocks)) return; internal::MutexLock l(&internal::g_gmock_mutex); - - int leaked_count = 0; - for (StateMap::const_iterator it = states_.begin(); it != states_.end(); - ++it) { - if (it->second.leakable) // The user said it's fine to leak this object. - continue; - - // FIXME: Print the type of the leaked object. - // This can help the user identify the leaked object. - std::cout << "\n"; - const MockObjectState& state = it->second; - std::cout << internal::FormatFileLocation(state.first_used_file, - state.first_used_line); - std::cout << " ERROR: this mock object"; - if (!state.first_used_test.empty()) { - std::cout << " (used in test " << state.first_used_test_suite << "." - << state.first_used_test << ")"; - } - std::cout << " should be deleted but never is. Its address is @" - << it->first << "."; - leaked_count++; - } - if (leaked_count > 0) { - std::cout << "\nERROR: " << leaked_count << " leaked mock " - << (leaked_count == 1 ? "object" : "objects") - << " found at program exit. Expectations on a mock object are " - "verified when the object is destructed. Leaking a mock " - "means that its expectations aren't verified, which is " - "usually a test bug. If you really intend to leak a mock, " - "you can suppress this error using " - "testing::Mock::AllowLeak(mock_object), or you may use a " - "fake or stub instead of a mock.\n"; - std::cout.flush(); - ::std::cerr.flush(); + std::string msg{}; + if (!ReportMockObjectRegistryLeaks(*this, msg)) { // RUN_ALL_TESTS() has already returned when this destructor is // called. Therefore we cannot use the normal Google Test // failure reporting mechanism. + std::cout << msg; + std::cout.flush(); + ::std::cerr.flush(); #ifdef GTEST_OS_QURT qurt_exception_raise_fatal(); #else @@ -537,12 +511,68 @@ class MockObjectRegistry { } } + StateMap const& states() const { return states_; } StateMap& states() { return states_; } private: StateMap states_; }; +// Checks the given MockObjectRegistry for leaks (i.e. MockObjectStates objects +// which are still in the given MockObjectRegistry and are not marked as +// leakable) and returns a report in msg. Furthermore it returns false if leaks +// were determined and true otherwise. +// NOTE: It is the callers job to make calls +// on "reg" safe, e.g. locking its mutex (if there is any). +bool ReportMockObjectRegistryLeaks(MockObjectRegistry const& reg, + std::string& msg) { + // In case user specified to ignore leaks via a cl flag return true. + if (!GMOCK_FLAG_GET(catch_leaked_mocks)) return true; + + typedef std::map StateMap; + + int leaked_count = 0; + for (StateMap::const_iterator it = reg.states().begin(); + it != reg.states().end(); ++it) { + if (it->second.leakable) // The user said it's fine to leak this object. + continue; + + // FIXME: Print the type of the leaked object. + // This can help the user identify the leaked object. + msg += "\n"; + const MockObjectState& state = it->second; + msg += internal::FormatFileLocation(state.first_used_file, + state.first_used_line); + msg += " ERROR: this mock object"; + if (!state.first_used_test.empty()) { + msg += " (used in test " + state.first_used_test_suite + "." + + state.first_used_test + ")"; + } + msg += " should be deleted but never is. Its address is @"; + std::ostringstream oss; + oss << it->first; + std::string address = oss.str(); + msg += address + "."; + + leaked_count++; + } + if (leaked_count > 0) { + msg += "\nERROR: " + std::to_string(leaked_count) + " leaked mock " + + (leaked_count == 1 ? "object" : "objects") + + " found at program exit. Expectations on a mock object are " + "verified when the object is destructed. Leaking a mock " + "means that its expectations aren't verified, which is " + "usually a test bug. If you really intend to leak a mock, " + "you can suppress this error using " + "testing::Mock::AllowLeak(mock_object), or you may use a " + "fake or stub instead of a mock.\n"; + + return false; + } + + return true; +} + // Protected by g_gmock_mutex. MockObjectRegistry g_mock_object_registry; @@ -615,6 +645,23 @@ void Mock::AllowLeak(const void* mock_obj) g_mock_object_registry.states()[mock_obj].leakable = true; } +// Tells Google Mock to instantly check for leftover mock objects and report +// them if there are. +void Mock::CheckLeakInstant(void) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + + std::string msg{}; + // Check for leftover mock objects at this point. + if (!ReportMockObjectRegistryLeaks(g_mock_object_registry, msg)) { + // If there are leftover (leaked) mock objects, fail the current test and + // report all leaks. + auto const& first_state = g_mock_object_registry.states().begin(); + testing::internal::Expect(false, first_state->second.first_used_file, + first_state->second.first_used_line, msg); + } +} + // Verifies and clears all expectations on the given mock object. If // the expectations aren't satisfied, generates one or more Google // Test non-fatal failures and returns false. diff --git a/googlemock/test/gmock_leak_test.py b/googlemock/test/gmock_leak_test.py index 8b02bc465..2fd98fc15 100755 --- a/googlemock/test/gmock_leak_test.py +++ b/googlemock/test/gmock_leak_test.py @@ -37,6 +37,8 @@ PROGRAM_PATH = gmock_test_utils.GetTestExecutablePath('gmock_leak_test_') TEST_WITH_EXPECT_CALL = [PROGRAM_PATH, '--gtest_filter=*ExpectCall*'] TEST_WITH_ON_CALL = [PROGRAM_PATH, '--gtest_filter=*OnCall*'] TEST_MULTIPLE_LEAKS = [PROGRAM_PATH, '--gtest_filter=*MultipleLeaked*'] +TEST_INSTANT_LEAK = [PROGRAM_PATH, '--gtest_filter=*InstantLeak*'] +TEST_INSTANT_NO_LEAK = [PROGRAM_PATH, '--gtest_filter=*InstantNoLeak*'] environ = gmock_test_utils.environ SetEnvVar = gmock_test_utils.SetEnvVar @@ -108,6 +110,28 @@ class GMockLeakTest(gmock_test_utils.TestCase): ).exit_code, ) + def testInstantLeakCheck(self): + self.assertNotEqual( + 0, + gmock_test_utils.Subprocess( + TEST_INSTANT_LEAK + ['--gmock_catch_leaked_mocks=1'], env=environ + ).exit_code, + ) + self.assertEqual( + 0, + gmock_test_utils.Subprocess( + TEST_INSTANT_LEAK + ['--gmock_catch_leaked_mocks=0'], env=environ + ).exit_code, + ) + + def testInstantNoLeak(self): + self.assertEqual( + 0, + gmock_test_utils.Subprocess( + TEST_INSTANT_NO_LEAK + ['--gmock_catch_leaked_mocks=1'], env=environ + ).exit_code, + ) + if __name__ == '__main__': gmock_test_utils.Main() diff --git a/googlemock/test/gmock_leak_test_.cc b/googlemock/test/gmock_leak_test_.cc index a6bb33921..d7f633983 100644 --- a/googlemock/test/gmock_leak_test_.cc +++ b/googlemock/test/gmock_leak_test_.cc @@ -96,4 +96,46 @@ TEST(LeakTest, CatchesMultipleLeakedMockObjects) { exit(0); } +TEST(LeakTest, InstantNoLeak) { + MockFoo* foo = new MockFoo; + + EXPECT_CALL(*foo, DoThis()); + foo->DoThis(); + + delete foo; + // Since foo is properly deleted instant leak check should not see a leaked + // mock object and therefore not fail the test. + testing::Mock::CheckLeakInstant(); +} + +TEST(LeakTest, InstantLeak) { + MockFoo* foo = new MockFoo; + + EXPECT_CALL(*foo, DoThis()); + foo->DoThis(); + + // At this point foo is still allocated. Calling the instant leak check should + // detect it and fail the test. + testing::Mock::CheckLeakInstant(); + + // Free foo in order to not fail the end of program leak check. + delete foo; +} + +TEST(LeakTest, InstantNoLeakAllowed) { + MockFoo* foo = new MockFoo; + testing::Mock::AllowLeak(foo); + + EXPECT_CALL(*foo, DoThis()); + foo->DoThis(); + + // At this point foo is still allocated However since we made foo a leakable + // mock object with AllowLeak() the instant leak check should ignore it and + // pass the test. + testing::Mock::CheckLeakInstant(); + + // Free foo in order to not fail the end of program leak check. + delete foo; +} + } // namespace diff --git a/googlemock/test/gmock_output_test.py b/googlemock/test/gmock_output_test.py index 7c24d6832..d26bc231e 100755 --- a/googlemock/test/gmock_output_test.py +++ b/googlemock/test/gmock_output_test.py @@ -166,12 +166,16 @@ class GMockOutputTest(gmock_test_utils.TestCase): # The normalized output should match the golden file. self.assertEqual(golden, output) - # The raw output should contain 2 leaked mock object errors for + # The raw output should contain 4 leaked mock object errors for # test GMockOutputTest.CatchesLeakedMocks. + # Two from the at end of program check and two from the instant leak check + # within GMockOutputTest.CatchesLeakedMocks. self.assertEqual( [ 'GMockOutputTest.CatchesLeakedMocks', 'GMockOutputTest.CatchesLeakedMocks', + 'GMockOutputTest.CatchesLeakedMocks', + 'GMockOutputTest.CatchesLeakedMocks', ], leaky_tests, ) diff --git a/googlemock/test/gmock_output_test_.cc b/googlemock/test/gmock_output_test_.cc index 03d842139..a58a55eb1 100644 --- a/googlemock/test/gmock_output_test_.cc +++ b/googlemock/test/gmock_output_test_.cc @@ -251,6 +251,8 @@ TEST_F(GMockOutputTest, CatchesLeakedMocks) { foo2->Bar2(1, 1); // Both foo1 and foo2 are deliberately leaked. + // Call the instant leak check in order to validate it's output. + testing::Mock::CheckLeakInstant(); } MATCHER_P2(IsPair, first, second, "") { diff --git a/googlemock/test/gmock_output_test_golden.txt b/googlemock/test/gmock_output_test_golden.txt index 4b47f33fd..66d145987 100644 --- a/googlemock/test/gmock_output_test_golden.txt +++ b/googlemock/test/gmock_output_test_golden.txt @@ -304,7 +304,15 @@ FILE:#: Stack trace: [ OK ] GMockOutputTest.ExplicitActionsRunOutWithDefaultAction [ RUN ] GMockOutputTest.CatchesLeakedMocks -[ OK ] GMockOutputTest.CatchesLeakedMocks +FILE:#: Failure + +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#. +FILE:#: ERROR: this mock object should be deleted but never is. Its address is @0x#. +ERROR: 3 leaked mock objects found at program exit. Expectations on a mock object are verified when the object is destructed. Leaking a mock means that its expectations aren't verified, which is usually a test bug. If you really intend to leak a mock, you can suppress this error using testing::Mock::AllowLeak(mock_object), or you may use a fake or stub instead of a mock. + + +[ FAILED ] GMockOutputTest.CatchesLeakedMocks [ RUN ] GMockOutputTest.PrintsMatcher FILE:#: Failure Value of: (std::pair(42, true)) @@ -326,6 +334,7 @@ Expected: is pair (first: is >= 48, second: true) [ FAILED ] GMockOutputTest.MismatchArgumentsAndWith [ FAILED ] GMockOutputTest.UnexpectedCallWithDefaultAction [ FAILED ] GMockOutputTest.ExcessiveCallWithDefaultAction +[ FAILED ] GMockOutputTest.CatchesLeakedMocks [ FAILED ] GMockOutputTest.PrintsMatcher