platform_build_blueprint/proptools/unpack_test.go
Cole Faust c472e38ec1
Add PostProcessors to configurable properties
Some module types currently evaluate configurable properties in load
hooks, modify the results, and pass them onto properties of other
modules. Evaluating configurable properties in load hooks is
problematic, it happens so early that we can't decide the configuration
beforehand.

Add a "post processors" mechanism to configurable properties where
the result of evaluating the property will be passed through a post
processing function before being returned from Get(). This essentially
allows you to modify the property without evaluating it.

Bug: 362579941
Test: m nothing --no-skip-soong-tests
Change-Id: Ibddb3f14b3433364ba474b964c701e8915d4dc85
2024-10-24 19:18:21 +02:00

1648 lines
29 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 proptools
import (
"bytes"
"reflect"
"testing"
"text/scanner"
"github.com/google/blueprint/parser"
)
var validUnpackTestCases = []struct {
name string
input string
output []interface{}
empty []interface{}
errs []error
}{
{
name: "blank and unset",
input: `
m {
s: "abc",
blank: "",
}
`,
output: []interface{}{
&struct {
S *string
Blank *string
Unset *string
}{
S: StringPtr("abc"),
Blank: StringPtr(""),
Unset: nil,
},
},
},
{
name: "string",
input: `
m {
s: "abc",
}
`,
output: []interface{}{
&struct {
S string
}{
S: "abc",
},
},
},
{
name: "bool",
input: `
m {
isGood: true,
}
`,
output: []interface{}{
&struct {
IsGood bool
}{
IsGood: true,
},
},
},
{
name: "boolptr",
input: `
m {
isGood: true,
isBad: false,
}
`,
output: []interface{}{
&struct {
IsGood *bool
IsBad *bool
IsUgly *bool
}{
IsGood: BoolPtr(true),
IsBad: BoolPtr(false),
IsUgly: nil,
},
},
},
{
name: "slice",
input: `
m {
stuff: ["asdf", "jkl;", "qwert",
"uiop", "bnm,"],
empty: []
}
`,
output: []interface{}{
&struct {
Stuff []string
Empty []string
Nil []string
NonString []struct{ S string } `blueprint:"mutated"`
}{
Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
Empty: []string{},
Nil: nil,
NonString: nil,
},
},
},
{
name: "double nested",
input: `
m {
nested: {
nested: {
s: "abc",
},
},
}
`,
output: []interface{}{
&struct {
Nested struct {
Nested struct {
S string
}
}
}{
Nested: struct{ Nested struct{ S string } }{
Nested: struct{ S string }{
S: "abc",
},
},
},
},
},
{
name: "nested",
input: `
m {
nested: {
s: "abc",
}
}
`,
output: []interface{}{
&struct {
Nested struct {
S string
}
}{
Nested: struct{ S string }{
S: "abc",
},
},
},
},
{
name: "nested interface",
input: `
m {
nested: {
s: "def",
}
}
`,
output: []interface{}{
&struct {
Nested interface{}
}{
Nested: &struct{ S string }{
S: "def",
},
},
},
},
{
name: "mixed",
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"},
},
},
},
{
name: "filter",
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"},
},
},
},
// List of maps
{
name: "list of structs",
input: `
m {
mapslist: [
{
foo: "abc",
bar: true,
},
{
foo: "def",
bar: false,
}
],
}
`,
output: []interface{}{
&struct {
Mapslist []struct {
Foo string
Bar bool
}
}{
Mapslist: []struct {
Foo string
Bar bool
}{
{Foo: "abc", Bar: true},
{Foo: "def", Bar: false},
},
},
},
},
// List of pointers to structs
{
name: "list of pointers to structs",
input: `
m {
mapslist: [
{
foo: "abc",
bar: true,
},
{
foo: "def",
bar: false,
}
],
}
`,
output: []interface{}{
&struct {
Mapslist []*struct {
Foo string
Bar bool
}
}{
Mapslist: []*struct {
Foo string
Bar bool
}{
{Foo: "abc", Bar: true},
{Foo: "def", Bar: false},
},
},
},
},
// List of lists
{
name: "list of lists",
input: `
m {
listoflists: [
["abc",],
["def",],
],
}
`,
output: []interface{}{
&struct {
Listoflists [][]string
}{
Listoflists: [][]string{
[]string{"abc"},
[]string{"def"},
},
},
},
},
// Multilevel
{
name: "multilevel",
input: `
m {
name: "mymodule",
flag: true,
settings: ["foo1", "foo2", "foo3",],
perarch: {
arm: "32",
arm64: "64",
},
configvars: [
{ var: "var1", values: ["1.1", "1.2", ], },
{ var: "var2", values: ["2.1", ], },
],
}
`,
output: []interface{}{
&struct {
Name string
Flag bool
Settings []string
Perarch *struct {
Arm string
Arm64 string
}
Configvars []struct {
Var string
Values []string
}
}{
Name: "mymodule",
Flag: true,
Settings: []string{"foo1", "foo2", "foo3"},
Perarch: &struct {
Arm string
Arm64 string
}{Arm: "32", Arm64: "64"},
Configvars: []struct {
Var string
Values []string
}{
{Var: "var1", Values: []string{"1.1", "1.2"}},
{Var: "var2", Values: []string{"2.1"}},
},
},
},
},
// Anonymous struct
{
name: "embedded struct",
input: `
m {
s: "abc",
nested: {
s: "def",
},
}
`,
output: []interface{}{
&struct {
EmbeddedStruct
Nested struct {
EmbeddedStruct
}
}{
EmbeddedStruct: EmbeddedStruct{
S: "abc",
},
Nested: struct {
EmbeddedStruct
}{
EmbeddedStruct: EmbeddedStruct{
S: "def",
},
},
},
},
},
// Anonymous interface
{
name: "embedded interface",
input: `
m {
s: "abc",
nested: {
s: "def",
},
}
`,
output: []interface{}{
&struct {
EmbeddedInterface
Nested struct {
EmbeddedInterface
}
}{
EmbeddedInterface: &struct{ S string }{
S: "abc",
},
Nested: struct {
EmbeddedInterface
}{
EmbeddedInterface: &struct{ S string }{
S: "def",
},
},
},
},
},
// Anonymous struct with name collision
{
name: "embedded name collision",
input: `
m {
s: "abc",
nested: {
s: "def",
},
}
`,
output: []interface{}{
&struct {
S string
EmbeddedStruct
Nested struct {
S string
EmbeddedStruct
}
}{
S: "abc",
EmbeddedStruct: EmbeddedStruct{
S: "abc",
},
Nested: struct {
S string
EmbeddedStruct
}{
S: "def",
EmbeddedStruct: EmbeddedStruct{
S: "def",
},
},
},
},
},
// Anonymous interface with name collision
{
name: "embeded interface name collision",
input: `
m {
s: "abc",
nested: {
s: "def",
},
}
`,
output: []interface{}{
&struct {
S string
EmbeddedInterface
Nested struct {
S string
EmbeddedInterface
}
}{
S: "abc",
EmbeddedInterface: &struct{ S string }{
S: "abc",
},
Nested: struct {
S string
EmbeddedInterface
}{
S: "def",
EmbeddedInterface: &struct{ S string }{
S: "def",
},
},
},
},
},
// Variables
{
name: "variables",
input: `
list = ["abc"]
string = "def"
list_with_variable = [string]
struct_value = { name: "foo" }
m {
s: string,
list: list,
list2: list_with_variable,
structattr: struct_value,
}
`,
output: []interface{}{
&struct {
S string
List []string
List2 []string
Structattr struct {
Name string
}
}{
S: "def",
List: []string{"abc"},
List2: []string{"def"},
Structattr: struct {
Name string
}{
Name: "foo",
},
},
},
},
// Multiple property structs
{
name: "multiple",
input: `
m {
nested: {
s: "abc",
}
}
`,
output: []interface{}{
&struct {
Nested struct {
S string
}
}{
Nested: struct{ S string }{
S: "abc",
},
},
&struct {
Nested struct {
S string
}
}{
Nested: struct{ S string }{
S: "abc",
},
},
&struct {
}{},
},
},
// Nil pointer to struct
{
name: "nil struct pointer",
input: `
m {
nested: {
s: "abc",
}
}
`,
output: []interface{}{
&struct {
Nested *struct {
S string
}
}{
Nested: &struct{ S string }{
S: "abc",
},
},
},
empty: []interface{}{
&struct {
Nested *struct {
S string
}
}{},
},
},
// Interface containing nil pointer to struct
{
name: "interface nil struct pointer",
input: `
m {
nested: {
s: "abc",
}
}
`,
output: []interface{}{
&struct {
Nested interface{}
}{
Nested: &EmbeddedStruct{
S: "abc",
},
},
},
empty: []interface{}{
&struct {
Nested interface{}
}{
Nested: (*EmbeddedStruct)(nil),
},
},
},
// Factory set properties
{
name: "factory 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: StringPtr("abc"),
Bool: true,
Bool_ptr: 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: StringPtr("012"),
Bool: true,
Bool_ptr: BoolPtr(true),
List: []string{"0", "1", "2"},
},
},
},
// Captitalized property
{
input: `
m {
CAPITALIZED: "foo",
}
`,
output: []interface{}{
&struct {
CAPITALIZED string
}{
CAPITALIZED: "foo",
},
},
},
{
name: "String configurable property that isn't configured",
input: `
m {
foo: "bar"
}
`,
output: []interface{}{
&struct {
Foo Configurable[string]
}{
Foo: newConfigurableWithPropertyName(
"foo",
nil,
[]ConfigurableCase[string]{{
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 17,
Line: 3,
Column: 10,
},
Value: "bar",
},
}},
false,
),
},
},
},
{
name: "Bool configurable property that isn't configured",
input: `
m {
foo: true,
}
`,
output: []interface{}{
&struct {
Foo Configurable[bool]
}{
Foo: newConfigurableWithPropertyName(
"foo",
nil,
[]ConfigurableCase[bool]{{
value: &parser.Bool{
LiteralPos: scanner.Position{
Offset: 17,
Line: 3,
Column: 10,
},
Value: true,
Token: "true",
},
}},
false,
),
},
},
},
{
name: "String list configurable property that isn't configured",
input: `
m {
foo: ["a", "b"],
}
`,
output: []interface{}{
&struct {
Foo Configurable[[]string]
}{
Foo: newConfigurableWithPropertyName(
"foo",
nil,
[]ConfigurableCase[[]string]{{
value: &parser.List{
LBracePos: scanner.Position{
Offset: 17,
Line: 3,
Column: 10,
},
RBracePos: scanner.Position{
Offset: 26,
Line: 3,
Column: 19,
},
Values: []parser.Expression{
&parser.String{
LiteralPos: scanner.Position{
Offset: 18,
Line: 3,
Column: 11,
},
Value: "a",
},
&parser.String{
LiteralPos: scanner.Position{
Offset: 23,
Line: 3,
Column: 16,
},
Value: "b",
},
},
},
}},
false,
),
},
},
},
{
name: "Configurable property",
input: `
m {
foo: select(soong_config_variable("my_namespace", "my_variable"), {
"a": "a2",
"b": "b2",
default: "c2",
})
}
`,
output: []interface{}{
&struct {
Foo Configurable[string]
}{
Foo: newConfigurableWithPropertyName(
"foo",
[]ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"my_variable",
},
}},
[]ConfigurableCase[string]{
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 90,
Line: 4,
Column: 11,
},
Value: "a2",
},
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 106,
Line: 5,
Column: 11,
},
Value: "b2",
},
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 126,
Line: 6,
Column: 15,
},
Value: "c2",
},
},
},
true,
),
},
},
},
{
name: "Configurable property appending",
input: `
m {
foo: select(soong_config_variable("my_namespace", "my_variable"), {
"a": "a2",
"b": "b2",
default: "c2",
}) + select(soong_config_variable("my_namespace", "my_2nd_variable"), {
"d": "d2",
"e": "e2",
default: "f2",
})
}
`,
output: []interface{}{
&struct {
Foo Configurable[string]
}{
Foo: func() Configurable[string] {
result := newConfigurableWithPropertyName(
"foo",
[]ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"my_variable",
},
}},
[]ConfigurableCase[string]{
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 90,
Line: 4,
Column: 11,
},
Value: "a2",
},
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 106,
Line: 5,
Column: 11,
},
Value: "b2",
},
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 126,
Line: 6,
Column: 15,
},
Value: "c2",
},
},
},
true,
)
result.Append(newConfigurableWithPropertyName(
"",
[]ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"my_2nd_variable",
},
}},
[]ConfigurableCase[string]{
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "d",
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 218,
Line: 8,
Column: 11,
},
Value: "d2",
},
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "e",
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 234,
Line: 9,
Column: 11,
},
Value: "e2",
},
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 254,
Line: 10,
Column: 15,
},
Value: "f2",
},
},
},
true,
))
return result
}(),
},
},
},
{
name: "Unpack variable to configurable property",
input: `
my_string_variable = "asdf"
my_bool_variable = true
m {
foo: my_string_variable,
bar: my_bool_variable,
}
`,
output: []interface{}{
&struct {
Foo Configurable[string]
Bar Configurable[bool]
}{
Foo: newConfigurableWithPropertyName(
"foo",
nil,
[]ConfigurableCase[string]{{
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 25,
Line: 2,
Column: 25,
},
Value: "asdf",
},
}},
false,
),
Bar: newConfigurableWithPropertyName(
"bar",
nil,
[]ConfigurableCase[bool]{{
value: &parser.Bool{
LiteralPos: scanner.Position{
Offset: 54,
Line: 3,
Column: 23,
},
Value: true,
Token: "true",
},
}},
false,
),
},
},
},
}
func TestUnpackProperties(t *testing.T) {
for _, testCase := range validUnpackTestCases {
t.Run(testCase.name, func(t *testing.T) {
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 {
for _, p := range testCase.empty {
output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
}
} else {
for _, p := range testCase.output {
output = append(output, 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]).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 TestUnpackErrors(t *testing.T) {
testCases := []struct {
name string
input string
output []interface{}
errors []string
}{
{
name: "missing",
input: `
m {
missing: true,
}
`,
output: []interface{}{},
errors: []string{`<input>:3:13: unrecognized property "missing"`},
},
{
name: "missing nested",
input: `
m {
nested: {
missing: true,
},
}
`,
output: []interface{}{
&struct {
Nested struct{}
}{},
},
errors: []string{`<input>:4:14: unrecognized property "nested.missing"`},
},
{
name: "mutated",
input: `
m {
mutated: true,
}
`,
output: []interface{}{
&struct {
Mutated bool `blueprint:"mutated"`
}{},
},
errors: []string{`<input>:3:13: mutated field mutated cannot be set in a Blueprint file`},
},
{
name: "nested mutated",
input: `
m {
nested: {
mutated: true,
},
}
`,
output: []interface{}{
&struct {
Nested struct {
Mutated bool `blueprint:"mutated"`
}
}{},
},
errors: []string{`<input>:4:14: mutated field nested.mutated cannot be set in a Blueprint file`},
},
{
name: "duplicate",
input: `
m {
exists: true,
exists: true,
}
`,
output: []interface{}{
&struct {
Exists bool
}{},
},
errors: []string{
`<input>:4:12: property "exists" already defined`,
`<input>:3:12: <-- previous definition here`,
},
},
{
name: "nested duplicate",
input: `
m {
nested: {
exists: true,
exists: true,
},
}
`,
output: []interface{}{
&struct {
Nested struct {
Exists bool
}
}{},
},
errors: []string{
`<input>:5:13: property "nested.exists" already defined`,
`<input>:4:13: <-- previous definition here`,
},
},
{
name: "wrong type",
input: `
m {
int: "foo",
}
`,
output: []interface{}{
&struct {
Int *int64
}{},
},
errors: []string{
`<input>:3:11: can't assign string value to int64 property "int"`,
},
},
{
name: "wrong type for map",
input: `
m {
map: "foo",
}
`,
output: []interface{}{
&struct {
Map struct {
S string
}
}{},
},
errors: []string{
`<input>:3:11: can't assign string value to map property "map"`,
},
},
{
name: "wrong type for list",
input: `
m {
list: "foo",
}
`,
output: []interface{}{
&struct {
List []string
}{},
},
errors: []string{
`<input>:3:12: can't assign string value to list property "list"`,
},
},
{
name: "wrong type for list of maps",
input: `
m {
map_list: "foo",
}
`,
output: []interface{}{
&struct {
Map_list []struct {
S string
}
}{},
},
errors: []string{
`<input>:3:16: can't assign string value to list property "map_list"`,
},
},
{
name: "non-existent property",
input: `
m {
foo: {
foo_prop1: true,
foo_prop2: false,
foo_prop3: true,
},
bar: {
bar_prop: false,
},
baz: true,
exist: false,
}
`,
output: []interface{}{
&struct {
Foo struct {
Foo_prop1 bool
}
Exist bool
}{},
},
errors: []string{
`<input>:5:16: unrecognized property "foo.foo_prop2"`,
`<input>:6:16: unrecognized property "foo.foo_prop3"`,
`<input>:9:15: unrecognized property "bar.bar_prop"`,
`<input>:11:9: unrecognized property "baz"`,
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
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{}
for _, p := range testCase.output {
output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface())
}
_, errs = UnpackProperties(module.Properties, output...)
printErrors := false
for _, expectedErr := range testCase.errors {
foundError := false
for _, err := range errs {
if err.Error() == expectedErr {
foundError = true
}
}
if !foundError {
t.Errorf("expected error %s", expectedErr)
printErrors = true
}
}
if printErrors {
t.Errorf("got errors:")
for _, err := range errs {
t.Errorf(" %s", err.Error())
}
}
}
})
}
}
func BenchmarkUnpackProperties(b *testing.B) {
run := func(b *testing.B, props []interface{}, input string) {
b.ReportAllocs()
b.StopTimer()
r := bytes.NewBufferString(input)
file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
if len(errs) != 0 {
b.Errorf("test case: %s", input)
b.Errorf("unexpected parse errors:")
for _, err := range errs {
b.Errorf(" %s", err)
}
b.FailNow()
}
for i := 0; i < b.N; i++ {
for _, def := range file.Defs {
module, ok := def.(*parser.Module)
if !ok {
continue
}
var output []interface{}
for _, p := range props {
output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
}
b.StartTimer()
_, errs = UnpackProperties(module.Properties, output...)
b.StopTimer()
if len(errs) > 0 {
b.Errorf("unexpected unpack errors:")
for _, err := range errs {
b.Errorf(" %s", err)
}
}
}
}
}
b.Run("basic", func(b *testing.B) {
props := []interface{}{
&struct {
Nested struct {
S string
}
}{},
}
bp := `
m {
nested: {
s: "abc",
},
}
`
run(b, props, bp)
})
b.Run("interface", func(b *testing.B) {
props := []interface{}{
&struct {
Nested interface{}
}{
Nested: (*struct {
S string
})(nil),
},
}
bp := `
m {
nested: {
s: "abc",
},
}
`
run(b, props, bp)
})
b.Run("many", func(b *testing.B) {
props := []interface{}{
&struct {
A *string
B *string
C *string
D *string
E *string
F *string
G *string
H *string
I *string
J *string
}{},
}
bp := `
m {
a: "a",
b: "b",
c: "c",
d: "d",
e: "e",
f: "f",
g: "g",
h: "h",
i: "i",
j: "j",
}
`
run(b, props, bp)
})
b.Run("deep", func(b *testing.B) {
props := []interface{}{
&struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
S string
}
}
}
}
}
}
}
}
}
}
}{},
}
bp := `
m {
nested: { nested: { nested: { nested: { nested: {
nested: { nested: { nested: { nested: { nested: {
s: "abc",
}, }, }, }, },
}, }, }, }, },
}
`
run(b, props, bp)
})
b.Run("mix", func(b *testing.B) {
props := []interface{}{
&struct {
Name string
Flag bool
Settings []string
Perarch *struct {
Arm string
Arm64 string
}
Configvars []struct {
Name string
Values []string
}
}{},
}
bp := `
m {
name: "mymodule",
flag: true,
settings: ["foo1", "foo2", "foo3",],
perarch: {
arm: "32",
arm64: "64",
},
configvars: [
{ name: "var1", values: ["var1:1", "var1:2", ], },
{ name: "var2", values: ["var2:1", "var2:2", ], },
],
}
`
run(b, props, bp)
})
}
func TestRemoveUnnecessaryUnusedNames(t *testing.T) {
testCases := []struct {
name string
input []string
output []string
}{
{
name: "no unused names",
input: []string{},
output: []string{},
},
{
name: "only one unused name",
input: []string{"a.b.c"},
output: []string{"a.b.c"},
},
{
name: "unused names in a chain",
input: []string{"a", "a.b", "a.b.c"},
output: []string{"a.b.c"},
},
{
name: "unused names unrelated",
input: []string{"a.b.c", "s.t", "x.y"},
output: []string{"a.b.c", "s.t", "x.y"},
},
{
name: "unused names partially related one",
input: []string{"a.b", "a.b.c", "a.b.d"},
output: []string{"a.b.c", "a.b.d"},
},
{
name: "unused names partially related two",
input: []string{"a", "a.b.c", "a.c"},
output: []string{"a.b.c", "a.c"},
},
{
name: "unused names partially related three",
input: []string{"a.b.c", "b.c", "c"},
output: []string{"a.b.c", "b.c", "c"},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
simplifiedNames := removeUnnecessaryUnusedNames(testCase.input)
if !reflect.DeepEqual(simplifiedNames, testCase.output) {
t.Errorf("test case: %s", testCase.name)
t.Errorf(" input: %s", testCase.input)
t.Errorf(" expect: %s", testCase.output)
t.Errorf(" got: %s", simplifiedNames)
}
})
}
}