// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package blueprint import ( "bytes" "fmt" "reflect" "testing" "text/scanner" "github.com/google/blueprint/parser" "github.com/google/blueprint/proptools" ) var validUnpackTestCases = []struct { input string output []interface{} empty []interface{} errs []error }{ { input: ` m { name: "abc", blank: "", } `, output: []interface{}{ struct { Name *string Blank *string Unset *string }{ Name: proptools.StringPtr("abc"), Blank: proptools.StringPtr(""), Unset: nil, }, }, }, { input: ` m { name: "abc", } `, output: []interface{}{ struct { Name string }{ Name: "abc", }, }, }, { input: ` m { isGood: true, } `, output: []interface{}{ struct { IsGood bool }{ IsGood: true, }, }, }, { input: ` m { isGood: true, isBad: false, } `, output: []interface{}{ struct { IsGood *bool IsBad *bool IsUgly *bool }{ IsGood: proptools.BoolPtr(true), IsBad: proptools.BoolPtr(false), IsUgly: nil, }, }, }, { input: ` m { stuff: ["asdf", "jkl;", "qwert", "uiop", "bnm,"], empty: [] } `, output: []interface{}{ struct { Stuff []string Empty []string Nil []string }{ Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"}, Empty: []string{}, Nil: nil, }, }, }, { input: ` m { nested: { name: "abc", } } `, output: []interface{}{ struct { Nested struct { Name string } }{ Nested: struct{ Name string }{ Name: "abc", }, }, }, }, { input: ` m { nested: { name: "def", } } `, output: []interface{}{ struct { Nested interface{} }{ Nested: &struct{ Name string }{ Name: "def", }, }, }, }, { input: ` m { nested: { foo: "abc", }, bar: false, baz: ["def", "ghi"], } `, output: []interface{}{ struct { Nested struct { Foo string } Bar bool Baz []string }{ Nested: struct{ Foo string }{ Foo: "abc", }, Bar: false, Baz: []string{"def", "ghi"}, }, }, }, { input: ` m { nested: { foo: "abc", }, bar: false, baz: ["def", "ghi"], } `, output: []interface{}{ struct { Nested struct { Foo string `allowNested:"true"` } `blueprint:"filter(allowNested:\"true\")"` Bar bool Baz []string }{ Nested: struct { Foo string `allowNested:"true"` }{ Foo: "abc", }, Bar: false, Baz: []string{"def", "ghi"}, }, }, }, { input: ` m { nested: { foo: "abc", }, bar: false, baz: ["def", "ghi"], } `, output: []interface{}{ struct { Nested struct { Foo string } `blueprint:"filter(allowNested:\"true\")"` Bar bool Baz []string }{ Nested: struct{ Foo string }{ Foo: "", }, Bar: false, Baz: []string{"def", "ghi"}, }, }, errs: []error{ &Error{ Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"), Pos: mkpos(30, 4, 9), }, }, }, // Anonymous struct { input: ` m { name: "abc", nested: { name: "def", }, } `, output: []interface{}{ struct { EmbeddedStruct Nested struct { EmbeddedStruct } }{ EmbeddedStruct: EmbeddedStruct{ Name: "abc", }, Nested: struct { EmbeddedStruct }{ EmbeddedStruct: EmbeddedStruct{ Name: "def", }, }, }, }, }, // Anonymous interface { input: ` m { name: "abc", nested: { name: "def", }, } `, output: []interface{}{ struct { EmbeddedInterface Nested struct { EmbeddedInterface } }{ EmbeddedInterface: &struct{ Name string }{ Name: "abc", }, Nested: struct { EmbeddedInterface }{ EmbeddedInterface: &struct{ Name string }{ Name: "def", }, }, }, }, }, // Anonymous struct with name collision { input: ` m { name: "abc", nested: { name: "def", }, } `, output: []interface{}{ struct { Name string EmbeddedStruct Nested struct { Name string EmbeddedStruct } }{ Name: "abc", EmbeddedStruct: EmbeddedStruct{ Name: "abc", }, Nested: struct { Name string EmbeddedStruct }{ Name: "def", EmbeddedStruct: EmbeddedStruct{ Name: "def", }, }, }, }, }, // Anonymous interface with name collision { input: ` m { name: "abc", nested: { name: "def", }, } `, output: []interface{}{ struct { Name string EmbeddedInterface Nested struct { Name string EmbeddedInterface } }{ Name: "abc", EmbeddedInterface: &struct{ Name string }{ Name: "abc", }, Nested: struct { Name string EmbeddedInterface }{ Name: "def", EmbeddedInterface: &struct{ Name string }{ Name: "def", }, }, }, }, }, // Variables { input: ` list = ["abc"] string = "def" list_with_variable = [string] m { name: string, list: list, list2: list_with_variable, } `, output: []interface{}{ struct { Name string List []string List2 []string }{ Name: "def", List: []string{"abc"}, List2: []string{"def"}, }, }, }, // Multiple property structs { input: ` m { nested: { name: "abc", } } `, output: []interface{}{ struct { Nested struct { Name string } }{ Nested: struct{ Name string }{ Name: "abc", }, }, struct { Nested struct { Name string } }{ Nested: struct{ Name string }{ Name: "abc", }, }, struct { }{}, }, }, // Nil pointer to struct { input: ` m { nested: { name: "abc", } } `, output: []interface{}{ struct { Nested *struct { Name string } }{ Nested: &struct{ Name string }{ Name: "abc", }, }, }, empty: []interface{}{ &struct { Nested *struct { Name string } }{}, }, }, // Interface containing nil pointer to struct { input: ` m { nested: { name: "abc", } } `, output: []interface{}{ struct { Nested interface{} }{ Nested: &EmbeddedStruct{ Name: "abc", }, }, }, empty: []interface{}{ &struct { Nested interface{} }{ Nested: (*EmbeddedStruct)(nil), }, }, }, } type EmbeddedStruct struct{ Name string } type EmbeddedInterface interface{} func TestUnpackProperties(t *testing.T) { for _, testCase := range validUnpackTestCases { r := bytes.NewBufferString(testCase.input) file, errs := parser.ParseAndEval("", r, parser.NewScope(nil)) if len(errs) != 0 { t.Errorf("test case: %s", testCase.input) t.Errorf("unexpected parse errors:") for _, err := range errs { t.Errorf(" %s", err) } t.FailNow() } for _, def := range file.Defs { module, ok := def.(*parser.Module) if !ok { continue } var output []interface{} if len(testCase.empty) > 0 { output = testCase.empty } else { for _, p := range testCase.output { output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface()) } } _, errs = unpackProperties(module.Properties, output...) if len(errs) != 0 && len(testCase.errs) == 0 { t.Errorf("test case: %s", testCase.input) t.Errorf("unexpected unpack errors:") for _, err := range errs { t.Errorf(" %s", err) } t.FailNow() } else if !reflect.DeepEqual(errs, testCase.errs) { t.Errorf("test case: %s", testCase.input) t.Errorf("incorrect errors:") t.Errorf(" expected: %+v", testCase.errs) t.Errorf(" got: %+v", errs) } if len(output) != len(testCase.output) { t.Fatalf("incorrect number of property structs, expected %d got %d", len(testCase.output), len(output)) } for i := range output { got := reflect.ValueOf(output[i]).Elem().Interface() if !reflect.DeepEqual(got, testCase.output[i]) { t.Errorf("test case: %s", testCase.input) t.Errorf("incorrect output:") t.Errorf(" expected: %+v", testCase.output[i]) t.Errorf(" got: %+v", got) } } } } } func mkpos(offset, line, column int) scanner.Position { return scanner.Position{ Offset: offset, Line: line, Column: column, } }