summaryrefslogtreecommitdiffstats
path: root/ceee/testing/utils/com_mock.py
blob: 4c633744e0bad8ef9cafe3faa3cef31df0a68f24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/bin/env python
# Copyright (c) 2010 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Generates a file with MOCK_METHODX(...) macros for a COM interface.

For interfaces that will not change, you can run this file by hand.  For
interfaces that may still change, you could run it as part of your build
system to generate code, then #include the generated list of methods inside an
appropriate wrapper class.

Usage:
  com_mock.py INTERFACE_NAME HEADER [ADD_HEADERS]* > output-inl.h
"""

import re
import sys

# Finds an interface block in a header file generated by IDL.
_INTERFACE_PATTERN = (r'MIDL_INTERFACE\("[A-Za-z0-9\-]+"\)\s*%s\s*:\s*'
                      r'public\s*(?P<base>\w+)\s*{\s*public:'
                      r'(?P<method_block>[^}]+)};')

# Parses out a method from within an interface block.
_METHOD_RE = re.compile(r'virtual\s*(:?/\*[^*]+\*/)?\s*(?P<ret>\w+)\s*'
                        r'STDMETHODCALLTYPE\s*(?P<name>\w+)\s*\('
                        r'(?P<params>[^\)]+)\)\s*=\s*0;')

# Finds inline C-style comments.
_COMMENTS_RE = re.compile('/\*.*?\*/')

# Finds __RPC_xyz defines.
_RPC_RE = re.compile(' __RPC\w*')

# Finds whitespace.
_WHITESPACE_RE = re.compile('\s+')

# Finds default argument values.
_DEFAULT_ARG_RE = re.compile('\s*=\s*[^,]*')

# Header for generated files.
_FILE_HEADER = '''\
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Auto-generated by com_mock.py

'''

class Mocker(object):
  """Given a set of header files, can generate mocky goodness for interfaces
  defined in them.
  """

  def __init__(self):
    self.headers_ = ''

  def AddHeaders(self, header_files):
    """Adds the specified files to the headers to parse."""
    assert header_files
    # Slurp all the header files into a single huge big string.
    # Yummy nonperformant code - but it probably doesn't need to be.
    headers = []
    for f in header_files:
      fo = open(f, 'r')
      headers.append(fo.read())
      fo.close()
    self.headers_ += '\n'.join(headers)

  def _GetInterfaceInfo(self, interface_name):
    """Returns a tuple (base_interface, method_block) or None."""
    m = re.search(_INTERFACE_PATTERN % interface_name, self.headers_)
    if m:
      return (m.group('base'), m.group('method_block'))
    else:
      return None

  @staticmethod
  def _ProcessParams(params):
    """Return a tuple (count, cleaned_up_params)."""
    if params == ' void':
      return (0, '')
    else:
      params = params.replace('\n', ' ')
      params = _COMMENTS_RE.sub('', params)
      params = _RPC_RE.sub(' ', params)
      params = _WHITESPACE_RE.sub(' ', params)
      params = _DEFAULT_ARG_RE.sub(' ', params)
      return (params.count(',') + 1, params.strip())

  def _MockMethodsInBlock(self, block):
    """Return a list of mock functions for each of the methods defined
    in 'block', which is the text between 'public:' and '};' in an interface
    definition.
    """
    methods = []
    for m in _METHOD_RE.finditer(block):
      return_type = m.group('ret')
      name = m.group('name')
      params = m.group('params')
      (param_count, clean_params) = self._ProcessParams(params)

      method_definition = ('MOCK_METHOD%d_WITH_CALLTYPE(__stdcall, %s, %s(%s));'
                           % (param_count, name, return_type, clean_params))
      if len(method_definition) > 78:
        method_definition = (
            'MOCK_METHOD%d_WITH_CALLTYPE(__stdcall, %s, %s(\n    %s));' %
            (param_count, name, return_type, clean_params))
      methods.append(method_definition)
    return methods

  def MockInterface(self, name):
    """Returns a list of all the mock methods needed for the given
    interface (including methods from inherited interfaces, but stopping
    short of IDispatch and IUnknown).
    """
    info = self._GetInterfaceInfo(name)
    if not info:
      return ''

    # Generate inherited methods
    methods = []
    if info[0] not in ('IUnknown', 'IDispatch'):
      methods.extend(self.MockInterface(info[0]))
    methods.extend(self._MockMethodsInBlock(info[1]))
    return methods


def MockMethods(interface_name, header_files):
  """Returns a string with a correctly filled-out MOCK_METHODX(...) line
  for each method in the given interface, plus all of its inherited interfaces,
  terminating when IDispatch or IUnknown is reached.

  You must list all header files required to find the interface itself and
  all of the interfaces it inherits from, except IDispatch and IUnknown.

  Header files must be IDL-generated for the pattern matching used in the
  code to work correctly.

  Args:
    interface_name: 'IWebBrowser2'
    header_files: ['c:\\platform_sdk\\files\\Include\\exdisp.h',
                   'c:\\platform_sdk\\files\\Include\\oaidl.h']

  Returns:
    'MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetWindow, HRESULT()); ...'
  """
  mocker = Mocker()
  mocker.AddHeaders(header_files)
  return '\n'.join(mocker.MockInterface(interface_name))


def Main(args):
  if not args or len(args) < 2:
    print __doc__
    return 1
  else:
    print _FILE_HEADER
    print MockMethods(args[0], args[1:])
    return 0


if __name__ == '__main__':
  sys.exit(Main(sys.argv[1:]))