|  | // 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()) | 
|  | } | 
|  | } |