blob: 4843135acdfb1321031d796c7da7ecef5fa4a3be [file] [log] [blame]
#!src/build/run_python
# Copyright 2014 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.
"""Generator of PPAPI mock source files."""
import os
import sys
import idl_c_proto
import idl_generator
import idl_option
import idl_parser
idl_option.Option('dstroot', 'Base directory of output', default='')
MAX_GMOCK_ARGS = 10
def _GetMockStructName(cgen, interface):
return cgen.GetStructName(interface, None) + '_Mock'
def _GetPpapiCHeaderPath(filenode):
path = filenode.GetProperty('NAME')
path = os.path.join('ppapi/c', os.path.splitext(path)[0] + '.h')
return os.path.normpath(path)
def _GetMockPathInternal(filenode, relpath, ext):
basename = os.path.basename(filenode.GetProperty('NAME'))
path = os.path.join(relpath, os.path.splitext(basename)[0] + ext)
return os.path.normpath(path)
def _GetMockHeaderPath(filenode, relpath):
return _GetMockPathInternal(filenode, relpath, '.h')
def _GetMockSourcePath(filenode, relpath):
return _GetMockPathInternal(filenode, relpath, '.cc')
def _GetGuardMacro(filenode, relpath):
header_path = _GetMockHeaderPath(filenode, relpath)
return header_path.replace('/', '_').replace('.', '_').upper() + '_'
class PpapiMockHeaderGenerator(idl_generator.GeneratorByFile):
HEADER_TEMPLATE = '\n'.join([
'// Copyright 2014 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.',
'//',
'// Provide %(mock_list)s.',
'//',
'// Auto-generated. Do not edit!',
'',
'#ifndef %(guard_macro)s',
'#define %(guard_macro)s',
'',
'#include "gmock/gmock.h"',
'',
'#include "ppapi/c/pp_completion_callback.h"',
'#include "%(path)s"',
''])
MOCK_CLASS_TEMPLATE = '\n'.join([
'class %(mock_name)s {',
' public:',
' %(mock_name)s();',
' virtual ~%(mock_name)s();',
'',
'%(method_list)s',
'};'])
def __init__(self):
idl_generator.Generator.__init__(
self, 'PPAPI Mock Header', 'ppapihgen',
'Generate the PPAPI Mock headers')
self.cgen = idl_c_proto.CGen()
def GenerateFile(self, filenode, releases, options):
if not os.path.basename(filenode.GetName()).startswith('ppb_'):
return
# Generator library has an issue that some getters changes its object's
# internal status, and the main usage of the generator in Chrome (i.e.
# generating PPAPI C/C++ headers and shims) relies on that behavior.
# So, here we mimic the key part of what the main usage relies on.
# Without this, it actually causes a problem that it produces the
# un-compilable source code.
for node in filenode.GetListOf('Typedef'):
node.GetUniqueReleases(releases)
# Generate file content.
file_header = self.GenerateFileHeader(filenode, releases)
file_body = self.GenerateFileBody(filenode, releases)
file_footer = self.GenerateFileFooter(filenode, releases)
# Write the content to the file.
path = _GetMockHeaderPath(filenode, idl_option.GetOption('dstroot'))
with open(path, 'w') as stream:
stream.writelines([
file_header, '\n', file_body, '\n', file_footer, '\n'])
def GenerateFileHeader(self, filenode, releases):
struct_name_list = [
self.cgen.GetStructName(interface, release, include_version=True)
for interface in filenode.GetListOf('Interface')
for release in interface.GetUniqueReleases(releases)]
return PpapiMockHeaderGenerator.HEADER_TEMPLATE % {
'mock_list': ', '.join(name + ' mock' for name in struct_name_list),
'guard_macro': _GetGuardMacro(filenode, 'ppapi_mocks'),
'path': _GetPpapiCHeaderPath(filenode),
}
def GenerateFileBody(self, filenode, releases):
# Generate mock class declaration list.
cgen = self.cgen
mock_class_list = []
for interface in filenode.GetListOf('Interface'):
mock_name = _GetMockStructName(cgen, interface)
method_list = []
signature_set = set()
unique_releases = interface.GetUniqueReleases(releases)
for member in interface.GetListOf('Member'):
for release in unique_releases:
if not member.InReleases([release]):
continue
return_type, name, arrayspec, callspec = cgen.GetComponents(
member, release, 'ref')
if len(callspec) > MAX_GMOCK_ARGS:
# We do not generate methods which have more than MAX_GMOCK_ARGS
# arguments.
continue
signature = cgen.Compose(
return_type, '', arrayspec, callspec, '', False, False, False)
if (name, signature) in signature_set:
continue
signature_set.add((name, signature))
method_list.append(
' MOCK_METHOD%d(%s, %s);' % (len(callspec), name, signature))
mock_class_list.append(PpapiMockHeaderGenerator.MOCK_CLASS_TEMPLATE % {
'mock_name': mock_name,
'method_list': '\n'.join(method_list)
})
return '\n\n'.join(mock_class_list) + '\n\n'
def GenerateFileFooter(self, filenode, release):
return '#endif // %s' % _GetGuardMacro(filenode, 'ppapi_mocks')
header_generator = PpapiMockHeaderGenerator()
class PpapiMockSourceGenerator(idl_generator.GeneratorByFile):
HEADER_TEMPLATE = '\n'.join([
'// Copyright 2014 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.',
'//',
'// Provide %(mock_list)s.',
'//',
'// Auto-generated. Do not edit!',
'',
'#include "%(mock_header_path)s"',
''
'#include "ppapi_mocks/ppapi_mock_factory.h"',
'#include "ppapi_mocks/ppapi_mock_impl.h"',
''])
MOCK_FUNCTION_TEMPLATE = '\n'.join([
'static %(signature)s {',
' return ppapi_mock::MockHolder<%(mock_name)s>::GetMock()->',
' %(name)s(%(args)s);',
'}'])
EMPTY_MOCK_FUNCTION_TEMPLATE = '\n'.join([
'static %(signature)s {',
' // FUNCTION CANNOT BE MOCKED.',
' return %(return_type)s();',
'}'])
MOCK_SOURCE_TEMPLATE = '\n'.join([
'%(mock_name)s::%(mock_name)s() {',
'}',
'%(mock_name)s::~%(mock_name)s() {',
'}',
'',
'namespace ppapi_mock {',
'template<> ::testing::NiceMock<%(mock_name)s>*',
'MockHolder<%(mock_name)s>::instance_ = NULL;',
'} // namespace ppapi_mock',
'',
'template<> void PpapiMockFactory::GetMock<%(mock_name)s>(',
' ::testing::NiceMock<%(mock_name)s>** result) {',
' *result = ppapi_mock::MockHolder<%(mock_name)s>::GetMock();',
'}',
'',
'%(struct_definition_list)s',
'',
'INVOKE_AT_OBJECT_LOAD_TIME(%(mock_name)s, {',
' ppapi_mock::MockRegistry::GetInstance()->Register<%(mock_name)s>();',
' %(struct_registration_list)s',
'});'])
INJECTED_STRUCT_TEMPLATE = '\n'.join([
'%(method_list)s',
'',
'static %(struct_name)s s_%(struct_name)s = {',
'%(struct_field_list)s',
'};',
''])
STRUCT_REGISTRATION_TEMPLATE = '\n'.join([
' ppapi_mock::InterfaceRegistry::GetInstance()->Register(',
' %(macro_name)s, &s_%(struct_name)s);'])
def __init__(self):
idl_generator.Generator.__init__(
self, 'PPAPI Mock source', 'ppapicgen',
'Generate the PPAPI Mock sources')
self.cgen = idl_c_proto.CGen()
def GenerateFile(self, filenode, releases, options):
if not os.path.basename(filenode.GetName()).startswith('ppb_'):
return
# Similar to PpapiMockHeaderGenerator, we call to GetUniqueReleases
# as a work around. Please see also PpapiMockHeaderGenerator.GenerateFile.
for node in filenode.GetListOf('Typedef'):
node.GetUniqueReleases(releases)
# Generate file content.
file_header = self.GenerateFileHeader(filenode, releases)
file_body = self.GenerateFileBody(filenode, releases)
# Write the content to the file.
path = _GetMockSourcePath(filenode, idl_option.GetOption('dstroot'))
with open(path, 'w') as stream:
stream.writelines([file_header, '\n', file_body, '\n\n'])
def GenerateFileHeader(self, filenode, releases):
struct_name_list = [
self.cgen.GetStructName(interface, release, include_version=True)
for interface in filenode.GetListOf('Interface')
for release in interface.GetUniqueReleases(releases)]
return PpapiMockSourceGenerator.HEADER_TEMPLATE % {
'mock_list': ', '.join(name + ' mock' for name in struct_name_list),
'mock_header_path': _GetMockHeaderPath(filenode, 'ppapi_mocks'),
}
def GenerateFileBody(self, filenode, releases):
cgen = self.cgen
result = []
for interface in filenode.GetListOf('Interface'):
mock_name = _GetMockStructName(cgen, interface)
struct_definition_list = []
struct_registration_list = []
for release in interface.GetUniqueReleases(releases):
struct_name = cgen.GetStructName(
interface, release, include_version=True)
macro_name = cgen.GetInterfaceMacro(
interface, interface.GetVersion(release))
method_list = []
method_name_list = []
for member in interface.GetListOf('Member'):
if not member.InReleases([release]):
continue
return_type, name, arrayspec, callspec = cgen.GetComponents(
member, release, 'ref')
signature = cgen.GetSignature(
member, release, 'ref', prefix=struct_name + '_',
func_as_ptr=False, include_name=True, include_version=False)
if len(callspec) <= MAX_GMOCK_ARGS:
method_template = PpapiMockSourceGenerator.MOCK_FUNCTION_TEMPLATE
else:
method_template = (
PpapiMockSourceGenerator.EMPTY_MOCK_FUNCTION_TEMPLATE)
method_list.append(method_template % {
'signature': signature,
'mock_name': mock_name,
'return_type': return_type,
'name': name,
'args': ', '.join(arg for _, arg, _, _ in callspec)
})
method_name_list.append(struct_name + '_' + name)
injected_struct = PpapiMockSourceGenerator.INJECTED_STRUCT_TEMPLATE % {
'method_list': '\n\n'.join(method_list),
'struct_field_list': '\n'.join(
' %s,' % method_name for method_name in method_name_list),
'struct_name': struct_name,
}
struct_definition_list.append(injected_struct)
registration = PpapiMockSourceGenerator.STRUCT_REGISTRATION_TEMPLATE % {
'macro_name': macro_name,
'struct_name': struct_name,
}
struct_registration_list.append(registration)
result.append(PpapiMockSourceGenerator.MOCK_SOURCE_TEMPLATE % {
'mock_name': mock_name,
'struct_definition_list': '\n\n'.join(struct_definition_list),
'struct_registration_list': '\n\n'.join(struct_registration_list)
})
return '\n\n'.join(result)
source_generator = PpapiMockSourceGenerator()
def main():
filenames = idl_option.ParseOptions(sys.argv[1:])
ast = idl_parser.ParseFiles(filenames)
assert not ast.errors, ast.errors
return idl_generator.Generator.Run(ast)
if __name__ == '__main__':
main()