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
This commit is contained in:
parent
aec881da86
commit
c3d731258a
8 changed files with 353 additions and 181 deletions
|
@ -152,15 +152,18 @@ func setDefaults(properties []Property, defaults reflect.Value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Type().Kind() == reflect.Interface {
|
if f.Kind() == reflect.Interface {
|
||||||
f = f.Elem()
|
f = f.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Type().Kind() == reflect.Ptr {
|
if f.Kind() == reflect.Ptr {
|
||||||
|
if f.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
f = f.Elem()
|
f = f.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Type().Kind() == reflect.Struct {
|
if f.Kind() == reflect.Struct {
|
||||||
setDefaults(prop.Properties, f)
|
setDefaults(prop.Properties, f)
|
||||||
} else {
|
} else {
|
||||||
prop.Default = fmt.Sprintf("%v", f.Interface())
|
prop.Default = fmt.Sprintf("%v", f.Interface())
|
||||||
|
|
|
@ -43,6 +43,7 @@ func CopyProperties(dstValue, srcValue reflect.Value) {
|
||||||
srcFieldValue := srcValue.Field(i)
|
srcFieldValue := srcValue.Field(i)
|
||||||
dstFieldValue := dstValue.Field(i)
|
dstFieldValue := dstValue.Field(i)
|
||||||
dstFieldInterfaceValue := reflect.Value{}
|
dstFieldInterfaceValue := reflect.Value{}
|
||||||
|
origDstFieldValue := dstFieldValue
|
||||||
|
|
||||||
switch srcFieldValue.Kind() {
|
switch srcFieldValue.Kind() {
|
||||||
case reflect.Bool, reflect.String, reflect.Int, reflect.Uint:
|
case reflect.Bool, reflect.String, reflect.Int, reflect.Uint:
|
||||||
|
@ -93,7 +94,7 @@ func CopyProperties(dstValue, srcValue reflect.Value) {
|
||||||
fallthrough
|
fallthrough
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
if srcFieldValue.IsNil() {
|
if srcFieldValue.IsNil() {
|
||||||
dstFieldValue.Set(srcFieldValue)
|
origDstFieldValue.Set(srcFieldValue)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,13 +111,13 @@ func CopyProperties(dstValue, srcValue reflect.Value) {
|
||||||
if dstFieldInterfaceValue.IsValid() {
|
if dstFieldInterfaceValue.IsValid() {
|
||||||
dstFieldInterfaceValue.Set(newValue)
|
dstFieldInterfaceValue.Set(newValue)
|
||||||
} else {
|
} else {
|
||||||
dstFieldValue.Set(newValue)
|
origDstFieldValue.Set(newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case reflect.Bool, reflect.String:
|
case reflect.Bool, reflect.String:
|
||||||
newValue := reflect.New(srcFieldValue.Type())
|
newValue := reflect.New(srcFieldValue.Type())
|
||||||
newValue.Elem().Set(srcFieldValue)
|
newValue.Elem().Set(srcFieldValue)
|
||||||
dstFieldValue.Set(newValue)
|
origDstFieldValue.Set(newValue)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("can't clone field %q: points to a %s",
|
panic(fmt.Errorf("can't clone field %q: points to a %s",
|
||||||
field.Name, srcFieldValue.Kind()))
|
field.Name, srcFieldValue.Kind()))
|
||||||
|
|
|
@ -191,11 +191,15 @@ func extendPropertyErrorf(property string, format string, a ...interface{}) *Ext
|
||||||
func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
|
func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
|
||||||
order ExtendPropertyOrderFunc) error {
|
order ExtendPropertyOrderFunc) error {
|
||||||
|
|
||||||
dstValue, err := getStruct(dst)
|
srcValue, err := getStruct(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(getStructEmptyError); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
srcValue, err := getStruct(src)
|
|
||||||
|
dstValue, err := getOrCreateStruct(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -212,20 +216,23 @@ func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFil
|
||||||
func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc,
|
func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc,
|
||||||
order ExtendPropertyOrderFunc) error {
|
order ExtendPropertyOrderFunc) error {
|
||||||
|
|
||||||
|
srcValue, err := getStruct(src)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(getStructEmptyError); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
dstValues := make([]reflect.Value, len(dst))
|
dstValues := make([]reflect.Value, len(dst))
|
||||||
for i := range dst {
|
for i := range dst {
|
||||||
var err error
|
var err error
|
||||||
dstValues[i], err = getStruct(dst[i])
|
dstValues[i], err = getOrCreateStruct(dst[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
srcValue, err := getStruct(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order)
|
return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +277,7 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
dstFieldValue := dstValue.FieldByIndex(dstField.Index)
|
dstFieldValue := dstValue.FieldByIndex(dstField.Index)
|
||||||
|
origDstFieldValue := dstFieldValue
|
||||||
|
|
||||||
if srcFieldValue.Kind() != dstFieldValue.Kind() {
|
if srcFieldValue.Kind() != dstFieldValue.Kind() {
|
||||||
return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
|
return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
|
||||||
|
@ -306,11 +314,12 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pointer to a struct
|
// Pointer to a struct
|
||||||
if dstFieldValue.IsNil() != srcFieldValue.IsNil() {
|
if srcFieldValue.IsNil() {
|
||||||
return extendPropertyErrorf(propertyName, "nilitude mismatch")
|
continue
|
||||||
}
|
}
|
||||||
if dstFieldValue.IsNil() {
|
if dstFieldValue.IsNil() {
|
||||||
continue
|
dstFieldValue = reflect.New(srcFieldValue.Type().Elem())
|
||||||
|
origDstFieldValue.Set(dstFieldValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
dstFieldValue = dstFieldValue.Elem()
|
dstFieldValue = dstFieldValue.Elem()
|
||||||
|
@ -435,14 +444,32 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type getStructEmptyError struct{}
|
||||||
|
|
||||||
|
func (getStructEmptyError) Error() string { return "interface containing nil pointer" }
|
||||||
|
|
||||||
|
func getOrCreateStruct(in interface{}) (reflect.Value, error) {
|
||||||
|
value, err := getStruct(in)
|
||||||
|
if _, ok := err.(getStructEmptyError); ok {
|
||||||
|
value := reflect.ValueOf(in)
|
||||||
|
newValue := reflect.New(value.Type().Elem())
|
||||||
|
value.Set(newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
func getStruct(in interface{}) (reflect.Value, error) {
|
func getStruct(in interface{}) (reflect.Value, error) {
|
||||||
value := reflect.ValueOf(in)
|
value := reflect.ValueOf(in)
|
||||||
if value.Kind() != reflect.Ptr {
|
if value.Kind() != reflect.Ptr {
|
||||||
return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
|
return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
|
||||||
}
|
}
|
||||||
value = value.Elem()
|
if value.Type().Elem().Kind() != reflect.Struct {
|
||||||
if value.Kind() != reflect.Struct {
|
|
||||||
return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
|
return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
|
||||||
}
|
}
|
||||||
|
if value.IsNil() {
|
||||||
|
return reflect.Value{}, getStructEmptyError{}
|
||||||
|
}
|
||||||
|
value = value.Elem()
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -496,12 +496,69 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Nil pointer to a struct
|
||||||
|
in1: &struct {
|
||||||
|
Nested *struct {
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
}{},
|
||||||
|
in2: &struct {
|
||||||
|
Nested *struct {
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
Nested: &struct {
|
||||||
|
S string
|
||||||
|
}{
|
||||||
|
S: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: &struct {
|
||||||
|
Nested *struct {
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
Nested: &struct {
|
||||||
|
S string
|
||||||
|
}{
|
||||||
|
S: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nil pointer to a struct in an interface
|
||||||
|
in1: &struct {
|
||||||
|
Nested interface{}
|
||||||
|
}{
|
||||||
|
Nested: (*struct{ S string })(nil),
|
||||||
|
},
|
||||||
|
in2: &struct {
|
||||||
|
Nested interface{}
|
||||||
|
}{
|
||||||
|
Nested: &struct {
|
||||||
|
S string
|
||||||
|
}{
|
||||||
|
S: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: &struct {
|
||||||
|
Nested interface{}
|
||||||
|
}{
|
||||||
|
Nested: &struct {
|
||||||
|
S string
|
||||||
|
}{
|
||||||
|
S: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
{
|
{
|
||||||
// Non-pointer in1
|
// Non-pointer in1
|
||||||
in1: struct{}{},
|
in1: struct{}{},
|
||||||
|
in2: &struct{}{},
|
||||||
err: errors.New("expected pointer to struct, got struct {}"),
|
err: errors.New("expected pointer to struct, got struct {}"),
|
||||||
out: struct{}{},
|
out: struct{}{},
|
||||||
},
|
},
|
||||||
|
@ -515,6 +572,7 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
|
||||||
{
|
{
|
||||||
// Non-struct in1
|
// Non-struct in1
|
||||||
in1: &[]string{"bad"},
|
in1: &[]string{"bad"},
|
||||||
|
in2: &struct{}{},
|
||||||
err: errors.New("expected pointer to struct, got *[]string"),
|
err: errors.New("expected pointer to struct, got *[]string"),
|
||||||
out: &[]string{"bad"},
|
out: &[]string{"bad"},
|
||||||
},
|
},
|
||||||
|
@ -606,23 +664,6 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
|
||||||
},
|
},
|
||||||
err: extendPropertyErrorf("s", "interface not a pointer"),
|
err: extendPropertyErrorf("s", "interface not a pointer"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// Pointer nilitude mismatch
|
|
||||||
in1: &struct{ S *struct{ S string } }{
|
|
||||||
S: &struct{ S string }{
|
|
||||||
S: "string1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
in2: &struct{ S *struct{ S string } }{
|
|
||||||
S: nil,
|
|
||||||
},
|
|
||||||
out: &struct{ S *struct{ S string } }{
|
|
||||||
S: &struct{ S string }{
|
|
||||||
S: "string1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: extendPropertyErrorf("s", "nilitude mismatch"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// Pointer not a struct
|
// Pointer not a struct
|
||||||
in1: &struct{ S *[]string }{
|
in1: &struct{ S *[]string }{
|
||||||
|
@ -912,6 +953,7 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
|
||||||
{
|
{
|
||||||
// Non-pointer in1
|
// Non-pointer in1
|
||||||
in1: []interface{}{struct{}{}},
|
in1: []interface{}{struct{}{}},
|
||||||
|
in2: &struct{}{},
|
||||||
err: errors.New("expected pointer to struct, got struct {}"),
|
err: errors.New("expected pointer to struct, got struct {}"),
|
||||||
out: []interface{}{struct{}{}},
|
out: []interface{}{struct{}{}},
|
||||||
},
|
},
|
||||||
|
@ -925,6 +967,7 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
|
||||||
{
|
{
|
||||||
// Non-struct in1
|
// Non-struct in1
|
||||||
in1: []interface{}{&[]string{"bad"}},
|
in1: []interface{}{&[]string{"bad"}},
|
||||||
|
in2: &struct{}{},
|
||||||
err: errors.New("expected pointer to struct, got *[]string"),
|
err: errors.New("expected pointer to struct, got *[]string"),
|
||||||
out: []interface{}{&[]string{"bad"}},
|
out: []interface{}{&[]string{"bad"}},
|
||||||
},
|
},
|
||||||
|
|
|
@ -46,12 +46,14 @@ func typeEqual(v1, v2 reflect.Value) bool {
|
||||||
if v1.Type().Elem().Kind() != reflect.Struct {
|
if v1.Type().Elem().Kind() != reflect.Struct {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if v1.IsNil() != v2.IsNil() {
|
if v1.IsNil() && !v2.IsNil() {
|
||||||
return false
|
return concreteType(v2)
|
||||||
}
|
} else if v2.IsNil() && !v1.IsNil() {
|
||||||
if v1.IsNil() {
|
return concreteType(v1)
|
||||||
|
} else if v1.IsNil() && v2.IsNil() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
v1 = v1.Elem()
|
v1 = v1.Elem()
|
||||||
v2 = v2.Elem()
|
v2 = v2.Elem()
|
||||||
}
|
}
|
||||||
|
@ -74,3 +76,34 @@ func typeEqual(v1, v2 reflect.Value) bool {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if v recursively contains no interfaces
|
||||||
|
func concreteType(v reflect.Value) bool {
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
if v.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
v := v.Field(i)
|
||||||
|
|
||||||
|
switch kind := v.Kind(); kind {
|
||||||
|
case reflect.Interface, reflect.Ptr, reflect.Struct:
|
||||||
|
if !concreteType(v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ var typeEqualTestCases = []struct {
|
||||||
// Mismatching nilitude embedded pointer to struct
|
// Mismatching nilitude embedded pointer to struct
|
||||||
in1: &struct{ S *struct{ S1 string } }{S: &struct{ S1 string }{}},
|
in1: &struct{ S *struct{ S1 string } }{S: &struct{ S1 string }{}},
|
||||||
in2: &struct{ S *struct{ S1 string } }{},
|
in2: &struct{ S *struct{ S1 string } }{},
|
||||||
out: false,
|
out: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Matching embedded interface to pointer to struct
|
// Matching embedded interface to pointer to struct
|
||||||
|
@ -116,13 +116,13 @@ var typeEqualTestCases = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Matching pointer to non-struct
|
// Matching pointer to non-struct
|
||||||
in1: struct{ S1 *string }{ S1: StringPtr("test1") },
|
in1: struct{ S1 *string }{S1: StringPtr("test1")},
|
||||||
in2: struct{ S1 *string }{ S1: StringPtr("test2") },
|
in2: struct{ S1 *string }{S1: StringPtr("test2")},
|
||||||
out: true,
|
out: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Matching nilitude pointer to non-struct
|
// Matching nilitude pointer to non-struct
|
||||||
in1: struct{ S1 *string }{ S1: StringPtr("test1") },
|
in1: struct{ S1 *string }{S1: StringPtr("test1")},
|
||||||
in2: struct{ S1 *string }{},
|
in2: struct{ S1 *string }{},
|
||||||
out: true,
|
out: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -139,6 +139,8 @@ func unpackStructValue(namePrefix string, structValue reflect.Value,
|
||||||
panic(fmt.Errorf("field %s is not settable", propertyName))
|
panic(fmt.Errorf("field %s is not settable", propertyName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
origFieldValue := fieldValue
|
||||||
|
|
||||||
// To make testing easier we validate the struct field's type regardless
|
// To make testing easier we validate the struct field's type regardless
|
||||||
// of whether or not the property was specified in the parsed string.
|
// of whether or not the property was specified in the parsed string.
|
||||||
switch kind := fieldValue.Kind(); kind {
|
switch kind := fieldValue.Kind(); kind {
|
||||||
|
@ -163,7 +165,8 @@ func unpackStructValue(namePrefix string, structValue reflect.Value,
|
||||||
switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
|
switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
if fieldValue.IsNil() {
|
if fieldValue.IsNil() {
|
||||||
panic(fmt.Errorf("field %s contains a nil pointer", propertyName))
|
fieldValue = reflect.New(fieldValue.Type().Elem())
|
||||||
|
origFieldValue.Set(fieldValue)
|
||||||
}
|
}
|
||||||
fieldValue = fieldValue.Elem()
|
fieldValue = fieldValue.Elem()
|
||||||
case reflect.Bool, reflect.String:
|
case reflect.Bool, reflect.String:
|
||||||
|
|
334
unpack_test.go
334
unpack_test.go
|
@ -28,15 +28,17 @@ import (
|
||||||
var validUnpackTestCases = []struct {
|
var validUnpackTestCases = []struct {
|
||||||
input string
|
input string
|
||||||
output []interface{}
|
output []interface{}
|
||||||
|
empty []interface{}
|
||||||
errs []error
|
errs []error
|
||||||
}{
|
}{
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
name: "abc",
|
m {
|
||||||
blank: "",
|
name: "abc",
|
||||||
}
|
blank: "",
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Name *string
|
Name *string
|
||||||
Blank *string
|
Blank *string
|
||||||
|
@ -47,46 +49,46 @@ var validUnpackTestCases = []struct {
|
||||||
Unset: nil,
|
Unset: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
name: "abc",
|
m {
|
||||||
}
|
name: "abc",
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Name string
|
Name string
|
||||||
}{
|
}{
|
||||||
Name: "abc",
|
Name: "abc",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
isGood: true,
|
m {
|
||||||
}
|
isGood: true,
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
IsGood bool
|
IsGood bool
|
||||||
}{
|
}{
|
||||||
IsGood: true,
|
IsGood: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
isGood: true,
|
m {
|
||||||
isBad: false,
|
isGood: true,
|
||||||
}
|
isBad: false,
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
IsGood *bool
|
IsGood *bool
|
||||||
IsBad *bool
|
IsBad *bool
|
||||||
|
@ -97,17 +99,17 @@ var validUnpackTestCases = []struct {
|
||||||
IsUgly: nil,
|
IsUgly: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
stuff: ["asdf", "jkl;", "qwert",
|
m {
|
||||||
"uiop", "bnm,"],
|
stuff: ["asdf", "jkl;", "qwert",
|
||||||
empty: []
|
"uiop", "bnm,"],
|
||||||
}
|
empty: []
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Stuff []string
|
Stuff []string
|
||||||
Empty []string
|
Empty []string
|
||||||
|
@ -118,17 +120,17 @@ var validUnpackTestCases = []struct {
|
||||||
Nil: nil,
|
Nil: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
nested: {
|
m {
|
||||||
name: "abc",
|
nested: {
|
||||||
|
name: "abc",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Nested struct {
|
Nested struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -139,17 +141,17 @@ var validUnpackTestCases = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
nested: {
|
m {
|
||||||
name: "def",
|
nested: {
|
||||||
|
name: "def",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Nested interface{}
|
Nested interface{}
|
||||||
}{
|
}{
|
||||||
|
@ -158,19 +160,19 @@ var validUnpackTestCases = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
nested: {
|
m {
|
||||||
foo: "abc",
|
nested: {
|
||||||
},
|
foo: "abc",
|
||||||
bar: false,
|
},
|
||||||
baz: ["def", "ghi"],
|
bar: false,
|
||||||
}
|
baz: ["def", "ghi"],
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Nested struct {
|
Nested struct {
|
||||||
Foo string
|
Foo string
|
||||||
|
@ -185,19 +187,19 @@ var validUnpackTestCases = []struct {
|
||||||
Baz: []string{"def", "ghi"},
|
Baz: []string{"def", "ghi"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
nested: {
|
m {
|
||||||
foo: "abc",
|
nested: {
|
||||||
},
|
foo: "abc",
|
||||||
bar: false,
|
},
|
||||||
baz: ["def", "ghi"],
|
bar: false,
|
||||||
}
|
baz: ["def", "ghi"],
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Nested struct {
|
Nested struct {
|
||||||
Foo string `allowNested:"true"`
|
Foo string `allowNested:"true"`
|
||||||
|
@ -214,19 +216,19 @@ var validUnpackTestCases = []struct {
|
||||||
Baz: []string{"def", "ghi"},
|
Baz: []string{"def", "ghi"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
nested: {
|
m {
|
||||||
foo: "abc",
|
nested: {
|
||||||
},
|
foo: "abc",
|
||||||
bar: false,
|
},
|
||||||
baz: ["def", "ghi"],
|
bar: false,
|
||||||
}
|
baz: ["def", "ghi"],
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Nested struct {
|
Nested struct {
|
||||||
Foo string
|
Foo string
|
||||||
|
@ -241,24 +243,25 @@ var validUnpackTestCases = []struct {
|
||||||
Baz: []string{"def", "ghi"},
|
Baz: []string{"def", "ghi"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[]error{
|
errs: []error{
|
||||||
&Error{
|
&Error{
|
||||||
Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"),
|
Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"),
|
||||||
Pos: mkpos(27, 4, 8),
|
Pos: mkpos(30, 4, 9),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Anonymous struct
|
// Anonymous struct
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
name: "abc",
|
m {
|
||||||
nested: {
|
name: "abc",
|
||||||
name: "def",
|
nested: {
|
||||||
},
|
name: "def",
|
||||||
}
|
},
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
EmbeddedStruct
|
EmbeddedStruct
|
||||||
Nested struct {
|
Nested struct {
|
||||||
|
@ -277,19 +280,19 @@ var validUnpackTestCases = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Anonymous interface
|
// Anonymous interface
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
name: "abc",
|
m {
|
||||||
nested: {
|
name: "abc",
|
||||||
name: "def",
|
nested: {
|
||||||
},
|
name: "def",
|
||||||
}
|
},
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
EmbeddedInterface
|
EmbeddedInterface
|
||||||
Nested struct {
|
Nested struct {
|
||||||
|
@ -308,19 +311,19 @@ var validUnpackTestCases = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Anonymous struct with name collision
|
// Anonymous struct with name collision
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
name: "abc",
|
m {
|
||||||
nested: {
|
name: "abc",
|
||||||
name: "def",
|
nested: {
|
||||||
},
|
name: "def",
|
||||||
}
|
},
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Name string
|
Name string
|
||||||
EmbeddedStruct
|
EmbeddedStruct
|
||||||
|
@ -344,19 +347,19 @@ var validUnpackTestCases = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Anonymous interface with name collision
|
// Anonymous interface with name collision
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
name: "abc",
|
m {
|
||||||
nested: {
|
name: "abc",
|
||||||
name: "def",
|
nested: {
|
||||||
},
|
name: "def",
|
||||||
}
|
},
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Name string
|
Name string
|
||||||
EmbeddedInterface
|
EmbeddedInterface
|
||||||
|
@ -380,21 +383,21 @@ var validUnpackTestCases = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
{`
|
{
|
||||||
list = ["abc"]
|
input: `
|
||||||
string = "def"
|
list = ["abc"]
|
||||||
list_with_variable = [string]
|
string = "def"
|
||||||
m {
|
list_with_variable = [string]
|
||||||
name: string,
|
m {
|
||||||
list: list,
|
name: string,
|
||||||
list2: list_with_variable,
|
list: list,
|
||||||
}
|
list2: list_with_variable,
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Name string
|
Name string
|
||||||
List []string
|
List []string
|
||||||
|
@ -405,18 +408,18 @@ var validUnpackTestCases = []struct {
|
||||||
List2: []string{"def"},
|
List2: []string{"def"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Multiple property structs
|
// Multiple property structs
|
||||||
{`
|
{
|
||||||
m {
|
input: `
|
||||||
nested: {
|
m {
|
||||||
name: "abc",
|
nested: {
|
||||||
|
name: "abc",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
[]interface{}{
|
output: []interface{}{
|
||||||
struct {
|
struct {
|
||||||
Nested struct {
|
Nested struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -438,7 +441,62 @@ var validUnpackTestCases = []struct {
|
||||||
struct {
|
struct {
|
||||||
}{},
|
}{},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,9 +522,13 @@ func TestUnpackProperties(t *testing.T) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
output := []interface{}{}
|
var output []interface{}
|
||||||
for _, p := range testCase.output {
|
if len(testCase.empty) > 0 {
|
||||||
output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface())
|
output = testCase.empty
|
||||||
|
} else {
|
||||||
|
for _, p := range testCase.output {
|
||||||
|
output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_, errs = unpackProperties(module.Properties, output...)
|
_, errs = unpackProperties(module.Properties, output...)
|
||||||
if len(errs) != 0 && len(testCase.errs) == 0 {
|
if len(errs) != 0 && len(testCase.errs) == 0 {
|
||||||
|
|
Loading…
Reference in a new issue