diff --git a/CHANGES b/CHANGES index e42e68fc..d6f2f760 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,8 @@ Changes for 1.7.0: * Improvement: the ElementsAreArray() matcher can now take a vector or iterator range as input, and makes a copy of its input elements before the conversion to a Matcher. +* Improvement: the Google Mock Generator can now generate mocks for + some class templates. * Bug fix: mock object destruction triggerred by another mock object's destruction no longer hangs. * Improvement: Google Mock Doctor works better with newer Clang and diff --git a/scripts/generator/cpp/ast.py b/scripts/generator/cpp/ast.py index 6f61f877..bb8226d7 100755 --- a/scripts/generator/cpp/ast.py +++ b/scripts/generator/cpp/ast.py @@ -1546,7 +1546,7 @@ class AstBuilder(object): self._AddBackToken(token) return class_type(class_token.start, class_token.end, class_name, - bases, None, body, self.namespace_stack) + bases, templated_types, body, self.namespace_stack) def handle_namespace(self): token = self._GetNextToken() diff --git a/scripts/generator/cpp/gmock_class.py b/scripts/generator/cpp/gmock_class.py index 427d206a..3c8a877d 100755 --- a/scripts/generator/cpp/gmock_class.py +++ b/scripts/generator/cpp/gmock_class.py @@ -88,7 +88,11 @@ def _GenerateMethods(output_lines, source, class_node): if source[first_param.start:first_param.end].strip() == 'void': # We must treat T(void) as a function with no parameters. num_parameters = 0 - mock_method_macro = 'MOCK_%sMETHOD%d' % (const, num_parameters) + tmpl = '' + if class_node.templated_types: + tmpl = '_T' + mock_method_macro = 'MOCK_%sMETHOD%d%s' % (const, num_parameters, tmpl) + args = '' if node.parameters: # Due to the parser limitations, it is impossible to keep comments @@ -126,6 +130,7 @@ def _GenerateMocks(filename, source, ast_list, desired_class_names): # desired_class_names being None means that all classes are selected. (not desired_class_names or node.name in desired_class_names)): class_name = node.name + parent_name = class_name processed_class_names.add(class_name) class_node = node # Add namespace before the class. @@ -133,8 +138,21 @@ def _GenerateMocks(filename, source, ast_list, desired_class_names): lines.extend(['namespace %s {' % n for n in class_node.namespace]) # } lines.append('') + # Add template args for templated classes. + if class_node.templated_types: + # TODO(paulchang): The AST doesn't preserve template argument order, + # so we have to make up names here. + # TODO(paulchang): Handle non-type template arguments (e.g. + # template). + template_arg_count = len(class_node.templated_types.keys()) + template_args = ['T%d' % n for n in range(template_arg_count)] + template_decls = ['typename ' + arg for arg in template_args] + lines.append('template <' + ', '.join(template_decls) + '>') + parent_name += '<' + ', '.join(template_args) + '>' + # Add the class prolog. - lines.append('class Mock%s : public %s {' % (class_name, class_name)) # } + lines.append('class Mock%s : public %s {' # } + % (class_name, parent_name)) lines.append('%spublic:' % (' ' * (_INDENT // 2))) # Add all the methods. diff --git a/scripts/generator/cpp/gmock_class_test.py b/scripts/generator/cpp/gmock_class_test.py index 7aa70276..07d59571 100755 --- a/scripts/generator/cpp/gmock_class_test.py +++ b/scripts/generator/cpp/gmock_class_test.py @@ -196,6 +196,18 @@ class Foo { 'MOCK_METHOD0(Bar,\nmap());', self.GenerateMethodSource(source)) + def testSimpleMethodInTemplatedClass(self): + source = """ +template +class Foo { + public: + virtual int Bar(); +}; +""" + self.assertEqualIgnoreLeadingWhitespace( + 'MOCK_METHOD0_T(Bar,\nint());', + self.GenerateMethodSource(source)) + class GenerateMocksTest(TestCase): @@ -255,5 +267,43 @@ void()); self.assertEqualIgnoreLeadingWhitespace( expected, self.GenerateMocks(source)) + def testTemplatedForwardDeclaration(self): + source = """ +template class Forward; // Forward declaration should be ignored. +class Test { + public: + virtual void Foo(); +}; +""" + expected = """\ +class MockTest : public Test { +public: +MOCK_METHOD0(Foo, +void()); +}; +""" + self.assertEqualIgnoreLeadingWhitespace( + expected, self.GenerateMocks(source)) + + def testTemplatedClass(self): + source = """ +template +class Test { + public: + virtual void Foo(); +}; +""" + expected = """\ +template +class MockTest : public Test { +public: +MOCK_METHOD0_T(Foo, +void()); +}; +""" + self.assertEqualIgnoreLeadingWhitespace( + expected, self.GenerateMocks(source)) + + if __name__ == '__main__': unittest.main() diff --git a/test/gmock_leak_test.py b/test/gmock_leak_test.py index 38ff9d01..997680ce 100755 --- a/test/gmock_leak_test.py +++ b/test/gmock_leak_test.py @@ -42,48 +42,66 @@ 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*'] +environ = gmock_test_utils.environ +SetEnvVar = gmock_test_utils.SetEnvVar + +# Tests in this file run a Google-Test-based test program and expect it +# to terminate prematurely. Therefore they are incompatible with +# the premature-exit-file protocol by design. Unset the +# premature-exit filepath to prevent Google Test from creating +# the file. +SetEnvVar(gmock_test_utils.PREMATURE_EXIT_FILE_ENV_VAR, None) + class GMockLeakTest(gmock_test_utils.TestCase): def testCatchesLeakedMockByDefault(self): self.assertNotEqual( 0, - gmock_test_utils.Subprocess(TEST_WITH_EXPECT_CALL).exit_code) + gmock_test_utils.Subprocess(TEST_WITH_EXPECT_CALL, + env=environ).exit_code) self.assertNotEqual( 0, - gmock_test_utils.Subprocess(TEST_WITH_ON_CALL).exit_code) + gmock_test_utils.Subprocess(TEST_WITH_ON_CALL, + env=environ).exit_code) def testDoesNotCatchLeakedMockWhenDisabled(self): self.assertEquals( 0, gmock_test_utils.Subprocess(TEST_WITH_EXPECT_CALL + - ['--gmock_catch_leaked_mocks=0']).exit_code) + ['--gmock_catch_leaked_mocks=0'], + env=environ).exit_code) self.assertEquals( 0, gmock_test_utils.Subprocess(TEST_WITH_ON_CALL + - ['--gmock_catch_leaked_mocks=0']).exit_code) + ['--gmock_catch_leaked_mocks=0'], + env=environ).exit_code) def testCatchesLeakedMockWhenEnabled(self): self.assertNotEqual( 0, gmock_test_utils.Subprocess(TEST_WITH_EXPECT_CALL + - ['--gmock_catch_leaked_mocks']).exit_code) + ['--gmock_catch_leaked_mocks'], + env=environ).exit_code) self.assertNotEqual( 0, gmock_test_utils.Subprocess(TEST_WITH_ON_CALL + - ['--gmock_catch_leaked_mocks']).exit_code) + ['--gmock_catch_leaked_mocks'], + env=environ).exit_code) def testCatchesLeakedMockWhenEnabledWithExplictFlagValue(self): self.assertNotEqual( 0, gmock_test_utils.Subprocess(TEST_WITH_EXPECT_CALL + - ['--gmock_catch_leaked_mocks=1']).exit_code) + ['--gmock_catch_leaked_mocks=1'], + env=environ).exit_code) def testCatchesMultipleLeakedMocks(self): self.assertNotEqual( 0, gmock_test_utils.Subprocess(TEST_MULTIPLE_LEAKS + - ['--gmock_catch_leaked_mocks']).exit_code) + ['--gmock_catch_leaked_mocks'], + env=environ).exit_code) if __name__ == '__main__': diff --git a/test/gmock_test_utils.py b/test/gmock_test_utils.py index ac3d67ae..20e3d3d4 100755 --- a/test/gmock_test_utils.py +++ b/test/gmock_test_utils.py @@ -96,11 +96,12 @@ def GetExitStatus(exit_code): # Suppresses the "Invalid const name" lint complaint # pylint: disable-msg=C6409 -# Exposes Subprocess from gtest_test_utils. +# Exposes utilities from gtest_test_utils. Subprocess = gtest_test_utils.Subprocess - -# Exposes TestCase from gtest_test_utils. TestCase = gtest_test_utils.TestCase +environ = gtest_test_utils.environ +SetEnvVar = gtest_test_utils.SetEnvVar +PREMATURE_EXIT_FILE_ENV_VAR = gtest_test_utils.PREMATURE_EXIT_FILE_ENV_VAR # pylint: enable-msg=C6409