platform_build_blueprint/unpack_test.go
Colin Cross c3d731258a Support nil pointers to structs in properties
Allow primary builders to reduce allocations of empty structures by
allowing nil pointers to concrete struct types.  Property readers will
not recurse into nil pointers, property writers will replace the nil
pointer with a pointer to the zero value of the pointer element type.

Allows a >50% primary builder time improvement with a trivial change in
Soong.

Change-Id: If6ad674bf7bf2a694c335378a074643a97d3c50b
2016-08-05 17:19:36 -07:00

572 lines
9 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{
&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,
}
}