1// Copyright 2020 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package soongconfig 16 17import ( 18 "reflect" 19 "testing" 20 21 "github.com/google/blueprint/proptools" 22) 23 24func Test_CanonicalizeToProperty(t *testing.T) { 25 tests := []struct { 26 name string 27 arg string 28 want string 29 }{ 30 { 31 name: "lowercase", 32 arg: "board", 33 want: "board", 34 }, 35 { 36 name: "uppercase", 37 arg: "BOARD", 38 want: "BOARD", 39 }, 40 { 41 name: "numbers", 42 arg: "BOARD123", 43 want: "BOARD123", 44 }, 45 { 46 name: "underscore", 47 arg: "TARGET_BOARD", 48 want: "TARGET_BOARD", 49 }, 50 { 51 name: "dash", 52 arg: "TARGET-BOARD", 53 want: "TARGET_BOARD", 54 }, 55 { 56 name: "unicode", 57 arg: "boardλ", 58 want: "board_", 59 }, 60 } 61 for _, tt := range tests { 62 t.Run(tt.name, func(t *testing.T) { 63 if got := CanonicalizeToProperty(tt.arg); got != tt.want { 64 t.Errorf("canonicalizeToProperty() = %v, want %v", got, tt.want) 65 } 66 }) 67 } 68} 69 70func Test_typeForPropertyFromPropertyStruct(t *testing.T) { 71 tests := []struct { 72 name string 73 ps interface{} 74 property string 75 want string 76 }{ 77 { 78 name: "string", 79 ps: struct { 80 A string 81 }{}, 82 property: "a", 83 want: "string", 84 }, 85 { 86 name: "list", 87 ps: struct { 88 A []string 89 }{}, 90 property: "a", 91 want: "[]string", 92 }, 93 { 94 name: "missing", 95 ps: struct { 96 A []string 97 }{}, 98 property: "b", 99 want: "", 100 }, 101 { 102 name: "nested", 103 ps: struct { 104 A struct { 105 B string 106 } 107 }{}, 108 property: "a.b", 109 want: "string", 110 }, 111 { 112 name: "missing nested", 113 ps: struct { 114 A struct { 115 B string 116 } 117 }{}, 118 property: "a.c", 119 want: "", 120 }, 121 { 122 name: "not a struct", 123 ps: struct { 124 A string 125 }{}, 126 property: "a.b", 127 want: "", 128 }, 129 { 130 name: "nested pointer", 131 ps: struct { 132 A *struct { 133 B string 134 } 135 }{}, 136 property: "a.b", 137 want: "string", 138 }, 139 { 140 name: "nested interface", 141 ps: struct { 142 A interface{} 143 }{ 144 A: struct { 145 B string 146 }{}, 147 }, 148 property: "a.b", 149 want: "string", 150 }, 151 { 152 name: "nested interface pointer", 153 ps: struct { 154 A interface{} 155 }{ 156 A: &struct { 157 B string 158 }{}, 159 }, 160 property: "a.b", 161 want: "string", 162 }, 163 { 164 name: "nested interface nil pointer", 165 ps: struct { 166 A interface{} 167 }{ 168 A: (*struct { 169 B string 170 })(nil), 171 }, 172 property: "a.b", 173 want: "string", 174 }, 175 } 176 for _, tt := range tests { 177 t.Run(tt.name, func(t *testing.T) { 178 typ := typeForPropertyFromPropertyStruct(tt.ps, tt.property) 179 got := "" 180 if typ != nil { 181 got = typ.String() 182 } 183 if got != tt.want { 184 t.Errorf("typeForPropertyFromPropertyStruct() = %v, want %v", got, tt.want) 185 } 186 }) 187 } 188} 189 190func Test_createAffectablePropertiesType(t *testing.T) { 191 tests := []struct { 192 name string 193 affectableProperties []string 194 factoryProps interface{} 195 want string 196 }{ 197 { 198 name: "string", 199 affectableProperties: []string{"cflags"}, 200 factoryProps: struct { 201 Cflags string 202 }{}, 203 want: "*struct { Cflags string }", 204 }, 205 { 206 name: "list", 207 affectableProperties: []string{"cflags"}, 208 factoryProps: struct { 209 Cflags []string 210 }{}, 211 want: "*struct { Cflags []string }", 212 }, 213 { 214 name: "string pointer", 215 affectableProperties: []string{"cflags"}, 216 factoryProps: struct { 217 Cflags *string 218 }{}, 219 want: "*struct { Cflags *string }", 220 }, 221 { 222 name: "subset", 223 affectableProperties: []string{"cflags"}, 224 factoryProps: struct { 225 Cflags string 226 Ldflags string 227 }{}, 228 want: "*struct { Cflags string }", 229 }, 230 { 231 name: "none", 232 affectableProperties: []string{"cflags"}, 233 factoryProps: struct { 234 Ldflags string 235 }{}, 236 want: "", 237 }, 238 { 239 name: "nested", 240 affectableProperties: []string{"multilib.lib32.cflags"}, 241 factoryProps: struct { 242 Multilib struct { 243 Lib32 struct { 244 Cflags string 245 } 246 } 247 }{}, 248 want: "*struct { Multilib struct { Lib32 struct { Cflags string } } }", 249 }, 250 { 251 name: "complex", 252 affectableProperties: []string{ 253 "cflags", 254 "multilib.lib32.cflags", 255 "multilib.lib32.ldflags", 256 "multilib.lib64.cflags", 257 "multilib.lib64.ldflags", 258 "zflags", 259 }, 260 factoryProps: struct { 261 Cflags string 262 Multilib struct { 263 Lib32 struct { 264 Cflags string 265 Ldflags string 266 } 267 Lib64 struct { 268 Cflags string 269 Ldflags string 270 } 271 } 272 Zflags string 273 }{}, 274 want: "*struct { Cflags string; Multilib struct { Lib32 struct { Cflags string; Ldflags string }; Lib64 struct { Cflags string; Ldflags string } }; Zflags string }", 275 }, 276 } 277 for _, tt := range tests { 278 t.Run(tt.name, func(t *testing.T) { 279 typ := createAffectablePropertiesType(tt.affectableProperties, []interface{}{tt.factoryProps}) 280 got := "" 281 if typ != nil { 282 got = typ.String() 283 } 284 if !reflect.DeepEqual(got, tt.want) { 285 t.Errorf("createAffectablePropertiesType() = %v, want %v", got, tt.want) 286 } 287 }) 288 } 289} 290 291type properties struct { 292 A *string 293 B bool 294 C []string 295} 296 297type varProps struct { 298 A *string 299 B bool 300 C []string 301 Conditions_default *properties 302} 303 304type boolSoongConfigVars struct { 305 Bool_var interface{} 306} 307 308type stringSoongConfigVars struct { 309 String_var interface{} 310} 311 312type valueSoongConfigVars struct { 313 My_value_var interface{} 314} 315 316type listProperties struct { 317 C []string 318} 319 320type listVarProps struct { 321 C []string 322 Conditions_default *listProperties 323} 324 325type listSoongConfigVars struct { 326 List_var interface{} 327} 328 329func Test_PropertiesToApply_Bool(t *testing.T) { 330 mt, _ := newModuleType(&ModuleTypeProperties{ 331 Module_type: "foo", 332 Config_namespace: "bar", 333 Bool_variables: []string{"bool_var"}, 334 Properties: []string{"a", "b"}, 335 }) 336 boolVarPositive := &properties{ 337 A: proptools.StringPtr("A"), 338 B: true, 339 } 340 conditionsDefault := &properties{ 341 A: proptools.StringPtr("default"), 342 B: false, 343 } 344 actualProps := &struct { 345 Soong_config_variables boolSoongConfigVars 346 }{ 347 Soong_config_variables: boolSoongConfigVars{ 348 Bool_var: &varProps{ 349 A: boolVarPositive.A, 350 B: boolVarPositive.B, 351 Conditions_default: conditionsDefault, 352 }, 353 }, 354 } 355 props := reflect.ValueOf(actualProps) 356 357 testCases := []struct { 358 name string 359 config SoongConfig 360 wantProps []interface{} 361 }{ 362 { 363 name: "no_vendor_config", 364 config: Config(map[string]string{}), 365 wantProps: []interface{}{conditionsDefault}, 366 }, 367 { 368 name: "vendor_config_false", 369 config: Config(map[string]string{"bool_var": "n"}), 370 wantProps: []interface{}{conditionsDefault}, 371 }, 372 { 373 name: "bool_var_true", 374 config: Config(map[string]string{"bool_var": "y"}), 375 wantProps: []interface{}{boolVarPositive}, 376 }, 377 } 378 379 for _, tc := range testCases { 380 gotProps, err := PropertiesToApply(mt, props, tc.config) 381 if err != nil { 382 t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) 383 } 384 385 if !reflect.DeepEqual(gotProps, tc.wantProps) { 386 t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) 387 } 388 } 389} 390 391func Test_PropertiesToApply_List(t *testing.T) { 392 mt, _ := newModuleType(&ModuleTypeProperties{ 393 Module_type: "foo", 394 Config_namespace: "bar", 395 List_variables: []string{"my_list_var"}, 396 Properties: []string{"c"}, 397 }) 398 conditionsDefault := &listProperties{ 399 C: []string{"default"}, 400 } 401 actualProps := &struct { 402 Soong_config_variables listSoongConfigVars 403 }{ 404 Soong_config_variables: listSoongConfigVars{ 405 List_var: &listVarProps{ 406 C: []string{"A=%s", "B=%s"}, 407 Conditions_default: conditionsDefault, 408 }, 409 }, 410 } 411 props := reflect.ValueOf(actualProps) 412 413 testCases := []struct { 414 name string 415 config SoongConfig 416 wantProps []interface{} 417 }{ 418 { 419 name: "no_vendor_config", 420 config: Config(map[string]string{}), 421 wantProps: []interface{}{conditionsDefault}, 422 }, 423 { 424 name: "value_var_set", 425 config: Config(map[string]string{"my_list_var": "hello there"}), 426 wantProps: []interface{}{&listProperties{ 427 C: []string{"A=hello", "A=there", "B=hello", "B=there"}, 428 }}, 429 }, 430 } 431 432 for _, tc := range testCases { 433 gotProps, err := PropertiesToApply(mt, props, tc.config) 434 if err != nil { 435 t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) 436 } 437 438 if !reflect.DeepEqual(gotProps, tc.wantProps) { 439 t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) 440 } 441 } 442} 443 444func Test_PropertiesToApply_Value(t *testing.T) { 445 mt, _ := newModuleType(&ModuleTypeProperties{ 446 Module_type: "foo", 447 Config_namespace: "bar", 448 Value_variables: []string{"my_value_var"}, 449 Properties: []string{"a", "b"}, 450 }) 451 conditionsDefault := &properties{ 452 A: proptools.StringPtr("default"), 453 B: false, 454 } 455 actualProps := &struct { 456 Soong_config_variables valueSoongConfigVars 457 }{ 458 Soong_config_variables: valueSoongConfigVars{ 459 My_value_var: &varProps{ 460 A: proptools.StringPtr("A=%s"), 461 B: true, 462 Conditions_default: conditionsDefault, 463 }, 464 }, 465 } 466 props := reflect.ValueOf(actualProps) 467 468 testCases := []struct { 469 name string 470 config SoongConfig 471 wantProps []interface{} 472 }{ 473 { 474 name: "no_vendor_config", 475 config: Config(map[string]string{}), 476 wantProps: []interface{}{conditionsDefault}, 477 }, 478 { 479 name: "value_var_set", 480 config: Config(map[string]string{"my_value_var": "Hello"}), 481 wantProps: []interface{}{&properties{ 482 A: proptools.StringPtr("A=Hello"), 483 B: true, 484 }}, 485 }, 486 } 487 488 for _, tc := range testCases { 489 gotProps, err := PropertiesToApply(mt, props, tc.config) 490 if err != nil { 491 t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) 492 } 493 494 if !reflect.DeepEqual(gotProps, tc.wantProps) { 495 t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) 496 } 497 } 498} 499 500func Test_PropertiesToApply_Value_Nested(t *testing.T) { 501 mt, _ := newModuleType(&ModuleTypeProperties{ 502 Module_type: "foo", 503 Config_namespace: "bar", 504 Value_variables: []string{"my_value_var"}, 505 Properties: []string{"a.b"}, 506 }) 507 type properties struct { 508 A struct { 509 B string 510 } 511 } 512 conditionsDefault := &properties{ 513 A: struct{ B string }{ 514 B: "default", 515 }, 516 } 517 type valueVarProps struct { 518 A struct { 519 B string 520 } 521 Conditions_default *properties 522 } 523 actualProps := &struct { 524 Soong_config_variables valueSoongConfigVars 525 }{ 526 Soong_config_variables: valueSoongConfigVars{ 527 My_value_var: &valueVarProps{ 528 A: struct{ B string }{ 529 B: "A.B=%s", 530 }, 531 Conditions_default: conditionsDefault, 532 }, 533 }, 534 } 535 props := reflect.ValueOf(actualProps) 536 537 testCases := []struct { 538 name string 539 config SoongConfig 540 wantProps []interface{} 541 }{ 542 { 543 name: "no_vendor_config", 544 config: Config(map[string]string{}), 545 wantProps: []interface{}{conditionsDefault}, 546 }, 547 { 548 name: "value_var_set", 549 config: Config(map[string]string{"my_value_var": "Hello"}), 550 wantProps: []interface{}{&properties{ 551 A: struct{ B string }{ 552 B: "A.B=Hello", 553 }, 554 }}, 555 }, 556 } 557 558 for _, tc := range testCases { 559 gotProps, err := PropertiesToApply(mt, props, tc.config) 560 if err != nil { 561 t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) 562 } 563 564 if !reflect.DeepEqual(gotProps, tc.wantProps) { 565 t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) 566 } 567 } 568} 569 570func Test_PropertiesToApply_String_Error(t *testing.T) { 571 mt, _ := newModuleType(&ModuleTypeProperties{ 572 Module_type: "foo", 573 Config_namespace: "bar", 574 Variables: []string{"string_var"}, 575 Properties: []string{"a", "b"}, 576 }) 577 mt.Variables = append(mt.Variables, &stringVariable{ 578 baseVariable: baseVariable{ 579 variable: "string_var", 580 }, 581 values: []string{"a", "b", "c"}, 582 }) 583 stringVarPositive := &properties{ 584 A: proptools.StringPtr("A"), 585 B: true, 586 } 587 conditionsDefault := &properties{ 588 A: proptools.StringPtr("default"), 589 B: false, 590 } 591 actualProps := &struct { 592 Soong_config_variables stringSoongConfigVars 593 }{ 594 Soong_config_variables: stringSoongConfigVars{ 595 String_var: &varProps{ 596 A: stringVarPositive.A, 597 B: stringVarPositive.B, 598 Conditions_default: conditionsDefault, 599 }, 600 }, 601 } 602 props := reflect.ValueOf(actualProps) 603 604 _, err := PropertiesToApply(mt, props, Config(map[string]string{ 605 "string_var": "x", 606 })) 607 expected := `Soong config property "string_var" must be one of [a b c], found "x"` 608 if err == nil { 609 t.Fatalf("Expected an error, got nil") 610 } else if err.Error() != expected { 611 t.Fatalf("Error message was not correct, expected %q, got %q", expected, err.Error()) 612 } 613} 614