gmock_class.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2008 Google Inc. All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Generate Google Mock classes from base classes.
  17. This program will read in a C++ source file and output the Google Mock
  18. classes for the specified classes. If no class is specified, all
  19. classes in the source file are emitted.
  20. Usage:
  21. gmock_class.py header-file.h [ClassName]...
  22. Output is sent to stdout.
  23. """
  24. import os
  25. import re
  26. import sys
  27. from cpp import ast
  28. from cpp import utils
  29. # Preserve compatibility with Python 2.3.
  30. try:
  31. _dummy = set
  32. except NameError:
  33. import sets
  34. set = sets.Set
  35. _VERSION = (1, 0, 1) # The version of this script.
  36. # How many spaces to indent. Can set me with the INDENT environment variable.
  37. _INDENT = 2
  38. def _RenderType(ast_type):
  39. """Renders the potentially recursively templated type into a string.
  40. Args:
  41. ast_type: The AST of the type.
  42. Returns:
  43. Rendered string of the type.
  44. """
  45. # Add modifiers like 'const'.
  46. modifiers = ''
  47. if ast_type.modifiers:
  48. modifiers = ' '.join(ast_type.modifiers) + ' '
  49. return_type = modifiers + ast_type.name
  50. if ast_type.templated_types:
  51. # Collect template args.
  52. template_args = []
  53. for arg in ast_type.templated_types:
  54. rendered_arg = _RenderType(arg)
  55. template_args.append(rendered_arg)
  56. return_type += '<' + ', '.join(template_args) + '>'
  57. if ast_type.pointer:
  58. return_type += '*'
  59. if ast_type.reference:
  60. return_type += '&'
  61. return return_type
  62. def _GenerateArg(source):
  63. """Strips out comments, default arguments, and redundant spaces from a single argument.
  64. Args:
  65. source: A string for a single argument.
  66. Returns:
  67. Rendered string of the argument.
  68. """
  69. # Remove end of line comments before eliminating newlines.
  70. arg = re.sub(r'//.*', '', source)
  71. # Remove c-style comments.
  72. arg = re.sub(r'/\*.*\*/', '', arg)
  73. # Remove default arguments.
  74. arg = re.sub(r'=.*', '', arg)
  75. # Collapse spaces and newlines into a single space.
  76. arg = re.sub(r'\s+', ' ', arg)
  77. return arg.strip()
  78. def _EscapeForMacro(s):
  79. """Escapes a string for use as an argument to a C++ macro."""
  80. paren_count = 0
  81. for c in s:
  82. if c == '(':
  83. paren_count += 1
  84. elif c == ')':
  85. paren_count -= 1
  86. elif c == ',' and paren_count == 0:
  87. return '(' + s + ')'
  88. return s
  89. def _GenerateMethods(output_lines, source, class_node):
  90. function_type = (
  91. ast.FUNCTION_VIRTUAL | ast.FUNCTION_PURE_VIRTUAL | ast.FUNCTION_OVERRIDE)
  92. ctor_or_dtor = ast.FUNCTION_CTOR | ast.FUNCTION_DTOR
  93. indent = ' ' * _INDENT
  94. for node in class_node.body:
  95. # We only care about virtual functions.
  96. if (isinstance(node, ast.Function) and node.modifiers & function_type and
  97. not node.modifiers & ctor_or_dtor):
  98. # Pick out all the elements we need from the original function.
  99. modifiers = 'override'
  100. if node.modifiers & ast.FUNCTION_CONST:
  101. modifiers = 'const, ' + modifiers
  102. return_type = 'void'
  103. if node.return_type:
  104. return_type = _EscapeForMacro(_RenderType(node.return_type))
  105. args = []
  106. for p in node.parameters:
  107. arg = _GenerateArg(source[p.start:p.end])
  108. if arg != 'void':
  109. args.append(_EscapeForMacro(arg))
  110. # Create the mock method definition.
  111. output_lines.extend([
  112. '%sMOCK_METHOD(%s, %s, (%s), (%s));' %
  113. (indent, return_type, node.name, ', '.join(args), modifiers)
  114. ])
  115. def _GenerateMocks(filename, source, ast_list, desired_class_names):
  116. processed_class_names = set()
  117. lines = []
  118. for node in ast_list:
  119. if (isinstance(node, ast.Class) and node.body and
  120. # desired_class_names being None means that all classes are selected.
  121. (not desired_class_names or node.name in desired_class_names)):
  122. class_name = node.name
  123. parent_name = class_name
  124. processed_class_names.add(class_name)
  125. class_node = node
  126. # Add namespace before the class.
  127. if class_node.namespace:
  128. lines.extend(['namespace %s {' % n for n in class_node.namespace]) # }
  129. lines.append('')
  130. # Add template args for templated classes.
  131. if class_node.templated_types:
  132. # TODO(paulchang): Handle non-type template arguments (e.g.
  133. # template<typename T, int N>).
  134. # class_node.templated_types is an OrderedDict from strings to a tuples.
  135. # The key is the name of the template, and the value is
  136. # (type_name, default). Both type_name and default could be None.
  137. template_args = class_node.templated_types.keys()
  138. template_decls = ['typename ' + arg for arg in template_args]
  139. lines.append('template <' + ', '.join(template_decls) + '>')
  140. parent_name += '<' + ', '.join(template_args) + '>'
  141. # Add the class prolog.
  142. lines.append('class Mock%s : public %s {' # }
  143. % (class_name, parent_name))
  144. lines.append('%spublic:' % (' ' * (_INDENT // 2)))
  145. # Add all the methods.
  146. _GenerateMethods(lines, source, class_node)
  147. # Close the class.
  148. if lines:
  149. # If there are no virtual methods, no need for a public label.
  150. if len(lines) == 2:
  151. del lines[-1]
  152. # Only close the class if there really is a class.
  153. lines.append('};')
  154. lines.append('') # Add an extra newline.
  155. # Close the namespace.
  156. if class_node.namespace:
  157. for i in range(len(class_node.namespace) - 1, -1, -1):
  158. lines.append('} // namespace %s' % class_node.namespace[i])
  159. lines.append('') # Add an extra newline.
  160. if desired_class_names:
  161. missing_class_name_list = list(desired_class_names - processed_class_names)
  162. if missing_class_name_list:
  163. missing_class_name_list.sort()
  164. sys.stderr.write('Class(es) not found in %s: %s\n' %
  165. (filename, ', '.join(missing_class_name_list)))
  166. elif not processed_class_names:
  167. sys.stderr.write('No class found in %s\n' % filename)
  168. return lines
  169. def main(argv=sys.argv):
  170. if len(argv) < 2:
  171. sys.stderr.write('Google Mock Class Generator v%s\n\n' %
  172. '.'.join(map(str, _VERSION)))
  173. sys.stderr.write(__doc__)
  174. return 1
  175. global _INDENT
  176. try:
  177. _INDENT = int(os.environ['INDENT'])
  178. except KeyError:
  179. pass
  180. except:
  181. sys.stderr.write('Unable to use indent of %s\n' % os.environ.get('INDENT'))
  182. filename = argv[1]
  183. desired_class_names = None # None means all classes in the source file.
  184. if len(argv) >= 3:
  185. desired_class_names = set(argv[2:])
  186. source = utils.ReadFile(filename)
  187. if source is None:
  188. return 1
  189. builder = ast.BuilderFromSource(source, filename)
  190. try:
  191. entire_ast = filter(None, builder.Generate())
  192. except KeyboardInterrupt:
  193. return
  194. except:
  195. # An error message was already printed since we couldn't parse.
  196. sys.exit(1)
  197. else:
  198. lines = _GenerateMocks(filename, source, entire_ast, desired_class_names)
  199. sys.stdout.write('\n'.join(lines))
  200. if __name__ == '__main__':
  201. main(sys.argv)