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 "testing" 21 "text/scanner" 22 23 "github.com/google/blueprint/parser" 24) 25 26var validUnpackTestCases = []struct { 27 name string 28 input string 29 output []interface{} 30 empty []interface{} 31 errs []error 32}{ 33 { 34 name: "blank and unset", 35 input: ` 36 m { 37 s: "abc", 38 blank: "", 39 } 40 `, 41 output: []interface{}{ 42 &struct { 43 S *string 44 Blank *string 45 Unset *string 46 }{ 47 S: StringPtr("abc"), 48 Blank: StringPtr(""), 49 Unset: nil, 50 }, 51 }, 52 }, 53 54 { 55 name: "string", 56 input: ` 57 m { 58 s: "abc", 59 } 60 `, 61 output: []interface{}{ 62 &struct { 63 S string 64 }{ 65 S: "abc", 66 }, 67 }, 68 }, 69 70 { 71 name: "bool", 72 input: ` 73 m { 74 isGood: true, 75 } 76 `, 77 output: []interface{}{ 78 &struct { 79 IsGood bool 80 }{ 81 IsGood: true, 82 }, 83 }, 84 }, 85 86 { 87 name: "boolptr", 88 input: ` 89 m { 90 isGood: true, 91 isBad: false, 92 } 93 `, 94 output: []interface{}{ 95 &struct { 96 IsGood *bool 97 IsBad *bool 98 IsUgly *bool 99 }{ 100 IsGood: BoolPtr(true), 101 IsBad: BoolPtr(false), 102 IsUgly: nil, 103 }, 104 }, 105 }, 106 107 { 108 name: "slice", 109 input: ` 110 m { 111 stuff: ["asdf", "jkl;", "qwert", 112 "uiop", "bnm,"], 113 empty: [] 114 } 115 `, 116 output: []interface{}{ 117 &struct { 118 Stuff []string 119 Empty []string 120 Nil []string 121 NonString []struct{ S string } `blueprint:"mutated"` 122 }{ 123 Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"}, 124 Empty: []string{}, 125 Nil: nil, 126 NonString: nil, 127 }, 128 }, 129 }, 130 131 { 132 name: "double nested", 133 input: ` 134 m { 135 nested: { 136 nested: { 137 s: "abc", 138 }, 139 }, 140 } 141 `, 142 output: []interface{}{ 143 &struct { 144 Nested struct { 145 Nested struct { 146 S string 147 } 148 } 149 }{ 150 Nested: struct{ Nested struct{ S string } }{ 151 Nested: struct{ S string }{ 152 S: "abc", 153 }, 154 }, 155 }, 156 }, 157 }, 158 159 { 160 name: "nested", 161 input: ` 162 m { 163 nested: { 164 s: "abc", 165 } 166 } 167 `, 168 output: []interface{}{ 169 &struct { 170 Nested struct { 171 S string 172 } 173 }{ 174 Nested: struct{ S string }{ 175 S: "abc", 176 }, 177 }, 178 }, 179 }, 180 181 { 182 name: "nested interface", 183 input: ` 184 m { 185 nested: { 186 s: "def", 187 } 188 } 189 `, 190 output: []interface{}{ 191 &struct { 192 Nested interface{} 193 }{ 194 Nested: &struct{ S string }{ 195 S: "def", 196 }, 197 }, 198 }, 199 }, 200 201 { 202 name: "mixed", 203 input: ` 204 m { 205 nested: { 206 foo: "abc", 207 }, 208 bar: false, 209 baz: ["def", "ghi"], 210 } 211 `, 212 output: []interface{}{ 213 &struct { 214 Nested struct { 215 Foo string 216 } 217 Bar bool 218 Baz []string 219 }{ 220 Nested: struct{ Foo string }{ 221 Foo: "abc", 222 }, 223 Bar: false, 224 Baz: []string{"def", "ghi"}, 225 }, 226 }, 227 }, 228 229 { 230 name: "filter", 231 input: ` 232 m { 233 nested: { 234 foo: "abc", 235 }, 236 bar: false, 237 baz: ["def", "ghi"], 238 } 239 `, 240 output: []interface{}{ 241 &struct { 242 Nested struct { 243 Foo string `allowNested:"true"` 244 } `blueprint:"filter(allowNested:\"true\")"` 245 Bar bool 246 Baz []string 247 }{ 248 Nested: struct { 249 Foo string `allowNested:"true"` 250 }{ 251 Foo: "abc", 252 }, 253 Bar: false, 254 Baz: []string{"def", "ghi"}, 255 }, 256 }, 257 }, 258 259 // List of maps 260 { 261 name: "list of structs", 262 input: ` 263 m { 264 mapslist: [ 265 { 266 foo: "abc", 267 bar: true, 268 }, 269 { 270 foo: "def", 271 bar: false, 272 } 273 ], 274 } 275 `, 276 output: []interface{}{ 277 &struct { 278 Mapslist []struct { 279 Foo string 280 Bar bool 281 } 282 }{ 283 Mapslist: []struct { 284 Foo string 285 Bar bool 286 }{ 287 {Foo: "abc", Bar: true}, 288 {Foo: "def", Bar: false}, 289 }, 290 }, 291 }, 292 }, 293 294 // List of pointers to structs 295 { 296 name: "list of pointers to structs", 297 input: ` 298 m { 299 mapslist: [ 300 { 301 foo: "abc", 302 bar: true, 303 }, 304 { 305 foo: "def", 306 bar: false, 307 } 308 ], 309 } 310 `, 311 output: []interface{}{ 312 &struct { 313 Mapslist []*struct { 314 Foo string 315 Bar bool 316 } 317 }{ 318 Mapslist: []*struct { 319 Foo string 320 Bar bool 321 }{ 322 {Foo: "abc", Bar: true}, 323 {Foo: "def", Bar: false}, 324 }, 325 }, 326 }, 327 }, 328 329 // List of lists 330 { 331 name: "list of lists", 332 input: ` 333 m { 334 listoflists: [ 335 ["abc",], 336 ["def",], 337 ], 338 } 339 `, 340 output: []interface{}{ 341 &struct { 342 Listoflists [][]string 343 }{ 344 Listoflists: [][]string{ 345 []string{"abc"}, 346 []string{"def"}, 347 }, 348 }, 349 }, 350 }, 351 352 // Multilevel 353 { 354 name: "multilevel", 355 input: ` 356 m { 357 name: "mymodule", 358 flag: true, 359 settings: ["foo1", "foo2", "foo3",], 360 perarch: { 361 arm: "32", 362 arm64: "64", 363 }, 364 configvars: [ 365 { var: "var1", values: ["1.1", "1.2", ], }, 366 { var: "var2", values: ["2.1", ], }, 367 ], 368 } 369 `, 370 output: []interface{}{ 371 &struct { 372 Name string 373 Flag bool 374 Settings []string 375 Perarch *struct { 376 Arm string 377 Arm64 string 378 } 379 Configvars []struct { 380 Var string 381 Values []string 382 } 383 }{ 384 Name: "mymodule", 385 Flag: true, 386 Settings: []string{"foo1", "foo2", "foo3"}, 387 Perarch: &struct { 388 Arm string 389 Arm64 string 390 }{Arm: "32", Arm64: "64"}, 391 Configvars: []struct { 392 Var string 393 Values []string 394 }{ 395 {Var: "var1", Values: []string{"1.1", "1.2"}}, 396 {Var: "var2", Values: []string{"2.1"}}, 397 }, 398 }, 399 }, 400 }, 401 // Anonymous struct 402 { 403 name: "embedded struct", 404 input: ` 405 m { 406 s: "abc", 407 nested: { 408 s: "def", 409 }, 410 } 411 `, 412 output: []interface{}{ 413 &struct { 414 EmbeddedStruct 415 Nested struct { 416 EmbeddedStruct 417 } 418 }{ 419 EmbeddedStruct: EmbeddedStruct{ 420 S: "abc", 421 }, 422 Nested: struct { 423 EmbeddedStruct 424 }{ 425 EmbeddedStruct: EmbeddedStruct{ 426 S: "def", 427 }, 428 }, 429 }, 430 }, 431 }, 432 433 // Anonymous interface 434 { 435 name: "embedded interface", 436 input: ` 437 m { 438 s: "abc", 439 nested: { 440 s: "def", 441 }, 442 } 443 `, 444 output: []interface{}{ 445 &struct { 446 EmbeddedInterface 447 Nested struct { 448 EmbeddedInterface 449 } 450 }{ 451 EmbeddedInterface: &struct{ S string }{ 452 S: "abc", 453 }, 454 Nested: struct { 455 EmbeddedInterface 456 }{ 457 EmbeddedInterface: &struct{ S string }{ 458 S: "def", 459 }, 460 }, 461 }, 462 }, 463 }, 464 465 // Anonymous struct with name collision 466 { 467 name: "embedded name collision", 468 input: ` 469 m { 470 s: "abc", 471 nested: { 472 s: "def", 473 }, 474 } 475 `, 476 output: []interface{}{ 477 &struct { 478 S string 479 EmbeddedStruct 480 Nested struct { 481 S string 482 EmbeddedStruct 483 } 484 }{ 485 S: "abc", 486 EmbeddedStruct: EmbeddedStruct{ 487 S: "abc", 488 }, 489 Nested: struct { 490 S string 491 EmbeddedStruct 492 }{ 493 S: "def", 494 EmbeddedStruct: EmbeddedStruct{ 495 S: "def", 496 }, 497 }, 498 }, 499 }, 500 }, 501 502 // Anonymous interface with name collision 503 { 504 name: "embeded interface name collision", 505 input: ` 506 m { 507 s: "abc", 508 nested: { 509 s: "def", 510 }, 511 } 512 `, 513 output: []interface{}{ 514 &struct { 515 S string 516 EmbeddedInterface 517 Nested struct { 518 S string 519 EmbeddedInterface 520 } 521 }{ 522 S: "abc", 523 EmbeddedInterface: &struct{ S string }{ 524 S: "abc", 525 }, 526 Nested: struct { 527 S string 528 EmbeddedInterface 529 }{ 530 S: "def", 531 EmbeddedInterface: &struct{ S string }{ 532 S: "def", 533 }, 534 }, 535 }, 536 }, 537 }, 538 539 // Variables 540 { 541 name: "variables", 542 input: ` 543 list = ["abc"] 544 string = "def" 545 list_with_variable = [string] 546 struct_value = { name: "foo" } 547 m { 548 s: string, 549 list: list, 550 list2: list_with_variable, 551 structattr: struct_value, 552 } 553 `, 554 output: []interface{}{ 555 &struct { 556 S string 557 List []string 558 List2 []string 559 Structattr struct { 560 Name string 561 } 562 }{ 563 S: "def", 564 List: []string{"abc"}, 565 List2: []string{"def"}, 566 Structattr: struct { 567 Name string 568 }{ 569 Name: "foo", 570 }, 571 }, 572 }, 573 }, 574 575 // Multiple property structs 576 { 577 name: "multiple", 578 input: ` 579 m { 580 nested: { 581 s: "abc", 582 } 583 } 584 `, 585 output: []interface{}{ 586 &struct { 587 Nested struct { 588 S string 589 } 590 }{ 591 Nested: struct{ S string }{ 592 S: "abc", 593 }, 594 }, 595 &struct { 596 Nested struct { 597 S string 598 } 599 }{ 600 Nested: struct{ S string }{ 601 S: "abc", 602 }, 603 }, 604 &struct { 605 }{}, 606 }, 607 }, 608 609 // Nil pointer to struct 610 { 611 name: "nil struct pointer", 612 input: ` 613 m { 614 nested: { 615 s: "abc", 616 } 617 } 618 `, 619 output: []interface{}{ 620 &struct { 621 Nested *struct { 622 S string 623 } 624 }{ 625 Nested: &struct{ S string }{ 626 S: "abc", 627 }, 628 }, 629 }, 630 empty: []interface{}{ 631 &struct { 632 Nested *struct { 633 S string 634 } 635 }{}, 636 }, 637 }, 638 639 // Interface containing nil pointer to struct 640 { 641 name: "interface nil struct pointer", 642 input: ` 643 m { 644 nested: { 645 s: "abc", 646 } 647 } 648 `, 649 output: []interface{}{ 650 &struct { 651 Nested interface{} 652 }{ 653 Nested: &EmbeddedStruct{ 654 S: "abc", 655 }, 656 }, 657 }, 658 empty: []interface{}{ 659 &struct { 660 Nested interface{} 661 }{ 662 Nested: (*EmbeddedStruct)(nil), 663 }, 664 }, 665 }, 666 667 // Factory set properties 668 { 669 name: "factory properties", 670 input: ` 671 m { 672 string: "abc", 673 string_ptr: "abc", 674 bool: false, 675 bool_ptr: false, 676 list: ["a", "b", "c"], 677 } 678 `, 679 output: []interface{}{ 680 &struct { 681 String string 682 String_ptr *string 683 Bool bool 684 Bool_ptr *bool 685 List []string 686 }{ 687 String: "012abc", 688 String_ptr: StringPtr("abc"), 689 Bool: true, 690 Bool_ptr: BoolPtr(false), 691 List: []string{"0", "1", "2", "a", "b", "c"}, 692 }, 693 }, 694 empty: []interface{}{ 695 &struct { 696 String string 697 String_ptr *string 698 Bool bool 699 Bool_ptr *bool 700 List []string 701 }{ 702 String: "012", 703 String_ptr: StringPtr("012"), 704 Bool: true, 705 Bool_ptr: BoolPtr(true), 706 List: []string{"0", "1", "2"}, 707 }, 708 }, 709 }, 710 // Captitalized property 711 { 712 input: ` 713 m { 714 CAPITALIZED: "foo", 715 } 716 `, 717 output: []interface{}{ 718 &struct { 719 CAPITALIZED string 720 }{ 721 CAPITALIZED: "foo", 722 }, 723 }, 724 }, 725 { 726 name: "String configurable property that isn't configured", 727 input: ` 728 m { 729 foo: "bar" 730 } 731 `, 732 output: []interface{}{ 733 &struct { 734 Foo Configurable[string] 735 }{ 736 Foo: newConfigurableWithPropertyName( 737 "foo", 738 nil, 739 []ConfigurableCase[string]{{ 740 value: &parser.String{ 741 LiteralPos: scanner.Position{ 742 Offset: 17, 743 Line: 3, 744 Column: 10, 745 }, 746 Value: "bar", 747 }, 748 }}, 749 false, 750 ), 751 }, 752 }, 753 }, 754 { 755 name: "Bool configurable property that isn't configured", 756 input: ` 757 m { 758 foo: true, 759 } 760 `, 761 output: []interface{}{ 762 &struct { 763 Foo Configurable[bool] 764 }{ 765 Foo: newConfigurableWithPropertyName( 766 "foo", 767 nil, 768 []ConfigurableCase[bool]{{ 769 value: &parser.Bool{ 770 LiteralPos: scanner.Position{ 771 Offset: 17, 772 Line: 3, 773 Column: 10, 774 }, 775 Value: true, 776 Token: "true", 777 }, 778 }}, 779 false, 780 ), 781 }, 782 }, 783 }, 784 { 785 name: "String list configurable property that isn't configured", 786 input: ` 787 m { 788 foo: ["a", "b"], 789 } 790 `, 791 output: []interface{}{ 792 &struct { 793 Foo Configurable[[]string] 794 }{ 795 Foo: newConfigurableWithPropertyName( 796 "foo", 797 nil, 798 []ConfigurableCase[[]string]{{ 799 value: &parser.List{ 800 LBracePos: scanner.Position{ 801 Offset: 17, 802 Line: 3, 803 Column: 10, 804 }, 805 RBracePos: scanner.Position{ 806 Offset: 26, 807 Line: 3, 808 Column: 19, 809 }, 810 Values: []parser.Expression{ 811 &parser.String{ 812 LiteralPos: scanner.Position{ 813 Offset: 18, 814 Line: 3, 815 Column: 11, 816 }, 817 Value: "a", 818 }, 819 &parser.String{ 820 LiteralPos: scanner.Position{ 821 Offset: 23, 822 Line: 3, 823 Column: 16, 824 }, 825 Value: "b", 826 }, 827 }, 828 }, 829 }}, 830 false, 831 ), 832 }, 833 }, 834 }, 835 { 836 name: "Configurable property", 837 input: ` 838 m { 839 foo: select(soong_config_variable("my_namespace", "my_variable"), { 840 "a": "a2", 841 "b": "b2", 842 default: "c2", 843 }) 844 } 845 `, 846 output: []interface{}{ 847 &struct { 848 Foo Configurable[string] 849 }{ 850 Foo: newConfigurableWithPropertyName( 851 "foo", 852 []ConfigurableCondition{{ 853 functionName: "soong_config_variable", 854 args: []string{ 855 "my_namespace", 856 "my_variable", 857 }, 858 }}, 859 []ConfigurableCase[string]{ 860 { 861 patterns: []ConfigurablePattern{{ 862 typ: configurablePatternTypeString, 863 stringValue: "a", 864 }}, 865 value: &parser.String{ 866 LiteralPos: scanner.Position{ 867 Offset: 90, 868 Line: 4, 869 Column: 11, 870 }, 871 Value: "a2", 872 }, 873 }, 874 { 875 patterns: []ConfigurablePattern{{ 876 typ: configurablePatternTypeString, 877 stringValue: "b", 878 }}, 879 value: &parser.String{ 880 LiteralPos: scanner.Position{ 881 Offset: 106, 882 Line: 5, 883 Column: 11, 884 }, 885 Value: "b2", 886 }, 887 }, 888 { 889 patterns: []ConfigurablePattern{{ 890 typ: configurablePatternTypeDefault, 891 }}, 892 value: &parser.String{ 893 LiteralPos: scanner.Position{ 894 Offset: 126, 895 Line: 6, 896 Column: 15, 897 }, 898 Value: "c2", 899 }, 900 }, 901 }, 902 true, 903 ), 904 }, 905 }, 906 }, 907 { 908 name: "Configurable property appending", 909 input: ` 910 m { 911 foo: select(soong_config_variable("my_namespace", "my_variable"), { 912 "a": "a2", 913 "b": "b2", 914 default: "c2", 915 }) + select(soong_config_variable("my_namespace", "my_2nd_variable"), { 916 "d": "d2", 917 "e": "e2", 918 default: "f2", 919 }) 920 } 921 `, 922 output: []interface{}{ 923 &struct { 924 Foo Configurable[string] 925 }{ 926 Foo: func() Configurable[string] { 927 result := newConfigurableWithPropertyName( 928 "foo", 929 []ConfigurableCondition{{ 930 functionName: "soong_config_variable", 931 args: []string{ 932 "my_namespace", 933 "my_variable", 934 }, 935 }}, 936 []ConfigurableCase[string]{ 937 { 938 patterns: []ConfigurablePattern{{ 939 typ: configurablePatternTypeString, 940 stringValue: "a", 941 }}, 942 value: &parser.String{ 943 LiteralPos: scanner.Position{ 944 Offset: 90, 945 Line: 4, 946 Column: 11, 947 }, 948 Value: "a2", 949 }, 950 }, 951 { 952 patterns: []ConfigurablePattern{{ 953 typ: configurablePatternTypeString, 954 stringValue: "b", 955 }}, 956 value: &parser.String{ 957 LiteralPos: scanner.Position{ 958 Offset: 106, 959 Line: 5, 960 Column: 11, 961 }, 962 Value: "b2", 963 }, 964 }, 965 { 966 patterns: []ConfigurablePattern{{ 967 typ: configurablePatternTypeDefault, 968 }}, 969 value: &parser.String{ 970 LiteralPos: scanner.Position{ 971 Offset: 126, 972 Line: 6, 973 Column: 15, 974 }, 975 Value: "c2", 976 }, 977 }, 978 }, 979 true, 980 ) 981 result.Append(newConfigurableWithPropertyName( 982 "", 983 []ConfigurableCondition{{ 984 functionName: "soong_config_variable", 985 args: []string{ 986 "my_namespace", 987 "my_2nd_variable", 988 }, 989 }}, 990 []ConfigurableCase[string]{ 991 { 992 patterns: []ConfigurablePattern{{ 993 typ: configurablePatternTypeString, 994 stringValue: "d", 995 }}, 996 value: &parser.String{ 997 LiteralPos: scanner.Position{ 998 Offset: 218, 999 Line: 8, 1000 Column: 11, 1001 }, 1002 Value: "d2", 1003 }, 1004 }, 1005 { 1006 patterns: []ConfigurablePattern{{ 1007 typ: configurablePatternTypeString, 1008 stringValue: "e", 1009 }}, 1010 value: &parser.String{ 1011 LiteralPos: scanner.Position{ 1012 Offset: 234, 1013 Line: 9, 1014 Column: 11, 1015 }, 1016 Value: "e2", 1017 }, 1018 }, 1019 { 1020 patterns: []ConfigurablePattern{{ 1021 typ: configurablePatternTypeDefault, 1022 }}, 1023 value: &parser.String{ 1024 LiteralPos: scanner.Position{ 1025 Offset: 254, 1026 Line: 10, 1027 Column: 15, 1028 }, 1029 Value: "f2", 1030 }, 1031 }, 1032 }, 1033 true, 1034 )) 1035 return result 1036 }(), 1037 }, 1038 }, 1039 }, 1040 { 1041 name: "Unpack variable to configurable property", 1042 input: ` 1043 my_string_variable = "asdf" 1044 my_bool_variable = true 1045 m { 1046 foo: my_string_variable, 1047 bar: my_bool_variable, 1048 } 1049 `, 1050 output: []interface{}{ 1051 &struct { 1052 Foo Configurable[string] 1053 Bar Configurable[bool] 1054 }{ 1055 Foo: newConfigurableWithPropertyName( 1056 "foo", 1057 nil, 1058 []ConfigurableCase[string]{{ 1059 value: &parser.String{ 1060 LiteralPos: scanner.Position{ 1061 Offset: 25, 1062 Line: 2, 1063 Column: 25, 1064 }, 1065 Value: "asdf", 1066 }, 1067 }}, 1068 false, 1069 ), 1070 Bar: newConfigurableWithPropertyName( 1071 "bar", 1072 nil, 1073 []ConfigurableCase[bool]{{ 1074 value: &parser.Bool{ 1075 LiteralPos: scanner.Position{ 1076 Offset: 54, 1077 Line: 3, 1078 Column: 23, 1079 }, 1080 Value: true, 1081 Token: "true", 1082 }, 1083 }}, 1084 false, 1085 ), 1086 }, 1087 }, 1088 }, 1089} 1090 1091func TestUnpackProperties(t *testing.T) { 1092 for _, testCase := range validUnpackTestCases { 1093 t.Run(testCase.name, func(t *testing.T) { 1094 r := bytes.NewBufferString(testCase.input) 1095 file, errs := parser.ParseAndEval("", r, parser.NewScope(nil)) 1096 if len(errs) != 0 { 1097 t.Errorf("test case: %s", testCase.input) 1098 t.Errorf("unexpected parse errors:") 1099 for _, err := range errs { 1100 t.Errorf(" %s", err) 1101 } 1102 t.FailNow() 1103 } 1104 1105 for _, def := range file.Defs { 1106 module, ok := def.(*parser.Module) 1107 if !ok { 1108 continue 1109 } 1110 1111 var output []interface{} 1112 if len(testCase.empty) > 0 { 1113 for _, p := range testCase.empty { 1114 output = append(output, CloneProperties(reflect.ValueOf(p)).Interface()) 1115 } 1116 } else { 1117 for _, p := range testCase.output { 1118 output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface()) 1119 } 1120 } 1121 1122 _, errs = UnpackProperties(module.Properties, output...) 1123 if len(errs) != 0 && len(testCase.errs) == 0 { 1124 t.Errorf("test case: %s", testCase.input) 1125 t.Errorf("unexpected unpack errors:") 1126 for _, err := range errs { 1127 t.Errorf(" %s", err) 1128 } 1129 t.FailNow() 1130 } else if !reflect.DeepEqual(errs, testCase.errs) { 1131 t.Errorf("test case: %s", testCase.input) 1132 t.Errorf("incorrect errors:") 1133 t.Errorf(" expected: %+v", testCase.errs) 1134 t.Errorf(" got: %+v", errs) 1135 } 1136 1137 if len(output) != len(testCase.output) { 1138 t.Fatalf("incorrect number of property structs, expected %d got %d", 1139 len(testCase.output), len(output)) 1140 } 1141 1142 for i := range output { 1143 got := reflect.ValueOf(output[i]).Interface() 1144 if !reflect.DeepEqual(got, testCase.output[i]) { 1145 t.Errorf("test case: %s", testCase.input) 1146 t.Errorf("incorrect output:") 1147 t.Errorf(" expected: %+v", testCase.output[i]) 1148 t.Errorf(" got: %+v", got) 1149 } 1150 } 1151 } 1152 }) 1153 } 1154} 1155 1156func TestUnpackErrors(t *testing.T) { 1157 testCases := []struct { 1158 name string 1159 input string 1160 output []interface{} 1161 errors []string 1162 }{ 1163 { 1164 name: "missing", 1165 input: ` 1166 m { 1167 missing: true, 1168 } 1169 `, 1170 output: []interface{}{}, 1171 errors: []string{`<input>:3:13: unrecognized property "missing"`}, 1172 }, 1173 { 1174 name: "missing nested", 1175 input: ` 1176 m { 1177 nested: { 1178 missing: true, 1179 }, 1180 } 1181 `, 1182 output: []interface{}{ 1183 &struct { 1184 Nested struct{} 1185 }{}, 1186 }, 1187 errors: []string{`<input>:4:14: unrecognized property "nested.missing"`}, 1188 }, 1189 { 1190 name: "mutated", 1191 input: ` 1192 m { 1193 mutated: true, 1194 } 1195 `, 1196 output: []interface{}{ 1197 &struct { 1198 Mutated bool `blueprint:"mutated"` 1199 }{}, 1200 }, 1201 errors: []string{`<input>:3:13: mutated field mutated cannot be set in a Blueprint file`}, 1202 }, 1203 { 1204 name: "nested mutated", 1205 input: ` 1206 m { 1207 nested: { 1208 mutated: true, 1209 }, 1210 } 1211 `, 1212 output: []interface{}{ 1213 &struct { 1214 Nested struct { 1215 Mutated bool `blueprint:"mutated"` 1216 } 1217 }{}, 1218 }, 1219 errors: []string{`<input>:4:14: mutated field nested.mutated cannot be set in a Blueprint file`}, 1220 }, 1221 { 1222 name: "duplicate", 1223 input: ` 1224 m { 1225 exists: true, 1226 exists: true, 1227 } 1228 `, 1229 output: []interface{}{ 1230 &struct { 1231 Exists bool 1232 }{}, 1233 }, 1234 errors: []string{ 1235 `<input>:4:12: property "exists" already defined`, 1236 `<input>:3:12: <-- previous definition here`, 1237 }, 1238 }, 1239 { 1240 name: "nested duplicate", 1241 input: ` 1242 m { 1243 nested: { 1244 exists: true, 1245 exists: true, 1246 }, 1247 } 1248 `, 1249 output: []interface{}{ 1250 &struct { 1251 Nested struct { 1252 Exists bool 1253 } 1254 }{}, 1255 }, 1256 errors: []string{ 1257 `<input>:5:13: property "nested.exists" already defined`, 1258 `<input>:4:13: <-- previous definition here`, 1259 }, 1260 }, 1261 { 1262 name: "wrong type", 1263 input: ` 1264 m { 1265 int: "foo", 1266 } 1267 `, 1268 output: []interface{}{ 1269 &struct { 1270 Int *int64 1271 }{}, 1272 }, 1273 errors: []string{ 1274 `<input>:3:11: can't assign string value to int64 property "int"`, 1275 }, 1276 }, 1277 { 1278 name: "wrong type for map", 1279 input: ` 1280 m { 1281 map: "foo", 1282 } 1283 `, 1284 output: []interface{}{ 1285 &struct { 1286 Map struct { 1287 S string 1288 } 1289 }{}, 1290 }, 1291 errors: []string{ 1292 `<input>:3:11: can't assign string value to map property "map"`, 1293 }, 1294 }, 1295 { 1296 name: "wrong type for list", 1297 input: ` 1298 m { 1299 list: "foo", 1300 } 1301 `, 1302 output: []interface{}{ 1303 &struct { 1304 List []string 1305 }{}, 1306 }, 1307 errors: []string{ 1308 `<input>:3:12: can't assign string value to list property "list"`, 1309 }, 1310 }, 1311 { 1312 name: "wrong type for list of maps", 1313 input: ` 1314 m { 1315 map_list: "foo", 1316 } 1317 `, 1318 output: []interface{}{ 1319 &struct { 1320 Map_list []struct { 1321 S string 1322 } 1323 }{}, 1324 }, 1325 errors: []string{ 1326 `<input>:3:16: can't assign string value to list property "map_list"`, 1327 }, 1328 }, 1329 { 1330 name: "non-existent property", 1331 input: ` 1332 m { 1333 foo: { 1334 foo_prop1: true, 1335 foo_prop2: false, 1336 foo_prop3: true, 1337 }, 1338 bar: { 1339 bar_prop: false, 1340 }, 1341 baz: true, 1342 exist: false, 1343 } 1344 `, 1345 output: []interface{}{ 1346 &struct { 1347 Foo struct { 1348 Foo_prop1 bool 1349 } 1350 Exist bool 1351 }{}, 1352 }, 1353 errors: []string{ 1354 `<input>:5:16: unrecognized property "foo.foo_prop2"`, 1355 `<input>:6:16: unrecognized property "foo.foo_prop3"`, 1356 `<input>:9:15: unrecognized property "bar.bar_prop"`, 1357 `<input>:11:9: unrecognized property "baz"`, 1358 }, 1359 }, 1360 } 1361 1362 for _, testCase := range testCases { 1363 t.Run(testCase.name, func(t *testing.T) { 1364 r := bytes.NewBufferString(testCase.input) 1365 file, errs := parser.ParseAndEval("", r, parser.NewScope(nil)) 1366 if len(errs) != 0 { 1367 t.Errorf("test case: %s", testCase.input) 1368 t.Errorf("unexpected parse errors:") 1369 for _, err := range errs { 1370 t.Errorf(" %s", err) 1371 } 1372 t.FailNow() 1373 } 1374 1375 for _, def := range file.Defs { 1376 module, ok := def.(*parser.Module) 1377 if !ok { 1378 continue 1379 } 1380 1381 var output []interface{} 1382 for _, p := range testCase.output { 1383 output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface()) 1384 } 1385 1386 _, errs = UnpackProperties(module.Properties, output...) 1387 1388 printErrors := false 1389 for _, expectedErr := range testCase.errors { 1390 foundError := false 1391 for _, err := range errs { 1392 if err.Error() == expectedErr { 1393 foundError = true 1394 } 1395 } 1396 if !foundError { 1397 t.Errorf("expected error %s", expectedErr) 1398 printErrors = true 1399 } 1400 } 1401 if printErrors { 1402 t.Errorf("got errors:") 1403 for _, err := range errs { 1404 t.Errorf(" %s", err.Error()) 1405 } 1406 } 1407 } 1408 }) 1409 } 1410} 1411 1412func BenchmarkUnpackProperties(b *testing.B) { 1413 run := func(b *testing.B, props []interface{}, input string) { 1414 b.ReportAllocs() 1415 b.StopTimer() 1416 r := bytes.NewBufferString(input) 1417 file, errs := parser.ParseAndEval("", r, parser.NewScope(nil)) 1418 if len(errs) != 0 { 1419 b.Errorf("test case: %s", input) 1420 b.Errorf("unexpected parse errors:") 1421 for _, err := range errs { 1422 b.Errorf(" %s", err) 1423 } 1424 b.FailNow() 1425 } 1426 1427 for i := 0; i < b.N; i++ { 1428 for _, def := range file.Defs { 1429 module, ok := def.(*parser.Module) 1430 if !ok { 1431 continue 1432 } 1433 1434 var output []interface{} 1435 for _, p := range props { 1436 output = append(output, CloneProperties(reflect.ValueOf(p)).Interface()) 1437 } 1438 1439 b.StartTimer() 1440 _, errs = UnpackProperties(module.Properties, output...) 1441 b.StopTimer() 1442 if len(errs) > 0 { 1443 b.Errorf("unexpected unpack errors:") 1444 for _, err := range errs { 1445 b.Errorf(" %s", err) 1446 } 1447 } 1448 } 1449 } 1450 } 1451 1452 b.Run("basic", func(b *testing.B) { 1453 props := []interface{}{ 1454 &struct { 1455 Nested struct { 1456 S string 1457 } 1458 }{}, 1459 } 1460 bp := ` 1461 m { 1462 nested: { 1463 s: "abc", 1464 }, 1465 } 1466 ` 1467 run(b, props, bp) 1468 }) 1469 1470 b.Run("interface", func(b *testing.B) { 1471 props := []interface{}{ 1472 &struct { 1473 Nested interface{} 1474 }{ 1475 Nested: (*struct { 1476 S string 1477 })(nil), 1478 }, 1479 } 1480 bp := ` 1481 m { 1482 nested: { 1483 s: "abc", 1484 }, 1485 } 1486 ` 1487 run(b, props, bp) 1488 }) 1489 1490 b.Run("many", func(b *testing.B) { 1491 props := []interface{}{ 1492 &struct { 1493 A *string 1494 B *string 1495 C *string 1496 D *string 1497 E *string 1498 F *string 1499 G *string 1500 H *string 1501 I *string 1502 J *string 1503 }{}, 1504 } 1505 bp := ` 1506 m { 1507 a: "a", 1508 b: "b", 1509 c: "c", 1510 d: "d", 1511 e: "e", 1512 f: "f", 1513 g: "g", 1514 h: "h", 1515 i: "i", 1516 j: "j", 1517 } 1518 ` 1519 run(b, props, bp) 1520 }) 1521 1522 b.Run("deep", func(b *testing.B) { 1523 props := []interface{}{ 1524 &struct { 1525 Nested struct { 1526 Nested struct { 1527 Nested struct { 1528 Nested struct { 1529 Nested struct { 1530 Nested struct { 1531 Nested struct { 1532 Nested struct { 1533 Nested struct { 1534 Nested struct { 1535 S string 1536 } 1537 } 1538 } 1539 } 1540 } 1541 } 1542 } 1543 } 1544 } 1545 } 1546 }{}, 1547 } 1548 bp := ` 1549 m { 1550 nested: { nested: { nested: { nested: { nested: { 1551 nested: { nested: { nested: { nested: { nested: { 1552 s: "abc", 1553 }, }, }, }, }, 1554 }, }, }, }, }, 1555 } 1556 ` 1557 run(b, props, bp) 1558 }) 1559 1560 b.Run("mix", func(b *testing.B) { 1561 props := []interface{}{ 1562 &struct { 1563 Name string 1564 Flag bool 1565 Settings []string 1566 Perarch *struct { 1567 Arm string 1568 Arm64 string 1569 } 1570 Configvars []struct { 1571 Name string 1572 Values []string 1573 } 1574 }{}, 1575 } 1576 bp := ` 1577 m { 1578 name: "mymodule", 1579 flag: true, 1580 settings: ["foo1", "foo2", "foo3",], 1581 perarch: { 1582 arm: "32", 1583 arm64: "64", 1584 }, 1585 configvars: [ 1586 { name: "var1", values: ["var1:1", "var1:2", ], }, 1587 { name: "var2", values: ["var2:1", "var2:2", ], }, 1588 ], 1589 } 1590 ` 1591 run(b, props, bp) 1592 }) 1593} 1594 1595func TestRemoveUnnecessaryUnusedNames(t *testing.T) { 1596 testCases := []struct { 1597 name string 1598 input []string 1599 output []string 1600 }{ 1601 { 1602 name: "no unused names", 1603 input: []string{}, 1604 output: []string{}, 1605 }, 1606 { 1607 name: "only one unused name", 1608 input: []string{"a.b.c"}, 1609 output: []string{"a.b.c"}, 1610 }, 1611 { 1612 name: "unused names in a chain", 1613 input: []string{"a", "a.b", "a.b.c"}, 1614 output: []string{"a.b.c"}, 1615 }, 1616 { 1617 name: "unused names unrelated", 1618 input: []string{"a.b.c", "s.t", "x.y"}, 1619 output: []string{"a.b.c", "s.t", "x.y"}, 1620 }, 1621 { 1622 name: "unused names partially related one", 1623 input: []string{"a.b", "a.b.c", "a.b.d"}, 1624 output: []string{"a.b.c", "a.b.d"}, 1625 }, 1626 { 1627 name: "unused names partially related two", 1628 input: []string{"a", "a.b.c", "a.c"}, 1629 output: []string{"a.b.c", "a.c"}, 1630 }, 1631 { 1632 name: "unused names partially related three", 1633 input: []string{"a.b.c", "b.c", "c"}, 1634 output: []string{"a.b.c", "b.c", "c"}, 1635 }, 1636 } 1637 for _, testCase := range testCases { 1638 t.Run(testCase.name, func(t *testing.T) { 1639 simplifiedNames := removeUnnecessaryUnusedNames(testCase.input) 1640 if !reflect.DeepEqual(simplifiedNames, testCase.output) { 1641 t.Errorf("test case: %s", testCase.name) 1642 t.Errorf(" input: %s", testCase.input) 1643 t.Errorf(" expect: %s", testCase.output) 1644 t.Errorf(" got: %s", simplifiedNames) 1645 } 1646 }) 1647 } 1648} 1649