// Copyright 2020 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 soongconfig import ( "reflect" "testing" "github.com/google/blueprint/proptools" ) func Test_CanonicalizeToProperty(t *testing.T) { tests := []struct { name string arg string want string }{ { name: "lowercase", arg: "board", want: "board", }, { name: "uppercase", arg: "BOARD", want: "BOARD", }, { name: "numbers", arg: "BOARD123", want: "BOARD123", }, { name: "underscore", arg: "TARGET_BOARD", want: "TARGET_BOARD", }, { name: "dash", arg: "TARGET-BOARD", want: "TARGET_BOARD", }, { name: "unicode", arg: "boardĪ»", want: "board_", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := CanonicalizeToProperty(tt.arg); got != tt.want { t.Errorf("canonicalizeToProperty() = %v, want %v", got, tt.want) } }) } } func Test_typeForPropertyFromPropertyStruct(t *testing.T) { tests := []struct { name string ps interface{} property string want string }{ { name: "string", ps: struct { A string }{}, property: "a", want: "string", }, { name: "list", ps: struct { A []string }{}, property: "a", want: "[]string", }, { name: "missing", ps: struct { A []string }{}, property: "b", want: "", }, { name: "nested", ps: struct { A struct { B string } }{}, property: "a.b", want: "string", }, { name: "missing nested", ps: struct { A struct { B string } }{}, property: "a.c", want: "", }, { name: "not a struct", ps: struct { A string }{}, property: "a.b", want: "", }, { name: "nested pointer", ps: struct { A *struct { B string } }{}, property: "a.b", want: "string", }, { name: "nested interface", ps: struct { A interface{} }{ A: struct { B string }{}, }, property: "a.b", want: "string", }, { name: "nested interface pointer", ps: struct { A interface{} }{ A: &struct { B string }{}, }, property: "a.b", want: "string", }, { name: "nested interface nil pointer", ps: struct { A interface{} }{ A: (*struct { B string })(nil), }, property: "a.b", want: "string", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { typ := typeForPropertyFromPropertyStruct(tt.ps, tt.property) got := "" if typ != nil { got = typ.String() } if got != tt.want { t.Errorf("typeForPropertyFromPropertyStruct() = %v, want %v", got, tt.want) } }) } } func Test_createAffectablePropertiesType(t *testing.T) { tests := []struct { name string affectableProperties []string factoryProps interface{} want string }{ { name: "string", affectableProperties: []string{"cflags"}, factoryProps: struct { Cflags string }{}, want: "*struct { Cflags string }", }, { name: "list", affectableProperties: []string{"cflags"}, factoryProps: struct { Cflags []string }{}, want: "*struct { Cflags []string }", }, { name: "string pointer", affectableProperties: []string{"cflags"}, factoryProps: struct { Cflags *string }{}, want: "*struct { Cflags *string }", }, { name: "subset", affectableProperties: []string{"cflags"}, factoryProps: struct { Cflags string Ldflags string }{}, want: "*struct { Cflags string }", }, { name: "none", affectableProperties: []string{"cflags"}, factoryProps: struct { Ldflags string }{}, want: "", }, { name: "nested", affectableProperties: []string{"multilib.lib32.cflags"}, factoryProps: struct { Multilib struct { Lib32 struct { Cflags string } } }{}, want: "*struct { Multilib struct { Lib32 struct { Cflags string } } }", }, { name: "complex", affectableProperties: []string{ "cflags", "multilib.lib32.cflags", "multilib.lib32.ldflags", "multilib.lib64.cflags", "multilib.lib64.ldflags", "zflags", }, factoryProps: struct { Cflags string Multilib struct { Lib32 struct { Cflags string Ldflags string } Lib64 struct { Cflags string Ldflags string } } Zflags string }{}, want: "*struct { Cflags string; Multilib struct { Lib32 struct { Cflags string; Ldflags string }; Lib64 struct { Cflags string; Ldflags string } }; Zflags string }", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { typ := createAffectablePropertiesType(tt.affectableProperties, []interface{}{tt.factoryProps}) got := "" if typ != nil { got = typ.String() } if !reflect.DeepEqual(got, tt.want) { t.Errorf("createAffectablePropertiesType() = %v, want %v", got, tt.want) } }) } } type properties struct { A *string B bool C []string } type varProps struct { A *string B bool C []string Conditions_default *properties } type boolSoongConfigVars struct { Bool_var interface{} } type stringSoongConfigVars struct { String_var interface{} } type valueSoongConfigVars struct { My_value_var interface{} } type listProperties struct { C []string } type listVarProps struct { C []string Conditions_default *listProperties } type listSoongConfigVars struct { List_var interface{} } func Test_PropertiesToApply_Bool(t *testing.T) { mt, _ := newModuleType(&ModuleTypeProperties{ Module_type: "foo", Config_namespace: "bar", Bool_variables: []string{"bool_var"}, Properties: []string{"a", "b"}, }) boolVarPositive := &properties{ A: proptools.StringPtr("A"), B: true, } conditionsDefault := &properties{ A: proptools.StringPtr("default"), B: false, } actualProps := &struct { Soong_config_variables boolSoongConfigVars }{ Soong_config_variables: boolSoongConfigVars{ Bool_var: &varProps{ A: boolVarPositive.A, B: boolVarPositive.B, Conditions_default: conditionsDefault, }, }, } props := reflect.ValueOf(actualProps) testCases := []struct { name string config SoongConfig wantProps []interface{} }{ { name: "no_vendor_config", config: Config(map[string]string{}), wantProps: []interface{}{conditionsDefault}, }, { name: "vendor_config_false", config: Config(map[string]string{"bool_var": "n"}), wantProps: []interface{}{conditionsDefault}, }, { name: "bool_var_true", config: Config(map[string]string{"bool_var": "y"}), wantProps: []interface{}{boolVarPositive}, }, } for _, tc := range testCases { gotProps, err := PropertiesToApply(mt, props, tc.config) if err != nil { t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) } if !reflect.DeepEqual(gotProps, tc.wantProps) { t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) } } } func Test_PropertiesToApply_List(t *testing.T) { mt, _ := newModuleType(&ModuleTypeProperties{ Module_type: "foo", Config_namespace: "bar", List_variables: []string{"my_list_var"}, Properties: []string{"c"}, }) conditionsDefault := &listProperties{ C: []string{"default"}, } actualProps := &struct { Soong_config_variables listSoongConfigVars }{ Soong_config_variables: listSoongConfigVars{ List_var: &listVarProps{ C: []string{"A=%s", "B=%s"}, Conditions_default: conditionsDefault, }, }, } props := reflect.ValueOf(actualProps) testCases := []struct { name string config SoongConfig wantProps []interface{} }{ { name: "no_vendor_config", config: Config(map[string]string{}), wantProps: []interface{}{conditionsDefault}, }, { name: "value_var_set", config: Config(map[string]string{"my_list_var": "hello there"}), wantProps: []interface{}{&listProperties{ C: []string{"A=hello", "A=there", "B=hello", "B=there"}, }}, }, } for _, tc := range testCases { gotProps, err := PropertiesToApply(mt, props, tc.config) if err != nil { t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) } if !reflect.DeepEqual(gotProps, tc.wantProps) { t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) } } } func Test_PropertiesToApply_Value(t *testing.T) { mt, _ := newModuleType(&ModuleTypeProperties{ Module_type: "foo", Config_namespace: "bar", Value_variables: []string{"my_value_var"}, Properties: []string{"a", "b"}, }) conditionsDefault := &properties{ A: proptools.StringPtr("default"), B: false, } actualProps := &struct { Soong_config_variables valueSoongConfigVars }{ Soong_config_variables: valueSoongConfigVars{ My_value_var: &varProps{ A: proptools.StringPtr("A=%s"), B: true, Conditions_default: conditionsDefault, }, }, } props := reflect.ValueOf(actualProps) testCases := []struct { name string config SoongConfig wantProps []interface{} }{ { name: "no_vendor_config", config: Config(map[string]string{}), wantProps: []interface{}{conditionsDefault}, }, { name: "value_var_set", config: Config(map[string]string{"my_value_var": "Hello"}), wantProps: []interface{}{&properties{ A: proptools.StringPtr("A=Hello"), B: true, }}, }, } for _, tc := range testCases { gotProps, err := PropertiesToApply(mt, props, tc.config) if err != nil { t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) } if !reflect.DeepEqual(gotProps, tc.wantProps) { t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) } } } func Test_PropertiesToApply_Value_Nested(t *testing.T) { mt, _ := newModuleType(&ModuleTypeProperties{ Module_type: "foo", Config_namespace: "bar", Value_variables: []string{"my_value_var"}, Properties: []string{"a.b"}, }) type properties struct { A struct { B string } } conditionsDefault := &properties{ A: struct{ B string }{ B: "default", }, } type valueVarProps struct { A struct { B string } Conditions_default *properties } actualProps := &struct { Soong_config_variables valueSoongConfigVars }{ Soong_config_variables: valueSoongConfigVars{ My_value_var: &valueVarProps{ A: struct{ B string }{ B: "A.B=%s", }, Conditions_default: conditionsDefault, }, }, } props := reflect.ValueOf(actualProps) testCases := []struct { name string config SoongConfig wantProps []interface{} }{ { name: "no_vendor_config", config: Config(map[string]string{}), wantProps: []interface{}{conditionsDefault}, }, { name: "value_var_set", config: Config(map[string]string{"my_value_var": "Hello"}), wantProps: []interface{}{&properties{ A: struct{ B string }{ B: "A.B=Hello", }, }}, }, } for _, tc := range testCases { gotProps, err := PropertiesToApply(mt, props, tc.config) if err != nil { t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) } if !reflect.DeepEqual(gotProps, tc.wantProps) { t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) } } } func Test_PropertiesToApply_String_Error(t *testing.T) { mt, _ := newModuleType(&ModuleTypeProperties{ Module_type: "foo", Config_namespace: "bar", Variables: []string{"string_var"}, Properties: []string{"a", "b"}, }) mt.Variables = append(mt.Variables, &stringVariable{ baseVariable: baseVariable{ variable: "string_var", }, values: []string{"a", "b", "c"}, }) stringVarPositive := &properties{ A: proptools.StringPtr("A"), B: true, } conditionsDefault := &properties{ A: proptools.StringPtr("default"), B: false, } actualProps := &struct { Soong_config_variables stringSoongConfigVars }{ Soong_config_variables: stringSoongConfigVars{ String_var: &varProps{ A: stringVarPositive.A, B: stringVarPositive.B, Conditions_default: conditionsDefault, }, }, } props := reflect.ValueOf(actualProps) _, err := PropertiesToApply(mt, props, Config(map[string]string{ "string_var": "x", })) expected := `Soong config property "string_var" must be one of [a b c], found "x"` if err == nil { t.Fatalf("Expected an error, got nil") } else if err.Error() != expected { t.Fatalf("Error message was not correct, expected %q, got %q", expected, err.Error()) } }