1// Copyright 2014 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 proptools 16 17import ( 18 "bytes" 19 "reflect" 20 21 "testing" 22 23 "github.com/google/blueprint/parser" 24) 25 26var validUnpackTestCases = []struct { 27 input string 28 output []interface{} 29 empty []interface{} 30 errs []error 31}{ 32 { 33 input: ` 34 m { 35 s: "abc", 36 blank: "", 37 } 38 `, 39 output: []interface{}{ 40 &struct { 41 S *string 42 Blank *string 43 Unset *string 44 }{ 45 S: StringPtr("abc"), 46 Blank: StringPtr(""), 47 Unset: nil, 48 }, 49 }, 50 }, 51 52 { 53 input: ` 54 m { 55 s: "abc", 56 } 57 `, 58 output: []interface{}{ 59 &struct { 60 S string 61 }{ 62 S: "abc", 63 }, 64 }, 65 }, 66 67 { 68 input: ` 69 m { 70 isGood: true, 71 } 72 `, 73 output: []interface{}{ 74 &struct { 75 IsGood bool 76 }{ 77 IsGood: true, 78 }, 79 }, 80 }, 81 82 { 83 input: ` 84 m { 85 isGood: true, 86 isBad: false, 87 } 88 `, 89 output: []interface{}{ 90 &struct { 91 IsGood *bool 92 IsBad *bool 93 IsUgly *bool 94 }{ 95 IsGood: BoolPtr(true), 96 IsBad: BoolPtr(false), 97 IsUgly: nil, 98 }, 99 }, 100 }, 101 102 { 103 input: ` 104 m { 105 stuff: ["asdf", "jkl;", "qwert", 106 "uiop", "bnm,"], 107 empty: [] 108 } 109 `, 110 output: []interface{}{ 111 &struct { 112 Stuff []string 113 Empty []string 114 Nil []string 115 NonString []struct{ S string } `blueprint:"mutated"` 116 }{ 117 Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"}, 118 Empty: []string{}, 119 Nil: nil, 120 NonString: nil, 121 }, 122 }, 123 }, 124 125 { 126 input: ` 127 m { 128 nested: { 129 s: "abc", 130 } 131 } 132 `, 133 output: []interface{}{ 134 &struct { 135 Nested struct { 136 S string 137 } 138 }{ 139 Nested: struct{ S string }{ 140 S: "abc", 141 }, 142 }, 143 }, 144 }, 145 146 { 147 input: ` 148 m { 149 nested: { 150 s: "def", 151 } 152 } 153 `, 154 output: []interface{}{ 155 &struct { 156 Nested interface{} 157 }{ 158 Nested: &struct{ S string }{ 159 S: "def", 160 }, 161 }, 162 }, 163 }, 164 165 { 166 input: ` 167 m { 168 nested: { 169 foo: "abc", 170 }, 171 bar: false, 172 baz: ["def", "ghi"], 173 } 174 `, 175 output: []interface{}{ 176 &struct { 177 Nested struct { 178 Foo string 179 } 180 Bar bool 181 Baz []string 182 }{ 183 Nested: struct{ Foo string }{ 184 Foo: "abc", 185 }, 186 Bar: false, 187 Baz: []string{"def", "ghi"}, 188 }, 189 }, 190 }, 191 192 { 193 input: ` 194 m { 195 nested: { 196 foo: "abc", 197 }, 198 bar: false, 199 baz: ["def", "ghi"], 200 } 201 `, 202 output: []interface{}{ 203 &struct { 204 Nested struct { 205 Foo string `allowNested:"true"` 206 } `blueprint:"filter(allowNested:\"true\")"` 207 Bar bool 208 Baz []string 209 }{ 210 Nested: struct { 211 Foo string `allowNested:"true"` 212 }{ 213 Foo: "abc", 214 }, 215 Bar: false, 216 Baz: []string{"def", "ghi"}, 217 }, 218 }, 219 }, 220 221 // List of maps 222 { 223 input: ` 224 m { 225 mapslist: [ 226 { 227 foo: "abc", 228 bar: true, 229 }, 230 { 231 foo: "def", 232 bar: false, 233 } 234 ], 235 } 236 `, 237 output: []interface{}{ 238 &struct { 239 Mapslist []struct { 240 Foo string 241 Bar bool 242 } 243 }{ 244 Mapslist: []struct { 245 Foo string 246 Bar bool 247 }{ 248 {Foo: "abc", Bar: true}, 249 {Foo: "def", Bar: false}, 250 }, 251 }, 252 }, 253 }, 254 255 // List of pointers to structs 256 { 257 input: ` 258 m { 259 mapslist: [ 260 { 261 foo: "abc", 262 bar: true, 263 }, 264 { 265 foo: "def", 266 bar: false, 267 } 268 ], 269 } 270 `, 271 output: []interface{}{ 272 &struct { 273 Mapslist []*struct { 274 Foo string 275 Bar bool 276 } 277 }{ 278 Mapslist: []*struct { 279 Foo string 280 Bar bool 281 }{ 282 {Foo: "abc", Bar: true}, 283 {Foo: "def", Bar: false}, 284 }, 285 }, 286 }, 287 }, 288 289 // List of lists 290 { 291 input: ` 292 m { 293 listoflists: [ 294 ["abc",], 295 ["def",], 296 ], 297 } 298 `, 299 output: []interface{}{ 300 &struct { 301 Listoflists [][]string 302 }{ 303 Listoflists: [][]string{ 304 []string{"abc"}, 305 []string{"def"}, 306 }, 307 }, 308 }, 309 }, 310 311 // Multilevel 312 { 313 input: ` 314 m { 315 name: "mymodule", 316 flag: true, 317 settings: ["foo1", "foo2", "foo3",], 318 perarch: { 319 arm: "32", 320 arm64: "64", 321 }, 322 configvars: [ 323 { var: "var1", values: ["1.1", "1.2", ], }, 324 { var: "var2", values: ["2.1", ], }, 325 ], 326 } 327 `, 328 output: []interface{}{ 329 &struct { 330 Name string 331 Flag bool 332 Settings []string 333 Perarch *struct { 334 Arm string 335 Arm64 string 336 } 337 Configvars []struct { 338 Var string 339 Values []string 340 } 341 }{ 342 Name: "mymodule", 343 Flag: true, 344 Settings: []string{"foo1", "foo2", "foo3"}, 345 Perarch: &struct { 346 Arm string 347 Arm64 string 348 }{Arm: "32", Arm64: "64"}, 349 Configvars: []struct { 350 Var string 351 Values []string 352 }{ 353 {Var: "var1", Values: []string{"1.1", "1.2"}}, 354 {Var: "var2", Values: []string{"2.1"}}, 355 }, 356 }, 357 }, 358 }, 359 // Anonymous struct 360 { 361 input: ` 362 m { 363 s: "abc", 364 nested: { 365 s: "def", 366 }, 367 } 368 `, 369 output: []interface{}{ 370 &struct { 371 EmbeddedStruct 372 Nested struct { 373 EmbeddedStruct 374 } 375 }{ 376 EmbeddedStruct: EmbeddedStruct{ 377 S: "abc", 378 }, 379 Nested: struct { 380 EmbeddedStruct 381 }{ 382 EmbeddedStruct: EmbeddedStruct{ 383 S: "def", 384 }, 385 }, 386 }, 387 }, 388 }, 389 390 // Anonymous interface 391 { 392 input: ` 393 m { 394 s: "abc", 395 nested: { 396 s: "def", 397 }, 398 } 399 `, 400 output: []interface{}{ 401 &struct { 402 EmbeddedInterface 403 Nested struct { 404 EmbeddedInterface 405 } 406 }{ 407 EmbeddedInterface: &struct{ S string }{ 408 S: "abc", 409 }, 410 Nested: struct { 411 EmbeddedInterface 412 }{ 413 EmbeddedInterface: &struct{ S string }{ 414 S: "def", 415 }, 416 }, 417 }, 418 }, 419 }, 420 421 // Anonymous struct with name collision 422 { 423 input: ` 424 m { 425 s: "abc", 426 nested: { 427 s: "def", 428 }, 429 } 430 `, 431 output: []interface{}{ 432 &struct { 433 S string 434 EmbeddedStruct 435 Nested struct { 436 S string 437 EmbeddedStruct 438 } 439 }{ 440 S: "abc", 441 EmbeddedStruct: EmbeddedStruct{ 442 S: "abc", 443 }, 444 Nested: struct { 445 S string 446 EmbeddedStruct 447 }{ 448 S: "def", 449 EmbeddedStruct: EmbeddedStruct{ 450 S: "def", 451 }, 452 }, 453 }, 454 }, 455 }, 456 457 // Anonymous interface with name collision 458 { 459 input: ` 460 m { 461 s: "abc", 462 nested: { 463 s: "def", 464 }, 465 } 466 `, 467 output: []interface{}{ 468 &struct { 469 S string 470 EmbeddedInterface 471 Nested struct { 472 S string 473 EmbeddedInterface 474 } 475 }{ 476 S: "abc", 477 EmbeddedInterface: &struct{ S string }{ 478 S: "abc", 479 }, 480 Nested: struct { 481 S string 482 EmbeddedInterface 483 }{ 484 S: "def", 485 EmbeddedInterface: &struct{ S string }{ 486 S: "def", 487 }, 488 }, 489 }, 490 }, 491 }, 492 493 // Variables 494 { 495 input: ` 496 list = ["abc"] 497 string = "def" 498 list_with_variable = [string] 499 struct_value = { name: "foo" } 500 m { 501 s: string, 502 list: list, 503 list2: list_with_variable, 504 structattr: struct_value, 505 } 506 `, 507 output: []interface{}{ 508 &struct { 509 S string 510 List []string 511 List2 []string 512 Structattr struct { 513 Name string 514 } 515 }{ 516 S: "def", 517 List: []string{"abc"}, 518 List2: []string{"def"}, 519 Structattr: struct { 520 Name string 521 }{ 522 Name: "foo", 523 }, 524 }, 525 }, 526 }, 527 528 // Multiple property structs 529 { 530 input: ` 531 m { 532 nested: { 533 s: "abc", 534 } 535 } 536 `, 537 output: []interface{}{ 538 &struct { 539 Nested struct { 540 S string 541 } 542 }{ 543 Nested: struct{ S string }{ 544 S: "abc", 545 }, 546 }, 547 &struct { 548 Nested struct { 549 S string 550 } 551 }{ 552 Nested: struct{ S string }{ 553 S: "abc", 554 }, 555 }, 556 &struct { 557 }{}, 558 }, 559 }, 560 561 // Nil pointer to struct 562 { 563 input: ` 564 m { 565 nested: { 566 s: "abc", 567 } 568 } 569 `, 570 output: []interface{}{ 571 &struct { 572 Nested *struct { 573 S string 574 } 575 }{ 576 Nested: &struct{ S string }{ 577 S: "abc", 578 }, 579 }, 580 }, 581 empty: []interface{}{ 582 &struct { 583 Nested *struct { 584 S string 585 } 586 }{}, 587 }, 588 }, 589 590 // Interface containing nil pointer to struct 591 { 592 input: ` 593 m { 594 nested: { 595 s: "abc", 596 } 597 } 598 `, 599 output: []interface{}{ 600 &struct { 601 Nested interface{} 602 }{ 603 Nested: &EmbeddedStruct{ 604 S: "abc", 605 }, 606 }, 607 }, 608 empty: []interface{}{ 609 &struct { 610 Nested interface{} 611 }{ 612 Nested: (*EmbeddedStruct)(nil), 613 }, 614 }, 615 }, 616 617 // Factory set properties 618 { 619 input: ` 620 m { 621 string: "abc", 622 string_ptr: "abc", 623 bool: false, 624 bool_ptr: false, 625 list: ["a", "b", "c"], 626 } 627 `, 628 output: []interface{}{ 629 &struct { 630 String string 631 String_ptr *string 632 Bool bool 633 Bool_ptr *bool 634 List []string 635 }{ 636 String: "012abc", 637 String_ptr: StringPtr("abc"), 638 Bool: true, 639 Bool_ptr: BoolPtr(false), 640 List: []string{"0", "1", "2", "a", "b", "c"}, 641 }, 642 }, 643 empty: []interface{}{ 644 &struct { 645 String string 646 String_ptr *string 647 Bool bool 648 Bool_ptr *bool 649 List []string 650 }{ 651 String: "012", 652 String_ptr: StringPtr("012"), 653 Bool: true, 654 Bool_ptr: BoolPtr(true), 655 List: []string{"0", "1", "2"}, 656 }, 657 }, 658 }, 659 // Captitalized property 660 { 661 input: ` 662 m { 663 CAPITALIZED: "foo", 664 } 665 `, 666 output: []interface{}{ 667 &struct { 668 CAPITALIZED string 669 }{ 670 CAPITALIZED: "foo", 671 }, 672 }, 673 }, 674} 675 676func TestUnpackProperties(t *testing.T) { 677 for _, testCase := range validUnpackTestCases { 678 r := bytes.NewBufferString(testCase.input) 679 file, errs := parser.ParseAndEval("", r, parser.NewScope(nil)) 680 if len(errs) != 0 { 681 t.Errorf("test case: %s", testCase.input) 682 t.Errorf("unexpected parse errors:") 683 for _, err := range errs { 684 t.Errorf(" %s", err) 685 } 686 t.FailNow() 687 } 688 689 for _, def := range file.Defs { 690 module, ok := def.(*parser.Module) 691 if !ok { 692 continue 693 } 694 695 var output []interface{} 696 if len(testCase.empty) > 0 { 697 output = testCase.empty 698 } else { 699 for _, p := range testCase.output { 700 output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface()) 701 } 702 } 703 _, errs = UnpackProperties(module.Properties, output...) 704 if len(errs) != 0 && len(testCase.errs) == 0 { 705 t.Errorf("test case: %s", testCase.input) 706 t.Errorf("unexpected unpack errors:") 707 for _, err := range errs { 708 t.Errorf(" %s", err) 709 } 710 t.FailNow() 711 } else if !reflect.DeepEqual(errs, testCase.errs) { 712 t.Errorf("test case: %s", testCase.input) 713 t.Errorf("incorrect errors:") 714 t.Errorf(" expected: %+v", testCase.errs) 715 t.Errorf(" got: %+v", errs) 716 } 717 718 if len(output) != len(testCase.output) { 719 t.Fatalf("incorrect number of property structs, expected %d got %d", 720 len(testCase.output), len(output)) 721 } 722 723 for i := range output { 724 got := reflect.ValueOf(output[i]).Interface() 725 if !reflect.DeepEqual(got, testCase.output[i]) { 726 t.Errorf("test case: %s", testCase.input) 727 t.Errorf("incorrect output:") 728 t.Errorf(" expected: %+v", testCase.output[i]) 729 t.Errorf(" got: %+v", got) 730 } 731 } 732 } 733 } 734} 735 736func BenchmarkUnpackProperties(b *testing.B) { 737 run := func(b *testing.B, props []interface{}, input string) { 738 b.ReportAllocs() 739 b.StopTimer() 740 r := bytes.NewBufferString(input) 741 file, errs := parser.ParseAndEval("", r, parser.NewScope(nil)) 742 if len(errs) != 0 { 743 b.Errorf("test case: %s", input) 744 b.Errorf("unexpected parse errors:") 745 for _, err := range errs { 746 b.Errorf(" %s", err) 747 } 748 b.FailNow() 749 } 750 751 for i := 0; i < b.N; i++ { 752 for _, def := range file.Defs { 753 module, ok := def.(*parser.Module) 754 if !ok { 755 continue 756 } 757 758 var output []interface{} 759 for _, p := range props { 760 output = append(output, CloneProperties(reflect.ValueOf(p)).Interface()) 761 } 762 763 b.StartTimer() 764 _, errs = UnpackProperties(module.Properties, output...) 765 b.StopTimer() 766 if len(errs) > 0 { 767 b.Errorf("unexpected unpack errors:") 768 for _, err := range errs { 769 b.Errorf(" %s", err) 770 } 771 } 772 } 773 } 774 } 775 776 b.Run("basic", func(b *testing.B) { 777 props := []interface{}{ 778 &struct { 779 Nested struct { 780 S string 781 } 782 }{}, 783 } 784 bp := ` 785 m { 786 nested: { 787 s: "abc", 788 }, 789 } 790 ` 791 run(b, props, bp) 792 }) 793 794 b.Run("interface", func(b *testing.B) { 795 props := []interface{}{ 796 &struct { 797 Nested interface{} 798 }{ 799 Nested: (*struct { 800 S string 801 })(nil), 802 }, 803 } 804 bp := ` 805 m { 806 nested: { 807 s: "abc", 808 }, 809 } 810 ` 811 run(b, props, bp) 812 }) 813 814 b.Run("many", func(b *testing.B) { 815 props := []interface{}{ 816 &struct { 817 A *string 818 B *string 819 C *string 820 D *string 821 E *string 822 F *string 823 G *string 824 H *string 825 I *string 826 J *string 827 }{}, 828 } 829 bp := ` 830 m { 831 a: "a", 832 b: "b", 833 c: "c", 834 d: "d", 835 e: "e", 836 f: "f", 837 g: "g", 838 h: "h", 839 i: "i", 840 j: "j", 841 } 842 ` 843 run(b, props, bp) 844 }) 845 846 b.Run("deep", func(b *testing.B) { 847 props := []interface{}{ 848 &struct { 849 Nested struct { 850 Nested struct { 851 Nested struct { 852 Nested struct { 853 Nested struct { 854 Nested struct { 855 Nested struct { 856 Nested struct { 857 Nested struct { 858 Nested struct { 859 S string 860 } 861 } 862 } 863 } 864 } 865 } 866 } 867 } 868 } 869 } 870 }{}, 871 } 872 bp := ` 873 m { 874 nested: { nested: { nested: { nested: { nested: { 875 nested: { nested: { nested: { nested: { nested: { 876 s: "abc", 877 }, }, }, }, }, 878 }, }, }, }, }, 879 } 880 ` 881 run(b, props, bp) 882 }) 883 884 b.Run("mix", func(b *testing.B) { 885 props := []interface{}{ 886 &struct { 887 Name string 888 Flag bool 889 Settings []string 890 Perarch *struct { 891 Arm string 892 Arm64 string 893 } 894 Configvars []struct { 895 Name string 896 Values []string 897 } 898 }{}, 899 } 900 bp := ` 901 m { 902 name: "mymodule", 903 flag: true, 904 settings: ["foo1", "foo2", "foo3",], 905 perarch: { 906 arm: "32", 907 arm64: "64", 908 }, 909 configvars: [ 910 { name: "var1", values: ["var1:1", "var1:2", ], }, 911 { name: "var2", values: ["var2:1", "var2:2", ], }, 912 ], 913 } 914 ` 915 run(b, props, bp) 916 }) 917} 918