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 blueprint
16
17import (
18	"bytes"
19	"fmt"
20	"reflect"
21	"testing"
22	"text/scanner"
23
24	"github.com/google/blueprint/parser"
25	"github.com/google/blueprint/proptools"
26)
27
28var validUnpackTestCases = []struct {
29	input  string
30	output interface{}
31	errs   []error
32}{
33	{`
34		m {
35			name: "abc",
36			blank: "",
37		}
38		`,
39		struct {
40			Name  *string
41			Blank *string
42			Unset *string
43		}{
44			Name:  proptools.StringPtr("abc"),
45			Blank: proptools.StringPtr(""),
46			Unset: nil,
47		},
48		nil,
49	},
50
51	{`
52		m {
53			name: "abc",
54		}
55		`,
56		struct {
57			Name string
58		}{
59			Name: "abc",
60		},
61		nil,
62	},
63
64	{`
65		m {
66			isGood: true,
67		}
68		`,
69		struct {
70			IsGood bool
71		}{
72			IsGood: true,
73		},
74		nil,
75	},
76
77	{`
78		m {
79			isGood: true,
80			isBad: false,
81		}
82		`,
83		struct {
84			IsGood *bool
85			IsBad  *bool
86			IsUgly *bool
87		}{
88			IsGood: proptools.BoolPtr(true),
89			IsBad:  proptools.BoolPtr(false),
90			IsUgly: nil,
91		},
92		nil,
93	},
94
95	{`
96		m {
97			stuff: ["asdf", "jkl;", "qwert",
98				"uiop", "bnm,"],
99			empty: []
100		}
101		`,
102		struct {
103			Stuff []string
104			Empty []string
105			Nil   []string
106		}{
107			Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
108			Empty: []string{},
109			Nil:   nil,
110		},
111		nil,
112	},
113
114	{`
115		m {
116			nested: {
117				name: "abc",
118			}
119		}
120		`,
121		struct {
122			Nested struct {
123				Name string
124			}
125		}{
126			Nested: struct{ Name string }{
127				Name: "abc",
128			},
129		},
130		nil,
131	},
132
133	{`
134		m {
135			nested: {
136				name: "def",
137			}
138		}
139		`,
140		struct {
141			Nested interface{}
142		}{
143			Nested: &struct{ Name string }{
144				Name: "def",
145			},
146		},
147		nil,
148	},
149
150	{`
151		m {
152			nested: {
153				foo: "abc",
154			},
155			bar: false,
156			baz: ["def", "ghi"],
157		}
158		`,
159		struct {
160			Nested struct {
161				Foo string
162			}
163			Bar bool
164			Baz []string
165		}{
166			Nested: struct{ Foo string }{
167				Foo: "abc",
168			},
169			Bar: false,
170			Baz: []string{"def", "ghi"},
171		},
172		nil,
173	},
174
175	{`
176		m {
177			nested: {
178				foo: "abc",
179			},
180			bar: false,
181			baz: ["def", "ghi"],
182		}
183		`,
184		struct {
185			Nested struct {
186				Foo string `allowNested:"true"`
187			} `blueprint:"filter(allowNested:\"true\")"`
188			Bar bool
189			Baz []string
190		}{
191			Nested: struct {
192				Foo string `allowNested:"true"`
193			}{
194				Foo: "abc",
195			},
196			Bar: false,
197			Baz: []string{"def", "ghi"},
198		},
199		nil,
200	},
201
202	{`
203		m {
204			nested: {
205				foo: "abc",
206			},
207			bar: false,
208			baz: ["def", "ghi"],
209		}
210		`,
211		struct {
212			Nested struct {
213				Foo string
214			} `blueprint:"filter(allowNested:\"true\")"`
215			Bar bool
216			Baz []string
217		}{
218			Nested: struct{ Foo string }{
219				Foo: "",
220			},
221			Bar: false,
222			Baz: []string{"def", "ghi"},
223		},
224		[]error{
225			&Error{
226				Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"),
227				Pos: scanner.Position{"", 27, 4, 8},
228			},
229		},
230	},
231
232	// Anonymous struct
233	{`
234		m {
235			name: "abc",
236			nested: {
237				name: "def",
238			},
239		}
240		`,
241		struct {
242			EmbeddedStruct
243			Nested struct {
244				EmbeddedStruct
245			}
246		}{
247			EmbeddedStruct: EmbeddedStruct{
248				Name: "abc",
249			},
250			Nested: struct {
251				EmbeddedStruct
252			}{
253				EmbeddedStruct: EmbeddedStruct{
254					Name: "def",
255				},
256			},
257		},
258		nil,
259	},
260
261	// Anonymous interface
262	{`
263		m {
264			name: "abc",
265			nested: {
266				name: "def",
267			},
268		}
269		`,
270		struct {
271			EmbeddedInterface
272			Nested struct {
273				EmbeddedInterface
274			}
275		}{
276			EmbeddedInterface: &struct{ Name string }{
277				Name: "abc",
278			},
279			Nested: struct {
280				EmbeddedInterface
281			}{
282				EmbeddedInterface: &struct{ Name string }{
283					Name: "def",
284				},
285			},
286		},
287		nil,
288	},
289
290	// Anonymous struct with name collision
291	{`
292		m {
293			name: "abc",
294			nested: {
295				name: "def",
296			},
297		}
298		`,
299		struct {
300			Name string
301			EmbeddedStruct
302			Nested struct {
303				Name string
304				EmbeddedStruct
305			}
306		}{
307			Name: "abc",
308			EmbeddedStruct: EmbeddedStruct{
309				Name: "abc",
310			},
311			Nested: struct {
312				Name string
313				EmbeddedStruct
314			}{
315				Name: "def",
316				EmbeddedStruct: EmbeddedStruct{
317					Name: "def",
318				},
319			},
320		},
321		nil,
322	},
323
324	// Anonymous interface with name collision
325	{`
326		m {
327			name: "abc",
328			nested: {
329				name: "def",
330			},
331		}
332		`,
333		struct {
334			Name string
335			EmbeddedInterface
336			Nested struct {
337				Name string
338				EmbeddedInterface
339			}
340		}{
341			Name: "abc",
342			EmbeddedInterface: &struct{ Name string }{
343				Name: "abc",
344			},
345			Nested: struct {
346				Name string
347				EmbeddedInterface
348			}{
349				Name: "def",
350				EmbeddedInterface: &struct{ Name string }{
351					Name: "def",
352				},
353			},
354		},
355		nil,
356	},
357}
358
359type EmbeddedStruct struct{ Name string }
360type EmbeddedInterface interface{}
361
362func TestUnpackProperties(t *testing.T) {
363	for _, testCase := range validUnpackTestCases {
364		r := bytes.NewBufferString(testCase.input)
365		file, errs := parser.Parse("", r, nil)
366		if len(errs) != 0 {
367			t.Errorf("test case: %s", testCase.input)
368			t.Errorf("unexpected parse errors:")
369			for _, err := range errs {
370				t.Errorf("  %s", err)
371			}
372			t.FailNow()
373		}
374
375		module := file.Defs[0].(*parser.Module)
376		properties := proptools.CloneProperties(reflect.ValueOf(testCase.output))
377		proptools.ZeroProperties(properties.Elem())
378		_, errs = unpackProperties(module.Properties, properties.Interface())
379		if len(errs) != 0 && len(testCase.errs) == 0 {
380			t.Errorf("test case: %s", testCase.input)
381			t.Errorf("unexpected unpack errors:")
382			for _, err := range errs {
383				t.Errorf("  %s", err)
384			}
385			t.FailNow()
386		} else if !reflect.DeepEqual(errs, testCase.errs) {
387			t.Errorf("test case: %s", testCase.input)
388			t.Errorf("incorrect errors:")
389			t.Errorf("  expected: %+v", testCase.errs)
390			t.Errorf("       got: %+v", errs)
391		}
392
393		output := properties.Elem().Interface()
394		if !reflect.DeepEqual(output, testCase.output) {
395			t.Errorf("test case: %s", testCase.input)
396			t.Errorf("incorrect output:")
397			t.Errorf("  expected: %+v", testCase.output)
398			t.Errorf("       got: %+v", output)
399		}
400	}
401}
402