blob: 3f4ca871e300a8ffdd00e945762665df48b03f5c [file] [log] [blame]
# 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
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
path = os.path.abspath(__file__)
while True:
path, tail = os.path.split(path)
assert tail
if tail == dirname:
return path
try:
imp.find_module("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
import mojom.parse.ast as ast
import mojom.parse.lexer as lexer
import mojom.parse.parser as 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', '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."""
# No values.
source1 = """\
enum MyEnum {
};
"""
with self.assertRaisesRegexp(
parser.ParseError,
r"^my_file\.mojom:2: Error: Unexpected '}':\n"
r" *};$"):
parser.Parse(source1, "my_file.mojom")
# 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', '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<data_pipe_consumer> b;
handle <data_pipe_producer> c;
handle < message_pipe > d;
handle
< shared_buffer
> e;
};
"""
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<data_pipe_consumer>',
None),
ast.StructField('c', None, None, 'handle<data_pipe_producer>',
None),
ast.StructField('d', None, None, 'handle<message_pipe>', None),
ast.StructField('e', None, None, 'handle<shared_buffer>',
None)]))])
self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
def testInvalidHandleType(self):
"""Tests an invalid (unknown) handle type."""
source = """\
struct MyStruct {
handle<wtf_is_this> foo;
};
"""
with self.assertRaisesRegexp(
parser.ParseError,
r"^my_file\.mojom:2: Error: "
r"Invalid handle type 'wtf_is_this':\n"
r" *handle<wtf_is_this> 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<int32> normal_array;
array<int32, 1> fixed_size_array_one_entry;
array<int32, 10> fixed_size_array_ten_entries;
array<array<array<int32, 1>>, 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<array<int32>> 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<int32, 0> zero_size_array;
};
"""
with self.assertRaisesRegexp(
parser.ParseError,
r"^my_file\.mojom:2: Error: Fixed array size 0 invalid:\n"
r" *array<int32, 0> zero_size_array;$"):
parser.Parse(source1, "my_file.mojom")
source2 = """\
struct MyStruct {
array<int32, 999999999999> too_big_array;
};
"""
with self.assertRaisesRegexp(
parser.ParseError,
r"^my_file\.mojom:2: Error: Fixed array size 999999999999 invalid:\n"
r" *array<int32, 999999999999> too_big_array;$"):
parser.Parse(source2, "my_file.mojom")
source3 = """\
struct MyStruct {
array<int32, abcdefg> not_a_number;
};
"""
with self.assertRaisesRegexp(
parser.ParseError,
r"^my_file\.mojom:2: Error: Unexpected 'abcdefg':\n"
r" *array<int32, abcdefg> 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<string, uint8> 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<string, uint8> 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<string, array<uint8>> 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', '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] struct MyStruct {
[Attr2=2] int32 a;
};
[Attr3=3] union MyUnion {
[Attr4=4] int32 a;
};
[Attr5=5] enum MyEnum {
[Attr6=6] a
};
[Attr7=7] interface MyInterface {
[Attr8=8] MyMethod([Attr9=9] int32 a) => ([Attr10=10] bool b);
};
"""
expected4 = ast.Mojom(
ast.Module(('IDENTIFIER', 'my_module'),
ast.AttributeList([ast.Attribute("Attr0", 0)])),
ast.ImportList(),
[ast.Struct(
'MyStruct',
ast.AttributeList(ast.Attribute("Attr1", 1)),
ast.StructBody(
ast.StructField(
'a', ast.AttributeList([ast.Attribute("Attr2", 2)]),
None, 'int32', None))),
ast.Union(
'MyUnion',
ast.AttributeList(ast.Attribute("Attr3", 3)),
ast.UnionBody(
ast.UnionField(
'a', ast.AttributeList([ast.Attribute("Attr4", 4)]), None,
'int32'))),
ast.Enum(
'MyEnum',
ast.AttributeList(ast.Attribute("Attr5", 5)),
ast.EnumValueList(
ast.EnumValue(
'VALUE', ast.AttributeList([ast.Attribute("Attr6", 6)]),
None))),
ast.Interface(
'MyInterface',
ast.AttributeList(ast.Attribute("Attr7", 7)),
ast.InterfaceBody(
ast.Method(
'MyMethod',
ast.AttributeList(ast.Attribute("Attr8", 8)),
None,
ast.ParameterList(
ast.Parameter(
'a', ast.AttributeList([ast.Attribute("Attr9", 9)]),
None, 'int32')),
ast.ParameterList(
ast.Parameter(
'b',
ast.AttributeList([ast.Attribute("Attr10", 10)]),
None, 'bool')))))])
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("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("somedir/my1.mojom"),
ast.Import("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("somedir/my1.mojom"),
ast.Import("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<int32> ? c;
array<string ? > ? d;
array<array<int32>?>? e;
array<int32, 1>? f;
array<string?, 1>? g;
some_struct? h;
handle? i;
handle<data_pipe_consumer>? j;
handle<data_pipe_producer>? k;
handle<message_pipe>? l;
handle<shared_buffer>? m;
some_interface&? n;
};
"""
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<data_pipe_consumer>?',
None),
ast.StructField('k', None, None,'handle<data_pipe_producer>?',
None),
ast.StructField('l', None, None,'handle<message_pipe>?', None),
ast.StructField('m', None, None,'handle<shared_buffer>?',
None),
ast.StructField('n', None, None,'some_interface&?', 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?<data_pipe_consumer> a;
};
"""
with self.assertRaisesRegexp(
parser.ParseError,
r"^my_file\.mojom:2: Error: Unexpected '<':\n"
r" *handle\?<data_pipe_consumer> 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<int32> 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<int32, string> 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<MyInterface>', None),
ast.StructField('b', None, None,'asso<MyInterface&>', None),
ast.StructField('c', None, None,'asso<MyInterface>?', None),
ast.StructField('d', None, None,'asso<MyInterface&>?',
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<A>')),
ast.ParameterList(
ast.Parameter('b', None, None, 'asso<B&>')))))])
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()