diff options
author | zhanyong.wan <zhanyong.wan@8415998a-534a-0410-bf83-d39667b30386> | 2009-04-22 22:25:31 +0000 |
---|---|---|
committer | zhanyong.wan <zhanyong.wan@8415998a-534a-0410-bf83-d39667b30386> | 2009-04-22 22:25:31 +0000 |
commit | df35a763b9d98d7040a00fc1e5cffe91a80ba9e0 (patch) | |
tree | 9c04a37b5aa0cf26aff318ae43cbf4cacca1755f /src | |
parent | 1c8eb1c059d6727d9fcf45864dc6efa3d844e184 (diff) | |
download | googletest-df35a763b9d98d7040a00fc1e5cffe91a80ba9e0.tar.gz googletest-df35a763b9d98d7040a00fc1e5cffe91a80ba9e0.tar.bz2 googletest-df35a763b9d98d7040a00fc1e5cffe91a80ba9e0.zip |
Implements --gmock_catch_leaked_mocks and Mock::AllowLeak.
Diffstat (limited to 'src')
-rw-r--r-- | src/gmock-spec-builders.cc | 124 | ||||
-rw-r--r-- | src/gmock.cc | 31 |
2 files changed, 142 insertions, 13 deletions
diff --git a/src/gmock-spec-builders.cc b/src/gmock-spec-builders.cc index 353bb2df..2bb72954 100644 --- a/src/gmock-spec-builders.cc +++ b/src/gmock-spec-builders.cc @@ -36,9 +36,17 @@ #include <gmock/gmock-spec-builders.h> +#include <stdlib.h> +#include <iostream> // NOLINT +#include <map> #include <set> +#include <gmock/gmock.h> #include <gtest/gtest.h> +#if GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC +#include <unistd.h> // NOLINT +#endif + namespace testing { namespace internal { @@ -148,10 +156,77 @@ void ReportUninterestingCall(CallReaction reaction, const string& msg) { namespace { typedef std::set<internal::UntypedFunctionMockerBase*> FunctionMockers; -typedef std::map<const void*, FunctionMockers> MockObjectRegistry; -// Maps a mock object to the set of mock methods it owns. Protected -// by g_gmock_mutex. +// The current state of a mock object. Such information is needed for +// detecting leaked mock objects and explicitly verifying a mock's +// expectations. +struct MockObjectState { + MockObjectState() + : first_used_file(NULL), first_used_line(-1), leakable(false) {} + + // Where in the source file an ON_CALL or EXPECT_CALL is first + // invoked on this mock object. + const char* first_used_file; + int first_used_line; + bool leakable; // true iff it's OK to leak the object. + FunctionMockers function_mockers; // All registered methods of the object. +}; + +// A global registry holding the state of all mock objects that are +// alive. A mock object is added to this registry the first time +// Mock::AllowLeak(), ON_CALL(), or EXPECT_CALL() is called on it. It +// is removed from the registry in the mock object's destructor. +class MockObjectRegistry { + public: + // Maps a mock object (identified by its address) to its state. + typedef std::map<const void*, MockObjectState> 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 + // failure, unless the user explicitly asked us to ignore it. + ~MockObjectRegistry() { + using ::std::cout; + + if (!GMOCK_FLAG(catch_leaked_mocks)) + return; + + 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; + + // TODO(wan@google.com): Print the type of the leaked object. + // This can help the user identify the leaked object. + cout << "\n"; + 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 << "."; + leaked_count++; + } + if (leaked_count > 0) { + cout << "\nERROR: " << leaked_count + << " leaked mock " << (leaked_count == 1 ? "object" : "objects") + << " found at program exit.\n"; + cout.flush(); + ::std::cerr.flush(); + // RUN_ALL_TESTS() has already returned when this destructor is + // called. Therefore we cannot use the normal Google Test + // failure reporting mechanism. + _exit(1); // We cannot call exit() as it is not reentrant and + // may already have been called. + } + } + + StateMap& states() { return states_; } + private: + StateMap states_; +}; + +// Protected by g_gmock_mutex. MockObjectRegistry g_mock_object_registry; // Maps a mock object to the reaction Google Mock should have when an @@ -208,6 +283,14 @@ internal::CallReaction Mock::GetReactionOnUninterestingCalls( internal::WARN : g_uninteresting_call_reaction[mock_obj]; } +// Tells Google Mock to ignore mock_obj when checking for leaked mock +// objects. +// L < g_gmock_mutex +void Mock::AllowLeak(const void* mock_obj) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_mock_object_registry.states()[mock_obj].leakable = true; +} + // 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. @@ -233,7 +316,7 @@ bool Mock::VerifyAndClear(void* mock_obj) { // L >= g_gmock_mutex bool Mock::VerifyAndClearExpectationsLocked(void* mock_obj) { internal::g_gmock_mutex.AssertHeld(); - if (g_mock_object_registry.count(mock_obj) == 0) { + if (g_mock_object_registry.states().count(mock_obj) == 0) { // No EXPECT_CALL() was set on the given mock object. return true; } @@ -241,7 +324,8 @@ bool Mock::VerifyAndClearExpectationsLocked(void* mock_obj) { // Verifies and clears the expectations on each mock method in the // given mock object. bool expectations_met = true; - FunctionMockers& mockers = g_mock_object_registry[mock_obj]; + FunctionMockers& mockers = + g_mock_object_registry.states()[mock_obj].function_mockers; for (FunctionMockers::const_iterator it = mockers.begin(); it != mockers.end(); ++it) { if (!(*it)->VerifyAndClearExpectationsLocked()) { @@ -259,7 +343,21 @@ bool Mock::VerifyAndClearExpectationsLocked(void* mock_obj) { void Mock::Register(const void* mock_obj, internal::UntypedFunctionMockerBase* mocker) { internal::MutexLock l(&internal::g_gmock_mutex); - g_mock_object_registry[mock_obj].insert(mocker); + g_mock_object_registry.states()[mock_obj].function_mockers.insert(mocker); +} + +// Tells Google Mock where in the source code mock_obj is used in an +// ON_CALL or EXPECT_CALL. In case mock_obj is leaked, this +// information helps the user identify which object it is. +// L < g_gmock_mutex +void Mock::RegisterUseByOnCallOrExpectCall( + const void* mock_obj, const char* file, int line) { + internal::MutexLock l(&internal::g_gmock_mutex); + MockObjectState& state = g_mock_object_registry.states()[mock_obj]; + if (state.first_used_file == NULL) { + state.first_used_file = file; + state.first_used_line = line; + } } // Unregisters a mock method; removes the owning mock object from the @@ -269,13 +367,14 @@ void Mock::Register(const void* mock_obj, // L >= g_gmock_mutex void Mock::UnregisterLocked(internal::UntypedFunctionMockerBase* mocker) { internal::g_gmock_mutex.AssertHeld(); - for (MockObjectRegistry::iterator it = g_mock_object_registry.begin(); - it != g_mock_object_registry.end(); ++it) { - FunctionMockers& mockers = it->second; + for (MockObjectRegistry::StateMap::iterator it = + g_mock_object_registry.states().begin(); + it != g_mock_object_registry.states().end(); ++it) { + FunctionMockers& mockers = it->second.function_mockers; if (mockers.erase(mocker) > 0) { // mocker was in mockers and has been just removed. if (mockers.empty()) { - g_mock_object_registry.erase(it); + g_mock_object_registry.states().erase(it); } return; } @@ -287,14 +386,15 @@ void Mock::UnregisterLocked(internal::UntypedFunctionMockerBase* mocker) { void Mock::ClearDefaultActionsLocked(void* mock_obj) { internal::g_gmock_mutex.AssertHeld(); - if (g_mock_object_registry.count(mock_obj) == 0) { + if (g_mock_object_registry.states().count(mock_obj) == 0) { // No ON_CALL() was set on the given mock object. return; } // Clears the default actions for each mock method in the given mock // object. - FunctionMockers& mockers = g_mock_object_registry[mock_obj]; + FunctionMockers& mockers = + g_mock_object_registry.states()[mock_obj].function_mockers; for (FunctionMockers::const_iterator it = mockers.begin(); it != mockers.end(); ++it) { (*it)->ClearDefaultActionsLocked(); diff --git a/src/gmock.cc b/src/gmock.cc index c017917d..dc9d3d22 100644 --- a/src/gmock.cc +++ b/src/gmock.cc @@ -34,6 +34,15 @@ namespace testing { +// TODO(wan@google.com): support using environment variables to +// control the flag values, like what Google Test does. + +// TODO(wan@google.com): change the default value to true after people +// have a chance to fix their leaked mocks. +GMOCK_DEFINE_bool_(catch_leaked_mocks, false, + "true iff Google Mock should report leaked mock objects " + "as failures."); + GMOCK_DEFINE_string_(verbose, internal::kWarningVerbosity, "Controls how verbose Google Mock's output is." " Valid values:\n" @@ -76,6 +85,24 @@ static const char* ParseGoogleMockFlagValue(const char* str, return flag_end + 1; } +// Parses a string for a Google Mock bool flag, in the form of +// "--gmock_flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +static bool ParseGoogleMockBoolFlag(const char* str, const char* flag, + bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseGoogleMockFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Converts the string value to a bool. + *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); + return true; +} + // Parses a string for a Google Mock string flag, in the form of // "--gmock_flag=value". // @@ -110,7 +137,9 @@ void InitGoogleMockImpl(int* argc, CharType** argv) { const char* const arg = arg_string.c_str(); // Do we see a Google Mock flag? - if (ParseGoogleMockStringFlag(arg, "verbose", &GMOCK_FLAG(verbose))) { + if (ParseGoogleMockBoolFlag(arg, "catch_leaked_mocks", + &GMOCK_FLAG(catch_leaked_mocks)) || + ParseGoogleMockStringFlag(arg, "verbose", &GMOCK_FLAG(verbose))) { // Yes. Shift the remainder of the argv list left by one. Note // that argv has (*argc + 1) elements, the last one always being // NULL. The following loop moves the trailing NULL element as |