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	input  string
28	output []interface{}
29	empty  []interface{}
30	errs   []error
31}{
32	{
33		input: `
34			m {
35				s: "abc",
36				blank: "",
37			}
38		`,
39		output: []interface{}{
40			&struct {
41				S     *string
42				Blank *string
43				Unset *string
44			}{
45				S:     StringPtr("abc"),
46				Blank: StringPtr(""),
47				Unset: nil,
48			},
49		},
50	},
51
52	{
53		input: `
54			m {
55				s: "abc",
56			}
57		`,
58		output: []interface{}{
59			&struct {
60				S string
61			}{
62				S: "abc",
63			},
64		},
65	},
66
67	{
68		input: `
69			m {
70				isGood: true,
71			}
72		`,
73		output: []interface{}{
74			&struct {
75				IsGood bool
76			}{
77				IsGood: true,
78			},
79		},
80	},
81
82	{
83		input: `
84			m {
85				isGood: true,
86				isBad: false,
87			}
88		`,
89		output: []interface{}{
90			&struct {
91				IsGood *bool
92				IsBad  *bool
93				IsUgly *bool
94			}{
95				IsGood: BoolPtr(true),
96				IsBad:  BoolPtr(false),
97				IsUgly: nil,
98			},
99		},
100	},
101
102	{
103		input: `
104			m {
105				stuff: ["asdf", "jkl;", "qwert",
106					"uiop", "bnm,"],
107				empty: []
108			}
109		`,
110		output: []interface{}{
111			&struct {
112				Stuff     []string
113				Empty     []string
114				Nil       []string
115				NonString []struct{ S string } `blueprint:"mutated"`
116			}{
117				Stuff:     []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
118				Empty:     []string{},
119				Nil:       nil,
120				NonString: nil,
121			},
122		},
123	},
124
125	{
126		input: `
127			m {
128				nested: {
129					s: "abc",
130				}
131			}
132		`,
133		output: []interface{}{
134			&struct {
135				Nested struct {
136					S string
137				}
138			}{
139				Nested: struct{ S string }{
140					S: "abc",
141				},
142			},
143		},
144	},
145
146	{
147		input: `
148			m {
149				nested: {
150					s: "def",
151				}
152			}
153		`,
154		output: []interface{}{
155			&struct {
156				Nested interface{}
157			}{
158				Nested: &struct{ S string }{
159					S: "def",
160				},
161			},
162		},
163	},
164
165	{
166		input: `
167			m {
168				nested: {
169					foo: "abc",
170				},
171				bar: false,
172				baz: ["def", "ghi"],
173			}
174		`,
175		output: []interface{}{
176			&struct {
177				Nested struct {
178					Foo string
179				}
180				Bar bool
181				Baz []string
182			}{
183				Nested: struct{ Foo string }{
184					Foo: "abc",
185				},
186				Bar: false,
187				Baz: []string{"def", "ghi"},
188			},
189		},
190	},
191
192	{
193		input: `
194			m {
195				nested: {
196					foo: "abc",
197				},
198				bar: false,
199				baz: ["def", "ghi"],
200			}
201		`,
202		output: []interface{}{
203			&struct {
204				Nested struct {
205					Foo string `allowNested:"true"`
206				} `blueprint:"filter(allowNested:\"true\")"`
207				Bar bool
208				Baz []string
209			}{
210				Nested: struct {
211					Foo string `allowNested:"true"`
212				}{
213					Foo: "abc",
214				},
215				Bar: false,
216				Baz: []string{"def", "ghi"},
217			},
218		},
219	},
220
221	// List of maps
222	{
223		input: `
224			m {
225				mapslist: [
226					{
227						foo: "abc",
228						bar: true,
229					},
230					{
231						foo: "def",
232						bar: false,
233					}
234				],
235			}
236		`,
237		output: []interface{}{
238			&struct {
239				Mapslist []struct {
240					Foo string
241					Bar bool
242				}
243			}{
244				Mapslist: []struct {
245					Foo string
246					Bar bool
247				}{
248					{Foo: "abc", Bar: true},
249					{Foo: "def", Bar: false},
250				},
251			},
252		},
253	},
254
255	// List of pointers to structs
256	{
257		input: `
258			m {
259				mapslist: [
260					{
261						foo: "abc",
262						bar: true,
263					},
264					{
265						foo: "def",
266						bar: false,
267					}
268				],
269			}
270		`,
271		output: []interface{}{
272			&struct {
273				Mapslist []*struct {
274					Foo string
275					Bar bool
276				}
277			}{
278				Mapslist: []*struct {
279					Foo string
280					Bar bool
281				}{
282					{Foo: "abc", Bar: true},
283					{Foo: "def", Bar: false},
284				},
285			},
286		},
287	},
288
289	// List of lists
290	{
291		input: `
292			m {
293				listoflists: [
294					["abc",],
295					["def",],
296				],
297			}
298		`,
299		output: []interface{}{
300			&struct {
301				Listoflists [][]string
302			}{
303				Listoflists: [][]string{
304					[]string{"abc"},
305					[]string{"def"},
306				},
307			},
308		},
309	},
310
311	// Multilevel
312	{
313		input: `
314			m {
315				name: "mymodule",
316				flag: true,
317				settings: ["foo1", "foo2", "foo3",],
318				perarch: {
319					arm: "32",
320					arm64: "64",
321				},
322				configvars: [
323					{ var: "var1", values: ["1.1", "1.2", ], },
324					{ var: "var2", values: ["2.1", ], },
325				],
326            }
327        `,
328		output: []interface{}{
329			&struct {
330				Name     string
331				Flag     bool
332				Settings []string
333				Perarch  *struct {
334					Arm   string
335					Arm64 string
336				}
337				Configvars []struct {
338					Var    string
339					Values []string
340				}
341			}{
342				Name:     "mymodule",
343				Flag:     true,
344				Settings: []string{"foo1", "foo2", "foo3"},
345				Perarch: &struct {
346					Arm   string
347					Arm64 string
348				}{Arm: "32", Arm64: "64"},
349				Configvars: []struct {
350					Var    string
351					Values []string
352				}{
353					{Var: "var1", Values: []string{"1.1", "1.2"}},
354					{Var: "var2", Values: []string{"2.1"}},
355				},
356			},
357		},
358	},
359	// Anonymous struct
360	{
361		input: `
362			m {
363				s: "abc",
364				nested: {
365					s: "def",
366				},
367			}
368		`,
369		output: []interface{}{
370			&struct {
371				EmbeddedStruct
372				Nested struct {
373					EmbeddedStruct
374				}
375			}{
376				EmbeddedStruct: EmbeddedStruct{
377					S: "abc",
378				},
379				Nested: struct {
380					EmbeddedStruct
381				}{
382					EmbeddedStruct: EmbeddedStruct{
383						S: "def",
384					},
385				},
386			},
387		},
388	},
389
390	// Anonymous interface
391	{
392		input: `
393			m {
394				s: "abc",
395				nested: {
396					s: "def",
397				},
398			}
399		`,
400		output: []interface{}{
401			&struct {
402				EmbeddedInterface
403				Nested struct {
404					EmbeddedInterface
405				}
406			}{
407				EmbeddedInterface: &struct{ S string }{
408					S: "abc",
409				},
410				Nested: struct {
411					EmbeddedInterface
412				}{
413					EmbeddedInterface: &struct{ S string }{
414						S: "def",
415					},
416				},
417			},
418		},
419	},
420
421	// Anonymous struct with name collision
422	{
423		input: `
424			m {
425				s: "abc",
426				nested: {
427					s: "def",
428				},
429			}
430		`,
431		output: []interface{}{
432			&struct {
433				S string
434				EmbeddedStruct
435				Nested struct {
436					S string
437					EmbeddedStruct
438				}
439			}{
440				S: "abc",
441				EmbeddedStruct: EmbeddedStruct{
442					S: "abc",
443				},
444				Nested: struct {
445					S string
446					EmbeddedStruct
447				}{
448					S: "def",
449					EmbeddedStruct: EmbeddedStruct{
450						S: "def",
451					},
452				},
453			},
454		},
455	},
456
457	// Anonymous interface with name collision
458	{
459		input: `
460			m {
461				s: "abc",
462				nested: {
463					s: "def",
464				},
465			}
466		`,
467		output: []interface{}{
468			&struct {
469				S string
470				EmbeddedInterface
471				Nested struct {
472					S string
473					EmbeddedInterface
474				}
475			}{
476				S: "abc",
477				EmbeddedInterface: &struct{ S string }{
478					S: "abc",
479				},
480				Nested: struct {
481					S string
482					EmbeddedInterface
483				}{
484					S: "def",
485					EmbeddedInterface: &struct{ S string }{
486						S: "def",
487					},
488				},
489			},
490		},
491	},
492
493	// Variables
494	{
495		input: `
496			list = ["abc"]
497			string = "def"
498			list_with_variable = [string]
499			struct_value = { name: "foo" }
500			m {
501				s: string,
502				list: list,
503				list2: list_with_variable,
504				structattr: struct_value,
505			}
506		`,
507		output: []interface{}{
508			&struct {
509				S          string
510				List       []string
511				List2      []string
512				Structattr struct {
513					Name string
514				}
515			}{
516				S:     "def",
517				List:  []string{"abc"},
518				List2: []string{"def"},
519				Structattr: struct {
520					Name string
521				}{
522					Name: "foo",
523				},
524			},
525		},
526	},
527
528	// Multiple property structs
529	{
530		input: `
531			m {
532				nested: {
533					s: "abc",
534				}
535			}
536		`,
537		output: []interface{}{
538			&struct {
539				Nested struct {
540					S string
541				}
542			}{
543				Nested: struct{ S string }{
544					S: "abc",
545				},
546			},
547			&struct {
548				Nested struct {
549					S string
550				}
551			}{
552				Nested: struct{ S string }{
553					S: "abc",
554				},
555			},
556			&struct {
557			}{},
558		},
559	},
560
561	// Nil pointer to struct
562	{
563		input: `
564			m {
565				nested: {
566					s: "abc",
567				}
568			}
569		`,
570		output: []interface{}{
571			&struct {
572				Nested *struct {
573					S string
574				}
575			}{
576				Nested: &struct{ S string }{
577					S: "abc",
578				},
579			},
580		},
581		empty: []interface{}{
582			&struct {
583				Nested *struct {
584					S string
585				}
586			}{},
587		},
588	},
589
590	// Interface containing nil pointer to struct
591	{
592		input: `
593			m {
594				nested: {
595					s: "abc",
596				}
597			}
598		`,
599		output: []interface{}{
600			&struct {
601				Nested interface{}
602			}{
603				Nested: &EmbeddedStruct{
604					S: "abc",
605				},
606			},
607		},
608		empty: []interface{}{
609			&struct {
610				Nested interface{}
611			}{
612				Nested: (*EmbeddedStruct)(nil),
613			},
614		},
615	},
616
617	// Factory set properties
618	{
619		input: `
620			m {
621				string: "abc",
622				string_ptr: "abc",
623				bool: false,
624				bool_ptr: false,
625				list: ["a", "b", "c"],
626			}
627		`,
628		output: []interface{}{
629			&struct {
630				String     string
631				String_ptr *string
632				Bool       bool
633				Bool_ptr   *bool
634				List       []string
635			}{
636				String:     "012abc",
637				String_ptr: StringPtr("abc"),
638				Bool:       true,
639				Bool_ptr:   BoolPtr(false),
640				List:       []string{"0", "1", "2", "a", "b", "c"},
641			},
642		},
643		empty: []interface{}{
644			&struct {
645				String     string
646				String_ptr *string
647				Bool       bool
648				Bool_ptr   *bool
649				List       []string
650			}{
651				String:     "012",
652				String_ptr: StringPtr("012"),
653				Bool:       true,
654				Bool_ptr:   BoolPtr(true),
655				List:       []string{"0", "1", "2"},
656			},
657		},
658	},
659	// Captitalized property
660	{
661		input: `
662			m {
663				CAPITALIZED: "foo",
664			}
665		`,
666		output: []interface{}{
667			&struct {
668				CAPITALIZED string
669			}{
670				CAPITALIZED: "foo",
671			},
672		},
673	},
674}
675
676func TestUnpackProperties(t *testing.T) {
677	for _, testCase := range validUnpackTestCases {
678		r := bytes.NewBufferString(testCase.input)
679		file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
680		if len(errs) != 0 {
681			t.Errorf("test case: %s", testCase.input)
682			t.Errorf("unexpected parse errors:")
683			for _, err := range errs {
684				t.Errorf("  %s", err)
685			}
686			t.FailNow()
687		}
688
689		for _, def := range file.Defs {
690			module, ok := def.(*parser.Module)
691			if !ok {
692				continue
693			}
694
695			var output []interface{}
696			if len(testCase.empty) > 0 {
697				output = testCase.empty
698			} else {
699				for _, p := range testCase.output {
700					output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface())
701				}
702			}
703			_, errs = UnpackProperties(module.Properties, output...)
704			if len(errs) != 0 && len(testCase.errs) == 0 {
705				t.Errorf("test case: %s", testCase.input)
706				t.Errorf("unexpected unpack errors:")
707				for _, err := range errs {
708					t.Errorf("  %s", err)
709				}
710				t.FailNow()
711			} else if !reflect.DeepEqual(errs, testCase.errs) {
712				t.Errorf("test case: %s", testCase.input)
713				t.Errorf("incorrect errors:")
714				t.Errorf("  expected: %+v", testCase.errs)
715				t.Errorf("       got: %+v", errs)
716			}
717
718			if len(output) != len(testCase.output) {
719				t.Fatalf("incorrect number of property structs, expected %d got %d",
720					len(testCase.output), len(output))
721			}
722
723			for i := range output {
724				got := reflect.ValueOf(output[i]).Interface()
725				if !reflect.DeepEqual(got, testCase.output[i]) {
726					t.Errorf("test case: %s", testCase.input)
727					t.Errorf("incorrect output:")
728					t.Errorf("  expected: %+v", testCase.output[i])
729					t.Errorf("       got: %+v", got)
730				}
731			}
732		}
733	}
734}
735
736func BenchmarkUnpackProperties(b *testing.B) {
737	run := func(b *testing.B, props []interface{}, input string) {
738		b.ReportAllocs()
739		b.StopTimer()
740		r := bytes.NewBufferString(input)
741		file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
742		if len(errs) != 0 {
743			b.Errorf("test case: %s", input)
744			b.Errorf("unexpected parse errors:")
745			for _, err := range errs {
746				b.Errorf("  %s", err)
747			}
748			b.FailNow()
749		}
750
751		for i := 0; i < b.N; i++ {
752			for _, def := range file.Defs {
753				module, ok := def.(*parser.Module)
754				if !ok {
755					continue
756				}
757
758				var output []interface{}
759				for _, p := range props {
760					output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
761				}
762
763				b.StartTimer()
764				_, errs = UnpackProperties(module.Properties, output...)
765				b.StopTimer()
766				if len(errs) > 0 {
767					b.Errorf("unexpected unpack errors:")
768					for _, err := range errs {
769						b.Errorf("  %s", err)
770					}
771				}
772			}
773		}
774	}
775
776	b.Run("basic", func(b *testing.B) {
777		props := []interface{}{
778			&struct {
779				Nested struct {
780					S string
781				}
782			}{},
783		}
784		bp := `
785			m {
786				nested: {
787					s: "abc",
788				},
789			}
790		`
791		run(b, props, bp)
792	})
793
794	b.Run("interface", func(b *testing.B) {
795		props := []interface{}{
796			&struct {
797				Nested interface{}
798			}{
799				Nested: (*struct {
800					S string
801				})(nil),
802			},
803		}
804		bp := `
805			m {
806				nested: {
807					s: "abc",
808				},
809			}
810		`
811		run(b, props, bp)
812	})
813
814	b.Run("many", func(b *testing.B) {
815		props := []interface{}{
816			&struct {
817				A *string
818				B *string
819				C *string
820				D *string
821				E *string
822				F *string
823				G *string
824				H *string
825				I *string
826				J *string
827			}{},
828		}
829		bp := `
830			m {
831				a: "a",
832				b: "b",
833				c: "c",
834				d: "d",
835				e: "e",
836				f: "f",
837				g: "g",
838				h: "h",
839				i: "i",
840				j: "j",
841			}
842		`
843		run(b, props, bp)
844	})
845
846	b.Run("deep", func(b *testing.B) {
847		props := []interface{}{
848			&struct {
849				Nested struct {
850					Nested struct {
851						Nested struct {
852							Nested struct {
853								Nested struct {
854									Nested struct {
855										Nested struct {
856											Nested struct {
857												Nested struct {
858													Nested struct {
859														S string
860													}
861												}
862											}
863										}
864									}
865								}
866							}
867						}
868					}
869				}
870			}{},
871		}
872		bp := `
873			m {
874				nested: { nested: { nested: { nested: { nested: {
875					nested: { nested: { nested: { nested: { nested: {
876						s: "abc",
877					}, }, }, }, },
878				}, }, }, }, },
879			}
880		`
881		run(b, props, bp)
882	})
883
884	b.Run("mix", func(b *testing.B) {
885		props := []interface{}{
886			&struct {
887				Name     string
888				Flag     bool
889				Settings []string
890				Perarch  *struct {
891					Arm   string
892					Arm64 string
893				}
894				Configvars []struct {
895					Name   string
896					Values []string
897				}
898			}{},
899		}
900		bp := `
901			m {
902				name: "mymodule",
903				flag: true,
904				settings: ["foo1", "foo2", "foo3",],
905				perarch: {
906					arm: "32",
907					arm64: "64",
908				},
909				configvars: [
910					{ name: "var1", values: ["var1:1", "var1:2", ], },
911					{ name: "var2", values: ["var2:1", "var2:2", ], },
912				],
913            }
914        `
915		run(b, props, bp)
916	})
917}
918