PYTHON   55

gmock_class.py

Guest on 2nd August 2021 03:29:50 PM

  1. #!/usr/bin/env python
  2. #
  3. # Copyright  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.  
  17. """Generate Google Mock classes from base classes.
  18.  
  19. This program will read in a C++ source file and output the Google Mock
  20. classes for the specified classes.  If no class is specified, all
  21. classes in the source file are emitted.
  22.  
  23. Usage:
  24.  gmock_class.py header-file.h [ClassName]...
  25.  
  26. Output is sent to stdout.
  27. """
  28.  
  29. __author__ = 'nnorwitz@google.com (Neal Norwitz)'
  30.  
  31.  
  32. import os
  33. import re
  34. import sys
  35.  
  36. from cpp import ast
  37. from cpp import utils
  38.  
  39. # Preserve compatibility with Python 2.3.
  40. try:
  41.   _dummy = set
  42. except NameError:
  43.   import sets
  44.   set = sets.Set
  45.  
  46. _VERSION = (1, 0, 1)  # The version of this script.
  47. # How many spaces to indent.  Can set me with the INDENT environment variable.
  48. _INDENT = 2
  49.  
  50.  
  51. def _GenerateMethods(output_lines, source, class_node):
  52.   function_type = (ast.FUNCTION_VIRTUAL | ast.FUNCTION_PURE_VIRTUAL |
  53.                    ast.FUNCTION_OVERRIDE)
  54.   ctor_or_dtor = ast.FUNCTION_CTOR | ast.FUNCTION_DTOR
  55.   indent = ' ' * _INDENT
  56.  
  57.   for node in class_node.body:
  58.     # We only care about virtual functions.
  59.     if (isinstance(node, ast.Function) and
  60.         node.modifiers & function_type and
  61.         not node.modifiers & ctor_or_dtor):
  62.       # Pick out all the elements we need from the original function.
  63.       const = ''
  64.       if node.modifiers & ast.FUNCTION_CONST:
  65.         const = 'CONST_'
  66.       return_type = 'void'
  67.       if node.return_type:
  68.         # Add modifiers like 'const'.
  69.         modifiers = ''
  70.         if node.return_type.modifiers:
  71.           modifiers = ' '.join(node.return_type.modifiers) + ' '
  72.         return_type = modifiers + node.return_type.name
  73.         template_args = [arg.name for arg in node.return_type.templated_types]
  74.         if template_args:
  75.           return_type += '<' + ', '.join(template_args) + '>'
  76.           if len(template_args) > 1:
  77.             for line in [
  78.                 '// The following line won\'t really compile, as the return',
  79.                 '// type has multiple template arguments.  To fix it, use a',
  80.                 '// typedef for the return type.']:
  81.               output_lines.append(indent + line)
  82.         if node.return_type.pointer:
  83.           return_type += '*'
  84.         if node.return_type.reference:
  85.           return_type += '&'
  86.         num_parameters = len(node.parameters)
  87.         if len(node.parameters) == 1:
  88.           first_param = node.parameters[0]
  89.           if source[first_param.start:first_param.end].strip() == 'void':
  90.             # We must treat T(void) as a function with no parameters.
  91.             num_parameters = 0
  92.       tmpl = ''
  93.       if class_node.templated_types:
  94.         tmpl = '_T'
  95.       mock_method_macro = 'MOCK_%sMETHOD%d%s' % (const, num_parameters, tmpl)
  96.  
  97.       args = ''
  98.       if node.parameters:
  99.         # Due to the parser limitations, it is impossible to keep comments
  100.         # while stripping the default parameters.  When defaults are
  101.         # present, we choose to strip them and comments (and produce
  102.         # compilable code).
  103.         # TODO(nnorwitz@google.com): Investigate whether it is possible to
  104.         # preserve parameter name when reconstructing parameter text from
  105.         # the AST.
  106.         if len([param for param in node.parameters if param.default]) > 0:
  107.           args = ', '.join(param.type.name for param in node.parameters)
  108.         else:
  109.           # Get the full text of the parameters from the start
  110.           # of the first parameter to the end of the last parameter.
  111.           start = node.parameters[0].start
  112.           end = node.parameters[-1].end
  113.           # Remove // comments.
  114.           args_strings = re.sub(r'//.*', '', source[start:end])
  115.           # Condense multiple spaces and eliminate newlines putting the
  116.           # parameters together on a single line.  Ensure there is a
  117.           # space in an argument which is split by a newline without
  118.           # intervening whitespace, e.g.: int\nBar
  119.           args = re.sub('  +', ' ', args_strings.replace('\n', ' '))
  120.  
  121.       # Create the mock method definition.
  122.       output_lines.extend(['%s%s(%s,' % (indent, mock_method_macro, node.name),
  123.                            '%s%s(%s));' % (indent*3, return_type, args)])
  124.  
  125.  
  126. def _GenerateMocks(filename, source, ast_list, desired_class_names):
  127.   processed_class_names = set()
  128.   lines = []
  129.   for node in ast_list:
  130.     if (isinstance(node, ast.Class) and node.body and
  131.         # desired_class_names being None means that all classes are selected.
  132.         (not desired_class_names or node.name in desired_class_names)):
  133.       class_name = node.name
  134.       parent_name = class_name
  135.       processed_class_names.add(class_name)
  136.       class_node = node
  137.       # Add namespace before the class.
  138.       if class_node.namespace:
  139.         lines.extend(['namespace %s {' % n for n in class_node.namespace])  # }
  140.         lines.append('')
  141.  
  142.       # Add template args for templated classes.
  143.       if class_node.templated_types:
  144.         # TODO(paulchang): The AST doesn't preserve template argument order,
  145.         # so we have to make up names here.
  146.         # TODO(paulchang): Handle non-type template arguments (e.g.
  147.         # template<typename T, int N>).
  148.         template_arg_count = len(class_node.templated_types.keys())
  149.         template_args = ['T%d' % n for n in range(template_arg_count)]
  150.         template_decls = ['typename ' + arg for arg in template_args]
  151.         lines.append('template <' + ', '.join(template_decls) + '>')
  152.         parent_name += '<' + ', '.join(template_args) + '>'
  153.  
  154.       # Add the class prolog.
  155.       lines.append('class Mock%s : public %s {'  # }
  156.                    % (class_name, parent_name))
  157.       lines.append('%spublic:' % (' ' * (_INDENT // 2)))
  158.  
  159.       # Add all the methods.
  160.       _GenerateMethods(lines, source, class_node)
  161.  
  162.       # Close the class.
  163.       if lines:
  164.         # If there are no virtual methods, no need for a public label.
  165.         if len(lines) == 2:
  166.           del lines[-1]
  167.  
  168.         # Only close the class if there really is a class.
  169.         lines.append('};')
  170.         lines.append('')  # Add an extra newline.
  171.  
  172.       # Close the namespace.
  173.       if class_node.namespace:
  174.         for i in range(len(class_node.namespace)-1, -1, -1):
  175.           lines.append('}  // namespace %s' % class_node.namespace[i])
  176.         lines.append('')  # Add an extra newline.
  177.  
  178.   if desired_class_names:
  179.     missing_class_name_list = list(desired_class_names - processed_class_names)
  180.     if missing_class_name_list:
  181.       missing_class_name_list.sort()
  182.       sys.stderr.write('Class(es) not found in %s: %s\n' %
  183.                        (filename, ', '.join(missing_class_name_list)))
  184.   elif not processed_class_names:
  185.     sys.stderr.write('No class found in %s\n' % filename)
  186.  
  187.   return lines
  188.  
  189.  
  190. def main(argv=sys.argv):
  191.   if len(argv) < 2:
  192.     sys.stderr.write('Google Mock Class Generator v%s\n\n' %
  193.                      '.'.join(map(str, _VERSION)))
  194.     sys.stderr.write(__doc__)
  195.     return 1
  196.  
  197.   global _INDENT
  198.   try:
  199.     _INDENT = int(os.environ['INDENT'])
  200.   except KeyError:
  201.     pass
  202.   except:
  203.     sys.stderr.write('Unable to use indent of %s\n' % os.environ.get('INDENT'))
  204.  
  205.   filename = argv[1]
  206.   desired_class_names = None  # None means all classes in the source file.
  207.   if len(argv) >= 3:
  208.     desired_class_names = set(argv[2:])
  209.   source = utils.ReadFile(filename)
  210.   if source is None:
  211.     return 1
  212.  
  213.   builder = ast.BuilderFromSource(source, filename)
  214.   try:
  215.     entire_ast = filter(None, builder.Generate())
  216.   except KeyboardInterrupt:
  217.     return
  218.   except:
  219.     # An error message was already printed since we couldn't parse.
  220.     sys.exit(1)
  221.   else:
  222.     lines = _GenerateMocks(filename, source, entire_ast, desired_class_names)
  223.     sys.stdout.write('\n'.join(lines))
  224.  
  225.  
  226. if __name__ == '__main__':
  227.   main(sys.argv)

Raw Paste


Login or Register to edit or fork this paste. It's free.