05b3607c37
Blueprint was using "replace" semantics when unpacking properties into property structs, meaning if a module factory pre-set property values they would be overwritten by whatever was in the Blueprint file. This is different than what would happen if the same property was updated using the Append*Properties functions in proptools, which would use "append" semantics, which append strings and lists, logically ORs booleans and replaces pointers to strings and booleans. Replace unpack's semantics with append semantics for consistency. Any previous users of pre-set properties can move to using a pointer to a string or boolean if they want the old behavior. Test: unpack_test.go Test: extend_test.go Change-Id: I02eebe80916e578938142f8e76889bd985223afc
615 lines
9.8 KiB
Go
615 lines
9.8 KiB
Go
// 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{
|
|
&BlueprintError{
|
|
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),
|
|
},
|
|
},
|
|
},
|
|
|
|
// Factory set properties
|
|
{
|
|
input: `
|
|
m {
|
|
string: "abc",
|
|
string_ptr: "abc",
|
|
bool: false,
|
|
bool_ptr: false,
|
|
list: ["a", "b", "c"],
|
|
}
|
|
`,
|
|
output: []interface{}{
|
|
struct {
|
|
String string
|
|
String_ptr *string
|
|
Bool bool
|
|
Bool_ptr *bool
|
|
List []string
|
|
}{
|
|
String: "012abc",
|
|
String_ptr: proptools.StringPtr("abc"),
|
|
Bool: true,
|
|
Bool_ptr: proptools.BoolPtr(false),
|
|
List: []string{"0", "1", "2", "a", "b", "c"},
|
|
},
|
|
},
|
|
empty: []interface{}{
|
|
&struct {
|
|
String string
|
|
String_ptr *string
|
|
Bool bool
|
|
Bool_ptr *bool
|
|
List []string
|
|
}{
|
|
String: "012",
|
|
String_ptr: proptools.StringPtr("012"),
|
|
Bool: true,
|
|
Bool_ptr: proptools.BoolPtr(true),
|
|
List: []string{"0", "1", "2"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|