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} 295 296type boolVarProps struct { 297 A *string 298 B bool 299 Conditions_default *properties 300} 301 302type soongConfigVars struct { 303 Bool_var interface{} 304} 305 306type stringSoongConfigVars struct { 307 String_var interface{} 308} 309 310func Test_PropertiesToApply(t *testing.T) { 311 mt, _ := newModuleType(&ModuleTypeProperties{ 312 Module_type: "foo", 313 Config_namespace: "bar", 314 Bool_variables: []string{"bool_var"}, 315 Properties: []string{"a", "b"}, 316 }) 317 boolVarPositive := &properties{ 318 A: proptools.StringPtr("A"), 319 B: true, 320 } 321 conditionsDefault := &properties{ 322 A: proptools.StringPtr("default"), 323 B: false, 324 } 325 actualProps := &struct { 326 Soong_config_variables soongConfigVars 327 }{ 328 Soong_config_variables: soongConfigVars{ 329 Bool_var: &boolVarProps{ 330 A: boolVarPositive.A, 331 B: boolVarPositive.B, 332 Conditions_default: conditionsDefault, 333 }, 334 }, 335 } 336 props := reflect.ValueOf(actualProps) 337 338 testCases := []struct { 339 name string 340 config SoongConfig 341 wantProps []interface{} 342 }{ 343 { 344 name: "no_vendor_config", 345 config: Config(map[string]string{}), 346 wantProps: []interface{}{conditionsDefault}, 347 }, 348 { 349 name: "vendor_config_false", 350 config: Config(map[string]string{"bool_var": "n"}), 351 wantProps: []interface{}{conditionsDefault}, 352 }, 353 { 354 name: "bool_var_true", 355 config: Config(map[string]string{"bool_var": "y"}), 356 wantProps: []interface{}{boolVarPositive}, 357 }, 358 } 359 360 for _, tc := range testCases { 361 gotProps, err := PropertiesToApply(mt, props, tc.config) 362 if err != nil { 363 t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err) 364 } 365 366 if !reflect.DeepEqual(gotProps, tc.wantProps) { 367 t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps) 368 } 369 } 370} 371 372func Test_PropertiesToApply_String_Error(t *testing.T) { 373 mt, _ := newModuleType(&ModuleTypeProperties{ 374 Module_type: "foo", 375 Config_namespace: "bar", 376 Variables: []string{"string_var"}, 377 Properties: []string{"a", "b"}, 378 }) 379 mt.Variables = append(mt.Variables, &stringVariable{ 380 baseVariable: baseVariable{ 381 variable: "string_var", 382 }, 383 values: []string{"a", "b", "c"}, 384 }) 385 stringVarPositive := &properties{ 386 A: proptools.StringPtr("A"), 387 B: true, 388 } 389 conditionsDefault := &properties{ 390 A: proptools.StringPtr("default"), 391 B: false, 392 } 393 actualProps := &struct { 394 Soong_config_variables stringSoongConfigVars 395 }{ 396 Soong_config_variables: stringSoongConfigVars{ 397 String_var: &boolVarProps{ 398 A: stringVarPositive.A, 399 B: stringVarPositive.B, 400 Conditions_default: conditionsDefault, 401 }, 402 }, 403 } 404 props := reflect.ValueOf(actualProps) 405 406 _, err := PropertiesToApply(mt, props, Config(map[string]string{ 407 "string_var": "x", 408 })) 409 expected := `Soong config property "string_var" must be one of [a b c], found "x"` 410 if err == nil { 411 t.Fatalf("Expected an error, got nil") 412 } else if err.Error() != expected { 413 t.Fatalf("Error message was not correct, expected %q, got %q", expected, err.Error()) 414 } 415} 416 417func Test_Bp2BuildSoongConfigDefinitionsAddVars(t *testing.T) { 418 testCases := []struct { 419 desc string 420 defs []*SoongConfigDefinition 421 expected Bp2BuildSoongConfigDefinitions 422 }{ 423 { 424 desc: "non-overlapping", 425 defs: []*SoongConfigDefinition{ 426 &SoongConfigDefinition{ 427 ModuleTypes: map[string]*ModuleType{ 428 "a": &ModuleType{ 429 ConfigNamespace: "foo", 430 Variables: []soongConfigVariable{ 431 &stringVariable{ 432 baseVariable: baseVariable{"string_var"}, 433 values: []string{"a", "b", "c"}, 434 }, 435 }, 436 }, 437 }, 438 }, 439 &SoongConfigDefinition{ 440 ModuleTypes: map[string]*ModuleType{ 441 "b": &ModuleType{ 442 ConfigNamespace: "foo", 443 Variables: []soongConfigVariable{ 444 &stringVariable{ 445 baseVariable: baseVariable{"string_var"}, 446 values: []string{"a", "b", "c"}, 447 }, 448 &boolVariable{baseVariable: baseVariable{"bool_var"}}, 449 &valueVariable{baseVariable: baseVariable{"variable_var"}}, 450 }, 451 }, 452 }, 453 }, 454 }, 455 expected: Bp2BuildSoongConfigDefinitions{ 456 StringVars: map[string]map[string]bool{ 457 "foo__string_var": map[string]bool{"a": true, "b": true, "c": true}, 458 }, 459 BoolVars: map[string]bool{"foo__bool_var": true}, 460 ValueVars: map[string]bool{"foo__variable_var": true}, 461 }, 462 }, 463 { 464 desc: "overlapping", 465 defs: []*SoongConfigDefinition{ 466 &SoongConfigDefinition{ 467 ModuleTypes: map[string]*ModuleType{ 468 "a": &ModuleType{ 469 ConfigNamespace: "foo", 470 Variables: []soongConfigVariable{ 471 &stringVariable{ 472 baseVariable: baseVariable{"string_var"}, 473 values: []string{"a", "b", "c"}, 474 }, 475 }, 476 }, 477 }, 478 }, 479 &SoongConfigDefinition{ 480 ModuleTypes: map[string]*ModuleType{ 481 "b": &ModuleType{ 482 ConfigNamespace: "foo", 483 Variables: []soongConfigVariable{ 484 &stringVariable{ 485 baseVariable: baseVariable{"string_var"}, 486 values: []string{"b", "c", "d"}, 487 }, 488 &boolVariable{baseVariable: baseVariable{"bool_var"}}, 489 &valueVariable{baseVariable: baseVariable{"variable_var"}}, 490 }, 491 }, 492 }, 493 }, 494 }, 495 expected: Bp2BuildSoongConfigDefinitions{ 496 StringVars: map[string]map[string]bool{ 497 "foo__string_var": map[string]bool{"a": true, "b": true, "c": true, "d": true}, 498 }, 499 BoolVars: map[string]bool{"foo__bool_var": true}, 500 ValueVars: map[string]bool{"foo__variable_var": true}, 501 }, 502 }, 503 } 504 505 for _, tc := range testCases { 506 t.Run(tc.desc, func(t *testing.T) { 507 actual := &Bp2BuildSoongConfigDefinitions{} 508 for _, d := range tc.defs { 509 func(def *SoongConfigDefinition) { 510 actual.AddVars(def) 511 }(d) 512 } 513 if !reflect.DeepEqual(*actual, tc.expected) { 514 t.Errorf("Expected %#v, got %#v", tc.expected, *actual) 515 } 516 }) 517 } 518 519} 520 521func Test_Bp2BuildSoongConfigDefinitions(t *testing.T) { 522 testCases := []struct { 523 desc string 524 defs Bp2BuildSoongConfigDefinitions 525 expected string 526 }{ 527 { 528 desc: "all empty", 529 defs: Bp2BuildSoongConfigDefinitions{}, 530 expected: `soong_config_bool_variables = {} 531 532soong_config_value_variables = {} 533 534soong_config_string_variables = {}`}, { 535 desc: "only bool", 536 defs: Bp2BuildSoongConfigDefinitions{ 537 BoolVars: map[string]bool{ 538 "bool_var": true, 539 }, 540 }, 541 expected: `soong_config_bool_variables = { 542 "bool_var": True, 543} 544 545soong_config_value_variables = {} 546 547soong_config_string_variables = {}`}, { 548 desc: "only value vars", 549 defs: Bp2BuildSoongConfigDefinitions{ 550 ValueVars: map[string]bool{ 551 "value_var": true, 552 }, 553 }, 554 expected: `soong_config_bool_variables = {} 555 556soong_config_value_variables = { 557 "value_var": True, 558} 559 560soong_config_string_variables = {}`}, { 561 desc: "only string vars", 562 defs: Bp2BuildSoongConfigDefinitions{ 563 StringVars: map[string]map[string]bool{ 564 "string_var": map[string]bool{ 565 "choice1": true, 566 "choice2": true, 567 "choice3": true, 568 }, 569 }, 570 }, 571 expected: `soong_config_bool_variables = {} 572 573soong_config_value_variables = {} 574 575soong_config_string_variables = { 576 "string_var": [ 577 "choice1", 578 "choice2", 579 "choice3", 580 ], 581}`}, { 582 desc: "all vars", 583 defs: Bp2BuildSoongConfigDefinitions{ 584 BoolVars: map[string]bool{ 585 "bool_var_one": true, 586 }, 587 ValueVars: map[string]bool{ 588 "value_var_one": true, 589 "value_var_two": true, 590 }, 591 StringVars: map[string]map[string]bool{ 592 "string_var_one": map[string]bool{ 593 "choice1": true, 594 "choice2": true, 595 "choice3": true, 596 }, 597 "string_var_two": map[string]bool{ 598 "foo": true, 599 "bar": true, 600 }, 601 }, 602 }, 603 expected: `soong_config_bool_variables = { 604 "bool_var_one": True, 605} 606 607soong_config_value_variables = { 608 "value_var_one": True, 609 "value_var_two": True, 610} 611 612soong_config_string_variables = { 613 "string_var_one": [ 614 "choice1", 615 "choice2", 616 "choice3", 617 ], 618 "string_var_two": [ 619 "bar", 620 "foo", 621 ], 622}`}, 623 } 624 for _, test := range testCases { 625 t.Run(test.desc, func(t *testing.T) { 626 actual := test.defs.String() 627 if actual != test.expected { 628 t.Errorf("Expected:\n%s\nbut got:\n%s", test.expected, actual) 629 } 630 }) 631 } 632} 633