# 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. import imp import os.path import sys import unittest from mojom.parse import ast from mojom.parse import lexer from mojom.parse import parser class ParserTest(unittest.TestCase): """Tests |parser.Parse()|.""" def testTrivialValidSource(self): """Tests a trivial, but valid, .mojom source.""" source = """\ // This is a comment. module my_module; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), []) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testSourceWithCrLfs(self): """Tests a .mojom source with CR-LFs instead of LFs.""" source = "// This is a comment.\r\n\r\nmodule my_module;\r\n" expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), []) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testUnexpectedEOF(self): """Tests a "truncated" .mojom source.""" source = """\ // This is a comment. module my_module """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom: Error: Unexpected end of file$"): parser.Parse(source, "my_file.mojom") def testCommentLineNumbers(self): """Tests that line numbers are correctly tracked when comments are present.""" source1 = """\ // Isolated C++-style comments. // Foo. asdf1 """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'asdf1':\n *asdf1$"): parser.Parse(source1, "my_file.mojom") source2 = """\ // Consecutive C++-style comments. // Foo. // Bar. struct Yada { // Baz. // Quux. int32 x; }; asdf2 """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:10: Error: Unexpected 'asdf2':\n *asdf2$"): parser.Parse(source2, "my_file.mojom") source3 = """\ /* Single-line C-style comments. */ /* Foobar. */ /* Baz. */ asdf3 """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:5: Error: Unexpected 'asdf3':\n *asdf3$"): parser.Parse(source3, "my_file.mojom") source4 = """\ /* Multi-line C-style comments. */ /* Foo. Bar. */ /* Baz Quux. */ asdf4 """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:10: Error: Unexpected 'asdf4':\n *asdf4$"): parser.Parse(source4, "my_file.mojom") def testSimpleStruct(self): """Tests a simple .mojom source that just defines a struct.""" source = """\ module my_module; struct MyStruct { int32 a; double b; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.StructField('a', None, None, 'int32', None), ast.StructField('b', None, None, 'double', None) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testSimpleStructWithoutModule(self): """Tests a simple struct without an explict module statement.""" source = """\ struct MyStruct { int32 a; double b; }; """ expected = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.StructField('a', None, None, 'int32', None), ast.StructField('b', None, None, 'double', None) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testValidStructDefinitions(self): """Tests all types of definitions that can occur in a struct.""" source = """\ struct MyStruct { enum MyEnum { VALUE }; const double kMyConst = 1.23; int32 a; SomeOtherStruct b; // Invalidity detected at another stage. }; """ expected = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.Enum('MyEnum', None, ast.EnumValueList(ast.EnumValue('VALUE', None, None))), ast.Const('kMyConst', None, 'double', '1.23'), ast.StructField('a', None, None, 'int32', None), ast.StructField('b', None, None, 'SomeOtherStruct', None) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testInvalidStructDefinitions(self): """Tests that definitions that aren't allowed in a struct are correctly detected.""" source1 = """\ struct MyStruct { MyMethod(int32 a); }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '\(':\n" r" *MyMethod\(int32 a\);$"): parser.Parse(source1, "my_file.mojom") source2 = """\ struct MyStruct { struct MyInnerStruct { int32 a; }; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" r" *struct MyInnerStruct {$"): parser.Parse(source2, "my_file.mojom") source3 = """\ struct MyStruct { interface MyInterface { MyMethod(int32 a); }; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'interface':\n" r" *interface MyInterface {$"): parser.Parse(source3, "my_file.mojom") def testMissingModuleName(self): """Tests an (invalid) .mojom with a missing module name.""" source1 = """\ // Missing module name. module ; struct MyStruct { int32 a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected ';':\n *module ;$"): parser.Parse(source1, "my_file.mojom") # Another similar case, but make sure that line-number tracking/reporting # is correct. source2 = """\ module // This line intentionally left unblank. struct MyStruct { int32 a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'struct':\n" r" *struct MyStruct {$"): parser.Parse(source2, "my_file.mojom") def testMultipleModuleStatements(self): """Tests an (invalid) .mojom with multiple module statements.""" source = """\ module foo; module bar; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Multiple \"module\" statements not " r"allowed:\n *module bar;$"): parser.Parse(source, "my_file.mojom") def testModuleStatementAfterImport(self): """Tests an (invalid) .mojom with a module statement after an import.""" source = """\ import "foo.mojom"; module foo; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: \"module\" statements must precede imports " r"and definitions:\n *module foo;$"): parser.Parse(source, "my_file.mojom") def testModuleStatementAfterDefinition(self): """Tests an (invalid) .mojom with a module statement after a definition.""" source = """\ struct MyStruct { int32 a; }; module foo; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: \"module\" statements must precede imports " r"and definitions:\n *module foo;$"): parser.Parse(source, "my_file.mojom") def testImportStatementAfterDefinition(self): """Tests an (invalid) .mojom with an import statement after a definition.""" source = """\ struct MyStruct { int32 a; }; import "foo.mojom"; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: \"import\" statements must precede " r"definitions:\n *import \"foo.mojom\";$"): parser.Parse(source, "my_file.mojom") def testEnums(self): """Tests that enum statements are correctly parsed.""" source = """\ module my_module; enum MyEnum1 { VALUE1, VALUE2 }; // No trailing comma. enum MyEnum2 { VALUE1 = -1, VALUE2 = 0, VALUE3 = + 987, // Check that space is allowed. VALUE4 = 0xAF12, VALUE5 = -0x09bcd, VALUE6 = VALUE5, VALUE7, // Leave trailing comma. }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Enum( 'MyEnum1', None, ast.EnumValueList([ ast.EnumValue('VALUE1', None, None), ast.EnumValue('VALUE2', None, None) ])), ast.Enum( 'MyEnum2', None, ast.EnumValueList([ ast.EnumValue('VALUE1', None, '-1'), ast.EnumValue('VALUE2', None, '0'), ast.EnumValue('VALUE3', None, '+987'), ast.EnumValue('VALUE4', None, '0xAF12'), ast.EnumValue('VALUE5', None, '-0x09bcd'), ast.EnumValue('VALUE6', None, ('IDENTIFIER', 'VALUE5')), ast.EnumValue('VALUE7', None, None) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testInvalidEnumInitializers(self): """Tests that invalid enum initializers are correctly detected.""" # Floating point value. source2 = "enum MyEnum { VALUE = 0.123 };" with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected '0\.123':\n" r"enum MyEnum { VALUE = 0\.123 };$"): parser.Parse(source2, "my_file.mojom") # Boolean value. source2 = "enum MyEnum { VALUE = true };" with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected 'true':\n" r"enum MyEnum { VALUE = true };$"): parser.Parse(source2, "my_file.mojom") def testConsts(self): """Tests some constants and struct members initialized with them.""" source = """\ module my_module; struct MyStruct { const int8 kNumber = -1; int8 number@0 = kNumber; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.Const('kNumber', None, 'int8', '-1'), ast.StructField('number', None, ast.Ordinal(0), 'int8', ('IDENTIFIER', 'kNumber')) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testNoConditionals(self): """Tests that ?: is not allowed.""" source = """\ module my_module; enum MyEnum { MY_ENUM_1 = 1 ? 2 : 3 }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected '\?':\n" r" *MY_ENUM_1 = 1 \? 2 : 3$"): parser.Parse(source, "my_file.mojom") def testSimpleOrdinals(self): """Tests that (valid) ordinal values are scanned correctly.""" source = """\ module my_module; // This isn't actually valid .mojom, but the problem (missing ordinals) // should be handled at a different level. struct MyStruct { int32 a0@0; int32 a1@1; int32 a2@2; int32 a9@9; int32 a10 @10; int32 a11 @11; int32 a29 @29; int32 a1234567890 @1234567890; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.StructField('a0', None, ast.Ordinal(0), 'int32', None), ast.StructField('a1', None, ast.Ordinal(1), 'int32', None), ast.StructField('a2', None, ast.Ordinal(2), 'int32', None), ast.StructField('a9', None, ast.Ordinal(9), 'int32', None), ast.StructField('a10', None, ast.Ordinal(10), 'int32', None), ast.StructField('a11', None, ast.Ordinal(11), 'int32', None), ast.StructField('a29', None, ast.Ordinal(29), 'int32', None), ast.StructField('a1234567890', None, ast.Ordinal(1234567890), 'int32', None) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testInvalidOrdinals(self): """Tests that (lexically) invalid ordinals are correctly detected.""" source1 = """\ module my_module; struct MyStruct { int32 a_missing@; }; """ with self.assertRaisesRegexp( lexer.LexError, r"^my_file\.mojom:4: Error: Missing ordinal value$"): parser.Parse(source1, "my_file.mojom") source2 = """\ module my_module; struct MyStruct { int32 a_octal@01; }; """ with self.assertRaisesRegexp( lexer.LexError, r"^my_file\.mojom:4: Error: " r"Octal and hexadecimal ordinal values not allowed$"): parser.Parse(source2, "my_file.mojom") source3 = """\ module my_module; struct MyStruct { int32 a_invalid_octal@08; }; """ with self.assertRaisesRegexp( lexer.LexError, r"^my_file\.mojom:1: Error: " r"Octal and hexadecimal ordinal values not allowed$"): parser.Parse(source3, "my_file.mojom") source4 = "module my_module; struct MyStruct { int32 a_hex@0x1aB9; };" with self.assertRaisesRegexp( lexer.LexError, r"^my_file\.mojom:1: Error: " r"Octal and hexadecimal ordinal values not allowed$"): parser.Parse(source4, "my_file.mojom") source5 = "module my_module; struct MyStruct { int32 a_hex@0X0; };" with self.assertRaisesRegexp( lexer.LexError, r"^my_file\.mojom:1: Error: " r"Octal and hexadecimal ordinal values not allowed$"): parser.Parse(source5, "my_file.mojom") source6 = """\ struct MyStruct { int32 a_too_big@999999999999; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: " r"Ordinal value 999999999999 too large:\n" r" *int32 a_too_big@999999999999;$"): parser.Parse(source6, "my_file.mojom") def testNestedNamespace(self): """Tests that "nested" namespaces work.""" source = """\ module my.mod; struct MyStruct { int32 a; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my.mod'), None), ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody(ast.StructField('a', None, None, 'int32', None))) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testValidHandleTypes(self): """Tests (valid) handle types.""" source = """\ struct MyStruct { handle a; handle b; handle c; handle < message_pipe > d; handle < shared_buffer > e; handle f; }; """ expected = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.StructField('a', None, None, 'handle', None), ast.StructField('b', None, None, 'handle', None), ast.StructField('c', None, None, 'handle', None), ast.StructField('d', None, None, 'handle', None), ast.StructField('e', None, None, 'handle', None), ast.StructField('f', None, None, 'handle', None) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testInvalidHandleType(self): """Tests an invalid (unknown) handle type.""" source = """\ struct MyStruct { handle foo; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: " r"Invalid handle type 'wtf_is_this':\n" r" *handle foo;$"): parser.Parse(source, "my_file.mojom") def testValidDefaultValues(self): """Tests default values that are valid (to the parser).""" source = """\ struct MyStruct { int16 a0 = 0; uint16 a1 = 0x0; uint16 a2 = 0x00; uint16 a3 = 0x01; uint16 a4 = 0xcd; int32 a5 = 12345; int64 a6 = -12345; int64 a7 = +12345; uint32 a8 = 0x12cd3; uint32 a9 = -0x12cD3; uint32 a10 = +0x12CD3; bool a11 = true; bool a12 = false; float a13 = 1.2345; float a14 = -1.2345; float a15 = +1.2345; float a16 = 123.; float a17 = .123; double a18 = 1.23E10; double a19 = 1.E-10; double a20 = .5E+10; double a21 = -1.23E10; double a22 = +.123E10; }; """ expected = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.StructField('a0', None, None, 'int16', '0'), ast.StructField('a1', None, None, 'uint16', '0x0'), ast.StructField('a2', None, None, 'uint16', '0x00'), ast.StructField('a3', None, None, 'uint16', '0x01'), ast.StructField('a4', None, None, 'uint16', '0xcd'), ast.StructField('a5', None, None, 'int32', '12345'), ast.StructField('a6', None, None, 'int64', '-12345'), ast.StructField('a7', None, None, 'int64', '+12345'), ast.StructField('a8', None, None, 'uint32', '0x12cd3'), ast.StructField('a9', None, None, 'uint32', '-0x12cD3'), ast.StructField('a10', None, None, 'uint32', '+0x12CD3'), ast.StructField('a11', None, None, 'bool', 'true'), ast.StructField('a12', None, None, 'bool', 'false'), ast.StructField('a13', None, None, 'float', '1.2345'), ast.StructField('a14', None, None, 'float', '-1.2345'), ast.StructField('a15', None, None, 'float', '+1.2345'), ast.StructField('a16', None, None, 'float', '123.'), ast.StructField('a17', None, None, 'float', '.123'), ast.StructField('a18', None, None, 'double', '1.23E10'), ast.StructField('a19', None, None, 'double', '1.E-10'), ast.StructField('a20', None, None, 'double', '.5E+10'), ast.StructField('a21', None, None, 'double', '-1.23E10'), ast.StructField('a22', None, None, 'double', '+.123E10') ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testValidFixedSizeArray(self): """Tests parsing a fixed size array.""" source = """\ struct MyStruct { array normal_array; array fixed_size_array_one_entry; array fixed_size_array_ten_entries; array>, 2> nested_arrays; }; """ expected = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.StructField('normal_array', None, None, 'int32[]', None), ast.StructField('fixed_size_array_one_entry', None, None, 'int32[1]', None), ast.StructField('fixed_size_array_ten_entries', None, None, 'int32[10]', None), ast.StructField('nested_arrays', None, None, 'int32[1][][2]', None) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testValidNestedArray(self): """Tests parsing a nested array.""" source = "struct MyStruct { array> nested_array; };" expected = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody( ast.StructField('nested_array', None, None, 'int32[][]', None))) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testInvalidFixedArraySize(self): """Tests that invalid fixed array bounds are correctly detected.""" source1 = """\ struct MyStruct { array zero_size_array; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Fixed array size 0 invalid:\n" r" *array zero_size_array;$"): parser.Parse(source1, "my_file.mojom") source2 = """\ struct MyStruct { array too_big_array; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Fixed array size 999999999999 invalid:\n" r" *array too_big_array;$"): parser.Parse(source2, "my_file.mojom") source3 = """\ struct MyStruct { array not_a_number; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'abcdefg':\n" r" *array not_a_number;"): parser.Parse(source3, "my_file.mojom") def testValidAssociativeArrays(self): """Tests that we can parse valid associative array structures.""" source1 = "struct MyStruct { map data; };" expected1 = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody( [ast.StructField('data', None, None, 'uint8{string}', None)])) ]) self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) source2 = "interface MyInterface { MyMethod(map a); };" expected2 = ast.Mojom(None, ast.ImportList(), [ ast.Interface( 'MyInterface', None, ast.InterfaceBody( ast.Method( 'MyMethod', None, None, ast.ParameterList( ast.Parameter('a', None, None, 'uint8{string}')), None))) ]) self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) source3 = "struct MyStruct { map> data; };" expected3 = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody( [ast.StructField('data', None, None, 'uint8[]{string}', None)])) ]) self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) def testValidMethod(self): """Tests parsing method declarations.""" source1 = "interface MyInterface { MyMethod(int32 a); };" expected1 = ast.Mojom(None, ast.ImportList(), [ ast.Interface( 'MyInterface', None, ast.InterfaceBody( ast.Method( 'MyMethod', None, None, ast.ParameterList(ast.Parameter('a', None, None, 'int32')), None))) ]) self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) source2 = """\ interface MyInterface { MyMethod1@0(int32 a@0, int64 b@1); MyMethod2@1() => (); }; """ expected2 = ast.Mojom(None, ast.ImportList(), [ ast.Interface( 'MyInterface', None, ast.InterfaceBody([ ast.Method( 'MyMethod1', None, ast.Ordinal(0), ast.ParameterList([ ast.Parameter('a', None, ast.Ordinal(0), 'int32'), ast.Parameter('b', None, ast.Ordinal(1), 'int64') ]), None), ast.Method('MyMethod2', None, ast.Ordinal(1), ast.ParameterList(), ast.ParameterList()) ])) ]) self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) source3 = """\ interface MyInterface { MyMethod(string a) => (int32 a, bool b); }; """ expected3 = ast.Mojom(None, ast.ImportList(), [ ast.Interface( 'MyInterface', None, ast.InterfaceBody( ast.Method( 'MyMethod', None, None, ast.ParameterList(ast.Parameter('a', None, None, 'string')), ast.ParameterList([ ast.Parameter('a', None, None, 'int32'), ast.Parameter('b', None, None, 'bool') ])))) ]) self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) def testInvalidMethods(self): """Tests that invalid method declarations are correctly detected.""" # No trailing commas. source1 = """\ interface MyInterface { MyMethod(string a,); }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '\)':\n" r" *MyMethod\(string a,\);$"): parser.Parse(source1, "my_file.mojom") # No leading commas. source2 = """\ interface MyInterface { MyMethod(, string a); }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected ',':\n" r" *MyMethod\(, string a\);$"): parser.Parse(source2, "my_file.mojom") def testValidInterfaceDefinitions(self): """Tests all types of definitions that can occur in an interface.""" source = """\ interface MyInterface { enum MyEnum { VALUE }; const int32 kMyConst = 123; MyMethod(int32 x) => (MyEnum y); }; """ expected = ast.Mojom(None, ast.ImportList(), [ ast.Interface( 'MyInterface', None, ast.InterfaceBody([ ast.Enum('MyEnum', None, ast.EnumValueList(ast.EnumValue('VALUE', None, None))), ast.Const('kMyConst', None, 'int32', '123'), ast.Method( 'MyMethod', None, None, ast.ParameterList(ast.Parameter('x', None, None, 'int32')), ast.ParameterList(ast.Parameter('y', None, None, 'MyEnum'))) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testInvalidInterfaceDefinitions(self): """Tests that definitions that aren't allowed in an interface are correctly detected.""" source1 = """\ interface MyInterface { struct MyStruct { int32 a; }; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" r" *struct MyStruct {$"): parser.Parse(source1, "my_file.mojom") source2 = """\ interface MyInterface { interface MyInnerInterface { MyMethod(int32 x); }; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'interface':\n" r" *interface MyInnerInterface {$"): parser.Parse(source2, "my_file.mojom") source3 = """\ interface MyInterface { int32 my_field; }; """ # The parser thinks that "int32" is a plausible name for a method, so it's # "my_field" that gives it away. with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'my_field':\n" r" *int32 my_field;$"): parser.Parse(source3, "my_file.mojom") def testValidAttributes(self): """Tests parsing attributes (and attribute lists).""" # Note: We use structs because they have (optional) attribute lists. # Empty attribute list. source1 = "[] struct MyStruct {};" expected1 = ast.Mojom( None, ast.ImportList(), [ast.Struct('MyStruct', ast.AttributeList(), ast.StructBody())]) self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) # One-element attribute list, with name value. source2 = "[MyAttribute=MyName] struct MyStruct {};" expected2 = ast.Mojom(None, ast.ImportList(), [ ast.Struct('MyStruct', ast.AttributeList(ast.Attribute("MyAttribute", "MyName")), ast.StructBody()) ]) self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) # Two-element attribute list, with one string value and one integer value. source3 = "[MyAttribute1 = \"hello\", MyAttribute2 = 5] struct MyStruct {};" expected3 = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', ast.AttributeList([ ast.Attribute("MyAttribute1", "hello"), ast.Attribute("MyAttribute2", 5) ]), ast.StructBody()) ]) self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) # Various places that attribute list is allowed. source4 = """\ [Attr0=0] module my_module; [Attr1=1] import "my_import"; [Attr2=2] struct MyStruct { [Attr3=3] int32 a; }; [Attr4=4] union MyUnion { [Attr5=5] int32 a; }; [Attr6=6] enum MyEnum { [Attr7=7] a }; [Attr8=8] interface MyInterface { [Attr9=9] MyMethod([Attr10=10] int32 a) => ([Attr11=11] bool b); }; [Attr12=12] const double kMyConst = 1.23; """ expected4 = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), ast.AttributeList([ast.Attribute("Attr0", 0)])), ast.ImportList( ast.Import( ast.AttributeList([ast.Attribute("Attr1", 1)]), "my_import")), [ ast.Struct( 'MyStruct', ast.AttributeList(ast.Attribute("Attr2", 2)), ast.StructBody( ast.StructField( 'a', ast.AttributeList([ast.Attribute("Attr3", 3)]), None, 'int32', None))), ast.Union( 'MyUnion', ast.AttributeList(ast.Attribute("Attr4", 4)), ast.UnionBody( ast.UnionField( 'a', ast.AttributeList([ast.Attribute("Attr5", 5)]), None, 'int32'))), ast.Enum( 'MyEnum', ast.AttributeList(ast.Attribute("Attr6", 6)), ast.EnumValueList( ast.EnumValue( 'VALUE', ast.AttributeList([ast.Attribute("Attr7", 7)]), None))), ast.Interface( 'MyInterface', ast.AttributeList(ast.Attribute("Attr8", 8)), ast.InterfaceBody( ast.Method( 'MyMethod', ast.AttributeList( ast.Attribute("Attr9", 9)), None, ast.ParameterList( ast.Parameter( 'a', ast.AttributeList([ast.Attribute("Attr10", 10) ]), None, 'int32')), ast.ParameterList( ast.Parameter( 'b', ast.AttributeList([ast.Attribute("Attr11", 11) ]), None, 'bool'))))), ast.Const('kMyConst', ast.AttributeList( ast.Attribute("Attr12", 12)), 'double', '1.23') ]) self.assertEquals(parser.Parse(source4, "my_file.mojom"), expected4) # TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()| # literal (non-name) values, which is extremely dubious.) def testInvalidAttributes(self): """Tests that invalid attributes and attribute lists are correctly detected.""" # Trailing commas not allowed. source1 = "[MyAttribute=MyName,] struct MyStruct {};" with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected '\]':\n" r"\[MyAttribute=MyName,\] struct MyStruct {};$"): parser.Parse(source1, "my_file.mojom") # Missing value. source2 = "[MyAttribute=] struct MyStruct {};" with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected '\]':\n" r"\[MyAttribute=\] struct MyStruct {};$"): parser.Parse(source2, "my_file.mojom") # Missing key. source3 = "[=MyName] struct MyStruct {};" with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected '=':\n" r"\[=MyName\] struct MyStruct {};$"): parser.Parse(source3, "my_file.mojom") def testValidImports(self): """Tests parsing import statements.""" # One import (no module statement). source1 = "import \"somedir/my.mojom\";" expected1 = ast.Mojom(None, ast.ImportList(ast.Import(None, "somedir/my.mojom")), []) self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) # Two imports (no module statement). source2 = """\ import "somedir/my1.mojom"; import "somedir/my2.mojom"; """ expected2 = ast.Mojom( None, ast.ImportList([ ast.Import(None, "somedir/my1.mojom"), ast.Import(None, "somedir/my2.mojom") ]), []) self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) # Imports with module statement. source3 = """\ module my_module; import "somedir/my1.mojom"; import "somedir/my2.mojom"; """ expected3 = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList([ ast.Import(None, "somedir/my1.mojom"), ast.Import(None, "somedir/my2.mojom") ]), []) self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) def testInvalidImports(self): """Tests that invalid import statements are correctly detected.""" source1 = """\ // Make the error occur on line 2. import invalid """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'invalid':\n" r" *import invalid$"): parser.Parse(source1, "my_file.mojom") source2 = """\ import // Missing string. struct MyStruct { int32 a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" r" *struct MyStruct {$"): parser.Parse(source2, "my_file.mojom") source3 = """\ import "foo.mojom" // Missing semicolon. struct MyStruct { int32 a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" r" *struct MyStruct {$"): parser.Parse(source3, "my_file.mojom") def testValidNullableTypes(self): """Tests parsing nullable types.""" source = """\ struct MyStruct { int32? a; // This is actually invalid, but handled at a different // level. string? b; array ? c; array ? d; array?>? e; array? f; array? g; some_struct? h; handle? i; handle? j; handle? k; handle? l; handle? m; some_interface&? n; handle? o; }; """ expected = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.StructField('a', None, None, 'int32?', None), ast.StructField('b', None, None, 'string?', None), ast.StructField('c', None, None, 'int32[]?', None), ast.StructField('d', None, None, 'string?[]?', None), ast.StructField('e', None, None, 'int32[]?[]?', None), ast.StructField('f', None, None, 'int32[1]?', None), ast.StructField('g', None, None, 'string?[1]?', None), ast.StructField('h', None, None, 'some_struct?', None), ast.StructField('i', None, None, 'handle?', None), ast.StructField('j', None, None, 'handle?', None), ast.StructField('k', None, None, 'handle?', None), ast.StructField('l', None, None, 'handle?', None), ast.StructField('m', None, None, 'handle?', None), ast.StructField('n', None, None, 'some_interface&?', None), ast.StructField('o', None, None, 'handle?', None) ])) ]) self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) def testInvalidNullableTypes(self): """Tests that invalid nullable types are correctly detected.""" source1 = """\ struct MyStruct { string?? a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '\?':\n" r" *string\?\? a;$"): parser.Parse(source1, "my_file.mojom") source2 = """\ struct MyStruct { handle? a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '<':\n" r" *handle\? a;$"): parser.Parse(source2, "my_file.mojom") source3 = """\ struct MyStruct { some_interface?& a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n" r" *some_interface\?& a;$"): parser.Parse(source3, "my_file.mojom") def testSimpleUnion(self): """Tests a simple .mojom source that just defines a union.""" source = """\ module my_module; union MyUnion { int32 a; double b; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Union( 'MyUnion', None, ast.UnionBody([ ast.UnionField('a', None, None, 'int32'), ast.UnionField('b', None, None, 'double') ])) ]) actual = parser.Parse(source, "my_file.mojom") self.assertEquals(actual, expected) def testUnionWithOrdinals(self): """Test that ordinals are assigned to fields.""" source = """\ module my_module; union MyUnion { int32 a @10; double b @30; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Union( 'MyUnion', None, ast.UnionBody([ ast.UnionField('a', None, ast.Ordinal(10), 'int32'), ast.UnionField('b', None, ast.Ordinal(30), 'double') ])) ]) actual = parser.Parse(source, "my_file.mojom") self.assertEquals(actual, expected) def testUnionWithStructMembers(self): """Test that struct members are accepted.""" source = """\ module my_module; union MyUnion { SomeStruct s; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Union( 'MyUnion', None, ast.UnionBody([ast.UnionField('s', None, None, 'SomeStruct')])) ]) actual = parser.Parse(source, "my_file.mojom") self.assertEquals(actual, expected) def testUnionWithArrayMember(self): """Test that array members are accepted.""" source = """\ module my_module; union MyUnion { array a; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Union( 'MyUnion', None, ast.UnionBody([ast.UnionField('a', None, None, 'int32[]')])) ]) actual = parser.Parse(source, "my_file.mojom") self.assertEquals(actual, expected) def testUnionWithMapMember(self): """Test that map members are accepted.""" source = """\ module my_module; union MyUnion { map m; }; """ expected = ast.Mojom( ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [ ast.Union( 'MyUnion', None, ast.UnionBody( [ast.UnionField('m', None, None, 'string{int32}')])) ]) actual = parser.Parse(source, "my_file.mojom") self.assertEquals(actual, expected) def testUnionDisallowNestedStruct(self): """Tests that structs cannot be nested in unions.""" source = """\ module my_module; union MyUnion { struct MyStruct { int32 a; }; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'struct':\n" r" *struct MyStruct {$"): parser.Parse(source, "my_file.mojom") def testUnionDisallowNestedInterfaces(self): """Tests that interfaces cannot be nested in unions.""" source = """\ module my_module; union MyUnion { interface MyInterface { MyMethod(int32 a); }; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'interface':\n" r" *interface MyInterface {$"): parser.Parse(source, "my_file.mojom") def testUnionDisallowNestedUnion(self): """Tests that unions cannot be nested in unions.""" source = """\ module my_module; union MyUnion { union MyOtherUnion { int32 a; }; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'union':\n" r" *union MyOtherUnion {$"): parser.Parse(source, "my_file.mojom") def testUnionDisallowNestedEnum(self): """Tests that enums cannot be nested in unions.""" source = """\ module my_module; union MyUnion { enum MyEnum { A, }; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'enum':\n" r" *enum MyEnum {$"): parser.Parse(source, "my_file.mojom") def testValidAssociatedKinds(self): """Tests parsing associated interfaces and requests.""" source1 = """\ struct MyStruct { associated MyInterface a; associated MyInterface& b; associated MyInterface? c; associated MyInterface&? d; }; """ expected1 = ast.Mojom(None, ast.ImportList(), [ ast.Struct( 'MyStruct', None, ast.StructBody([ ast.StructField('a', None, None, 'asso', None), ast.StructField('b', None, None, 'asso', None), ast.StructField('c', None, None, 'asso?', None), ast.StructField('d', None, None, 'asso?', None) ])) ]) self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) source2 = """\ interface MyInterface { MyMethod(associated A a) =>(associated B& b); };""" expected2 = ast.Mojom(None, ast.ImportList(), [ ast.Interface( 'MyInterface', None, ast.InterfaceBody( ast.Method( 'MyMethod', None, None, ast.ParameterList( ast.Parameter('a', None, None, 'asso')), ast.ParameterList( ast.Parameter('b', None, None, 'asso'))))) ]) self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) def testInvalidAssociatedKinds(self): """Tests that invalid associated interfaces and requests are correctly detected.""" source1 = """\ struct MyStruct { associated associated SomeInterface a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'associated':\n" r" *associated associated SomeInterface a;$"): parser.Parse(source1, "my_file.mojom") source2 = """\ struct MyStruct { associated handle a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'handle':\n" r" *associated handle a;$"): parser.Parse(source2, "my_file.mojom") source3 = """\ struct MyStruct { associated? MyInterface& a; }; """ with self.assertRaisesRegexp( parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '\?':\n" r" *associated\? MyInterface& a;$"): parser.Parse(source3, "my_file.mojom") if __name__ == "__main__": unittest.main()