blob: 78b4649627ceaa8ba0cf42a3f34cfe13483e77a6 [file] [log] [blame]
// Copyright 2015 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.
package parser
import (
"fmt"
"mojom/mojom_parser/lexer"
"mojom/mojom_parser/mojom"
"strings"
"testing"
)
// TestSuccessfulParsing contains a series of test cases in which we
// run the parser on a valid mojom input string and compare the resulting
// MojomFile to an expected one.
func TestSuccessfulParsing(t *testing.T) {
type testCase struct {
fileName string
mojomContents string
expectedFile *mojom.MojomFile
}
cases := make([]testCase, 0)
testCaseNum := 0
var expectedFile *mojom.MojomFile
startTestCase := func(moduleNameSpace string) {
descriptor := mojom.NewMojomDescriptor()
fileName := fmt.Sprintf("file%d", testCaseNum)
expectedFile = descriptor.AddMojomFile(fileName)
expectedFile.InitializeFileScope(moduleNameSpace)
cases = append(cases, testCase{fileName, "", expectedFile})
}
endTestCase := func() {
testCaseNum += 1
}
// Note(rudominer) The structure of this method is designed to allow
// test cases to be rearranged and new test cases to be inserted at
// arbitrary locations. Do not hard-code anything that refers to the
// position of a test case in the list.
////////////////////////////////////////////////////////////
// Test Case (empty file)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = ""
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (module statement only)
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `module mojom.test;`
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (module statement with attributes)
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `[cool=true]module mojom.test;`
expectedFile.Attributes = mojom.NewAttributes()
expectedFile.Attributes.List = append(expectedFile.Attributes.List,
mojom.MojomAttribute{"cool", mojom.MakeBoolLiteralValue(true)})
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (import statements only)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `import "a.file";`
expectedFile.AddImport("a.file")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (module and import statements only)
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `
module mojom.test;
import "a.file";`
{
expectedFile.AddImport("a.file")
endTestCase()
}
////////////////////////////////////////////////////////////
// Test Case (module with attributes and import statements only)
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `
[cool=true]
module mojom.test;
import "a.file";`
{
expectedFile.Attributes = mojom.NewAttributes()
expectedFile.Attributes.List = append(expectedFile.Attributes.List,
mojom.MojomAttribute{"cool", mojom.MakeBoolLiteralValue(true)})
expectedFile.AddImport("a.file")
endTestCase()
}
////////////////////////////////////////////////////////////
// Test Case (one empty sruct)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `struct Foo{};`
expectedFile.AddStruct(mojom.NewMojomStruct(mojom.DeclTestData("Foo")))
endTestCase()
////////////////////////////////////////////////////////////
// Test Case
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `
module mojom.test;
struct Foo{
int32 x;
};`
{
structFoo := mojom.NewMojomStruct(mojom.DeclTestData("Foo"))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestData("x"), mojom.SimpleTypeInt32, nil))
expectedFile.AddStruct(structFoo)
}
endTestCase()
////////////////////////////////////////////////////////////
// Test Case
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `
module mojom.test;
import "another.file";
import "and.another.file";
struct Foo{
[happy=true] int32 x@4;
};`
{
expectedFile.AddImport("another.file")
expectedFile.AddImport("and.another.file")
structFoo := mojom.NewMojomStruct(mojom.DeclTestData("Foo"))
attributes := mojom.NewAttributes()
attributes.List = append(attributes.List, mojom.MojomAttribute{"happy", mojom.MakeBoolLiteralValue(true)})
structFoo.AddField(mojom.NewStructField(mojom.DeclTestDataAWithOrdinal("x", attributes, 4), mojom.SimpleTypeInt32, nil))
expectedFile.AddStruct(structFoo)
}
endTestCase()
////////////////////////////////////////////////////////////
// Test Case
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `
module mojom.test;
import "another.file";
import "and.another.file";
struct Foo{
int32 x@4 = 42;
[age=7, level="high"] string y = "Howdy!";
string? z;
};`
{
expectedFile.AddImport("another.file")
expectedFile.AddImport("and.another.file")
structFoo := mojom.NewMojomStruct(mojom.DeclTestData("Foo"))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestDataWithOrdinal("x", 4), mojom.SimpleTypeInt32, mojom.MakeInt64LiteralValue(42)))
attributes := mojom.NewAttributes()
attributes.List = append(attributes.List, mojom.MojomAttribute{"age", mojom.MakeInt64LiteralValue(7)})
attributes.List = append(attributes.List, mojom.MojomAttribute{"level", mojom.MakeStringLiteralValue("high")})
structFoo.AddField(mojom.NewStructField(mojom.DeclTestDataA("y", attributes), mojom.BuiltInType("string"), mojom.MakeStringLiteralValue("Howdy!")))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestData("z"), mojom.BuiltInType("string?"), nil))
expectedFile.AddStruct(structFoo)
}
endTestCase()
////////////////////////////////////////////////////////////
// Test Case
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `
module mojom.test;
import "another.file";
import "and.another.file";
struct Foo{
int32 x;
string y;
string? z;
};
interface Doer {
DoIt(int8 lemon, handle<message_pipe> pipe) => (array<Foo> someFoos, Foo? anotherFoo);
};
`
{
expectedFile.AddImport("another.file")
expectedFile.AddImport("and.another.file")
structFoo := mojom.NewMojomStruct(mojom.DeclTestData("Foo"))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestData("x"), mojom.SimpleTypeInt32, nil))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestData("y"), mojom.BuiltInType("string"), nil))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestData("z"), mojom.BuiltInType("string?"), nil))
expectedFile.AddStruct(structFoo)
interfaceDoer := mojom.NewMojomInterface(mojom.DeclTestData("Doer"))
// The first reference to Foo inside of interface Doer
fooRef1 := mojom.NewUserTypeRef("Foo", false, false, interfaceDoer.Scope(), lexer.Token{})
// The second reference to Foo inside of interface Doer. nullable=true
fooRef2 := mojom.NewUserTypeRef("Foo", true, false, interfaceDoer.Scope(), lexer.Token{})
params := mojom.NewMojomStruct(mojom.DeclTestData("dummy"))
params.AddField(mojom.NewStructField(mojom.DeclTestData("lemon"), mojom.SimpleTypeInt8, nil))
params.AddField(mojom.NewStructField(mojom.DeclTestData("pipe"), mojom.BuiltInType("handle<message_pipe>"), nil))
responseParams := mojom.NewMojomStruct(mojom.DeclTestData("dummy"))
responseParams.AddField(mojom.NewStructField(mojom.DeclTestData("someFoos"), mojom.NewArrayTypeRef(fooRef1, -1, false), nil))
responseParams.AddField(mojom.NewStructField(mojom.DeclTestData("anotherFoo"), fooRef2, nil))
interfaceDoer.AddMethod(mojom.NewMojomMethod(mojom.DeclTestData("DoIt"), params, responseParams))
expectedFile.AddInterface(interfaceDoer)
}
endTestCase()
////////////////////////////////////////////////////////////
// Test Case
////////////////////////////////////////////////////////////
startTestCase("mojom.test")
cases[testCaseNum].mojomContents = `
[php_namespace="mojom.test.php"]
module mojom.test;
import "another.file";
import "and.another.file";
const int8 TOO_SMALL_VALUE = 6;
enum ErrorCodes {
TOO_BIG = 5,
TOO_SMALL = TOO_SMALL_VALUE,
JUST_RIGHT,
};
struct Foo{
int32 x;
string y;
string? z;
};
interface Doer {
DoIt(int8 lemon, handle<message_pipe> pipe) => (array<Foo> someFoos, Foo? anotherFoo);
};
`
{
expectedFile.Attributes = mojom.NewAttributes()
expectedFile.Attributes.List = append(expectedFile.Attributes.List,
mojom.MojomAttribute{"php_namespace", mojom.MakeStringLiteralValue("mojom.test.php")})
expectedFile.AddImport("another.file")
expectedFile.AddImport("and.another.file")
expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("TOO_SMALL_VALUE"),
mojom.SimpleTypeInt8, mojom.MakeInt64LiteralValue(6)))
errorCodeEnum := mojom.NewMojomEnum(mojom.DeclTestData("ErrorCodes"))
errorCodeEnum.InitAsScope(expectedFile.FileScope)
// The reference to TOO_SMALL_VALUE from within the ErrorCodes enum.
assigneeType := mojom.NewResolvedUserTypeRef("ErrorCodes", errorCodeEnum)
tooSmallValueRef := mojom.NewUserValueRef(assigneeType, "TOO_SMALL_VALUE",
expectedFile.FileScope, lexer.Token{})
errorCodeEnum.AddEnumValue(mojom.DeclTestData("TOO_BIG"), mojom.MakeInt64LiteralValue(5))
errorCodeEnum.AddEnumValue(mojom.DeclTestData("TOO_SMALL"), tooSmallValueRef)
errorCodeEnum.AddEnumValue(mojom.DeclTestData("JUST_RIGHT"), nil)
expectedFile.AddEnum(errorCodeEnum)
structFoo := mojom.NewMojomStruct(mojom.DeclTestData("Foo"))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestData("x"), mojom.SimpleTypeInt32, nil))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestData("y"), mojom.BuiltInType("string"), nil))
structFoo.AddField(mojom.NewStructField(mojom.DeclTestData("z"), mojom.BuiltInType("string?"), nil))
expectedFile.AddStruct(structFoo)
interfaceDoer := mojom.NewMojomInterface(mojom.DeclTestData("Doer"))
// The first reference to Foo inside of interface Doer
fooRef1 := mojom.NewUserTypeRef("Foo", false, false, interfaceDoer.Scope(), lexer.Token{})
// The second reference to Foo inside of interface Doer. nullable=true
fooRef2 := mojom.NewUserTypeRef("Foo", true, false, interfaceDoer.Scope(), lexer.Token{})
params := mojom.NewMojomStruct(mojom.DeclTestData("dummy"))
params.AddField(mojom.NewStructField(mojom.DeclTestData("lemon"), mojom.SimpleTypeInt8, nil))
params.AddField(mojom.NewStructField(mojom.DeclTestData("pipe"), mojom.BuiltInType("handle<message_pipe>"), nil))
responseParams := mojom.NewMojomStruct(mojom.DeclTestData("dummy"))
responseParams.AddField(mojom.NewStructField(mojom.DeclTestData("someFoos"), mojom.NewArrayTypeRef(fooRef1, -1, false), nil))
responseParams.AddField(mojom.NewStructField(mojom.DeclTestData("anotherFoo"), fooRef2, nil))
interfaceDoer.AddMethod(mojom.NewMojomMethod(mojom.DeclTestData("DoIt"), params, responseParams))
expectedFile.AddInterface(interfaceDoer)
}
endTestCase()
////////////////////////////////////////////////////////////
// Execute all of the test cases.
////////////////////////////////////////////////////////////
for _, c := range cases {
descriptor := mojom.NewMojomDescriptor()
parser := MakeParser(c.fileName, c.mojomContents, descriptor)
parser.Parse()
if !parser.OK() {
t.Errorf("Parsing error for %s: %s", c.fileName, parser.GetError().Error())
} else {
got := parser.GetMojomFile().String()
expected := c.expectedFile.String()
if got != expected {
t.Errorf("%s:\n*****expected:\n%s\n****actual\n%s", c.fileName, expected, got)
}
}
}
}
// TestErrorParsing contains a series of test cases in which we
// run the parser on invalid mojom input string and compare the resulting
// error message to an expected one.
func TestErrorParsing(t *testing.T) {
type testCase struct {
fileName string
mojomContents string
expectedErrors []string
}
cases := make([]testCase, 0)
testCaseNum := 0
startTestCase := func(moduleNameSpace string) {
fileName := fmt.Sprintf("file%d", testCaseNum)
cases = append(cases, testCase{fileName: fileName})
}
expectError := func(expectedError string) {
cases[testCaseNum].expectedErrors = append(cases[testCaseNum].expectedErrors, expectedError)
}
endTestCase := func() {
testCaseNum += 1
}
// Note(rudominer) The structure of this method is designed to allow
// test cases to be rearranged and new test cases to be inserted at
// arbitrary locations. Do not hard-code anything that refers to the
// position of a test case in the list.
////////////////////////////////////////////////////////////
// Test Case (naked attributes)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = "[cool=true]"
expectError("The .mojom file contains an attributes section but nothing else.")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (attributes directly before an import)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `
[cool=true]
import "another.file";
`
expectError("Attributes are not allowed before an import statement.")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (two sets of initial naked attributes)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `
[cool=true]
[not-cool=false]
`
expectError("Unexpected token")
expectError("'['")
expectError("Expecting module, import, interface, struct, union, enum or constant.")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (two sets of initial attributes with a module)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `
[cool=true]
[not-cool=false]
module mojom.test;
`
expectError("Unexpected token")
expectError("'['")
expectError("Expecting module, import, interface, struct, union, enum or constant.")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (import before module)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `
import "another.file";
module mojom.test;
`
expectError("The module declaration must come before the import statements.")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (duplicate method names)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `
interface MyInterface {
MethodA();
MethodB();
MethodC();
MethodB();
MethodD();
};
`
expectError("Duplicate definition of method 'MethodB'. There is already a method with that name in interface MyInterface.")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (Invalid method ordinal: too big for uint32)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `
interface MyInterface {
MethodA@4294967295();
};
`
expectError("MethodA")
expectError("4294967295")
expectError("Invalid ordinal string")
expectError("Ordinals must be decimal integers between 0 and 4294967294")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (Invalid method ordinal: too big for int64)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `
interface MyInterface {
MethodA@999999999999999999999999999999999999999();
};
`
expectError("MethodA")
expectError("999999999999999999999999999999999999999")
expectError("Invalid ordinal string")
expectError("Ordinals must be decimal integers between 0 and 4294967294")
endTestCase()
////////////////////////////////////////////////////////////
// Test Case (Invalid method ordinal: negative)
////////////////////////////////////////////////////////////
startTestCase("")
cases[testCaseNum].mojomContents = `
interface MyInterface {
MethodA@-1();
};
`
expectError("MethodA")
// Note that the lexer return "@" as the ordinal token, stopping when
// it sees the non-digit "-".
expectError("Invalid ordinal string")
expectError("Ordinals must be decimal integers between 0 and 4294967294")
endTestCase()
////////////////////////////////////////////////////////////
// Execute all of the test cases.
////////////////////////////////////////////////////////////
for i, c := range cases {
descriptor := mojom.NewMojomDescriptor()
parser := MakeParser(c.fileName, c.mojomContents, descriptor)
parser.Parse()
if parser.OK() {
t.Errorf("Parsing was supposed to fail but did not for test case %d", i)
} else {
got := parser.GetError().Error()
for _, expected := range c.expectedErrors {
if !strings.Contains(got, expected) {
t.Errorf("%s:\n*****expected to contain:\n%s\n****actual\n%s", c.fileName, expected, got)
}
}
}
}
}