xref: /aosp_15_r20/build/blueprint/proptools/unpack_test.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
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