summaryrefslogtreecommitdiffstats
path: root/ceee/testing/utils/com_mock.py
diff options
context:
space:
mode:
Diffstat (limited to 'ceee/testing/utils/com_mock.py')
-rw-r--r--ceee/testing/utils/com_mock.py166
1 files changed, 166 insertions, 0 deletions
diff --git a/ceee/testing/utils/com_mock.py b/ceee/testing/utils/com_mock.py
new file mode 100644
index 0000000..4c63374
--- /dev/null
+++ b/ceee/testing/utils/com_mock.py
@@ -0,0 +1,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:]))