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