diff options
author | davemoore@chromium.org <davemoore@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-08 22:39:30 +0000 |
---|---|---|
committer | davemoore@chromium.org <davemoore@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-08 22:39:30 +0000 |
commit | b1057c433bb70fd9df337fe0851ac181b951d07b (patch) | |
tree | cb0e293035f352719b5c60c218c732e152baeea0 /mojo | |
parent | d4c07bf4b5f415352d7f27f67735559c39fbf3c6 (diff) | |
download | chromium_src-b1057c433bb70fd9df337fe0851ac181b951d07b.zip chromium_src-b1057c433bb70fd9df337fe0851ac181b951d07b.tar.gz chromium_src-b1057c433bb70fd9df337fe0851ac181b951d07b.tar.bz2 |
Initial support for mojo module bindings
BUG=None
TEST=Included
R=darin@chromium.org, darin
Review URL: https://codereview.chromium.org/26361003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@227615 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'mojo')
-rw-r--r-- | mojo/public/bindings/generators/mojom.py | 131 | ||||
-rw-r--r-- | mojo/public/bindings/generators/mojom_data.py | 164 | ||||
-rw-r--r-- | mojo/public/bindings/generators/mojom_data_tests.py | 84 | ||||
-rwxr-xr-x | mojo/public/bindings/generators/mojom_test.py | 193 | ||||
-rwxr-xr-x | mojo/public/bindings/generators/mojom_tests.py | 33 | ||||
-rwxr-xr-x | mojo/public/bindings/generators/run_mojom_tests.py | 35 |
6 files changed, 640 insertions, 0 deletions
diff --git a/mojo/public/bindings/generators/mojom.py b/mojo/public/bindings/generators/mojom.py new file mode 100644 index 0000000..b12ec31 --- /dev/null +++ b/mojo/public/bindings/generators/mojom.py @@ -0,0 +1,131 @@ +# Copyright (c) 2013 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. + +# mojom's classes provide an interface to mojo modules. Modules are collections +# of interfaces and structs to be used by mojo ipc clients and servers. +# +# A simple interface would be created this way: +# module = mojom.Module('Foo') +# interface = module.AddInterface('Bar') +# method = interface.AddMethod('Tat', 0) +# method.AddParameter('baz', 0, mojom.INT32) +# +class Kind(object): + def __init__(self, spec = None): + self.spec = spec + + +# Initialize the set of primitive types. These can be accessed by clients. +BOOL = Kind('b') +INT8 = Kind('i8') +INT16 = Kind('i16') +INT32 = Kind('i32') +INT64 = Kind('i64') +UINT8 = Kind('u8') +UINT16 = Kind('u16') +UINT32 = Kind('u32') +UINT64 = Kind('u64') +FLOAT = Kind('f') +DOUBLE = Kind('d') +STRING = Kind('s') + + +# Collection of all Primitive types +PRIMITIVES = [ + BOOL, + INT8, + INT16, + INT32, + INT64, + UINT8, + UINT16, + UINT32, + UINT64, + FLOAT, + DOUBLE, + STRING, + // TODO(davemoore): Add HANDLE. +] + + +class Field(object): + def __init__(self, name = None, kind = None, ordinal = None, default = None): + self.name = name + self.kind = kind + self.ordinal = ordinal + self.default = default + + +class Struct(Kind): + def __init__(self, name = None): + self.name = name + if name != None: + spec = 'x:' + name + else: + spec = None + Kind.__init__(self, spec) + self.fields = [] + + def AddField(self, name, kind, ordinal = None, default = None): + field = Field(name, kind, ordinal, default) + self.fields.append(field) + return field + + +class Array(Kind): + def __init__(self, kind = None): + self.kind = kind + if kind != None: + Kind.__init__(self, 'a:' + kind.spec) + else: + Kind.__init__(self) + + +class Parameter(object): + def __init__(self, name = None, kind = None, ordinal = None, default = None): + self.name = name + self.ordinal = ordinal + self.kind = kind + self.default = default + + +class Method(object): + def __init__(self, name = None, ordinal = None): + self.name = name + self.ordinal = ordinal + self.parameters = [] + + def AddParameter(self, name, kind, ordinal = None, default = None): + parameter = Parameter(name, kind, ordinal, default) + self.parameters.append(parameter) + return parameter + + +class Interface(object): + def __init__(self, name = None): + self.name = name + self.methods = [] + + def AddMethod(self, name, ordinal = None): + method = Method(name, ordinal) + self.methods.append(method) + return method; + + +class Module(object): + def __init__(self, name = None, namespace = None): + self.name = name + self.namespace = namespace + self.structs = [] + self.interfaces = [] + + def AddInterface(self, name): + interface = Interface(name); + self.interfaces.append(interface) + return interface; + + def AddStruct(self, name): + struct = Struct(name) + self.structs.append(struct) + return struct; diff --git a/mojo/public/bindings/generators/mojom_data.py b/mojo/public/bindings/generators/mojom_data.py new file mode 100644 index 0000000..3593511 --- /dev/null +++ b/mojo/public/bindings/generators/mojom_data.py @@ -0,0 +1,164 @@ +# Copyright (c) 2013 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. + +import mojom + +# mojom_data provides a mechanism to turn mojom Modules to dictionaries and +# back again. This can be used to persist a mojom Module created progromatically +# or to read a dictionary from code or a file. +# Example: +# test_dict = { +# 'name': 'test', +# 'namespace': 'testspace', +# 'structs': [{ +# 'name': 'teststruct', +# 'fields': [ +# {'name': 'testfield1', 'kind': 'i32'}, +# {'name': 'testfield2', 'kind': 'a:i32', 'ordinal': 42}]}], +# 'interfaces': [{ +# 'name': 'Server', +# 'methods': [{ +# 'name': 'Foo', +# 'parameters': [{ +# 'name': 'foo', 'kind': 'i32'}, +# {'name': 'bar', 'kind': 'a:x:teststruct'}], +# 'ordinal': 42}]}] +# } +# test_module = mojom_data.ModuleFromData(test_dict) + + +# Used to create a subclass of str that supports sorting by index, to make +# pretty printing maintain the order. +def istr(index, string): + class IndexedString(str): + def __lt__(self, other): + return self.__index__ < other.__index__ + + istr = IndexedString(string) + istr.__index__ = index + return istr + +def KindToData(kind): + return kind.spec + +def KindFromData(kinds, data): + if kinds.has_key(data): + return kinds[data] + if data.startswith('a:'): + kind = mojom.Array() + kind.kind = KindFromData(kinds, data[2:]) + else: + kind = mojom.Kind() + kind.spec = data + kinds[data] = kind + return kind + +def StructToData(struct): + return { + istr(0, 'name'): struct.name, + istr(1, 'fields'): map(FieldToData, struct.fields) + } + +def StructFromData(kinds, data): + struct = mojom.Struct() + struct.name = data['name'] + struct.spec = 'x:' + struct.name + kinds[struct.spec] = struct + struct.fields = map(lambda field: FieldFromData(kinds, field), data['fields']) + return struct + +def FieldToData(field): + data = { + istr(0, 'name'): field.name, + istr(1, 'kind'): KindToData(field.kind) + } + if field.ordinal != None: + data[istr(2, 'ordinal')] = field.ordinal + if field.default != None: + data[istr(3, 'default')] = field.default + return data + +def FieldFromData(kinds, data): + field = mojom.Field() + field.name = data['name'] + field.kind = KindFromData(kinds, data['kind']) + field.ordinal = data.get('ordinal') + field.default = data.get('default') + return field + +def ParameterToData(parameter): + data = { + istr(0, 'name'): parameter.name, + istr(1, 'kind'): parameter.kind.spec + } + if parameter.ordinal != None: + data[istr(2, 'ordinal')] = parameter.ordinal + if parameter.default != None: + data[istr(3, 'default')] = parameter.default + return data + +def ParameterFromData(kinds, data): + parameter = mojom.Parameter() + parameter.name = data['name'] + parameter.kind = KindFromData(kinds, data['kind']) + parameter.ordinal = data.get('ordinal') + parameter.default = data.get('default') + return parameter + +def MethodToData(method): + data = { + istr(0, 'name'): method.name, + istr(1, 'parameters'): map(ParameterToData, method.parameters) + } + if method.ordinal != None: + data[istr(2, 'ordinal')] = method.ordinal + return data + +def MethodFromData(kinds, data): + method = mojom.Method() + method.name = data['name'] + method.ordinal = data.get('ordinal') + method.default = data.get('default') + method.parameters = map( + lambda parameter: ParameterFromData(kinds, parameter), data['parameters']) + return method + +def InterfaceToData(interface): + return { + istr(0, 'name'): interface.name, + istr(1, 'methods'): map(MethodToData, interface.methods) + } + +def InterfaceFromData(kinds, data): + interface = mojom.Interface() + interface.name = data['name'] + interface.methods = map( + lambda method: MethodFromData(kinds, method), data['methods']) + return interface + +def ModuleToData(module): + return { + istr(0, 'name'): module.name, + istr(1, 'namespace'): module.namespace, + istr(2, 'structs'): map(StructToData, module.structs), + istr(3, 'interfaces'): map(InterfaceToData, module.interfaces) + } + +def ModuleFromData(data): + kinds = {} + for kind in mojom.PRIMITIVES: + kinds[kind.spec] = kind + + module = mojom.Module() + module.name = data['name'] + module.namespace = data['namespace'] + module.structs = map( + lambda struct: StructFromData(kinds, struct), data['structs']) + module.interfaces = map( + lambda interface: InterfaceFromData(kinds, interface), data['interfaces']) + return module + + + + diff --git a/mojo/public/bindings/generators/mojom_data_tests.py b/mojo/public/bindings/generators/mojom_data_tests.py new file mode 100644 index 0000000..c891bd4 --- /dev/null +++ b/mojo/public/bindings/generators/mojom_data_tests.py @@ -0,0 +1,84 @@ +# Copyright (c) 2013 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. + +import mojom_data +import mojom_test +import sys + +EXPECT_EQ = mojom_test.EXPECT_EQ +EXPECT_TRUE = mojom_test.EXPECT_TRUE +RunTest = mojom_test.RunTest + + +def DeepEquals(d1, d2): + if d1 == d2: + return True + if d2.__class__ != d2.__class__: + return False + if isinstance(d1, dict): + if set(d1.keys()) != set(d2.keys()): + return False + for key in d1.keys(): + if not DeepEquals(d1[key], d2[key]): + return False + return True + if isinstance(d1, (list, tuple)): + if len(d1) != len(d2): + return False + for i in range(len(d1)): + if not DeepEquals(d1[i], d2[i]): + return False + return True + return False + + +test_dict = { + 'name': 'test', + 'namespace': 'testspace', + 'structs': [{ + 'name': 'teststruct', + 'fields': [ + {'name': 'testfield1', 'kind': 'i32'}, + {'name': 'testfield2', 'kind': 'a:i32', 'ordinal': 42}]}], + 'interfaces': [{ + 'name': 'Server', + 'methods': [{ + 'name': 'Foo', + 'parameters': [ + {'name': 'foo', 'kind': 'i32'}, + {'name': 'bar', 'kind': 'a:x:teststruct'}], + 'ordinal': 42}]}] +} + + +def TestRead(): + module = mojom_data.ModuleFromData(test_dict) + return mojom_test.TestTestModule(module) + + +def TestWrite(): + module = mojom_test.BuildTestModule() + d = mojom_data.ModuleToData(module) + return EXPECT_TRUE(DeepEquals(test_dict, d)) + + +def TestWriteRead(): + module1 = mojom_test.BuildTestModule() + + dict1 = mojom_data.ModuleToData(module1) + module2 = mojom_data.ModuleFromData(dict1) + return EXPECT_TRUE(mojom_test.ModulesAreEqual(module1, module2)) + + +def Main(args): + errors = 0 + errors += RunTest(TestWriteRead) + errors += RunTest(TestRead) + errors += RunTest(TestWrite) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/mojo/public/bindings/generators/mojom_test.py b/mojo/public/bindings/generators/mojom_test.py new file mode 100755 index 0000000..40d9a33 --- /dev/null +++ b/mojo/public/bindings/generators/mojom_test.py @@ -0,0 +1,193 @@ +# Copyright (c) 2013 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. + +import mojom +import sys +import traceback + +# Support for writing mojom test cases. +# RunTest(fn) will execute fn, catching any exceptions. fn should return +# the number of errors that are encountered. +# +# EXPECT_EQ(a, b) and EXPECT_TRUE(b) will print error information if the +# expectations are not true and return a non zero value. This allows test cases +# to be written like this +# +# def Foo(): +# errors = 0 +# errors += EXPECT_EQ('test', test()) +# ... +# return errors +# +# RunTest(foo) + +def FieldsAreEqual(field1, field2): + if field1 == field2: + return True + return field1.name == field2.name and \ + KindsAreEqual(field1.kind, field2.kind) and \ + field1.ordinal == field2.ordinal and \ + field1.default == field2.default + + +def KindsAreEqual(kind1, kind2): + if kind1 == kind2: + return True + if kind1.__class__ != kind2.__class__ or kind1.spec != kind2.spec: + return False + if kind1.__class__ == mojom.Kind: + return kind1.spec == kind2.spec + if kind1.__class__ == mojom.Struct: + if kind1.name != kind2.name or \ + kind1.spec != kind2.spec or \ + len(kind1.fields) != len(kind2.fields): + return False + for i in range(len(kind1.fields)): + if not FieldsAreEqual(kind1.fields[i], kind2.fields[i]): + return False + return True + if kind1.__class__ == mojom.Array: + return KindsAreEqual(kind1.kind, kind2.kind) + print 'Unknown Kind class: ', kind1.__class__.__name__ + return False + + +def ParametersAreEqual(parameter1, parameter2): + if parameter1 == parameter2: + return True + return parameter1.name == parameter2.name and \ + parameter1.ordinal == parameter2.ordinal and \ + parameter1.default == parameter2.default and \ + KindsAreEqual(parameter1.kind, parameter2.kind) + + +def MethodsAreEqual(method1, method2): + if method1 == method2: + return True + if method1.name != method2.name or \ + method1.ordinal != method2.ordinal or \ + len(method1.parameters) != len(method2.parameters): + return False + for i in range(len(method1.parameters)): + if not ParametersAreEqual(method1.parameters[i], method2.parameters[i]): + return False + return True + + +def InterfacesAreEqual(interface1, interface2): + if interface1 == interface2: + return True + if interface1.name != interface2.name or \ + len(interface1.methods) != len(interface2.methods): + return False + for i in range(len(interface1.methods)): + if not MethodsAreEqual(interface1.methods[i], interface2.methods[i]): + return False + return True + + +def ModulesAreEqual(module1, module2): + if module1 == module2: + return True + if module1.name != module2.name or \ + module1.namespace != module2.namespace or \ + len(module1.structs) != len(module2.structs) or \ + len(module1.interfaces) != len(module2.interfaces): + return False + for i in range(len(module1.structs)): + if not KindsAreEqual(module1.structs[i], module2.structs[i]): + return False + for i in range(len(module1.interfaces)): + if not InterfacesAreEqual(module1.interfaces[i], module2.interfaces[i]): + return False + return True + + +# Builds and returns a Module suitable for testing/ +def BuildTestModule(): + module = mojom.Module('test', 'testspace') + struct = module.AddStruct('teststruct') + struct.AddField('testfield1', mojom.INT32) + struct.AddField('testfield2', mojom.Array(mojom.INT32), 42) + + interface = module.AddInterface('Server') + method = interface.AddMethod('Foo', 42) + method.AddParameter('foo', mojom.INT32) + method.AddParameter('bar', mojom.Array(struct)) + + return module + + +# Tests if |module| is as built by BuildTestModule(). Returns the number of +# errors +def TestTestModule(module): + errors = 0 + + errors += EXPECT_EQ('test', module.name) + errors += EXPECT_EQ('testspace', module.namespace) + errors += EXPECT_EQ(1, len(module.structs)) + errors += EXPECT_EQ('teststruct', module.structs[0].name) + errors += EXPECT_EQ(2, len(module.structs[0].fields)) + errors += EXPECT_EQ('testfield1', module.structs[0].fields[0].name) + errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[0].kind) + errors += EXPECT_EQ('testfield2', module.structs[0].fields[1].name) + errors += EXPECT_EQ(mojom.Array, module.structs[0].fields[1].kind.__class__) + errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[1].kind.kind) + + errors += EXPECT_EQ(1, len(module.interfaces)) + errors += EXPECT_EQ('Server', module.interfaces[0].name) + errors += EXPECT_EQ(1, len(module.interfaces[0].methods)) + errors += EXPECT_EQ('Foo', module.interfaces[0].methods[0].name) + errors += EXPECT_EQ(2, len(module.interfaces[0].methods[0].parameters)) + errors += EXPECT_EQ('foo', module.interfaces[0].methods[0].parameters[0].name) + errors += EXPECT_EQ(mojom.INT32, + module.interfaces[0].methods[0].parameters[0].kind) + errors += EXPECT_EQ('bar', module.interfaces[0].methods[0].parameters[1].name) + errors += EXPECT_EQ( + mojom.Array, + module.interfaces[0].methods[0].parameters[1].kind.__class__) + errors += EXPECT_EQ( + module.structs[0], + module.interfaces[0].methods[0].parameters[1].kind.kind) + return errors + + +def PrintFailure(string): + stack = traceback.extract_stack() + frame = stack[len(stack)-3] + sys.stderr.write("ERROR at %s:%d, %s\n" % (frame[0], frame[1], string)) + print "Traceback:" + for line in traceback.format_list(stack[:len(stack)-2]): + sys.stderr.write(line) + + +def EXPECT_EQ(a, b): + if a != b: + PrintFailure("%s != %s" % (a, b)) + return 1 + return 0 + + +def EXPECT_TRUE(a): + if not a: + PrintFailure('Expecting True') + return 1 + return 0 + + +def RunTest(fn): + sys.stdout.write('Running %s...' % fn.__name__) + success = True; + try: + errors = fn() + except: + traceback.print_exc(sys.stderr) + errors = 1 + if errors == 0: + sys.stdout.write('OK\n') + elif errors == 1: + sys.stdout.write('1 ERROR\n') + else: + sys.stdout.write('%d ERRORS\n' % errors) + return errors diff --git a/mojo/public/bindings/generators/mojom_tests.py b/mojo/public/bindings/generators/mojom_tests.py new file mode 100755 index 0000000..a5a5ed0 --- /dev/null +++ b/mojo/public/bindings/generators/mojom_tests.py @@ -0,0 +1,33 @@ +# Copyright (c) 2013 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. + +import mojom_test +import sys + +EXPECT_EQ = mojom_test.EXPECT_EQ +EXPECT_TRUE = mojom_test.EXPECT_TRUE +RunTest = mojom_test.RunTest +ModulesAreEqual = mojom_test.ModulesAreEqual +BuildTestModule = mojom_test.BuildTestModule +TestTestModule = mojom_test.TestTestModule + + +def BuildAndTestModule(): + return TestTestModule(BuildTestModule()) + + +def TestModulesEqual(): + return EXPECT_TRUE(ModulesAreEqual(BuildTestModule(), BuildTestModule())) + + +def Main(args): + errors = 0 + errors += RunTest(BuildAndTestModule) + errors += RunTest(TestModulesEqual) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/mojo/public/bindings/generators/run_mojom_tests.py b/mojo/public/bindings/generators/run_mojom_tests.py new file mode 100755 index 0000000..ad41183 --- /dev/null +++ b/mojo/public/bindings/generators/run_mojom_tests.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# Copyright (c) 2013 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. + +""" Test runner for Mojom """ + +import subprocess +import sys + +def TestMojom(testname, args): + print '\nRunning unit tests for %s.' % testname + try: + args = [sys.executable, testname] + args + subprocess.check_call(args, stdout=sys.stdout) + print 'Succeeded' + return 0 + except subprocess.CalledProcessError as err: + print 'Failed with %s.' % str(err) + return 1 + + +def main(args): + errors = 0 + errors += TestMojom('mojom_tests.py', ['--test']) + errors += TestMojom('mojom_data_tests.py', ['--test']) + + if errors: + print '\nFailed tests.' + return errors + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) + |