• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package proptools
16
17import (
18	"bytes"
19	"reflect"
20
21	"testing"
22
23	"github.com/google/blueprint/parser"
24)
25
26var validUnpackTestCases = []struct {
27	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: "map",
133		input: `
134			m {
135				stuff: { "asdf": "jkl;", "qwert": "uiop"},
136				empty: {},
137				nested: {
138					other_stuff: {},
139				},
140			}
141		`,
142		output: []interface{}{
143			&struct {
144				Stuff     map[string]string
145				Empty     map[string]string
146				Nil       map[string]string
147				NonString map[string]struct{ S string } `blueprint:"mutated"`
148				Nested    struct {
149					Other_stuff map[string]string
150				}
151			}{
152				Stuff:     map[string]string{"asdf": "jkl;", "qwert": "uiop"},
153				Empty:     map[string]string{},
154				Nil:       nil,
155				NonString: nil,
156				Nested: struct{ Other_stuff map[string]string }{
157					Other_stuff: map[string]string{},
158				},
159			},
160		},
161	},
162
163	{
164		name: "map with slice",
165		input: `
166			m {
167				stuff: { "asdf": ["jkl;"], "qwert": []},
168				empty: {},
169			}
170		`,
171		output: []interface{}{
172			&struct {
173				Stuff     map[string][]string
174				Empty     map[string][]string
175				Nil       map[string][]string
176				NonString map[string]struct{ S string } `blueprint:"mutated"`
177			}{
178				Stuff:     map[string][]string{"asdf": []string{"jkl;"}, "qwert": []string{}},
179				Empty:     map[string][]string{},
180				Nil:       nil,
181				NonString: nil,
182			},
183		},
184	},
185
186	{
187		name: "map with struct",
188		input: `
189			m {
190				stuff: { "asdf": {s:"a"}},
191				empty: {},
192			}
193		`,
194		output: []interface{}{
195			&struct {
196				Stuff map[string]struct{ S string }
197				Empty map[string]struct{ S string }
198				Nil   map[string]struct{ S string }
199			}{
200				Stuff: map[string]struct{ S string }{"asdf": struct{ S string }{"a"}},
201				Empty: map[string]struct{ S string }{},
202				Nil:   nil,
203			},
204		},
205	},
206
207	{
208		name: "double nested",
209		input: `
210			m {
211				nested: {
212					nested: {
213						s: "abc",
214					},
215				},
216			}
217		`,
218		output: []interface{}{
219			&struct {
220				Nested struct {
221					Nested struct {
222						S string
223					}
224				}
225			}{
226				Nested: struct{ Nested struct{ S string } }{
227					Nested: struct{ S string }{
228						S: "abc",
229					},
230				},
231			},
232		},
233	},
234
235	{
236		name: "nested",
237		input: `
238			m {
239				nested: {
240					s: "abc",
241				}
242			}
243		`,
244		output: []interface{}{
245			&struct {
246				Nested struct {
247					S string
248				}
249			}{
250				Nested: struct{ S string }{
251					S: "abc",
252				},
253			},
254		},
255	},
256
257	{
258		name: "nested interface",
259		input: `
260			m {
261				nested: {
262					s: "def",
263				}
264			}
265		`,
266		output: []interface{}{
267			&struct {
268				Nested interface{}
269			}{
270				Nested: &struct{ S string }{
271					S: "def",
272				},
273			},
274		},
275	},
276
277	{
278		name: "mixed",
279		input: `
280			m {
281				nested: {
282					foo: "abc",
283				},
284				bar: false,
285				baz: ["def", "ghi"],
286			}
287		`,
288		output: []interface{}{
289			&struct {
290				Nested struct {
291					Foo string
292				}
293				Bar bool
294				Baz []string
295			}{
296				Nested: struct{ Foo string }{
297					Foo: "abc",
298				},
299				Bar: false,
300				Baz: []string{"def", "ghi"},
301			},
302		},
303	},
304
305	{
306		name: "filter",
307		input: `
308			m {
309				nested: {
310					foo: "abc",
311				},
312				bar: false,
313				baz: ["def", "ghi"],
314			}
315		`,
316		output: []interface{}{
317			&struct {
318				Nested struct {
319					Foo string `allowNested:"true"`
320				} `blueprint:"filter(allowNested:\"true\")"`
321				Bar bool
322				Baz []string
323			}{
324				Nested: struct {
325					Foo string `allowNested:"true"`
326				}{
327					Foo: "abc",
328				},
329				Bar: false,
330				Baz: []string{"def", "ghi"},
331			},
332		},
333	},
334
335	// List of maps
336	{
337		name: "list of structs",
338		input: `
339			m {
340				mapslist: [
341					{
342						foo: "abc",
343						bar: true,
344					},
345					{
346						foo: "def",
347						bar: false,
348					}
349				],
350			}
351		`,
352		output: []interface{}{
353			&struct {
354				Mapslist []struct {
355					Foo string
356					Bar bool
357				}
358			}{
359				Mapslist: []struct {
360					Foo string
361					Bar bool
362				}{
363					{Foo: "abc", Bar: true},
364					{Foo: "def", Bar: false},
365				},
366			},
367		},
368	},
369
370	// List of pointers to structs
371	{
372		name: "list of pointers to structs",
373		input: `
374			m {
375				mapslist: [
376					{
377						foo: "abc",
378						bar: true,
379					},
380					{
381						foo: "def",
382						bar: false,
383					}
384				],
385			}
386		`,
387		output: []interface{}{
388			&struct {
389				Mapslist []*struct {
390					Foo string
391					Bar bool
392				}
393			}{
394				Mapslist: []*struct {
395					Foo string
396					Bar bool
397				}{
398					{Foo: "abc", Bar: true},
399					{Foo: "def", Bar: false},
400				},
401			},
402		},
403	},
404
405	// List of lists
406	{
407		name: "list of lists",
408		input: `
409			m {
410				listoflists: [
411					["abc",],
412					["def",],
413				],
414			}
415		`,
416		output: []interface{}{
417			&struct {
418				Listoflists [][]string
419			}{
420				Listoflists: [][]string{
421					[]string{"abc"},
422					[]string{"def"},
423				},
424			},
425		},
426	},
427
428	// Multilevel
429	{
430		name: "multilevel",
431		input: `
432			m {
433				name: "mymodule",
434				flag: true,
435				settings: ["foo1", "foo2", "foo3",],
436				perarch: {
437					arm: "32",
438					arm64: "64",
439				},
440				configvars: [
441					{ var: "var1", values: ["1.1", "1.2", ], },
442					{ var: "var2", values: ["2.1", ], },
443				],
444            }
445        `,
446		output: []interface{}{
447			&struct {
448				Name     string
449				Flag     bool
450				Settings []string
451				Perarch  *struct {
452					Arm   string
453					Arm64 string
454				}
455				Configvars []struct {
456					Var    string
457					Values []string
458				}
459			}{
460				Name:     "mymodule",
461				Flag:     true,
462				Settings: []string{"foo1", "foo2", "foo3"},
463				Perarch: &struct {
464					Arm   string
465					Arm64 string
466				}{Arm: "32", Arm64: "64"},
467				Configvars: []struct {
468					Var    string
469					Values []string
470				}{
471					{Var: "var1", Values: []string{"1.1", "1.2"}},
472					{Var: "var2", Values: []string{"2.1"}},
473				},
474			},
475		},
476	},
477	// Anonymous struct
478	{
479		name: "embedded struct",
480		input: `
481			m {
482				s: "abc",
483				nested: {
484					s: "def",
485				},
486			}
487		`,
488		output: []interface{}{
489			&struct {
490				EmbeddedStruct
491				Nested struct {
492					EmbeddedStruct
493				}
494			}{
495				EmbeddedStruct: EmbeddedStruct{
496					S: "abc",
497				},
498				Nested: struct {
499					EmbeddedStruct
500				}{
501					EmbeddedStruct: EmbeddedStruct{
502						S: "def",
503					},
504				},
505			},
506		},
507	},
508
509	// Anonymous interface
510	{
511		name: "embedded interface",
512		input: `
513			m {
514				s: "abc",
515				nested: {
516					s: "def",
517				},
518			}
519		`,
520		output: []interface{}{
521			&struct {
522				EmbeddedInterface
523				Nested struct {
524					EmbeddedInterface
525				}
526			}{
527				EmbeddedInterface: &struct{ S string }{
528					S: "abc",
529				},
530				Nested: struct {
531					EmbeddedInterface
532				}{
533					EmbeddedInterface: &struct{ S string }{
534						S: "def",
535					},
536				},
537			},
538		},
539	},
540
541	// Anonymous struct with name collision
542	{
543		name: "embedded name collision",
544		input: `
545			m {
546				s: "abc",
547				nested: {
548					s: "def",
549				},
550			}
551		`,
552		output: []interface{}{
553			&struct {
554				S string
555				EmbeddedStruct
556				Nested struct {
557					S string
558					EmbeddedStruct
559				}
560			}{
561				S: "abc",
562				EmbeddedStruct: EmbeddedStruct{
563					S: "abc",
564				},
565				Nested: struct {
566					S string
567					EmbeddedStruct
568				}{
569					S: "def",
570					EmbeddedStruct: EmbeddedStruct{
571						S: "def",
572					},
573				},
574			},
575		},
576	},
577
578	// Anonymous interface with name collision
579	{
580		name: "embeded interface name collision",
581		input: `
582			m {
583				s: "abc",
584				nested: {
585					s: "def",
586				},
587			}
588		`,
589		output: []interface{}{
590			&struct {
591				S string
592				EmbeddedInterface
593				Nested struct {
594					S string
595					EmbeddedInterface
596				}
597			}{
598				S: "abc",
599				EmbeddedInterface: &struct{ S string }{
600					S: "abc",
601				},
602				Nested: struct {
603					S string
604					EmbeddedInterface
605				}{
606					S: "def",
607					EmbeddedInterface: &struct{ S string }{
608						S: "def",
609					},
610				},
611			},
612		},
613	},
614
615	// Variables
616	{
617		name: "variables",
618		input: `
619			list = ["abc"]
620			string = "def"
621			list_with_variable = [string]
622			struct_value = { name: "foo" }
623			m {
624				s: string,
625				list: list,
626				list2: list_with_variable,
627				structattr: struct_value,
628			}
629		`,
630		output: []interface{}{
631			&struct {
632				S          string
633				List       []string
634				List2      []string
635				Structattr struct {
636					Name string
637				}
638			}{
639				S:     "def",
640				List:  []string{"abc"},
641				List2: []string{"def"},
642				Structattr: struct {
643					Name string
644				}{
645					Name: "foo",
646				},
647			},
648		},
649	},
650
651	// Multiple property structs
652	{
653		name: "multiple",
654		input: `
655			m {
656				nested: {
657					s: "abc",
658				}
659			}
660		`,
661		output: []interface{}{
662			&struct {
663				Nested struct {
664					S string
665				}
666			}{
667				Nested: struct{ S string }{
668					S: "abc",
669				},
670			},
671			&struct {
672				Nested struct {
673					S string
674				}
675			}{
676				Nested: struct{ S string }{
677					S: "abc",
678				},
679			},
680			&struct {
681			}{},
682		},
683	},
684
685	// Nil pointer to struct
686	{
687		name: "nil struct pointer",
688		input: `
689			m {
690				nested: {
691					s: "abc",
692				}
693			}
694		`,
695		output: []interface{}{
696			&struct {
697				Nested *struct {
698					S string
699				}
700			}{
701				Nested: &struct{ S string }{
702					S: "abc",
703				},
704			},
705		},
706		empty: []interface{}{
707			&struct {
708				Nested *struct {
709					S string
710				}
711			}{},
712		},
713	},
714
715	// Interface containing nil pointer to struct
716	{
717		name: "interface nil struct pointer",
718		input: `
719			m {
720				nested: {
721					s: "abc",
722				}
723			}
724		`,
725		output: []interface{}{
726			&struct {
727				Nested interface{}
728			}{
729				Nested: &EmbeddedStruct{
730					S: "abc",
731				},
732			},
733		},
734		empty: []interface{}{
735			&struct {
736				Nested interface{}
737			}{
738				Nested: (*EmbeddedStruct)(nil),
739			},
740		},
741	},
742
743	// Factory set properties
744	{
745		name: "factory properties",
746		input: `
747			m {
748				string: "abc",
749				string_ptr: "abc",
750				bool: false,
751				bool_ptr: false,
752				list: ["a", "b", "c"],
753			}
754		`,
755		output: []interface{}{
756			&struct {
757				String     string
758				String_ptr *string
759				Bool       bool
760				Bool_ptr   *bool
761				List       []string
762			}{
763				String:     "012abc",
764				String_ptr: StringPtr("abc"),
765				Bool:       true,
766				Bool_ptr:   BoolPtr(false),
767				List:       []string{"0", "1", "2", "a", "b", "c"},
768			},
769		},
770		empty: []interface{}{
771			&struct {
772				String     string
773				String_ptr *string
774				Bool       bool
775				Bool_ptr   *bool
776				List       []string
777			}{
778				String:     "012",
779				String_ptr: StringPtr("012"),
780				Bool:       true,
781				Bool_ptr:   BoolPtr(true),
782				List:       []string{"0", "1", "2"},
783			},
784		},
785	},
786	// Captitalized property
787	{
788		input: `
789			m {
790				CAPITALIZED: "foo",
791			}
792		`,
793		output: []interface{}{
794			&struct {
795				CAPITALIZED string
796			}{
797				CAPITALIZED: "foo",
798			},
799		},
800	},
801}
802
803func TestUnpackProperties(t *testing.T) {
804	for _, testCase := range validUnpackTestCases {
805		t.Run(testCase.name, func(t *testing.T) {
806			r := bytes.NewBufferString(testCase.input)
807			file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
808			if len(errs) != 0 {
809				t.Errorf("test case: %s", testCase.input)
810				t.Errorf("unexpected parse errors:")
811				for _, err := range errs {
812					t.Errorf("  %s", err)
813				}
814				t.FailNow()
815			}
816
817			for _, def := range file.Defs {
818				module, ok := def.(*parser.Module)
819				if !ok {
820					continue
821				}
822
823				var output []interface{}
824				if len(testCase.empty) > 0 {
825					for _, p := range testCase.empty {
826						output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
827					}
828				} else {
829					for _, p := range testCase.output {
830						output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface())
831					}
832				}
833
834				_, errs = unpackProperties(module.Properties, []string{"stuff", "empty", "nil", "nested.other_stuff"}, output...)
835				if len(errs) != 0 && len(testCase.errs) == 0 {
836					t.Errorf("test case: %s", testCase.input)
837					t.Errorf("unexpected unpack errors:")
838					for _, err := range errs {
839						t.Errorf("  %s", err)
840					}
841					t.FailNow()
842				} else if !reflect.DeepEqual(errs, testCase.errs) {
843					t.Errorf("test case: %s", testCase.input)
844					t.Errorf("incorrect errors:")
845					t.Errorf("  expected: %+v", testCase.errs)
846					t.Errorf("       got: %+v", errs)
847				}
848
849				if len(output) != len(testCase.output) {
850					t.Fatalf("incorrect number of property structs, expected %d got %d",
851						len(testCase.output), len(output))
852				}
853
854				for i := range output {
855					got := reflect.ValueOf(output[i]).Interface()
856					if !reflect.DeepEqual(got, testCase.output[i]) {
857						t.Errorf("test case: %s", testCase.input)
858						t.Errorf("incorrect output:")
859						t.Errorf("  expected: %+v", testCase.output[i])
860						t.Errorf("       got: %+v", got)
861					}
862				}
863			}
864		})
865	}
866}
867
868func TestUnpackErrors(t *testing.T) {
869	testCases := []struct {
870		name   string
871		input  string
872		output []interface{}
873		errors []string
874	}{
875		{
876			name: "missing",
877			input: `
878				m {
879					missing: true,
880				}
881			`,
882			output: []interface{}{},
883			errors: []string{`<input>:3:13: unrecognized property "missing"`},
884		},
885		{
886			name: "missing nested",
887			input: `
888				m {
889					nested: {
890						missing: true,
891					},
892				}
893			`,
894			output: []interface{}{
895				&struct {
896					Nested struct{}
897				}{},
898			},
899			errors: []string{`<input>:4:14: unrecognized property "nested.missing"`},
900		},
901		{
902			name: "mutated",
903			input: `
904				m {
905					mutated: true,
906				}
907			`,
908			output: []interface{}{
909				&struct {
910					Mutated bool `blueprint:"mutated"`
911				}{},
912			},
913			errors: []string{`<input>:3:13: mutated field mutated cannot be set in a Blueprint file`},
914		},
915		{
916			name: "nested mutated",
917			input: `
918				m {
919					nested: {
920						mutated: true,
921					},
922				}
923			`,
924			output: []interface{}{
925				&struct {
926					Nested struct {
927						Mutated bool `blueprint:"mutated"`
928					}
929				}{},
930			},
931			errors: []string{`<input>:4:14: mutated field nested.mutated cannot be set in a Blueprint file`},
932		},
933		{
934			name: "duplicate",
935			input: `
936				m {
937					exists: true,
938					exists: true,
939				}
940			`,
941			output: []interface{}{
942				&struct {
943					Exists bool
944				}{},
945			},
946			errors: []string{
947				`<input>:4:12: property "exists" already defined`,
948				`<input>:3:12: <-- previous definition here`,
949			},
950		},
951		{
952			name: "nested duplicate",
953			input: `
954				m {
955					nested: {
956						exists: true,
957						exists: true,
958					},
959				}
960			`,
961			output: []interface{}{
962				&struct {
963					Nested struct {
964						Exists bool
965					}
966				}{},
967			},
968			errors: []string{
969				`<input>:5:13: property "nested.exists" already defined`,
970				`<input>:4:13: <-- previous definition here`,
971			},
972		},
973		{
974			name: "wrong type",
975			input: `
976				m {
977					int: "foo",
978				}
979			`,
980			output: []interface{}{
981				&struct {
982					Int *int64
983				}{},
984			},
985			errors: []string{
986				`<input>:3:11: can't assign string value to int64 property "int"`,
987			},
988		},
989		{
990			name: "wrong type for map",
991			input: `
992				m {
993					map: "foo",
994				}
995			`,
996			output: []interface{}{
997				&struct {
998					Map struct {
999						S string
1000					}
1001				}{},
1002			},
1003			errors: []string{
1004				`<input>:3:11: can't assign string value to map property "map"`,
1005			},
1006		},
1007		{
1008			name: "wrong type for list",
1009			input: `
1010				m {
1011					list: "foo",
1012				}
1013			`,
1014			output: []interface{}{
1015				&struct {
1016					List []string
1017				}{},
1018			},
1019			errors: []string{
1020				`<input>:3:12: can't assign string value to list property "list"`,
1021			},
1022		},
1023		{
1024			name: "wrong type for list of maps",
1025			input: `
1026				m {
1027					map_list: "foo",
1028				}
1029			`,
1030			output: []interface{}{
1031				&struct {
1032					Map_list []struct {
1033						S string
1034					}
1035				}{},
1036			},
1037			errors: []string{
1038				`<input>:3:16: can't assign string value to list property "map_list"`,
1039			},
1040		},
1041		{
1042			name: "invalid use of maps",
1043			input: `
1044				m {
1045					map: {"foo": "bar"},
1046				}
1047			`,
1048			output: []interface{}{
1049				&struct {
1050					Map map[string]string
1051				}{},
1052			},
1053			errors: []string{
1054				`<input>: Uses of maps for properties must be allowlisted. "map" is an unsupported use case`,
1055			},
1056		},
1057		{
1058			name: "invalid use of maps, not used in bp file",
1059			input: `
1060				m {
1061				}
1062			`,
1063			output: []interface{}{
1064				&struct {
1065					Map map[string]string
1066				}{},
1067			},
1068			errors: []string{
1069				`<input>: Uses of maps for properties must be allowlisted. "map" is an unsupported use case`,
1070			},
1071		},
1072	}
1073
1074	for _, testCase := range testCases {
1075		t.Run(testCase.name, func(t *testing.T) {
1076			r := bytes.NewBufferString(testCase.input)
1077			file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
1078			if len(errs) != 0 {
1079				t.Errorf("test case: %s", testCase.input)
1080				t.Errorf("unexpected parse errors:")
1081				for _, err := range errs {
1082					t.Errorf("  %s", err)
1083				}
1084				t.FailNow()
1085			}
1086
1087			for _, def := range file.Defs {
1088				module, ok := def.(*parser.Module)
1089				if !ok {
1090					continue
1091				}
1092
1093				var output []interface{}
1094				for _, p := range testCase.output {
1095					output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface())
1096				}
1097
1098				_, errs = UnpackProperties(module.Properties, output...)
1099
1100				printErrors := false
1101				for _, expectedErr := range testCase.errors {
1102					foundError := false
1103					for _, err := range errs {
1104						if err.Error() == expectedErr {
1105							foundError = true
1106						}
1107					}
1108					if !foundError {
1109						t.Errorf("expected error %s", expectedErr)
1110						printErrors = true
1111					}
1112				}
1113				if printErrors {
1114					t.Errorf("got errors:")
1115					for _, err := range errs {
1116						t.Errorf("   %s", err.Error())
1117					}
1118				}
1119			}
1120		})
1121	}
1122}
1123
1124func BenchmarkUnpackProperties(b *testing.B) {
1125	run := func(b *testing.B, props []interface{}, input string) {
1126		b.ReportAllocs()
1127		b.StopTimer()
1128		r := bytes.NewBufferString(input)
1129		file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
1130		if len(errs) != 0 {
1131			b.Errorf("test case: %s", input)
1132			b.Errorf("unexpected parse errors:")
1133			for _, err := range errs {
1134				b.Errorf("  %s", err)
1135			}
1136			b.FailNow()
1137		}
1138
1139		for i := 0; i < b.N; i++ {
1140			for _, def := range file.Defs {
1141				module, ok := def.(*parser.Module)
1142				if !ok {
1143					continue
1144				}
1145
1146				var output []interface{}
1147				for _, p := range props {
1148					output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
1149				}
1150
1151				b.StartTimer()
1152				_, errs = UnpackProperties(module.Properties, output...)
1153				b.StopTimer()
1154				if len(errs) > 0 {
1155					b.Errorf("unexpected unpack errors:")
1156					for _, err := range errs {
1157						b.Errorf("  %s", err)
1158					}
1159				}
1160			}
1161		}
1162	}
1163
1164	b.Run("basic", func(b *testing.B) {
1165		props := []interface{}{
1166			&struct {
1167				Nested struct {
1168					S string
1169				}
1170			}{},
1171		}
1172		bp := `
1173			m {
1174				nested: {
1175					s: "abc",
1176				},
1177			}
1178		`
1179		run(b, props, bp)
1180	})
1181
1182	b.Run("interface", func(b *testing.B) {
1183		props := []interface{}{
1184			&struct {
1185				Nested interface{}
1186			}{
1187				Nested: (*struct {
1188					S string
1189				})(nil),
1190			},
1191		}
1192		bp := `
1193			m {
1194				nested: {
1195					s: "abc",
1196				},
1197			}
1198		`
1199		run(b, props, bp)
1200	})
1201
1202	b.Run("many", func(b *testing.B) {
1203		props := []interface{}{
1204			&struct {
1205				A *string
1206				B *string
1207				C *string
1208				D *string
1209				E *string
1210				F *string
1211				G *string
1212				H *string
1213				I *string
1214				J *string
1215			}{},
1216		}
1217		bp := `
1218			m {
1219				a: "a",
1220				b: "b",
1221				c: "c",
1222				d: "d",
1223				e: "e",
1224				f: "f",
1225				g: "g",
1226				h: "h",
1227				i: "i",
1228				j: "j",
1229			}
1230		`
1231		run(b, props, bp)
1232	})
1233
1234	b.Run("deep", func(b *testing.B) {
1235		props := []interface{}{
1236			&struct {
1237				Nested struct {
1238					Nested struct {
1239						Nested struct {
1240							Nested struct {
1241								Nested struct {
1242									Nested struct {
1243										Nested struct {
1244											Nested struct {
1245												Nested struct {
1246													Nested struct {
1247														S string
1248													}
1249												}
1250											}
1251										}
1252									}
1253								}
1254							}
1255						}
1256					}
1257				}
1258			}{},
1259		}
1260		bp := `
1261			m {
1262				nested: { nested: { nested: { nested: { nested: {
1263					nested: { nested: { nested: { nested: { nested: {
1264						s: "abc",
1265					}, }, }, }, },
1266				}, }, }, }, },
1267			}
1268		`
1269		run(b, props, bp)
1270	})
1271
1272	b.Run("mix", func(b *testing.B) {
1273		props := []interface{}{
1274			&struct {
1275				Name     string
1276				Flag     bool
1277				Settings []string
1278				Perarch  *struct {
1279					Arm   string
1280					Arm64 string
1281				}
1282				Configvars []struct {
1283					Name   string
1284					Values []string
1285				}
1286			}{},
1287		}
1288		bp := `
1289			m {
1290				name: "mymodule",
1291				flag: true,
1292				settings: ["foo1", "foo2", "foo3",],
1293				perarch: {
1294					arm: "32",
1295					arm64: "64",
1296				},
1297				configvars: [
1298					{ name: "var1", values: ["var1:1", "var1:2", ], },
1299					{ name: "var2", values: ["var2:1", "var2:2", ], },
1300				],
1301            }
1302        `
1303		run(b, props, bp)
1304	})
1305}
1306