// TestSingleFileResolutionSuccess() iterates through a series of test cases. // For each case we expect for parsing and resolution to succeed. Then we // execute a given callback test function to test that resolution produced // the desired result. func TestSingleFileResolutionSuccess(t *testing.T) { test := singleFileSuccessTest{} //////////////////////////////////////////////////////////// // Test Case: A local constant name shadows an enum name. //////////////////////////////////////////////////////////// { contents := ` enum Color{ RED, BLUE }; struct MyStruct { const Color RED = BLUE; Color a_color = RED; // This should resolve to the local constant RED, // and therefore the concrete value should be BLUE. };` testFunc := func(descriptor *mojom.MojomDescriptor) error { myStructType := descriptor.TypesByKey["TYPE_KEY:MyStruct"].(*mojom.MojomStruct) aColorField := myStructType.Fields[0] concreteValue := aColorField.DefaultValue.ResolvedConcreteValue().(*mojom.EnumValue) key := concreteValue.ValueKey() if key != "TYPE_KEY:Color.BLUE" { return fmt.Errorf("%s != TYPE_KEY:Color.BLUE", key) } return nil } test.addTestCase("", contents, testFunc) } //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range test.cases { // Parse and resolve the mojom input. descriptor := mojom.NewMojomDescriptor() fileName := fmt.Sprintf("file%d", i) parser := MakeParser(fileName, fileName, c.mojomContents, descriptor, nil) parser.Parse() if !parser.OK() { t.Errorf("Parsing error for %s: %s", fileName, parser.GetError().Error()) continue } err := descriptor.Resolve() if err != nil { t.Errorf("Resolution failed for test case %d: %s", i, err.Error()) continue } if c.testFunc != nil { if err := c.testFunc(descriptor); err != nil { t.Errorf("%s:\n%s", fileName, err.Error()) continue } } } }
// Parses each of the given .mojom files and all of the files in the // import graph rooted by each file. A single MojomDescriptor is created and // populated with the result of parsing all of those files. If the parsing is // successful then err will be nil. // // fileNames must not be nil or we will panic. func (d *ParseDriver) ParseFiles(fileNames []string) (descriptor *mojom.MojomDescriptor, err error) { if fileNames == nil { // We panic instead of returning an error here because this would be a programming error // as opposed to an error in the input. panic("fileNames may not be nil.") } filesToProcess := make([]*FileReference, len(fileNames)) descriptor = mojom.NewMojomDescriptor() for i, fileName := range fileNames { filesToProcess[i] = &FileReference{specifiedPath: fileName} } for len(filesToProcess) > 0 { currentFile := filesToProcess[0] filesToProcess = filesToProcess[1:] if err = d.fileProvider.findFile(currentFile); err != nil { return } if !descriptor.ContainsFile(currentFile.absolutePath) { contents, fileReadError := d.fileProvider.provideContents(currentFile) if fileReadError != nil { err = fileReadError return } parser := MakeParser(currentFile.absolutePath, contents, descriptor) parser.SetDebugMode(d.debugMode) parser.Parse() if d.debugMode { fmt.Printf("\nParseTree for %s:", currentFile) fmt.Println(parser.GetParseTree()) } if !parser.OK() { err = fmt.Errorf("\nError while parsing %s: \n%s\n", currentFile, parser.GetError().Error()) return } mojomFile := d.fileExtractor.extractMojomFile(&parser) for _, importedFile := range mojomFile.Imports { filesToProcess = append(filesToProcess, &FileReference{importedFrom: currentFile, specifiedPath: importedFile}) } } } // Perform type and value resolution if err = descriptor.Resolve(); err != nil { return } // Compute data for generators. err = descriptor.ComputeDataForGenerators() return }
// Parses each of the given .mojom files and all of the files in the // import graph rooted by each file. A single MojomDescriptor is created and // populated with the result of parsing all of those files. The returned // ParseResult contains the populuted MojomDescriptor and any error that // occurred. func (d *ParseDriver) ParseFiles(fileNames []string) ParseResult { if fileNames == nil { panic("fileNames may not be nil.") } filesToProcess := make([]*FileReference, len(fileNames)) descriptor := mojom.NewMojomDescriptor() for i, fileName := range fileNames { filesToProcess[i] = &FileReference{specifiedPath: fileName} } for len(filesToProcess) > 0 { currentFile := filesToProcess[0] filesToProcess = filesToProcess[1:] if err := d.fileProvider.FindFile(currentFile); err != nil { return ParseResult{Err: err, Descriptor: descriptor} } if !descriptor.ContainsFile(currentFile.absolutePath) { contents, fileReadError := d.fileProvider.ProvideContents(currentFile) if fileReadError != nil { return ParseResult{Err: fileReadError, Descriptor: descriptor} } parser := MakeParser(currentFile.absolutePath, contents, descriptor) parser.SetDebugMode(d.debugMode) parser.Parse() if d.debugMode { fmt.Printf("\nParseTree for %s:", currentFile) fmt.Println(parser.GetParseTree()) } if !parser.OK() { parseError := fmt.Errorf("\nError while parsing %s: \n%s\n", currentFile, parser.GetError().Error()) return ParseResult{Err: parseError, Descriptor: descriptor} } mojomFile := parser.GetMojomFile() for _, importedFile := range mojomFile.Imports { filesToProcess = append(filesToProcess, &FileReference{importedFrom: currentFile, specifiedPath: importedFile}) } } } // Perform type and value resolution if resolutionError := descriptor.Resolve(); resolutionError != nil { return ParseResult{Err: resolutionError, Descriptor: descriptor} } // Compute data for generators. computationError := descriptor.ComputeDataForGenerators() return ParseResult{Err: computationError, Descriptor: descriptor} }
// TestTwoFileSerialization uses a series of test cases in which the text of two .mojom // files is specified and the expected MojomFileGraph is specified using Go struct literals. func TestTwoFileSerialization(t *testing.T) { test := twoFileTest{} ///////////////////////////////////////////////////////////// // Test Case: Two top-level files with no import relationship ///////////////////////////////////////////////////////////// { contentsA := ` module a.b.c; struct FooA{ };` contentsB := ` module a.b.c; struct FooB{ };` importingName := "" // File A is not importing file B. topLevel := true // Simulate that File B is a top-level file. test.addTestCase("a.b.c", contentsA, contentsB, topLevel, importingName) // DeclaredMojomObjects test.expectedFileA().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:a.b.c.FooA"} test.expectedFileB().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:a.b.c.FooB"} // struct FooA test.expectedGraph().ResolvedTypes["TYPE_KEY:a.b.c.FooA"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: newDeclData(test.expectedFileA().FileName, "FooA", "a.b.c.FooA"), Fields: []mojom_types.StructField{}}} // struct FooB test.expectedGraph().ResolvedTypes["TYPE_KEY:a.b.c.FooB"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: newDeclData(test.expectedFileB().FileName, "FooB", "a.b.c.FooB"), Fields: []mojom_types.StructField{}}} test.endTestCase() } ///////////////////////////////////////////////////////////// // Test Case: Two top-level files where the first imports the second. ///////////////////////////////////////////////////////////// { contentsA := ` module a.b.c; import "myLittleFriend"; struct FooA{ };` contentsB := ` module a.b.c; struct FooB{ };` importingName := "myLittleFriend" // File A is importing File B using this name. topLevel := true // Simulate the File B is a top-level file. test.addTestCase("a.b.c", contentsA, contentsB, topLevel, importingName) // DeclaredMojomObjects test.expectedFileA().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:a.b.c.FooA"} test.expectedFileB().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:a.b.c.FooB"} // struct FooA test.expectedGraph().ResolvedTypes["TYPE_KEY:a.b.c.FooA"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: newDeclData(test.expectedFileA().FileName, "FooA", "a.b.c.FooA"), Fields: []mojom_types.StructField{}}} // struct FooB test.expectedGraph().ResolvedTypes["TYPE_KEY:a.b.c.FooB"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: newDeclData(test.expectedFileB().FileName, "FooB", "a.b.c.FooB"), Fields: []mojom_types.StructField{}}} test.endTestCase() } ///////////////////////////////////////////////////////////// // Test Case: A top-level file that imports a non-top-level file. ///////////////////////////////////////////////////////////// { contentsA := ` module a.b.c; import "myLittleFriend"; struct FooA{ };` contentsB := ` module a.b.c; struct FooB{ };` importingName := "myLittleFriend" // File A is importing File B using this name. topLevel := false // Simulate the File B is not a top-level file. test.addTestCase("a.b.c", contentsA, contentsB, topLevel, importingName) // DeclaredMojomObjects test.expectedFileA().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:a.b.c.FooA"} test.expectedFileB().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:a.b.c.FooB"} // struct FooA test.expectedGraph().ResolvedTypes["TYPE_KEY:a.b.c.FooA"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: newDeclData(test.expectedFileA().FileName, "FooA", "a.b.c.FooA"), Fields: []mojom_types.StructField{}}} // struct FooB test.expectedGraph().ResolvedTypes["TYPE_KEY:a.b.c.FooB"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: newDeclData(test.expectedFileB().FileName, "FooB", "a.b.c.FooB"), Fields: []mojom_types.StructField{}}} test.endTestCase() } //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range test.cases { descriptor := mojom.NewMojomDescriptor() // Parse file A. parserA := parser.MakeParser(c.expectedFileA.FileName, *c.expectedFileA.SpecifiedFileName, c.mojomContentsA, descriptor, nil) parserA.Parse() if !parserA.OK() { t.Errorf("Parsing error for %s: %s", c.expectedFileA.FileName, parserA.GetError().Error()) continue } mojomFileA := parserA.GetMojomFile() // Parse file B. var importedFrom *mojom.MojomFile if !c.topLevel { // If file B is not a top-level file then when the parser for it is constructed we give it a non-nil |importedFrom|. importedFrom = mojomFileA } parserB := parser.MakeParser(c.expectedFileB.FileName, *c.expectedFileB.SpecifiedFileName, c.mojomContentsB, descriptor, importedFrom) parserB.Parse() if !parserB.OK() { t.Errorf("Parsing error for %s: %s", c.expectedFileB.FileName, parserB.GetError().Error()) continue } mojomFileB := parserB.GetMojomFile() // Set the canonical file name for the imported files. In real operation // this step is done in parser_driver.go when each of the imported files are parsed. if c.importingName != "" { // The call to SetCanonicalImportName does a lookup in a map for a key corresponding to the // first argument. Thus here we are also testing that |importingName| is in fact string // that was parsed from the .mojom file. mojomFileA.SetCanonicalImportName(c.importingName, mojomFileB.CanonicalFileName) } // Resolve if err := descriptor.Resolve(); err != nil { t.Errorf("Resolve error for case %d: %s", i, err.Error()) continue } if err := descriptor.ComputeEnumValueIntegers(); err != nil { t.Errorf("ComputeEnumValueIntegers error for case %d: %s", i, err.Error()) continue } if err := descriptor.ComputeDataForGenerators(); err != nil { t.Errorf("ComputeDataForGenerators error for case %d: %s", i, err.Error()) continue } // Serialize EmitLineAndColumnNumbers = c.lineAndcolumnNumbers bytes, _, err := Serialize(descriptor, false) if err != nil { t.Errorf("Serialization error for case %d: %s", i, err.Error()) continue } // Deserialize decoder := bindings.NewDecoder(bytes, nil) fileGraph := mojom_files.MojomFileGraph{} fileGraph.Decode(decoder) // Compare if err := compareFileGraphs(c.expectedGraph, &fileGraph); err != nil { t.Errorf("case %d:\n%s", i, err.Error()) continue } } }
// TestSingleFileSerialization uses a series of test cases in which the text of a .mojom // file is specified and the expected MojomFileGraph is specified using Go struct literals. func TestSingleFileSerialization(t *testing.T) { test := singleFileTest{} //////////////////////////////////////////////////////////// // Test Case: array of int32 //////////////////////////////////////////////////////////// { contents := ` struct Foo{ array<int32> bar1; array<int32, 7> bar2; array<int32>? bar3; array<int32, 8>? bar4; };` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:Foo"} // ResolvedTypes // struct Foo test.expectedGraph().ResolvedTypes["TYPE_KEY:Foo"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: test.newDeclData("Foo", "Foo"), Fields: []mojom_types.StructField{ // field bar1 is not nullable and not fixed length { DeclData: test.newDeclData("bar1", ""), Type: &mojom_types.TypeArrayType{mojom_types.ArrayType{ false, -1, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar2 is not nullable and fixed length of 7 { DeclData: test.newDeclData("bar2", ""), Type: &mojom_types.TypeArrayType{mojom_types.ArrayType{ false, 7, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar3 is nullable and not fixed length { DeclData: test.newDeclData("bar3", ""), Type: &mojom_types.TypeArrayType{mojom_types.ArrayType{ true, -1, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar4 is nullable and fixed length of 8 { DeclData: test.newDeclData("bar4", ""), Type: &mojom_types.TypeArrayType{mojom_types.ArrayType{ true, 8, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case: map string to int32 //////////////////////////////////////////////////////////// { contents := ` struct Foo{ map<string, int32> bar1; map<string?, int32> bar2; map<string, int32>? bar3; map<string?, int32>? bar4; };` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:Foo"} // ResolvedTypes // struct Foo test.expectedGraph().ResolvedTypes["TYPE_KEY:Foo"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: test.newDeclData("Foo", "Foo"), Fields: []mojom_types.StructField{ // field bar1 is non-nullable with a non-nullable key. { DeclData: test.newDeclData("bar1", ""), Type: &mojom_types.TypeMapType{mojom_types.MapType{ false, &mojom_types.TypeStringType{mojom_types.StringType{false}}, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar2 is non-nullable with a nullable key. { DeclData: test.newDeclData("bar2", ""), Type: &mojom_types.TypeMapType{mojom_types.MapType{ false, &mojom_types.TypeStringType{mojom_types.StringType{true}}, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar3 is nullable with a non-nullable key. { DeclData: test.newDeclData("bar3", ""), Type: &mojom_types.TypeMapType{mojom_types.MapType{ true, &mojom_types.TypeStringType{mojom_types.StringType{false}}, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar4 is nullable with a nullable key. { DeclData: test.newDeclData("bar4", ""), Type: &mojom_types.TypeMapType{mojom_types.MapType{ true, &mojom_types.TypeStringType{mojom_types.StringType{true}}, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case: enum value initializer //////////////////////////////////////////////////////////// { contents := ` enum Foo{ X1 = 42, X2 = X1 };` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.TopLevelEnums = &[]string{"TYPE_KEY:Foo"} // Resolved Values // Foo.X1 test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X1"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("X1", "Foo.X1"), EnumTypeKey: "TYPE_KEY:Foo", InitializerValue: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueInt64Value{42}}, IntValue: -1, }} // Foo.X2 test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X2"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("X2", "Foo.X2"), EnumTypeKey: "TYPE_KEY:Foo", InitializerValue: &mojom_types.ValueUserValueReference{mojom_types.UserValueReference{ Identifier: "X1", ValueKey: stringPointer("TYPE_KEY:Foo.X1"), }}, IntValue: -1, }} // ResolvedTypes // enum Foo test.expectedGraph().ResolvedTypes["TYPE_KEY:Foo"] = &mojom_types.UserDefinedTypeEnumType{mojom_types.MojomEnum{ DeclData: test.newDeclData("Foo", "Foo"), Values: []mojom_types.EnumValue{ // Note(rudominer) It is a bug that we need to copy the enum values here. // See https://github.com/domokit/mojo/issues/513. // value TOP test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X1"].(*mojom_types.UserDefinedValueEnumValue).Value, // value COWBOY test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X2"].(*mojom_types.UserDefinedValueEnumValue).Value, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case //////////////////////////////////////////////////////////// { contents := ` [go_namespace="go.test", lucky=true, planet=EARTH] module mojom.test; import "another.file"; import "and.another.file"; const uint16 NUM_MAGI = 3; struct Foo{ int32 x; string y = "hello"; string? z; enum Hats { TOP, COWBOY = NUM_MAGI }; };` test.addTestCase("mojom.test", contents) // Attributes test.expectedFile().Attributes = &[]mojom_types.Attribute{ {"go_namespace", "go.test"}, {"lucky", "true"}, {"planet", "EARTH"}, } // Imports test.expectedFile().Imports = &[]string{ "another.file.canonical", "and.another.file.canonical", } // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:mojom.test.Foo"} test.expectedFile().DeclaredMojomObjects.TopLevelConstants = &[]string{"TYPE_KEY:mojom.test.NUM_MAGI"} // Resolved Values // NUM_MAGI test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.NUM_MAGI"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("NUM_MAGI", "mojom.test.NUM_MAGI"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_UinT16}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueInt64Value{3}}, }} // Hats.TOP test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.TOP"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("TOP", "mojom.test.Foo.Hats.TOP"), EnumTypeKey: "TYPE_KEY:mojom.test.Foo.Hats", IntValue: -1, }} // Hats.COWBOY test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.COWBOY"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("COWBOY", "mojom.test.Foo.Hats.COWBOY"), EnumTypeKey: "TYPE_KEY:mojom.test.Foo.Hats", IntValue: -1, InitializerValue: &mojom_types.ValueUserValueReference{mojom_types.UserValueReference{ Identifier: "NUM_MAGI", ValueKey: stringPointer("TYPE_KEY:mojom.test.NUM_MAGI"), }}, }} // ResolvedTypes // struct Foo test.expectedGraph().ResolvedTypes["TYPE_KEY:mojom.test.Foo"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: &mojom_types.DeclarationData{ ShortName: stringPointer("Foo"), FullIdentifier: stringPointer("mojom.test.Foo"), DeclaredOrdinal: -1, DeclarationOrder: -1, SourceFileInfo: &mojom_types.SourceFileInfo{ FileName: test.fileName(), }, ContainedDeclarations: &mojom_types.ContainedDeclarations{ Enums: &[]string{"TYPE_KEY:mojom.test.Foo.Hats"}}, }, Fields: []mojom_types.StructField{ // field x { DeclData: test.newDeclData("x", ""), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}, }, // field y { DeclData: test.newDeclData("y", ""), Type: &mojom_types.TypeStringType{mojom_types.StringType{false}}, DefaultValue: &mojom_types.DefaultFieldValueValue{&mojom_types.ValueLiteralValue{&mojom_types.LiteralValueStringValue{"hello"}}}, }, // field z { DeclData: test.newDeclData("z", ""), Type: &mojom_types.TypeStringType{mojom_types.StringType{true}}, }, }, }} // enum Hats test.expectedGraph().ResolvedTypes["TYPE_KEY:mojom.test.Foo.Hats"] = &mojom_types.UserDefinedTypeEnumType{mojom_types.MojomEnum{ DeclData: test.newDeclData("Hats", "mojom.test.Foo.Hats"), Values: []mojom_types.EnumValue{ // Note(rudominer) It is a bug that we need to copy the enum values here. // See https://github.com/domokit/mojo/issues/513. // value TOP test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.TOP"].(*mojom_types.UserDefinedValueEnumValue).Value, // value COWBOY test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.COWBOY"].(*mojom_types.UserDefinedValueEnumValue).Value, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for _, c := range test.cases { // Parse and resolve the mojom input. descriptor := mojom.NewMojomDescriptor() parser := parser.MakeParser(c.fileName, c.mojomContents, descriptor) parser.Parse() if !parser.OK() { t.Errorf("Parsing error for %s: %s", c.fileName, parser.GetError().Error()) continue } if err := descriptor.Resolve(); err != nil { t.Errorf("Resolve error for %s: %s", c.fileName, err.Error()) continue } if err := descriptor.ComputeEnumValueIntegers(); err != nil { t.Errorf("ComputeEnumValueIntegers error for %s: %s", c.fileName, err.Error()) continue } if err := descriptor.ComputeDataForGenerators(); err != nil { t.Errorf("ComputeDataForGenerators error for %s: %s", c.fileName, err.Error()) continue } // Simulate setting the canonical file name for the imported files. In real operation // this step is done in parser_driver.go when each of the imported files are parsed. mojomFile := parser.GetMojomFile() if mojomFile.Imports != nil { for _, imp := range mojomFile.Imports { imp.CanonicalFileName = fmt.Sprintf("%s.canonical", imp.SpecifiedName) } } // Serialize EmitLineAndColumnNumbers = c.lineAndcolumnNumbers bytes, err := Serialize(descriptor) if err != nil { t.Errorf("Serialization error for %s: %s", c.fileName, err.Error()) continue } // Deserialize decoder := bindings.NewDecoder(bytes, nil) fileGraph := mojom_files.MojomFileGraph{} fileGraph.Decode(decoder) // Compare if err := compareFileGraphs(c.expectedGraph, &fileGraph); err != nil { t.Errorf("%s:\n%s", c.fileName, err.Error()) continue } } }
// TestSingleFileSerialization uses a series of test cases in which the text of a .mojom // file is specified and the expected MojomFileGraph is specified using Go struct literals. func TestSingleFileSerialization(t *testing.T) { test := singleFileTest{} //////////////////////////////////////////////////////////// // Test Case: array of int32 //////////////////////////////////////////////////////////// { contents := ` struct Foo{ array<int32> bar1; array<int32, 7> bar2; array<int32>? bar3; array<int32, 8>? bar4; };` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:Foo"} // ResolvedTypes // struct Foo test.expectedGraph().ResolvedTypes["TYPE_KEY:Foo"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: test.newDeclData("Foo", "Foo"), Fields: []mojom_types.StructField{ // field bar1 is not nullable and not fixed length { DeclData: test.newShortDeclData("bar1"), Type: &mojom_types.TypeArrayType{mojom_types.ArrayType{ false, -1, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar2 is not nullable and fixed length of 7 { DeclData: test.newShortDeclData("bar2"), Type: &mojom_types.TypeArrayType{mojom_types.ArrayType{ false, 7, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar3 is nullable and not fixed length { DeclData: test.newShortDeclData("bar3"), Type: &mojom_types.TypeArrayType{mojom_types.ArrayType{ true, -1, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar4 is nullable and fixed length of 8 { DeclData: test.newShortDeclData("bar4"), Type: &mojom_types.TypeArrayType{mojom_types.ArrayType{ true, 8, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case: map string to int32 //////////////////////////////////////////////////////////// { contents := ` struct Foo{ map<string, int32> bar1; map<string?, int32> bar2; map<string, int32>? bar3; map<string?, int32>? bar4; };` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:Foo"} // ResolvedTypes // struct Foo test.expectedGraph().ResolvedTypes["TYPE_KEY:Foo"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: test.newDeclData("Foo", "Foo"), Fields: []mojom_types.StructField{ // field bar1 is non-nullable with a non-nullable key. { DeclData: test.newShortDeclData("bar1"), Type: &mojom_types.TypeMapType{mojom_types.MapType{ false, &mojom_types.TypeStringType{mojom_types.StringType{false}}, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar2 is non-nullable with a nullable key. { DeclData: test.newShortDeclData("bar2"), Type: &mojom_types.TypeMapType{mojom_types.MapType{ false, &mojom_types.TypeStringType{mojom_types.StringType{true}}, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar3 is nullable with a non-nullable key. { DeclData: test.newShortDeclData("bar3"), Type: &mojom_types.TypeMapType{mojom_types.MapType{ true, &mojom_types.TypeStringType{mojom_types.StringType{false}}, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, // field bar4 is nullable with a nullable key. { DeclData: test.newShortDeclData("bar4"), Type: &mojom_types.TypeMapType{mojom_types.MapType{ true, &mojom_types.TypeStringType{mojom_types.StringType{true}}, &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}}}, }, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case: enum value initializer //////////////////////////////////////////////////////////// { contents := ` enum Foo{ X0, X1 = 42, X2 = X1 };` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.TopLevelEnums = &[]string{"TYPE_KEY:Foo"} // Resolved Values // Foo.X0 test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X0"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("X0", "Foo.X0"), EnumTypeKey: "TYPE_KEY:Foo", IntValue: 0, }} // Foo.X1 test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X1"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("X1", "Foo.X1"), EnumTypeKey: "TYPE_KEY:Foo", InitializerValue: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueInt8Value{42}}, IntValue: 42, }} // Foo.X2 test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X2"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("X2", "Foo.X2"), EnumTypeKey: "TYPE_KEY:Foo", InitializerValue: &mojom_types.ValueUserValueReference{mojom_types.UserValueReference{ Identifier: "X1", ValueKey: stringPointer("TYPE_KEY:Foo.X1"), }}, IntValue: 42, }} // ResolvedTypes // enum Foo test.expectedGraph().ResolvedTypes["TYPE_KEY:Foo"] = &mojom_types.UserDefinedTypeEnumType{mojom_types.MojomEnum{ DeclData: test.newDeclData("Foo", "Foo"), Values: []mojom_types.EnumValue{ // Note(rudominer) It is a bug that we need to copy the enum values here. // See https://github.com/domokit/mojo/issues/513. // value X1 test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X0"].(*mojom_types.UserDefinedValueEnumValue).Value, // value X1 test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X1"].(*mojom_types.UserDefinedValueEnumValue).Value, // value X2 test.expectedGraph().ResolvedValues["TYPE_KEY:Foo.X2"].(*mojom_types.UserDefinedValueEnumValue).Value, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case: enum value name is shadowed by local constant declaration. //////////////////////////////////////////////////////////// { contents := ` enum Color{ RED, BLUE }; struct MyStruct { const Color RED = BLUE; Color a_color = RED; // This should resolve to the local constant RED. };` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.TopLevelEnums = &[]string{"TYPE_KEY:Color"} test.expectedFile().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:MyStruct"} // Resolved Values // Color.RED test.expectedGraph().ResolvedValues["TYPE_KEY:Color.RED"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("RED", "Color.RED"), EnumTypeKey: "TYPE_KEY:Color", IntValue: 0, }} // Color.BLUE test.expectedGraph().ResolvedValues["TYPE_KEY:Color.BLUE"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("BLUE", "Color.BLUE"), EnumTypeKey: "TYPE_KEY:Color", IntValue: 1, }} // MyStruct.RED test.expectedGraph().ResolvedValues["TYPE_KEY:MyStruct.RED"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newContainedDeclData("RED", "MyStruct.RED", stringPointer("TYPE_KEY:MyStruct")), Type: &mojom_types.TypeTypeReference{mojom_types.TypeReference{ false, false, stringPointer("Color"), stringPointer("TYPE_KEY:Color")}}, Value: &mojom_types.ValueUserValueReference{ mojom_types.UserValueReference{ Identifier: "BLUE", ValueKey: stringPointer("TYPE_KEY:Color.BLUE")}}, }} // ResolvedTypes // enum Color test.expectedGraph().ResolvedTypes["TYPE_KEY:Color"] = &mojom_types.UserDefinedTypeEnumType{mojom_types.MojomEnum{ DeclData: test.newDeclData("Color", "Color"), Values: []mojom_types.EnumValue{ // Note(rudominer) It is a bug that we need to copy the enum values here. // See https://github.com/domokit/mojo/issues/513. // value RED test.expectedGraph().ResolvedValues["TYPE_KEY:Color.RED"].(*mojom_types.UserDefinedValueEnumValue).Value, // value BLUE test.expectedGraph().ResolvedValues["TYPE_KEY:Color.BLUE"].(*mojom_types.UserDefinedValueEnumValue).Value, }, }} // struct MyStruct test.expectedGraph().ResolvedTypes["TYPE_KEY:MyStruct"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: &mojom_types.DeclarationData{ ShortName: stringPointer("MyStruct"), FullIdentifier: stringPointer("MyStruct"), DeclaredOrdinal: -1, DeclarationOrder: -1, SourceFileInfo: &mojom_types.SourceFileInfo{ FileName: test.fileName(), }, ContainedDeclarations: &mojom_types.ContainedDeclarations{ Constants: &[]string{"TYPE_KEY:MyStruct.RED"}}, }, Fields: []mojom_types.StructField{ // field a_color { DeclData: test.newShortDeclData("a_color"), Type: &mojom_types.TypeTypeReference{mojom_types.TypeReference{ false, false, stringPointer("Color"), stringPointer("TYPE_KEY:Color")}}, DefaultValue: &mojom_types.DefaultFieldValueValue{&mojom_types.ValueUserValueReference{ mojom_types.UserValueReference{ Identifier: "RED", ValueKey: stringPointer("TYPE_KEY:MyStruct.RED")}}}, // Note this refers to MyStruct.RED and not Color.RED. }, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case: In-Out method parameters with the same name. //////////////////////////////////////////////////////////// { contents := ` module test; interface EchoService { EchoString(string? value) => (string? value); DelayedEchoString(string? value, int32 millis) => (string? value); };` test.addTestCase("test", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.Interfaces = &[]string{"TYPE_KEY:test.EchoService"} // ResolvedTypes // interface EchoService test.expectedGraph().ResolvedTypes["TYPE_KEY:test.EchoService"] = &mojom_types.UserDefinedTypeInterfaceType{mojom_types.MojomInterface{ DeclData: test.newDeclData("EchoService", "test.EchoService"), InterfaceName: "EchoService", Methods: map[uint32]mojom_types.MojomMethod{ 0: mojom_types.MojomMethod{ DeclData: test.newDeclData("EchoString", ""), Parameters: mojom_types.MojomStruct{ DeclData: test.newDeclData("EchoString-request", ""), Fields: []mojom_types.StructField{ mojom_types.StructField{ DeclData: test.newDeclData("value", ""), Type: &mojom_types.TypeStringType{mojom_types.StringType{true}}, }, }, }, ResponseParams: &mojom_types.MojomStruct{ DeclData: test.newDeclData("EchoString-response", ""), Fields: []mojom_types.StructField{ mojom_types.StructField{ DeclData: test.newDeclData("value", ""), Type: &mojom_types.TypeStringType{mojom_types.StringType{true}}, }, }, }, }, 1: mojom_types.MojomMethod{ DeclData: test.newDeclData("DelayedEchoString", ""), Parameters: mojom_types.MojomStruct{ DeclData: test.newDeclData("DelayedEchoString-request", ""), Fields: []mojom_types.StructField{ mojom_types.StructField{ DeclData: test.newDeclData("value", ""), Type: &mojom_types.TypeStringType{mojom_types.StringType{true}}, }, mojom_types.StructField{ DeclData: test.newDeclData("millis", ""), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}, }, }, }, ResponseParams: &mojom_types.MojomStruct{ DeclData: test.newDeclData("DelayedEchoString-response", ""), Fields: []mojom_types.StructField{ mojom_types.StructField{ DeclData: test.newDeclData("value", ""), Type: &mojom_types.TypeStringType{mojom_types.StringType{true}}, }, }, }, Ordinal: 1, }, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case: Integer constants //////////////////////////////////////////////////////////// { contents := ` const uint8 xu8 = 255; const int8 x8 = -127; const uint16 xu16 = 0xFFFF; const int16 x16 = -0x7FFF; const uint32 xu32 = 4294967295; const int32 x32 = -2147483647; const uint64 xu64 = 0xFFFFFFFFFFFFFFFF; const int64 x64 = -0x7FFFFFFFFFFFFFFF; ` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.TopLevelConstants = &[]string{ "TYPE_KEY:xu8", "TYPE_KEY:x8", "TYPE_KEY:xu16", "TYPE_KEY:x16", "TYPE_KEY:xu32", "TYPE_KEY:x32", "TYPE_KEY:xu64", "TYPE_KEY:x64"} // Resolved Values // xu8 test.expectedGraph().ResolvedValues["TYPE_KEY:xu8"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("xu8", "xu8"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_UinT8}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueUint8Value{255}}, }} // x8 test.expectedGraph().ResolvedValues["TYPE_KEY:x8"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("x8", "x8"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT8}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueInt8Value{-127}}, }} // xu16 test.expectedGraph().ResolvedValues["TYPE_KEY:xu16"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("xu16", "xu16"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_UinT16}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueUint16Value{0xFFFF}}, }} // x16 test.expectedGraph().ResolvedValues["TYPE_KEY:x16"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("x16", "x16"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT16}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueInt16Value{-0x7FFF}}, }} // xu32 test.expectedGraph().ResolvedValues["TYPE_KEY:xu32"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("xu32", "xu32"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_UinT32}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueUint32Value{4294967295}}, }} // x32 test.expectedGraph().ResolvedValues["TYPE_KEY:x32"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("x32", "x32"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueInt32Value{-2147483647}}, }} // xu64 test.expectedGraph().ResolvedValues["TYPE_KEY:xu64"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("xu64", "xu64"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_UinT64}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueUint64Value{0xFFFFFFFFFFFFFFFF}}, }} // x64 test.expectedGraph().ResolvedValues["TYPE_KEY:x64"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("x64", "x64"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT64}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueInt64Value{-0x7FFFFFFFFFFFFFFF}}, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case: Builtin Floating-Point Constants //////////////////////////////////////////////////////////// { contents := ` const float f1 = float.INFINITY; const float f2 = float.NEGATIVE_INFINITY; const float f3 = float.NAN; const double d1 = double.INFINITY; const double d2 = double.NEGATIVE_INFINITY; const double d3 = double.NAN; ` test.addTestCase("", contents) // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.TopLevelConstants = &[]string{ "TYPE_KEY:f1", "TYPE_KEY:f2", "TYPE_KEY:f3", "TYPE_KEY:d1", "TYPE_KEY:d2", "TYPE_KEY:d3"} // Resolved Values // f1 test.expectedGraph().ResolvedValues["TYPE_KEY:f1"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("f1", "f1"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_Float}, Value: &mojom_types.ValueBuiltinValue{mojom_types.BuiltinConstantValue_FloatInfinity}, }} // f2 test.expectedGraph().ResolvedValues["TYPE_KEY:f2"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("f2", "f2"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_Float}, Value: &mojom_types.ValueBuiltinValue{mojom_types.BuiltinConstantValue_FloatNegativeInfinity}, }} // f3 test.expectedGraph().ResolvedValues["TYPE_KEY:f3"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("f3", "f3"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_Float}, Value: &mojom_types.ValueBuiltinValue{mojom_types.BuiltinConstantValue_FloatNan}, }} // d1 test.expectedGraph().ResolvedValues["TYPE_KEY:d1"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("d1", "d1"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_Double}, Value: &mojom_types.ValueBuiltinValue{mojom_types.BuiltinConstantValue_DoubleInfinity}, }} // d2 test.expectedGraph().ResolvedValues["TYPE_KEY:d2"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("d2", "d2"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_Double}, Value: &mojom_types.ValueBuiltinValue{mojom_types.BuiltinConstantValue_DoubleNegativeInfinity}, }} // d3 test.expectedGraph().ResolvedValues["TYPE_KEY:d3"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("d3", "d3"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_Double}, Value: &mojom_types.ValueBuiltinValue{mojom_types.BuiltinConstantValue_DoubleNan}, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Test Case //////////////////////////////////////////////////////////// { contents := ` [go_namespace="go.test", lucky=true, planet=EARTH] module mojom.test; import "another.file"; import "and.another.file"; const uint16 NUM_MAGI = 3; struct Foo{ int32 x; [min_version=2] string y = "hello"; string? z; enum Hats { TOP, COWBOY = NUM_MAGI, HARD, }; };` test.addTestCase("mojom.test", contents) // Attributes test.expectedFile().Attributes = &[]mojom_types.Attribute{ {"go_namespace", &mojom_types.LiteralValueStringValue{"go.test"}}, {"lucky", &mojom_types.LiteralValueBoolValue{true}}, {"planet", &mojom_types.LiteralValueStringValue{"EARTH"}}, } // Imports test.expectedFile().Imports = &[]string{ "another.file.canonical", "and.another.file.canonical", } // DeclaredMojomObjects test.expectedFile().DeclaredMojomObjects.Structs = &[]string{"TYPE_KEY:mojom.test.Foo"} test.expectedFile().DeclaredMojomObjects.TopLevelConstants = &[]string{"TYPE_KEY:mojom.test.NUM_MAGI"} // Resolved Values // NUM_MAGI test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.NUM_MAGI"] = &mojom_types.UserDefinedValueDeclaredConstant{mojom_types.DeclaredConstant{ DeclData: *test.newDeclData("NUM_MAGI", "mojom.test.NUM_MAGI"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_UinT16}, Value: &mojom_types.ValueLiteralValue{&mojom_types.LiteralValueInt8Value{3}}, }} // Hats.TOP test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.TOP"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("TOP", "mojom.test.Foo.Hats.TOP"), EnumTypeKey: "TYPE_KEY:mojom.test.Foo.Hats", IntValue: 0, }} // Hats.COWBOY test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.COWBOY"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("COWBOY", "mojom.test.Foo.Hats.COWBOY"), EnumTypeKey: "TYPE_KEY:mojom.test.Foo.Hats", IntValue: 3, InitializerValue: &mojom_types.ValueUserValueReference{mojom_types.UserValueReference{ Identifier: "NUM_MAGI", ValueKey: stringPointer("TYPE_KEY:mojom.test.NUM_MAGI"), }}, }} // Hats.HARD test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.HARD"] = &mojom_types.UserDefinedValueEnumValue{mojom_types.EnumValue{ DeclData: test.newDeclData("HARD", "mojom.test.Foo.Hats.HARD"), EnumTypeKey: "TYPE_KEY:mojom.test.Foo.Hats", IntValue: 4, }} // ResolvedTypes // struct Foo test.expectedGraph().ResolvedTypes["TYPE_KEY:mojom.test.Foo"] = &mojom_types.UserDefinedTypeStructType{mojom_types.MojomStruct{ DeclData: &mojom_types.DeclarationData{ ShortName: stringPointer("Foo"), FullIdentifier: stringPointer("mojom.test.Foo"), DeclaredOrdinal: -1, DeclarationOrder: -1, SourceFileInfo: &mojom_types.SourceFileInfo{ FileName: test.fileName(), }, ContainedDeclarations: &mojom_types.ContainedDeclarations{ Enums: &[]string{"TYPE_KEY:mojom.test.Foo.Hats"}}, }, Fields: []mojom_types.StructField{ // field x { DeclData: test.newShortDeclData("x"), Type: &mojom_types.TypeSimpleType{mojom_types.SimpleType_InT32}, }, // field y { DeclData: test.newShortDeclDataA("y", &[]mojom_types.Attribute{{"min_version", &mojom_types.LiteralValueInt8Value{2}}}), Type: &mojom_types.TypeStringType{mojom_types.StringType{false}}, DefaultValue: &mojom_types.DefaultFieldValueValue{&mojom_types.ValueLiteralValue{&mojom_types.LiteralValueStringValue{"hello"}}}, }, // field z { DeclData: test.newShortDeclData("z"), Type: &mojom_types.TypeStringType{mojom_types.StringType{true}}, }, }, }} // enum Hats test.expectedGraph().ResolvedTypes["TYPE_KEY:mojom.test.Foo.Hats"] = &mojom_types.UserDefinedTypeEnumType{mojom_types.MojomEnum{ DeclData: test.newContainedDeclData("Hats", "mojom.test.Foo.Hats", stringPointer("TYPE_KEY:mojom.test.Foo")), Values: []mojom_types.EnumValue{ // Note(rudominer) It is a bug that we need to copy the enum values here. // See https://github.com/domokit/mojo/issues/513. // value TOP test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.TOP"].(*mojom_types.UserDefinedValueEnumValue).Value, // value COWBOY test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.COWBOY"].(*mojom_types.UserDefinedValueEnumValue).Value, // value HARD test.expectedGraph().ResolvedValues["TYPE_KEY:mojom.test.Foo.Hats.HARD"].(*mojom_types.UserDefinedValueEnumValue).Value, }, }} test.endTestCase() } //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for _, c := range test.cases { // Parse and resolve the mojom input. descriptor := mojom.NewMojomDescriptor() parser := parser.MakeParser(c.fileName, c.fileName, c.mojomContents, descriptor, nil) parser.Parse() if !parser.OK() { t.Errorf("Parsing error for %s: %s", c.fileName, parser.GetError().Error()) continue } if err := descriptor.Resolve(); err != nil { t.Errorf("Resolve error for %s: %s", c.fileName, err.Error()) continue } if err := descriptor.ComputeEnumValueIntegers(); err != nil { t.Errorf("ComputeEnumValueIntegers error for %s: %s", c.fileName, err.Error()) continue } if err := descriptor.ComputeDataForGenerators(); err != nil { t.Errorf("ComputeDataForGenerators error for %s: %s", c.fileName, err.Error()) continue } // Simulate setting the canonical file name for the imported files. In real operation // this step is done in parser_driver.go when each of the imported files are parsed. mojomFile := parser.GetMojomFile() if mojomFile.Imports != nil { for _, imp := range mojomFile.Imports { imp.CanonicalFileName = fmt.Sprintf("%s.canonical", imp.SpecifiedName) } } // Serialize EmitLineAndColumnNumbers = c.lineAndcolumnNumbers bytes, _, err := Serialize(descriptor, false) if err != nil { t.Errorf("Serialization error for %s: %s", c.fileName, err.Error()) continue } // Deserialize decoder := bindings.NewDecoder(bytes, nil) fileGraph := mojom_files.MojomFileGraph{} fileGraph.Decode(decoder) // Compare if err := compareFileGraphs(c.expectedGraph, &fileGraph); err != nil { t.Errorf("%s:\n%s", c.fileName, err.Error()) continue } } }
// TestSingleFileResolutionErrors() tests that appropriate error messages are generated // when a .mojom file contains unresolved references. func TestSingleFileResolutionErrors(t *testing.T) { test := singleFileTest{} //////////////////////////////////////////////////////////// // Test Case: One unresolved value reference //////////////////////////////////////////////////////////// { contents := ` struct Foo{ int32 x = Bar; };` test.addTestCase(contents, []string{ "Undefined value: \"Bar\"", "int32 x = Bar;", "^^^"}) } //////////////////////////////////////////////////////////// // Test Case:Two unresolved value references //////////////////////////////////////////////////////////// { contents := ` const bool F = Baz; struct Foo{ int32 x = Bar; };` test.addTestCase(contents, []string{ "Undefined value: \"Baz\"", "Undefined value: \"Bar\""}) } //////////////////////////////////////////////////////////// // Test Case: One unresolved type reference //////////////////////////////////////////////////////////// { contents := ` struct Foo{ Bar x; };` test.addTestCase(contents, []string{"Undefined type: \"Bar\""}) } //////////////////////////////////////////////////////////// // Test Case: Multiple unresolved types and values. //////////////////////////////////////////////////////////// { contents := ` const int32 X = Boom; struct Foo{ Bar x; Baz y = Bing; int32 z = X; }; struct Foo2 { Foo f = default; };` test.addTestCase(contents, []string{ "Undefined type: \"Bar\"", "Undefined type: \"Baz\"", "Undefined value: \"Boom\"", "Undefined value: \"Bing\"", "Use of unresolved value: \"X\"", "^^^^"}) // There will be four carets in the snippet because of "Boom" } //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range test.cases { // Parse and resolve the mojom input. descriptor := mojom.NewMojomDescriptor() specifiedName := "" if c.importedFrom == nil { specifiedName = c.fileName } parser := MakeParser(c.fileName, specifiedName, c.mojomContents, descriptor, c.importedFrom) parser.Parse() if !parser.OK() { t.Errorf("Parsing error for %s: %s", c.fileName, parser.GetError().Error()) continue } err := descriptor.Resolve() if err == nil { t.Errorf("Resolution unexpectedly succeeded for test case %d.", i) continue } got := err.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) } } } }
// TestSingleFileTypeValidationErrors() test the method UserTypeRef.validateAfterResolution(). // It is similar to TestSingleFileValueValidationErrors() // except that it tests the validation of types phase which occurs before the validation // of values phase. This phase detects errors that may not be detected during parsing // but that may be detected without resolving value references. func TestSingleFileTypeValidationErrors(t *testing.T) { test := singleFileTest{} //////////////////////////////////////////////////////////// // Group 1: Left-hand-side is a struct //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Use struct as constant type //////////////////////////////////////////////////////////// { contents := ` struct Foo{ }; const Foo MyFoo = 3; ` test.addTestCase(contents, []string{ "The type Foo is not allowed as the type of a declared constant.", "Only simple types, strings and enum types may be used"}) } //////////////////////////////////////////////////////////// // Test Case: Use struct as map key //////////////////////////////////////////////////////////// { contents := ` struct Foo{ }; struct Bar{ map<Foo, int32> x; }; ` test.addTestCase(contents, []string{ "The type Foo is not allowed as the key type of a map.", "Only simple types, strings and enum types may be map keys"}) } //////////////////////////////////////////////////////////// // Test Case: Assign an integer to a struct variable. //////////////////////////////////////////////////////////// { contents := ` struct Foo{ }; struct Bar{ Foo x = 42; }; ` test.addTestCase(contents, []string{ "Illegal assignment", "Field x of type Foo may not be assigned the value 42 of type int8"}) } //////////////////////////////////////////////////////////// // Group 2: Left-hand-side is an enum //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Assign an integer to an enum variable in a struct field. //////////////////////////////////////////////////////////// { contents := ` enum Hats { COWBOY, TOP }; struct Bar{ Hats my_hat = 1; }; ` test.addTestCase(contents, []string{ "Illegal assignment", "Field my_hat of type Hats may not be assigned the value 1 of type int8."}) } //////////////////////////////////////////////////////////// // Test Case: Assign an integer to an enum variable in a constant. //////////////////////////////////////////////////////////// { contents := ` enum Hats { COWBOY, TOP }; const Hats MY_HAT = 1; ` test.addTestCase(contents, []string{ "Illegal assignment", "Const MY_HAT of type Hats may not be assigned the value 1 of type int8."}) } //////////////////////////////////////////////////////////// // Group 3: Invalid use of interface request. //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Make an interface request out of a struct //////////////////////////////////////////////////////////// { contents := ` struct Foo{ }; struct Bar{ Foo& x; }; ` test.addTestCase(contents, []string{ "Invalid interface request specification", "Foo&. Foo is not an interface type"}) } //////////////////////////////////////////////////////////// // Test Case: Make a nullable interface request out of a struct //////////////////////////////////////////////////////////// { contents := ` struct Foo{ }; struct Bar{ Foo&? x; }; ` test.addTestCase(contents, []string{ "Invalid interface request specification", "Foo&?. Foo is not an interface type"}) } //////////////////////////////////////////////////////////// // Group 4: Invalid use of nullable //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Make nullable enum. //////////////////////////////////////////////////////////// { contents := ` enum Hats { COWBOY, TOP }; struct Bar{ Hats? my_hat; }; ` test.addTestCase(contents, []string{ "The type Hats? is invalid because Hats is an enum type and these may not be made nullable."}) } //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range test.cases { // Parse and resolve the mojom input. descriptor := mojom.NewMojomDescriptor() specifiedName := "" if c.importedFrom == nil { specifiedName = c.fileName } parser := MakeParser(c.fileName, specifiedName, c.mojomContents, descriptor, c.importedFrom) parser.Parse() if !parser.OK() { t.Errorf("Parsing error for %s: %s", c.fileName, parser.GetError().Error()) continue } err := descriptor.Resolve() if err == nil { t.Errorf("Resolution unexpectedly succeeded for test case %d.", i) continue } got := err.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) } } } }
// TestFindReachableTypes() iterates through a series of test cases. // For each case we expect for parsing and resolution to succeed. Then we // invoke FindReachableTypes() on |typeToSearch| and compare the result // to |expectedReachableTypes|. func TestFindReachableTypes(t *testing.T) { test := typeGraphTest{} //////////////////////////////////////////////////////////// // Test Case //////////////////////////////////////////////////////////// { contents := ` struct Struct1{ }; struct Struct2{ Struct1 x; }; struct Struct3{ Struct2 x; }; struct Struct4{ Struct3 x; }; union Union1 { Struct3 x; Struct2 y; }; union Union2 { Struct2 x; }; ` test.addTestCase(contents, "Struct4", []string{"Struct1", "Struct2", "Struct3", "Struct4"}, ) test.addTestCase(contents, "Union1", []string{"Union1", "Struct1", "Struct2", "Struct3"}, ) test.addTestCase(contents, "Union2", []string{"Union2", "Struct1", "Struct2"}, ) } //////////////////////////////////////////////////////////// // Test Case //////////////////////////////////////////////////////////// { contents := ` struct Struct1{ Struct4 x; }; struct Struct2{ Struct1 x; }; struct Struct3{ Struct2 x; }; struct Struct4{ Struct3 x; }; union Union1 { Struct2 x; }; ` test.addTestCase(contents, "Union1", []string{"Union1", "Struct1", "Struct2", "Struct3", "Struct4"}, ) } //////////////////////////////////////////////////////////// // Test Cases //////////////////////////////////////////////////////////// { contents := ` enum Height { SHORT, TALL }; struct Struct1{ enum Color { RED, BLUE }; }; struct Struct2{ const Struct1.Color FAVORITE = RED; }; ` test.addTestCase(contents, "Struct1", []string{"Struct1", "Struct1.Color"}, ) test.addTestCase(contents, "Struct2", []string{"Struct2", "Struct1.Color"}, ) } //////////////////////////////////////////////////////////// // Test Cases //////////////////////////////////////////////////////////// { contents := ` enum Color { RED, BLUE }; enum Height { SHORT, TALL }; struct Struct1{}; struct Struct2{}; interface Interface1 { const Color FAVORITE_COLOR = RED; Foo(int32 x) => (string y); }; interface Interface2 { Foo(int32 x) => (Struct1 y); }; ` test.addTestCase(contents, "Interface1", []string{"Interface1", "Color"}, ) test.addTestCase(contents, "Interface2", []string{"Interface2", "Struct1"}, ) } //////////////////////////////////////////////////////////// // Test Cases //////////////////////////////////////////////////////////// { contents := ` enum Color { RED, BLUE }; enum Height { SHORT, TALL }; struct Struct1{}; struct Struct2{}; interface Interface1 { const Color FAVORITE_COLOR = RED; Foo(map<Height, int8> x) => (string y); }; interface Interface2 { Foo(int32 x) => (array<Struct1?> y); }; ` test.addTestCase(contents, "Interface1", []string{"Interface1", "Color", "Height"}, ) test.addTestCase(contents, "Interface2", []string{"Interface2", "Struct1"}, ) } //////////////////////////////////////////////////////////// // Test Cases //////////////////////////////////////////////////////////// { contents := ` enum Color { RED, BLUE }; enum Height { SHORT, TALL }; struct Struct1{}; struct Struct2{}; interface Interface1 { const Color FAVORITE_COLOR = RED; Foo(int32 x) => (string y); Bar(map<string, Height> z) => (); }; interface Interface2 { Foo(int32 x) => (map<Height, Struct1?> y); }; ` test.addTestCase(contents, "Interface1", []string{"Interface1", "Color", "Height"}, ) test.addTestCase(contents, "Interface2", []string{"Interface2", "Height", "Struct1"}, ) } //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range test.cases { // Parse and resolve the mojom input. descriptor := mojom.NewMojomDescriptor() fileName := fmt.Sprintf("file%d", i) parser := MakeParser(fileName, fileName, c.mojomContents, descriptor, nil) parser.Parse() if !parser.OK() { t.Errorf("Parsing error for %s: %s", fileName, parser.GetError().Error()) continue } err := descriptor.Resolve() if err != nil { t.Errorf("Resolution failed for test case %d: %s", i, err.Error()) continue } userDefinedType := descriptor.TypesByKey[mojom.ComputeTypeKey(c.typeToSearch)] result := userDefinedType.FindReachableTypes() if err := compareTypeSets(descriptor, c.expectedReachableTypes, result); err != nil { t.Errorf("Case %d, unexpected typeset for %s: %s\n", i, c.typeToSearch, err.Error()) continue } } }
// TestSingleFileValueValidationErrors() test the function // UserValueRef.validateAfterResolution(). It tests that appropriate error messages are generated // when a .mojom file contains validation errors. In particular we are testing errors // that are not detected during parsing but are only detected after all names have // been resolved. Thus we are not testing here, for example, an attempt to assign // an int32 literal value to a string variable, because that type of error can be // detected during parsing. But we are testing the case of a constant whose value is // an int32 literal being assigned to a string variable. func TestSingleFileValueValidationErrors(t *testing.T) { test := singleFileTest{} //////////////////////////////////////////////////////////// // Group 1: The left-hand-side is an int32 variable. //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Assign constant of bool type to int32 variable. //////////////////////////////////////////////////////////// { contents := ` const bool Bar = true; struct Foo{ int32 x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value true of type bool may not be assigned to x of type int32"}) } //////////////////////////////////////////////////////////// // Test Case: Assign constant of string type to int32 variable. //////////////////////////////////////////////////////////// { contents := ` const string Bar = "Hi Bar"; struct Foo{ int32 x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value \"Hi Bar\" of type string may not be assigned to x of type int32"}) } //////////////////////////////////////////////////////////// // Test Case: Assign constant with value double.INFINITY to int32 variable. //////////////////////////////////////////////////////////// { contents := ` const double Bar = double.INFINITY; struct Foo{ int32 x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value double.INFINITY may not be assigned to x of type int32"}) } //////////////////////////////////////////////////////////// // Test Case: Assign double.INFINITY directly to an int32 variable. //////////////////////////////////////////////////////////// { contents := ` struct Foo{ int32 x = double.INFINITY; };` test.addTestCase(contents, []string{ "Illegal assignment", "double.INFINITY may not be assigned to x of type int32"}) } //////////////////////////////////////////////////////////// // Test Case: Assign constant of enum value to int32 variable. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; const MyEnum Bar = TWO; struct Foo{ int32 x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value MyEnum.TWO may not be assigned to x of type int32"}) } //////////////////////////////////////////////////////////// // Test Case: Assign enum value directly to int32 variable. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; const int32 Bar = MyEnum.TWO; ` test.addTestCase(contents, []string{ "Illegal assignment", "The enum value MyEnum.TWO of type MyEnum may not be assigned to Bar of type int32"}) } //////////////////////////////////////////////////////////// // Group 2: The left-hand-side is a string variable. //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Assign constant of int32 type to string variable. //////////////////////////////////////////////////////////// { contents := ` const int32 Bar = 42; struct Foo{ string? x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value 42 of type int8 may not be assigned to x of type string?"}) } //////////////////////////////////////////////////////////// // Test Case: Assign constant with value double.INFINITY to a string variable. //////////////////////////////////////////////////////////// { contents := ` const double Bar = double.INFINITY; struct Foo{ string x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value double.INFINITY may not be assigned to x of type string"}) } //////////////////////////////////////////////////////////// // Test Case: Assign double.INFINITY directly to a string variable. //////////////////////////////////////////////////////////// { contents := ` const string Bar = double.INFINITY; ` test.addTestCase(contents, []string{ "Illegal assignment", "double.INFINITY may not be assigned to Bar of type string"}) } //////////////////////////////////////////////////////////// // Test Case: Assign constant of enum value to string variable. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; const MyEnum Bar = TWO; struct Foo{ string x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value MyEnum.TWO may not be assigned to x of type string"}) } //////////////////////////////////////////////////////////// // Test Case: Assign enum value directly to a string variable. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; const string Bar = MyEnum.TWO; ` test.addTestCase(contents, []string{ "Illegal assignment", "The enum value MyEnum.TWO of type MyEnum may not be assigned to Bar of type string"}) } //////////////////////////////////////////////////////////// // Group 3: The left-hand-side is an enum variable. //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Assign constant of bool type to an enum variable. //////////////////////////////////////////////////////////// { contents := ` const bool Bar = true; enum MyEnum { ONE, TWO, THREE }; struct Foo{ MyEnum x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value true of type bool may not be assigned to x of type MyEnum"}) } //////////////////////////////////////////////////////////// // Test Case: Assign constant of value double.INFINITY to an enum variable. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; const double Bar = double.INFINITY; struct Foo{ MyEnum x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value double.INFINITY may not be assigned to x of type MyEnum"}) } //////////////////////////////////////////////////////////// // Test Case: Assign double.INFINITY directly to an enum variable. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; const MyEnum Bar = double.INFINITY; ` test.addTestCase(contents, []string{ "Illegal assignment", "double.INFINITY may not be assigned to Bar of type MyEnum"}) } //////////////////////////////////////////////////////////// // Test Case: Assign constant of enum value to a variable of a different enum type. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; enum MyOtherEnum { ONE, TWO, THREE }; const MyEnum Bar = TWO; struct Foo{ MyOtherEnum x = Bar; };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value MyEnum.TWO may not be assigned to x of type MyOtherEnum"}) } //////////////////////////////////////////////////////////// // Test Case: Assign enum value to a variable of a different enum type. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; enum MyOtherEnum { ONE, TWO, THREE }; const MyEnum Bar = MyOtherEnum.TWO; ` test.addTestCase(contents, []string{ "Illegal assignment", "The enum value MyOtherEnum.TWO of type MyOtherEnum may not be assigned to Bar of type MyEnum"}) } //////////////////////////////////////////////////////////// // Test Case: Use an enum value from a different enum as an enum value initializer. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; enum MyOtherEnum { ONE, TWO = MyEnum.TWO, THREE }; ` test.addTestCase(contents, []string{ "Illegal assignment", "The enum value MyEnum.TWO of type MyEnum may not be used as an initializer for TWO of type MyOtherEnum."}) } //////////////////////////////////////////////////////////// // Test Case: Use a constant with a value of an enum value from a different enum as an enum value initializer. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; const MyEnum Bar = MyEnum.TWO; enum MyOtherEnum { ONE, TWO = Bar, THREE }; ` test.addTestCase(contents, []string{ "Illegal assignment", "Bar with the value MyEnum.TWO may not be used as an initializer for TWO of type MyOtherEnum."}) } //////////////////////////////////////////////////////////// // Test Case: Assign the default keyword to a variable of enum type. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { }; struct Foo{ MyEnum x = default; };` test.addTestCase(contents, []string{ "Illegal assignment", "The 'default' keyword may not be used with the field x of type MyEnum."}) } //////////////////////////////////////////////////////////// // Group 4: The left-hand-side is an interface varialbe. //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Assign an enum value to an interface //////////////////////////////////////////////////////////// { contents := ` interface Foo{ enum MyEnum { ONE }; }; struct Bar{ Foo&? x = Foo.MyEnum.ONE; };` test.addTestCase(contents, []string{ "Illegal assignment", "The enum value Foo.MyEnum.ONE of type Foo.MyEnum may not be assigned to x of type Foo&?."}) } //////////////////////////////////////////////////////////// // Test Case: Assign constant of value double.INFINITY to an enum variable. //////////////////////////////////////////////////////////// { contents := ` interface Foo{ const double BIG = double.INFINITY; }; struct Bar{ Foo? x = Foo.BIG; };` test.addTestCase(contents, []string{ "Illegal assignment", "Foo.BIG with the value double.INFINITY may not be assigned to x of type Foo?."}) } //////////////////////////////////////////////////////////// // Test Case: Assign double.INFINITY directly to an interface variable. //////////////////////////////////////////////////////////// { contents := ` interface Foo{ }; struct Bar{ Foo& x = double.INFINITY; }; ` test.addTestCase(contents, []string{ "Illegal assignment", "double.INFINITY may not be assigned to x of type Foo&."}) } //////////////////////////////////////////////////////////// // Test Case: Assign the default keyword to a variable of interface type. //////////////////////////////////////////////////////////// { contents := ` interface Foo{ }; struct Bar{ Foo& x = default; };` test.addTestCase(contents, []string{ "Illegal assignment", "The 'default' keyword may not be used with the field x of type Foo&."}) } //////////////////////////////////////////////////////////// // Group 5: Multiple indirection //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Assign doubly-indirected constant of bool type to int32 variable. //////////////////////////////////////////////////////////// { contents := ` const bool Bar = true; const bool Baz = Bar; struct Foo{ int32 x = Baz; };` test.addTestCase(contents, []string{ "Illegal assignment", "Baz with the value true of type bool may not be assigned to x of type int32"}) } //////////////////////////////////////////////////////////// // Test Case: Assign doubly-indirected constant with value double.INFINITY to int32 variable. //////////////////////////////////////////////////////////// { contents := ` const double Bar = double.INFINITY; const double Baz = Bar; struct Foo{ int32 x = Baz; };` test.addTestCase(contents, []string{ "Illegal assignment", "Baz with the value double.INFINITY may not be assigned to x of type int32"}) } //////////////////////////////////////////////////////////// // Test Case: Assign doubly-indirected constant of enum value to int32 variable. //////////////////////////////////////////////////////////// { contents := ` enum MyEnum { ONE, TWO, THREE }; const MyEnum Bar = TWO; const MyEnum Baz = Bar; struct Foo{ int32 x = Baz; };` test.addTestCase(contents, []string{ "Illegal assignment", "Baz with the value MyEnum.TWO may not be assigned to x of type int32"}) } //////////////////////////////////////////////////////////// // Group 4: Invalid EnumValue initializers. //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Use string as enum value initializer. //////////////////////////////////////////////////////////// { contents := ` const int32 Foo = 8; const string Bar = "fly"; enum MyEnum { ONE = Foo, TWO = Bar, THREE };` test.addTestCase(contents, []string{ "Illegal assignment", "Bar cannot be used as an enum value initializer because its value, \"fly\", is not a signed 32-bit integer"}) } //////////////////////////////////////////////////////////// // Test Case: Use double.NAN as an enum value initializer. //////////////////////////////////////////////////////////// { contents := ` const int32 Foo = 8; const int64 Bar = 9; enum MyEnum { ONE = Foo, TWO = Bar, THREE = double.NAN };` test.addTestCase(contents, []string{ "Illegal assignment", "double.NAN cannot be used as an enum value initializer"}) } //////////////////////////////////////////////////////////// // Group 5: Non-referent shadows referent //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Field name shadows constant name //////////////////////////////////////////////////////////// { contents := ` const int32 foo = 42; struct MyStruct { string foo = "hello"; int32 bar = foo; };` test.addTestCase(contents, []string{ "Error", "\"foo\" does not refer to a value. It refers to the field MyStruct.foo at"}) } //////////////////////////////////////////////////////////// // Test Case: Method name shadows constant name //////////////////////////////////////////////////////////// { contents := ` const int32 foo = 42; interface MyInterface { foo(); const int32 bar = foo; };` test.addTestCase(contents, []string{ "Error", "\"foo\" does not refer to a value. It refers to the method MyInterface.foo at"}) } //////////////////////////////////////////////////////////// // Test Case: Field name shadows type name //////////////////////////////////////////////////////////// { contents := ` struct SomethingGood{}; struct MySecondStruct { int32 SomethingGood = 7; SomethingGood x = default; };` test.addTestCase(contents, []string{ "Error", "\"SomethingGood\" does not refer to a type. It refers to the field MySecondStruct.SomethingGood at"}) } //////////////////////////////////////////////////////////// // Test Case: Method name shadows type name //////////////////////////////////////////////////////////// { contents := ` struct SomethingGood{}; interface MyInterface { SomethingGood(); DoIt(SomethingGood x); };` test.addTestCase(contents, []string{ "Error", "\"SomethingGood\" does not refer to a type. It refers to the method MyInterface.SomethingGood at"}) } //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range test.cases { // Parse and resolve the mojom input. descriptor := mojom.NewMojomDescriptor() specifiedName := "" if c.importedFrom == nil { specifiedName = c.fileName } parser := MakeParser(c.fileName, specifiedName, c.mojomContents, descriptor, c.importedFrom) parser.Parse() if !parser.OK() { t.Errorf("Parsing error for %s: %s", c.fileName, parser.GetError().Error()) continue } err := descriptor.Resolve() if err == nil { t.Errorf("Resolution unexpectedly succeeded for test case %d.", i) continue } got := err.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) } } } }
// Parses each of the given .mojom files and all of the files in the // import graph rooted by each file. A single MojomDescriptor is created and // populated with the result of parsing all of those files. If the parsing is // successful then err will be nil. // // fileNames must not be nil or we will panic. func (d *ParseDriver) ParseFiles(fileNames []string) (descriptor *mojom.MojomDescriptor, err error) { if fileNames == nil { // We panic instead of returning an error here because this would be a programming error // as opposed to an error in the input. panic("fileNames may not be nil.") } filesToProcess := make([]*FileReference, len(fileNames)) descriptor = mojom.NewMojomDescriptor() for i, fileName := range fileNames { filesToProcess[i] = &FileReference{specifiedPath: fileName} } for len(filesToProcess) > 0 { currentFile := filesToProcess[0] filesToProcess = filesToProcess[1:] if err = d.fileProvider.findFile(currentFile); err != nil { return } if currentFile.importedFrom != nil { // Tell the importing file about the absolute path of the imported file. // Note that we must do this even if the imported file has already been processed // because a given file may be imported by multiple files and each of those need // to be told about the absolute path of the imported file. currentFile.importedFrom.mojomFile.SetCanonicalImportName(currentFile.specifiedPath, currentFile.absolutePath) } if !descriptor.ContainsFile(currentFile.absolutePath) { contents, fileReadError := d.fileProvider.provideContents(currentFile) if fileReadError != nil { err = fileReadError return } parser := MakeParser(currentFile.absolutePath, contents, descriptor) parser.SetDebugMode(d.debugMode) // Invoke parser.Parse() (but skip doing so in tests sometimes.) d.parseInvoker.invokeParse(&parser) if d.debugMode { fmt.Printf("\nParseTree for %s:", currentFile) fmt.Println(parser.GetParseTree()) } if !parser.OK() { err = fmt.Errorf("\nError while parsing %s: \n%s\n", currentFile, parser.GetError().Error()) return } currentFile.mojomFile = d.fileExtractor.extractMojomFile(&parser) for _, importedFile := range currentFile.mojomFile.Imports { // Note that it is important that we append all of the imported files here even // if some of them have already been processed. That is because when the imported // file is pulled from the queue it will be pre-processed during which time the // absolute path to the file will be discovered and this absolute path will be // set in |mojomFile| which is necessary for serializing mojomFile. filesToProcess = append(filesToProcess, &FileReference{importedFrom: currentFile, specifiedPath: importedFile.SpecifiedName}) } } } // Perform type and value resolution if err = descriptor.Resolve(); err != nil { return } // Compute enum value integers. if err = descriptor.ComputeEnumValueIntegers(); err != nil { return } // Compute data for generators. err = descriptor.ComputeDataForGenerators() return }
func TestDuplicateNameErrorsTwoFiles(t *testing.T) { type testFile struct { fileName string mojomContents string } type testCase struct { file1, file2 testFile expectedErrors []string } cases := make([]testCase, 0) testCaseNum := 0 startTestCase := func() { fileNameA := fmt.Sprintf("file%dA", testCaseNum) fileNameB := fmt.Sprintf("file%dB", testCaseNum) cases = append(cases, testCase{ file1: testFile{fileName: fileNameA}, file2: testFile{fileName: fileNameB}}) } expectError := func(expectedError string) { cases[testCaseNum].expectedErrors = append(cases[testCaseNum].expectedErrors, expectedError) } fileName1 := func() string { return cases[testCaseNum].file1.fileName } endTestCase := func() { testCaseNum += 1 } //////////////////////////////////////////////////////////// // Test Case (two types with the same name in different scopes) //////////////////////////////////////////////////////////// startTestCase() cases[testCaseNum].file1.mojomContents = ` module a.b.c; struct Foo { }; ` cases[testCaseNum].file2.mojomContents = ` module a.b; struct c { enum Foo{}; }; ` expectError("Duplicate definition for \"Foo\". Previous definition with the same fully-qualified name:") expectError("struct a.b.c.Foo at " + fileName1()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (two vales with the same name in different scopes) //////////////////////////////////////////////////////////// startTestCase() cases[testCaseNum].file1.mojomContents = ` module a.b.c; enum Hats { COWBOY = 1, TOP }; ` cases[testCaseNum].file2.mojomContents = ` module a.b.c.Hats; const double TOP = 4.9; ` expectError("Duplicate definition for \"TOP\". Previous definition with the same fully-qualified name:") expectError("enum value a.b.c.Hats.TOP at " + fileName1()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (a value with the same name as a type in a different scope) //////////////////////////////////////////////////////////// startTestCase() cases[testCaseNum].file1.mojomContents = ` module a.b.c; interface Hats{}; ` cases[testCaseNum].file2.mojomContents = ` module a.b.c; const double Hats = 4.9; ` expectError("Duplicate definition for \"Hats\". Previous definition with the same fully-qualified name:") expectError("interface a.b.c.Hats at " + fileName1()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (another value with the same name as a type in a different scope) //////////////////////////////////////////////////////////// startTestCase() cases[testCaseNum].file1.mojomContents = ` module a.b.c.Hats; interface COWBOY{}; ` cases[testCaseNum].file2.mojomContents = ` module a.b.c; enum Hats { COWBOY = 1, TOP }; ` expectError("Duplicate definition for \"COWBOY\". Previous definition with the same fully-qualified name:") expectError("interface a.b.c.Hats.COWBOY at " + fileName1()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (a type with the same name as a value in a different scope) //////////////////////////////////////////////////////////// startTestCase() cases[testCaseNum].file1.mojomContents = ` module a.b.c; enum Hats { COWBOY = 1, TOP }; ` cases[testCaseNum].file2.mojomContents = ` module a.b.c.Hats; interface COWBOY{}; ` expectError("Duplicate definition for \"COWBOY\". Previous definition with the same fully-qualified name:") expectError("enum value a.b.c.Hats.COWBOY at " + fileName1()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (a value with the same name as a method in a different scope) //////////////////////////////////////////////////////////// startTestCase() cases[testCaseNum].file1.mojomContents = ` module a.b; interface Big { Run(); }; ` cases[testCaseNum].file2.mojomContents = ` module a.b.Big; const int32 Run = 0; ` expectError("Duplicate definition for \"Run\". Previous definition with the same fully-qualified name:") expectError("method a.b.Big.Run at " + fileName1()) endTestCase() //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range cases { descriptor := mojom.NewMojomDescriptor() parser := MakeParser(c.file1.fileName, c.file1.fileName, c.file1.mojomContents, descriptor, nil) parser.Parse() if !parser.OK() { t.Errorf("Parsing was supposed to succeed for file1 but did not for test case %d: %s", i, parser.GetError().Error()) } parser = MakeParser(c.file2.fileName, c.file2.fileName, c.file2.mojomContents, descriptor, nil) parser.Parse() if parser.OK() { t.Errorf("Parsing was supposed to fail for file2 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.file2.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) } } } } }
func TestDuplicateNameErrorsSingleFile(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) } fileName := func() string { return cases[testCaseNum].fileName } endTestCase := func() { testCaseNum += 1 } //////////////////////////////////////////////////////////// // Test Case: Duplicate struct field name. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { int32 x = 3; string y = "hello"; float x = 1.7; }; ` expectError("Error") expectError("Duplicate definition of 'x'.") expectError("There is already a field with that name in struct Foo.") endTestCase() //////////////////////////////////////////////////////////// // Test Case: Duplicate union field name. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` union Foo { int32 x; string y; float x ; }; ` expectError("Error") expectError("Duplicate definition of 'x'.") expectError("There is already a field with that name in union Foo.") endTestCase() //////////////////////////////////////////////////////////// // Test Case: Duplicate method request parameter name. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` interface Foo { DoIt(int32 x, string y, float x); }; ` expectError("Error") expectError("Duplicate definition of 'x'.") expectError("There is already a request parameter with that name in method DoIt.") endTestCase() //////////////////////////////////////////////////////////// // Test Case: Duplicate method response parameter name. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` interface Foo { DoIt() => (int32 x, string y, float x); }; ` expectError("Error") expectError("Duplicate definition of 'x'.") expectError("There is already a response parameter with that name in method DoIt.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (duplicate method names) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` interface MyInterface { MethodA(); MethodB(); MethodC(); MethodB(); MethodD(); }; ` expectError("Duplicate definition for \"MethodB\". Previous definition with the same fully-qualified name:") expectError("method MyInterface.MethodB at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (A method and an enum with the same name) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` interface MyInterface { Foo(); Bar(); enum Foo{ PLAID = 1, CHECKERED = 2 }; }; ` expectError("Duplicate definition for \"Foo\". Previous definition with the same fully-qualified name:") expectError("method MyInterface.Foo at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (An enum and a method with the same name) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` interface MyInterface { Bar(); enum Foo{ PLAID = 1, CHECKERED = 2 }; Foo(); }; ` expectError("Duplicate definition for \"Foo\". Previous definition with the same fully-qualified name:") expectError("enum MyInterface.Foo at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (A method and a constant with the same name) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` interface MyInterface { Foo(); Bar(); const int32 Foo = 7; }; ` expectError("Duplicate definition for \"Foo\". Previous definition with the same fully-qualified name:") expectError("method MyInterface.Foo at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (A constant and a method with the same name) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` interface MyInterface { const int32 Foo = 7; Foo(); Bar(); }; ` expectError("Duplicate definition for \"Foo\". Previous definition with the same fully-qualified name:") expectError("const MyInterface.Foo at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (A constant and a field with the same name) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct MyStruct { const int32 Bar = 7; string Foo; int32 Bar; }; ` expectError("Duplicate definition for \"Bar\". Previous definition with the same fully-qualified name:") expectError("const MyStruct.Bar at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (A field and a constant with the same name) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct MyStruct { string Foo; int32 Bar; const int32 Bar = 7; }; ` expectError("Duplicate definition for \"Bar\". Previous definition with the same fully-qualified name:") expectError("field MyStruct.Bar at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (two types with the same name in file scope) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` module a.b.c; interface Foo { }; struct Bar { }; struct Foo { }; ` expectError("Duplicate definition for \"Foo\". Previous definition with the same fully-qualified name:") expectError("interface a.b.c.Foo at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (two values with the same name in file scope) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` module a.b.c; const int32 NUM_HATS = 6; const string NUM_HATS = "6"; ` expectError("Duplicate definition for \"NUM_HATS\". Previous definition with the same fully-qualified name:") expectError("const a.b.c.NUM_HATS at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (a value with the same name as a type at file scope) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` module a.b.c; union Foo { }; const int32 Foo = 42; ` expectError("Duplicate definition for \"Foo\". Previous definition with the same fully-qualified name:") expectError("union a.b.c.Foo at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Test Case (a type with the same name as a value) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` module a.b.c; const int32 Foo = 42; union Foo { }; ` expectError("Duplicate definition for \"Foo\". Previous definition with the same fully-qualified name:") expectError("const a.b.c.Foo at " + fileName()) endTestCase() //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range cases { descriptor := mojom.NewMojomDescriptor() parser := MakeParser(c.fileName, c.fileName, c.mojomContents, descriptor, nil) 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) } } } } }
// 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) } } } }
// TestLexerErrors 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. The particular type of error we are testing // here are cases in which the lexer detects an error and returns one of // its error tokens. func TestLexerErrors(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 } //////////////////////////////////////////////////////////// // Group 1: Unterminated comment //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case: Unterminated comment at start of file. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` /* * The woods are lovely * dark and deep * but I have promises to keep. struct Foo { int32 x = "hello"; }; ` expectError("Error") expectError("unterminated comment") endTestCase() //////////////////////////////////////////////////////////// // Test Case: Unterminated comment at end of file. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { int32 x ; }; /* * The woods are lovely * dark and deep * but I have promises to keep. ` expectError("Error") expectError("unterminated comment") endTestCase() //////////////////////////////////////////////////////////// // Test Case: Unterminated comment in the middle. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { int32 x ; /* * The woods are lovely * dark and deep * but I have promises to keep. };` expectError("Error") expectError("unterminated comment") endTestCase() //////////////////////////////////////////////////////////// // Group 2: Unterminated string literal //////////////////////////////////////////////////////////// /// //////////////////////////////////////////////////////////// // Test Case: Unterminated string literal in import. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` import "foo.bar struct Foo { int32 x = 42; }; ` expectError("Error") expectError("unterminated string literal") endTestCase() /// //////////////////////////////////////////////////////////// // Test Case: Unterminated string literal in assignment. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` import "foo.bar"; struct Foo { string x = "hello; }; ` expectError("Error") expectError("unterminated string literal") endTestCase() //////////////////////////////////////////////////////////// // Group 3: ErrorIllegalChar //////////////////////////////////////////////////////////// /// //////////////////////////////////////////////////////////// // Test Case: ErrorIllegalChar at the beginning of a file. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` /? What the ?/ import "foo.bar" struct Foo { int32 x = 42; }; ` expectError("Error:") expectError("Unexpected \"/\"") endTestCase() /// //////////////////////////////////////////////////////////// // Test Case: ErrorIllegalChar in the middle. //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` import "foo.bar"; struct Foo { int32 x = %42; }; ` expectError("Error:") expectError("Unexpected \"%\"") endTestCase() /// //////////////////////////////////////////////////////////// // Test Case: ErrorIllegalChar at the end //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` import "foo.bar"; struct Foo { int32 x = 42; }; * ` expectError("Error:") expectError("Unexpected \"*\"") endTestCase() //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range cases { descriptor := mojom.NewMojomDescriptor() parser := MakeParser(c.fileName, c.fileName, c.mojomContents, descriptor, nil) 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) } } } } }
// TestInvalidAssignmentDuringParsing 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. The particular type of error we are testing // here is invalid assignments of values to variables that may be detected during // parsing. func TestInvalidAssignmentDuringParsing(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 } //////////////////////////////////////////////////////////// // Group 1: Assign to struct field default value. //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case (Assign string to int32) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { int32 x = "hello"; }; ` expectError("Illegal assignment") expectError("Field x of type int32 may not be assigned the value \"hello\" of type string.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Assign int32 to string) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { string? x = 42; }; ` expectError("Illegal assignment") expectError("Field x of type string? may not be assigned the value 42 of type int8.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Assign negative number to unit8) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { uint8 x = -1; }; ` expectError("Illegal assignment") expectError("Field x of type uint8 may not be assigned the value -1 of type int8.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Assign large integer to unit8) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { uint8 x = 9999999999; }; ` expectError("Illegal assignment") expectError("Field x of type uint8 may not be assigned the value 9999999999 of type int64.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Assign large float to float32) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { float x = 3.14159E40; }; ` expectError("Illegal assignment") expectError("Field x of type float may not be assigned the value 3.14159e+40 of type double.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Assign default keyword to string) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { string x = default; }; ` expectError("Illegal assignment") expectError("The 'default' keyword may not be used with the field x of type string") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Assign integer to array field) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { }; struct Bar { array<Foo?, 8>? x = 7; }; ` expectError("Illegal assignment") expectError("Field x of type array<Foo? ,8>? may not be assigned the value 7 of type int8.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Assign default keyword to map field) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { }; struct Bar { map<bool, Foo?>? x = default; }; ` expectError("Illegal assignment") expectError("The 'default' keyword may not be used with the field x of type map<bool, Foo?>?.") endTestCase() //////////////////////////////////////////////////////////// // Group 2: Assign to constant. //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Test Case (Assign boolean to int32) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` const int32 Foo = true; ` expectError("Illegal assignment") expectError("Constant Foo of type int32 may not be assigned the value true of type bool.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Assign string to bool) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` const bool Foo = "true"; ` expectError("Illegal assignment") expectError("Constant Foo of type bool may not be assigned the value \"true\" of type string.") endTestCase() //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range cases { descriptor := mojom.NewMojomDescriptor() parser := MakeParser(c.fileName, c.fileName, c.mojomContents, descriptor, nil) 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) } } } } }
// 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 ") 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 ") 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 (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() //////////////////////////////////////////////////////////// // Test Case (Constant integer too big for uint64) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` const uint64 manyNines = 99999999999999999999; ` expectError("Integer literal value out of range") expectError("99999999999999999999") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Constant float too big for double) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` const uint64 veryBig = 3.14159E400; ` expectError("Float literal value out of range: 3.14159E400") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Use array as constant type) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` const array<uint64> Foo = 0; ` expectError("The type array<uint64> is not allowed as the type of a declared constant.") expectError("Only simple types, strings and enum types may be the types of constants.") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Identifier ends with a dot) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` const array<my.Foo.Type.> Foo = 0; ` expectError("Invalid identifier") expectError("\"my.Foo.Type.\"") expectError("Identifiers may not end with a dot") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Unrecognized type of handle) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { handle<drawer> x; }; ` expectError("Unrecognized type of handle: handle<drawer>") endTestCase() //////////////////////////////////////////////////////////// // Test Case (Nullable bool) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` struct Foo { bool? x; }; ` expectError("The type bool? is invalid because the type bool may not be made nullable") endTestCase() //////////////////////////////////////////////////////////// // Execute all of the test cases. //////////////////////////////////////////////////////////// for i, c := range cases { descriptor := mojom.NewMojomDescriptor() parser := MakeParser(c.fileName, c.fileName, c.mojomContents, descriptor, nil) 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) } } } } }
// 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, fileName, nil, "") 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 (Integer constants) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` const uint8 xu8 = 255; const int8 x8 = -127; const uint16 xu16 = 0xFFFF; const int16 x16 = -0x7FFF; const uint32 xu32 = 4294967295; const int32 x32 = -2147483647; const uint64 xu64 = 0xFFFFFFFFFFFFFFFF; const int64 x64 = -0x7FFFFFFFFFFFFFFF; const uint64 manyNines = 9999999999999999999; ` expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("xu8"), mojom.SimpleTypeUInt8, mojom.MakeUint8LiteralValue(0xFF))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("x8"), mojom.SimpleTypeInt8, mojom.MakeInt8LiteralValue(-0x7F))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("xu16"), mojom.SimpleTypeUInt16, mojom.MakeUint16LiteralValue(65535))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("x16"), mojom.SimpleTypeInt16, mojom.MakeInt16LiteralValue(-32767))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("xu32"), mojom.SimpleTypeUInt32, mojom.MakeUint32LiteralValue(0xFFFFFFFF))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("x32"), mojom.SimpleTypeInt32, mojom.MakeInt32LiteralValue(-0x7FFFFFFF))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("xu64"), mojom.SimpleTypeUInt64, mojom.MakeUint64LiteralValue(18446744073709551615))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("x64"), mojom.SimpleTypeInt64, mojom.MakeInt64LiteralValue(-9223372036854775807))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("manyNines"), mojom.SimpleTypeUInt64, mojom.MakeUint64LiteralValue(9999999999999999999))) endTestCase() //////////////////////////////////////////////////////////// // Test Case (float and double constants) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` const float x = 123.456E7; const float y = 123456789.123456789; const float z = -0.01; const double w = -0.01; const double r = 3.14159E40; ` { expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("x"), mojom.SimpleTypeFloat, mojom.MakeDoubleLiteralValue(1234560000))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("y"), mojom.SimpleTypeFloat, mojom.MakeDoubleLiteralValue(123456789.123456789))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("z"), mojom.SimpleTypeFloat, mojom.MakeDoubleLiteralValue(-0.01))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("w"), mojom.SimpleTypeDouble, mojom.MakeDoubleLiteralValue(-0.01))) expectedFile.AddConstant(mojom.NewUserDefinedConstant(mojom.DeclTestData("r"), mojom.SimpleTypeDouble, mojom.MakeDoubleLiteralValue(3.14159e+40))) } endTestCase() //////////////////////////////////////////////////////////// // Test Case //////////////////////////////////////////////////////////// startTestCase("mojom.test") cases[testCaseNum].mojomContents = ` module mojom.test; struct Foo{ int32 x; };` { structFoo := mojom.NewMojomStruct(mojom.DeclTestData("Foo")) structFoo.InitAsScope(mojom.NewTestFileScope("test.scope")) 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")) structFoo.InitAsScope(mojom.NewTestFileScope("test.scope")) 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; bool w@6 = false; };` { expectedFile.AddImport("another.file") expectedFile.AddImport("and.another.file") structFoo := mojom.NewMojomStruct(mojom.DeclTestData("Foo")) structFoo.InitAsScope(mojom.NewTestFileScope("test.scope")) structFoo.AddField(mojom.NewStructField(mojom.DeclTestDataWithOrdinal("x", 4), mojom.SimpleTypeInt32, mojom.MakeInt8LiteralValue(42))) attributes := mojom.NewAttributes() attributes.List = append(attributes.List, mojom.MojomAttribute{"age", mojom.MakeInt8LiteralValue(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)) structFoo.AddField(mojom.NewStructField(mojom.DeclTestDataWithOrdinal("w", 6), mojom.BuiltInType("bool"), mojom.MakeBoolLiteralValue(false))) 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.InitAsScope(mojom.NewTestFileScope("test.scope")) 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")) interfaceDoer.InitAsScope(mojom.NewTestFileScope("test.scope")) // 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.InitAsScope(mojom.NewTestFileScope("test.scope")) 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.InitAsScope(mojom.NewTestFileScope("test.scope")) 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 (Annotation right after imports) //////////////////////////////////////////////////////////// startTestCase("") cases[testCaseNum].mojomContents = ` import "gpu/interfaces/command_buffer.mojom"; [ServiceName="mojo::Gpu"] interface Gpu { }; ` { expectedFile.AddImport("gpu/interfaces/command_buffer.mojom") attributes := mojom.NewAttributes() attributes.List = append(attributes.List, mojom.MojomAttribute{"ServiceName", mojom.MakeStringLiteralValue("mojo::Gpu")}) interfaceGpu := mojom.NewMojomInterface(mojom.DeclTestDataA("Gpu", attributes)) expectedFile.AddInterface(interfaceGpu) } 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.MakeInt8LiteralValue(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(mojom.AssigneeSpec{"assignee", assigneeType}, "TOO_SMALL_VALUE", expectedFile.FileScope, lexer.Token{}) errorCodeEnum.AddEnumValue(mojom.DeclTestData("TOO_BIG"), mojom.MakeInt8LiteralValue(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.InitAsScope(mojom.NewTestFileScope("test.scope")) 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")) interfaceDoer.InitAsScope(mojom.NewTestFileScope("test.scope")) // 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.InitAsScope(mojom.NewTestFileScope("test.scope")) 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.InitAsScope(mojom.NewTestFileScope("test.scope")) 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.fileName, c.mojomContents, descriptor, nil) 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) } } } }