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