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 | |
| parent | 1c8eb1c059d6727d9fcf45864dc6efa3d844e184 (diff) | |
| download | googletest-df35a763b9d98d7040a00fc1e5cffe91a80ba9e0.tar.gz googletest-df35a763b9d98d7040a00fc1e5cffe91a80ba9e0.tar.bz2 googletest-df35a763b9d98d7040a00fc1e5cffe91a80ba9e0.zip | |
Implements --gmock_catch_leaked_mocks and Mock::AllowLeak.
| -rw-r--r-- | Makefile.am | 20 | ||||
| -rw-r--r-- | include/gmock/gmock-spec-builders.h | 27 | ||||
| -rw-r--r-- | include/gmock/gmock.h | 1 | ||||
| -rw-r--r-- | include/gmock/internal/gmock-port.h | 30 | ||||
| -rwxr-xr-x | msvc/gmock-spec-builders_test.vcproj | 205 | ||||
| -rw-r--r-- | msvc/gmock.sln | 6 | ||||
| -rw-r--r-- | msvc/gmock_output_test_.vcproj | 4 | ||||
| -rw-r--r-- | msvc/gmock_test.vcproj | 4 | ||||
| -rw-r--r-- | src/gmock-spec-builders.cc | 124 | ||||
| -rw-r--r-- | src/gmock.cc | 31 | ||||
| -rw-r--r-- | test/gmock-spec-builders_test.cc | 48 | ||||
| -rwxr-xr-x | test/gmock_leak_test.py | 84 | ||||
| -rw-r--r-- | test/gmock_leak_test_.cc | 95 | ||||
| -rwxr-xr-x | test/gmock_output_test.py | 2 | ||||
| -rw-r--r-- | test/gmock_output_test_.cc | 29 | ||||
| -rw-r--r-- | test/gmock_output_test_golden.txt | 7 | ||||
| -rw-r--r-- | test/gmock_test.cc | 7 | 
17 files changed, 681 insertions, 43 deletions
| diff --git a/Makefile.am b/Makefile.am index f70a8085..7a821a02 100644 --- a/Makefile.am +++ b/Makefile.am @@ -128,7 +128,7 @@ test_gmock_printers_test_LDADD = $(GTEST_LIBS) lib/libgmock_main.la  TESTS += test/gmock-spec-builders_test  check_PROGRAMS += test/gmock-spec-builders_test  test_gmock_spec_builders_test_SOURCES = test/gmock-spec-builders_test.cc -test_gmock_spec_builders_test_LDADD = $(GTEST_LIBS) lib/libgmock_main.la +test_gmock_spec_builders_test_LDADD = $(GTEST_LIBS) lib/libgmock.la  TESTS += test/gmock_test  check_PROGRAMS += test/gmock_test @@ -147,6 +147,11 @@ dist_check_SCRIPTS =  # Python modules used by multiple Python tests below.  dist_check_SCRIPTS += test/gmock_test_utils.py +check_PROGRAMS += test/gmock_leak_test_ +test_gmock_leak_test__SOURCES = test/gmock_leak_test_.cc +test_gmock_leak_test__LDADD = $(GTEST_LIBS) lib/libgmock_main.la +dist_check_SCRIPTS += test/gmock_leak_test.py +  check_PROGRAMS += test/gmock_output_test_  test_gmock_output_test__SOURCES = test/gmock_output_test_.cc  test_gmock_output_test__LDADD = $(GTEST_LIBS) lib/libgmock_main.la @@ -155,15 +160,17 @@ EXTRA_DIST += test/gmock_output_test_golden.txt  # Enable all the python driven tests when we can run them.  if HAVE_PYTHON -TESTS += test/gmock_output_test.py +TESTS += \ +    test/gmock_leak_test.py \ +    test/gmock_output_test.py  endif  # Nonstandard package files for distribution.  EXTRA_DIST += \ -  CHANGES \ -  CONTRIBUTORS \ -  make/Makefile \ -  src/gmock-all.cc +    CHANGES \ +    CONTRIBUTORS \ +    make/Makefile \ +    src/gmock-all.cc  # Pump scripts for generating Google Mock headers.  # TODO(chandlerc@google.com): automate the generation of *.h from *.h.pump. @@ -199,4 +206,5 @@ EXTRA_DIST += \      msvc/gmock_link_test.vcproj \      msvc/gmock_main.vcproj \      msvc/gmock_output_test_.vcproj \ +    msvc/gmock-spec-builders_test.vcproj \      msvc/gmock_test.vcproj diff --git a/include/gmock/gmock-spec-builders.h b/include/gmock/gmock-spec-builders.h index 3b4c0853..0fc43d6d 100644 --- a/include/gmock/gmock-spec-builders.h +++ b/include/gmock/gmock-spec-builders.h @@ -246,6 +246,10 @@ class Mock {   public:    // The following public methods can be called concurrently. +  // Tells Google Mock to ignore mock_obj when checking for leaked +  // mock objects. +  static void AllowLeak(const void* mock_obj); +    // 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. @@ -311,6 +315,13 @@ class Mock {    static void Register(const void* mock_obj,                         internal::UntypedFunctionMockerBase* 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 +  static void RegisterUseByOnCallOrExpectCall( +      const void* mock_obj, const char* file, int line); +    // Unregisters a mock method; removes the owning mock object from    // the registry when the last mock method associated with it has    // been unregistered.  This is called only in the destructor of @@ -1081,7 +1092,12 @@ class FunctionMockerBase : public UntypedFunctionMockerBase {    // Registers this function mocker and the mock object owning it;    // returns a reference to the function mocker object.  This is only    // called by the ON_CALL() and EXPECT_CALL() macros. +  // L < g_gmock_mutex    FunctionMocker<F>& RegisterOwner(const void* mock_obj) { +    { +      MutexLock l(&g_gmock_mutex); +      mock_obj_ = mock_obj; +    }      Mock::Register(mock_obj, this);      return *::testing::internal::down_cast<FunctionMocker<F>*>(this);    } @@ -1155,17 +1171,21 @@ class FunctionMockerBase : public UntypedFunctionMockerBase {    }    // Adds and returns a default action spec for this mock function. +  // L < g_gmock_mutex    DefaultActionSpec<F>& AddNewDefaultActionSpec(        const char* file, int line,        const ArgumentMatcherTuple& m) { +    Mock::RegisterUseByOnCallOrExpectCall(MockObject(), file, line);      default_actions_.push_back(DefaultActionSpec<F>(file, line, m));      return default_actions_.back();    }    // Adds and returns an expectation spec for this mock function. +  // L < g_gmock_mutex    Expectation<F>& AddNewExpectation(        const char* file, int line,        const ArgumentMatcherTuple& m) { +    Mock::RegisterUseByOnCallOrExpectCall(MockObject(), file, line);      const linked_ptr<Expectation<F> > expectation(          new Expectation<F>(this, file, line, m));      expectations_.push_back(expectation); @@ -1314,10 +1334,13 @@ class FunctionMockerBase : public UntypedFunctionMockerBase {      }    } -  // Address of the mock object this mock method belongs to. +  // Address of the mock object this mock method belongs to.  Only +  // valid after this mock method has been called or +  // ON_CALL/EXPECT_CALL has been invoked on it.    const void* mock_obj_;  // Protected by g_gmock_mutex. -  // Name of the function being mocked. +  // Name of the function being mocked.  Only valid after this mock +  // method has been called.    const char* name_;  // Protected by g_gmock_mutex.    // The current spec (either default action spec or expectation spec) diff --git a/include/gmock/gmock.h b/include/gmock/gmock.h index 41d175f1..22e70287 100644 --- a/include/gmock/gmock.h +++ b/include/gmock/gmock.h @@ -68,6 +68,7 @@  namespace testing {  // Declares Google Mock flags that we want a user to use programmatically. +GMOCK_DECLARE_bool_(catch_leaked_mocks);  GMOCK_DECLARE_string_(verbose);  // Initializes Google Mock.  This must be called before running the diff --git a/include/gmock/internal/gmock-port.h b/include/gmock/internal/gmock-port.h index cb352192..b98cb113 100644 --- a/include/gmock/internal/gmock-port.h +++ b/include/gmock/internal/gmock-port.h @@ -242,6 +242,21 @@ typedef ::wstring wstring;  typedef ::std::wstring wstring;  #endif  // GTEST_HAS_GLOBAL_WSTRING +// Prints the file location in the format native to the compiler. +inline void FormatFileLocation(const char* file, int line, ::std::ostream* os) { +  if (file == NULL) +    file = "unknown file"; +  if (line < 0) { +    *os << file << ":"; +  } else { +#if _MSC_VER +    *os << file << "(" << line << "):"; +#else +    *os << file << ":" << line << ":"; +#endif +  } +} +  // INTERNAL IMPLEMENTATION - DO NOT USE.  //  // GMOCK_CHECK_ is an all mode assert. It aborts the program if the condition @@ -260,26 +275,13 @@ typedef ::std::wstring wstring;  class GMockCheckProvider {   public:    GMockCheckProvider(const char* condition, const char* file, int line) { -    FormatFileLocation(file, line); +    FormatFileLocation(file, line, &::std::cerr);      ::std::cerr << " ERROR: Condition " << condition << " failed. ";    }    ~GMockCheckProvider() {      ::std::cerr << ::std::endl;      abort();    } -  void FormatFileLocation(const char* file, int line) { -    if (file == NULL) -      file = "unknown file"; -    if (line < 0) { -      ::std::cerr << file << ":"; -    } else { -#if _MSC_VER -      ::std::cerr << file << "(" << line << "):"; -#else -      ::std::cerr << file << ":" << line << ":"; -#endif -    } -  }    ::std::ostream& GetStream() { return ::std::cerr; }  };  #define GMOCK_CHECK_(condition) \ diff --git a/msvc/gmock-spec-builders_test.vcproj b/msvc/gmock-spec-builders_test.vcproj new file mode 100755 index 00000000..84407420 --- /dev/null +++ b/msvc/gmock-spec-builders_test.vcproj @@ -0,0 +1,205 @@ +<?xml version="1.0" encoding="Windows-1252"?>
 +<VisualStudioProject
 +	ProjectType="Visual C++"
 +	Version="8.00"
 +	Name="gmock-spec-builders_test"
 +	ProjectGUID="{46972604-5BE0-4493-BAE3-878DB825FDCB}"
 +	RootNamespace="gmockspecbuilders_test"
 +	Keyword="Win32Proj"
 +	>
 +	<Platforms>
 +		<Platform
 +			Name="Win32"
 +		/>
 +	</Platforms>
 +	<ToolFiles>
 +	</ToolFiles>
 +	<Configurations>
 +		<Configuration
 +			Name="Debug|Win32"
 +			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
 +			IntermediateDirectory="$(ConfigurationName)"
 +			ConfigurationType="1"
 +			InheritedPropertySheets=".\gmock_config.vsprops"
 +			CharacterSet="1"
 +			>
 +			<Tool
 +				Name="VCPreBuildEventTool"
 +			/>
 +			<Tool
 +				Name="VCCustomBuildTool"
 +			/>
 +			<Tool
 +				Name="VCXMLDataGeneratorTool"
 +			/>
 +			<Tool
 +				Name="VCWebServiceProxyGeneratorTool"
 +			/>
 +			<Tool
 +				Name="VCMIDLTool"
 +			/>
 +			<Tool
 +				Name="VCCLCompilerTool"
 +				Optimization="0"
 +				AdditionalIncludeDirectories="../include"
 +				PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
 +				MinimalRebuild="true"
 +				BasicRuntimeChecks="3"
 +				RuntimeLibrary="1"
 +				UsePrecompiledHeader="0"
 +				WarningLevel="3"
 +				Detect64BitPortabilityProblems="true"
 +				DebugInformationFormat="4"
 +			/>
 +			<Tool
 +				Name="VCManagedResourceCompilerTool"
 +			/>
 +			<Tool
 +				Name="VCResourceCompilerTool"
 +			/>
 +			<Tool
 +				Name="VCPreLinkEventTool"
 +			/>
 +			<Tool
 +				Name="VCLinkerTool"
 +				LinkIncremental="2"
 +				GenerateDebugInformation="true"
 +				SubSystem="1"
 +				TargetMachine="1"
 +			/>
 +			<Tool
 +				Name="VCALinkTool"
 +			/>
 +			<Tool
 +				Name="VCManifestTool"
 +			/>
 +			<Tool
 +				Name="VCXDCMakeTool"
 +			/>
 +			<Tool
 +				Name="VCBscMakeTool"
 +			/>
 +			<Tool
 +				Name="VCFxCopTool"
 +			/>
 +			<Tool
 +				Name="VCAppVerifierTool"
 +			/>
 +			<Tool
 +				Name="VCWebDeploymentTool"
 +			/>
 +			<Tool
 +				Name="VCPostBuildEventTool"
 +			/>
 +		</Configuration>
 +		<Configuration
 +			Name="Release|Win32"
 +			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
 +			IntermediateDirectory="$(ConfigurationName)"
 +			ConfigurationType="1"
 +			InheritedPropertySheets=".\gmock_config.vsprops"
 +			CharacterSet="1"
 +			WholeProgramOptimization="1"
 +			>
 +			<Tool
 +				Name="VCPreBuildEventTool"
 +			/>
 +			<Tool
 +				Name="VCCustomBuildTool"
 +			/>
 +			<Tool
 +				Name="VCXMLDataGeneratorTool"
 +			/>
 +			<Tool
 +				Name="VCWebServiceProxyGeneratorTool"
 +			/>
 +			<Tool
 +				Name="VCMIDLTool"
 +			/>
 +			<Tool
 +				Name="VCCLCompilerTool"
 +				AdditionalIncludeDirectories="../include"
 +				PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
 +				RuntimeLibrary="0"
 +				UsePrecompiledHeader="0"
 +				WarningLevel="3"
 +				Detect64BitPortabilityProblems="true"
 +				DebugInformationFormat="3"
 +			/>
 +			<Tool
 +				Name="VCManagedResourceCompilerTool"
 +			/>
 +			<Tool
 +				Name="VCResourceCompilerTool"
 +			/>
 +			<Tool
 +				Name="VCPreLinkEventTool"
 +			/>
 +			<Tool
 +				Name="VCLinkerTool"
 +				LinkIncremental="1"
 +				GenerateDebugInformation="true"
 +				SubSystem="1"
 +				OptimizeReferences="2"
 +				EnableCOMDATFolding="2"
 +				TargetMachine="1"
 +			/>
 +			<Tool
 +				Name="VCALinkTool"
 +			/>
 +			<Tool
 +				Name="VCManifestTool"
 +			/>
 +			<Tool
 +				Name="VCXDCMakeTool"
 +			/>
 +			<Tool
 +				Name="VCBscMakeTool"
 +			/>
 +			<Tool
 +				Name="VCFxCopTool"
 +			/>
 +			<Tool
 +				Name="VCAppVerifierTool"
 +			/>
 +			<Tool
 +				Name="VCWebDeploymentTool"
 +			/>
 +			<Tool
 +				Name="VCPostBuildEventTool"
 +			/>
 +		</Configuration>
 +	</Configurations>
 +	<References>
 +		<ProjectReference
 +			ReferencedProjectIdentifier="{34681F0D-CE45-415D-B5F2-5C662DFE3BD5}"
 +			RelativePathToProject=".\gmock.vcproj"
 +		/>
 +	</References>
 +	<Files>
 +		<Filter
 +			Name="Source Files"
 +			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
 +			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
 +			>
 +			<File
 +				RelativePath="..\test\gmock-spec-builders_test.cc"
 +				>
 +			</File>
 +		</Filter>
 +		<Filter
 +			Name="Header Files"
 +			Filter="h;hpp;hxx;hm;inl;inc;xsd"
 +			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
 +			>
 +		</Filter>
 +		<Filter
 +			Name="Resource Files"
 +			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
 +			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
 +			>
 +		</Filter>
 +	</Files>
 +	<Globals>
 +	</Globals>
 +</VisualStudioProject>
 diff --git a/msvc/gmock.sln b/msvc/gmock.sln index aeb6a614..cd1502a9 100644 --- a/msvc/gmock.sln +++ b/msvc/gmock.sln @@ -11,6 +11,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gmock_output_test_", "gmock  EndProject
  Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gmock_main", "gmock_main.vcproj", "{E4EF614B-30DF-4954-8C53-580A0BF6B589}"
  EndProject
 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gmock-spec-builders_test", "gmock-spec-builders_test.vcproj", "{46972604-5BE0-4493-BAE3-878DB825FDCB}"
 +EndProject
  Global
  	GlobalSection(SolutionConfigurationPlatforms) = preSolution
  		Debug|Win32 = Debug|Win32
 @@ -37,6 +39,10 @@ Global  		{E4EF614B-30DF-4954-8C53-580A0BF6B589}.Debug|Win32.Build.0 = Debug|Win32
  		{E4EF614B-30DF-4954-8C53-580A0BF6B589}.Release|Win32.ActiveCfg = Release|Win32
  		{E4EF614B-30DF-4954-8C53-580A0BF6B589}.Release|Win32.Build.0 = Release|Win32
 +		{46972604-5BE0-4493-BAE3-878DB825FDCB}.Debug|Win32.ActiveCfg = Debug|Win32
 +		{46972604-5BE0-4493-BAE3-878DB825FDCB}.Debug|Win32.Build.0 = Debug|Win32
 +		{46972604-5BE0-4493-BAE3-878DB825FDCB}.Release|Win32.ActiveCfg = Release|Win32
 +		{46972604-5BE0-4493-BAE3-878DB825FDCB}.Release|Win32.Build.0 = Release|Win32
  	EndGlobalSection
  	GlobalSection(SolutionProperties) = preSolution
  		HideSolutionNode = FALSE
 diff --git a/msvc/gmock_output_test_.vcproj b/msvc/gmock_output_test_.vcproj index 051dd149..8e846ec1 100644 --- a/msvc/gmock_output_test_.vcproj +++ b/msvc/gmock_output_test_.vcproj @@ -172,8 +172,8 @@  	</Configurations>
  	<References>
  		<ProjectReference
 -			ReferencedProjectIdentifier="{E4EF614B-30DF-4954-8C53-580A0BF6B589}"
 -			RelativePathToProject=".\gmock_main.vcproj"
 +			ReferencedProjectIdentifier="{34681F0D-CE45-415D-B5F2-5C662DFE3BD5}"
 +			RelativePathToProject=".\gmock.vcproj"
  		/>
  	</References>
  	<Files>
 diff --git a/msvc/gmock_test.vcproj b/msvc/gmock_test.vcproj index 135b2e6c..60e1b4bc 100644 --- a/msvc/gmock_test.vcproj +++ b/msvc/gmock_test.vcproj @@ -227,10 +227,6 @@  				>
  			</File>
  			<File
 -				RelativePath="..\test\gmock-spec-builders_test.cc"
 -				>
 -			</File>
 -			<File
  				RelativePath="..\test\gmock_test.cc"
  				>
  			</File>
 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 diff --git a/test/gmock-spec-builders_test.cc b/test/gmock-spec-builders_test.cc index 287a63e2..e8c39028 100644 --- a/test/gmock-spec-builders_test.cc +++ b/test/gmock-spec-builders_test.cc @@ -1612,6 +1612,43 @@ TEST_F(GMockVerboseFlagTest, InvalidFlagIsTreatedAsWarning) {  #endif  // 0 +TEST(AllowLeakTest, AllowsLeakingUnusedMockObject) { +  MockA* a = new MockA; +  Mock::AllowLeak(a); +} + +TEST(AllowLeakTest, CanBeCalledBeforeOnCall) { +  MockA* a = new MockA; +  Mock::AllowLeak(a); +  ON_CALL(*a, DoA(_)).WillByDefault(Return()); +  a->DoA(0); +} + +TEST(AllowLeakTest, CanBeCalledAfterOnCall) { +  MockA* a = new MockA; +  ON_CALL(*a, DoA(_)).WillByDefault(Return()); +  Mock::AllowLeak(a); +} + +TEST(AllowLeakTest, CanBeCalledBeforeExpectCall) { +  MockA* a = new MockA; +  Mock::AllowLeak(a); +  EXPECT_CALL(*a, DoA(_)); +  a->DoA(0); +} + +TEST(AllowLeakTest, CanBeCalledAfterExpectCall) { +  MockA* a = new MockA; +  EXPECT_CALL(*a, DoA(_)).Times(AnyNumber()); +  Mock::AllowLeak(a); +} + +TEST(AllowLeakTest, WorksWhenBothOnCallAndExpectCallArePresent) { +  MockA* a = new MockA; +  ON_CALL(*a, DoA(_)).WillByDefault(Return()); +  EXPECT_CALL(*a, DoA(_)).Times(AnyNumber()); +  Mock::AllowLeak(a); +}  // Tests that we can verify and clear a mock object's expectations  // when none of its methods has expectations. @@ -1916,3 +1953,14 @@ void Helper(MockC* c) {  }  }  // namespace + +int main(int argc, char **argv) { +  testing::InitGoogleMock(&argc, argv); + +  // Ensures that the tests pass no matter what value of +  // --gmock_catch_leaked_mocks and --gmock_verbose the user specifies. +  testing::GMOCK_FLAG(catch_leaked_mocks) = true; +  testing::GMOCK_FLAG(verbose) = testing::internal::kWarningVerbosity; + +  return RUN_ALL_TESTS(); +} diff --git a/test/gmock_leak_test.py b/test/gmock_leak_test.py new file mode 100755 index 00000000..51358f06 --- /dev/null +++ b/test/gmock_leak_test.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# +# Copyright 2009, 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 that leaked mock objects can be caught be Google Mock.""" + +__author__ = 'wan@google.com (Zhanyong Wan)' + +import gmock_test_utils +import os +import unittest + +IS_WINDOWS = os.name == 'nt' + +if IS_WINDOWS: +  # TODO(wan@google.com): test the opt build too.  We should do it +  # when Vlad Losev's work on Google Test's Python test driver is +  # done, such that we can reuse the work. +  PROGRAM = r'..\build.dbg\gmock_leak_test_.exe' +else: +  PROGRAM = 'gmock_leak_test_' + +PROGRAM_PATH = os.path.join(gmock_test_utils.GetBuildDir(), PROGRAM) +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*' + + +class GMockLeakTest(unittest.TestCase): + +  def testDoesNotCatchLeakedMockByDefault(self): +    self.assertEquals(0, os.system(TEST_WITH_EXPECT_CALL)) +    self.assertEquals(0, os.system(TEST_WITH_ON_CALL)) + +  def testDoesNotCatchLeakedMockWhenDisabled(self): +    self.assertEquals( +        0, os.system(TEST_WITH_EXPECT_CALL + ' --gmock_catch_leaked_mocks=0')) +    self.assertEquals( +        0, os.system(TEST_WITH_ON_CALL + ' --gmock_catch_leaked_mocks=0')) + +  def testCatchesLeakedMockWhenEnabled(self): +    self.assertNotEqual( +        os.system(TEST_WITH_EXPECT_CALL + ' --gmock_catch_leaked_mocks'), 0) +    self.assertNotEqual( +        os.system(TEST_WITH_ON_CALL + ' --gmock_catch_leaked_mocks'), 0) + +  def testCatchesLeakedMockWhenEnabledWithExplictFlagValue(self): +    self.assertNotEqual( +        os.system(TEST_WITH_EXPECT_CALL + ' --gmock_catch_leaked_mocks=1'), 0) + +  def testCatchesMultipleLeakedMocks(self): +    self.assertNotEqual( +        os.system(TEST_MULTIPLE_LEAKS + ' --gmock_catch_leaked_mocks'), 0) + + +if __name__ == '__main__': +  gmock_test_utils.Main() diff --git a/test/gmock_leak_test_.cc b/test/gmock_leak_test_.cc new file mode 100644 index 00000000..157bd7ec --- /dev/null +++ b/test/gmock_leak_test_.cc @@ -0,0 +1,95 @@ +// Copyright 2009, 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This program is for verifying that a leaked mock object can be +// caught by Google Mock's leak detector. + +#include <gmock/gmock.h> + +namespace { + +using ::testing::Return; + +class FooInterface { + public: +  virtual ~FooInterface() {} +  virtual void DoThis() = 0; +}; + +class MockFoo : public FooInterface { + public: +  MOCK_METHOD0(DoThis, void()); +}; + +TEST(LeakTest, LeakedMockWithExpectCallCausesFailureWhenLeakCheckingIsEnabled) { +  MockFoo* foo = new MockFoo; + +  EXPECT_CALL(*foo, DoThis()); +  foo->DoThis(); + +  // In order to test the leak detector, we deliberately leak foo. + +  // Makes sure Google Mock's leak detector can change the exit code +  // to 1 even when the code is already exiting with 0. +  exit(0); +} + +TEST(LeakTest, LeakedMockWithOnCallCausesFailureWhenLeakCheckingIsEnabled) { +  MockFoo* foo = new MockFoo; + +  ON_CALL(*foo, DoThis()).WillByDefault(Return()); + +  // In order to test the leak detector, we deliberately leak foo. + +  // Makes sure Google Mock's leak detector can change the exit code +  // to 1 even when the code is already exiting with 0. +  exit(0); +} + +TEST(LeakTest, CatchesMultipleLeakedMockObjects) { +  MockFoo* foo1 = new MockFoo; +  MockFoo* foo2 = new MockFoo; + +  ON_CALL(*foo1, DoThis()).WillByDefault(Return()); +  EXPECT_CALL(*foo2, DoThis()); +  foo2->DoThis(); + +  // In order to test the leak detector, we deliberately leak foo1 and +  // foo2. + +  // Makes sure Google Mock's leak detector can change the exit code +  // to 1 even when the code is already exiting with 0. +  exit(0); +} + +}  // namespace diff --git a/test/gmock_output_test.py b/test/gmock_output_test.py index f7f37abb..2e992190 100755 --- a/test/gmock_output_test.py +++ b/test/gmock_output_test.py @@ -59,7 +59,7 @@ else:    PROGRAM = 'gmock_output_test_'  PROGRAM_PATH = os.path.join(gmock_test_utils.GetBuildDir(), PROGRAM) -COMMAND = PROGRAM_PATH + ' --gtest_stack_trace_depth=0' +COMMAND = PROGRAM_PATH + ' --gtest_stack_trace_depth=0 --gtest_print_time=0'  GOLDEN_NAME = 'gmock_output_test_golden.txt'  GOLDEN_PATH = os.path.join(gmock_test_utils.GetSourceDir(),                             GOLDEN_NAME) diff --git a/test/gmock_output_test_.cc b/test/gmock_output_test_.cc index bb56b7cd..c97bc78c 100644 --- a/test/gmock_output_test_.cc +++ b/test/gmock_output_test_.cc @@ -40,6 +40,7 @@  #include <gtest/gtest.h>  using testing::_; +using testing::AnyNumber;  using testing::Ge;  using testing::InSequence;  using testing::Ref; @@ -239,3 +240,31 @@ TEST_F(GMockOutputTest, ExplicitActionsRunOutWithDefaultAction) {    foo_.Bar2(2, 2);    foo_.Bar2(1, 1);  // Explicit actions in EXPECT_CALL run out.  } + +TEST_F(GMockOutputTest, CatchesLeakedMocks) { +  MockFoo* foo1 = new MockFoo; +  MockFoo* foo2 = new MockFoo; + +  // Invokes ON_CALL on foo1. +  ON_CALL(*foo1, Bar(_, _, _)).WillByDefault(Return('a')); + +  // Invokes EXPECT_CALL on foo2. +  EXPECT_CALL(*foo2, Bar2(_, _)); +  EXPECT_CALL(*foo2, Bar2(1, _)); +  EXPECT_CALL(*foo2, Bar3(_, _)).Times(AnyNumber()); +  foo2->Bar2(2, 1); +  foo2->Bar2(1, 1); + +  // Both foo1 and foo2 are deliberately leaked. +} + +int main(int argc, char **argv) { +  testing::InitGoogleMock(&argc, argv); + +  // Ensures that the tests pass no matter what value of +  // --gmock_catch_leaked_mocks and --gmock_verbose the user specifies. +  testing::GMOCK_FLAG(catch_leaked_mocks) = true; +  testing::GMOCK_FLAG(verbose) = "warning"; + +  return RUN_ALL_TESTS(); +} diff --git a/test/gmock_output_test_golden.txt b/test/gmock_output_test_golden.txt index 374e6659..50ef7b75 100644 --- a/test/gmock_output_test_golden.txt +++ b/test/gmock_output_test_golden.txt @@ -1,4 +1,3 @@ -Running main() from gmock_main.cc  [ RUN      ] GMockOutputTest.ExpectedCall  FILE:#: EXPECT_CALL(foo_, Bar2(0, _)) invoked @@ -280,6 +279,8 @@ Called 2 times, but only 1 WillOnce() is specified - taking default action speci  FILE:#:  Stack trace:  [       OK ] GMockOutputTest.ExplicitActionsRunOutWithDefaultAction +[ RUN      ] GMockOutputTest.CatchesLeakedMocks +[       OK ] GMockOutputTest.CatchesLeakedMocks  [  FAILED  ] GMockOutputTest.UnexpectedCall  [  FAILED  ] GMockOutputTest.UnexpectedCallToVoidFunction  [  FAILED  ] GMockOutputTest.ExcessiveCall @@ -294,3 +295,7 @@ Stack trace:  [  FAILED  ] GMockOutputTest.UnexpectedCallWithDefaultAction  [  FAILED  ] GMockOutputTest.ExcessiveCallWithDefaultAction + +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. diff --git a/test/gmock_test.cc b/test/gmock_test.cc index 63c3fe8d..0c832607 100644 --- a/test/gmock_test.cc +++ b/test/gmock_test.cc @@ -246,3 +246,10 @@ TEST(WideInitGoogleMockTest, CallsInitGoogleTest) {    TestInitGoogleMock(argv, new_argv, "error");    EXPECT_EQ(old_init_gtest_count + 1, g_init_gtest_count);  } + +// Makes sure Google Mock flags can be accessed in code. +TEST(FlagTest, IsAccessibleInCode) { +  bool dummy = testing::GMOCK_FLAG(catch_leaked_mocks) && +      testing::GMOCK_FLAG(verbose) == ""; +  dummy = dummy;  // Avoids the "unused local variable" warning. +} | 
