1// Copyright 2020 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 soongconfig
16
17import (
18	"reflect"
19	"testing"
20
21	"github.com/google/blueprint/proptools"
22)
23
24func Test_CanonicalizeToProperty(t *testing.T) {
25	tests := []struct {
26		name string
27		arg  string
28		want string
29	}{
30		{
31			name: "lowercase",
32			arg:  "board",
33			want: "board",
34		},
35		{
36			name: "uppercase",
37			arg:  "BOARD",
38			want: "BOARD",
39		},
40		{
41			name: "numbers",
42			arg:  "BOARD123",
43			want: "BOARD123",
44		},
45		{
46			name: "underscore",
47			arg:  "TARGET_BOARD",
48			want: "TARGET_BOARD",
49		},
50		{
51			name: "dash",
52			arg:  "TARGET-BOARD",
53			want: "TARGET_BOARD",
54		},
55		{
56			name: "unicode",
57			arg:  "boardλ",
58			want: "board_",
59		},
60	}
61	for _, tt := range tests {
62		t.Run(tt.name, func(t *testing.T) {
63			if got := CanonicalizeToProperty(tt.arg); got != tt.want {
64				t.Errorf("canonicalizeToProperty() = %v, want %v", got, tt.want)
65			}
66		})
67	}
68}
69
70func Test_typeForPropertyFromPropertyStruct(t *testing.T) {
71	tests := []struct {
72		name     string
73		ps       interface{}
74		property string
75		want     string
76	}{
77		{
78			name: "string",
79			ps: struct {
80				A string
81			}{},
82			property: "a",
83			want:     "string",
84		},
85		{
86			name: "list",
87			ps: struct {
88				A []string
89			}{},
90			property: "a",
91			want:     "[]string",
92		},
93		{
94			name: "missing",
95			ps: struct {
96				A []string
97			}{},
98			property: "b",
99			want:     "",
100		},
101		{
102			name: "nested",
103			ps: struct {
104				A struct {
105					B string
106				}
107			}{},
108			property: "a.b",
109			want:     "string",
110		},
111		{
112			name: "missing nested",
113			ps: struct {
114				A struct {
115					B string
116				}
117			}{},
118			property: "a.c",
119			want:     "",
120		},
121		{
122			name: "not a struct",
123			ps: struct {
124				A string
125			}{},
126			property: "a.b",
127			want:     "",
128		},
129		{
130			name: "nested pointer",
131			ps: struct {
132				A *struct {
133					B string
134				}
135			}{},
136			property: "a.b",
137			want:     "string",
138		},
139		{
140			name: "nested interface",
141			ps: struct {
142				A interface{}
143			}{
144				A: struct {
145					B string
146				}{},
147			},
148			property: "a.b",
149			want:     "string",
150		},
151		{
152			name: "nested interface pointer",
153			ps: struct {
154				A interface{}
155			}{
156				A: &struct {
157					B string
158				}{},
159			},
160			property: "a.b",
161			want:     "string",
162		},
163		{
164			name: "nested interface nil pointer",
165			ps: struct {
166				A interface{}
167			}{
168				A: (*struct {
169					B string
170				})(nil),
171			},
172			property: "a.b",
173			want:     "string",
174		},
175	}
176	for _, tt := range tests {
177		t.Run(tt.name, func(t *testing.T) {
178			typ := typeForPropertyFromPropertyStruct(tt.ps, tt.property)
179			got := ""
180			if typ != nil {
181				got = typ.String()
182			}
183			if got != tt.want {
184				t.Errorf("typeForPropertyFromPropertyStruct() = %v, want %v", got, tt.want)
185			}
186		})
187	}
188}
189
190func Test_createAffectablePropertiesType(t *testing.T) {
191	tests := []struct {
192		name                 string
193		affectableProperties []string
194		factoryProps         interface{}
195		want                 string
196	}{
197		{
198			name:                 "string",
199			affectableProperties: []string{"cflags"},
200			factoryProps: struct {
201				Cflags string
202			}{},
203			want: "*struct { Cflags string }",
204		},
205		{
206			name:                 "list",
207			affectableProperties: []string{"cflags"},
208			factoryProps: struct {
209				Cflags []string
210			}{},
211			want: "*struct { Cflags []string }",
212		},
213		{
214			name:                 "string pointer",
215			affectableProperties: []string{"cflags"},
216			factoryProps: struct {
217				Cflags *string
218			}{},
219			want: "*struct { Cflags *string }",
220		},
221		{
222			name:                 "subset",
223			affectableProperties: []string{"cflags"},
224			factoryProps: struct {
225				Cflags  string
226				Ldflags string
227			}{},
228			want: "*struct { Cflags string }",
229		},
230		{
231			name:                 "none",
232			affectableProperties: []string{"cflags"},
233			factoryProps: struct {
234				Ldflags string
235			}{},
236			want: "",
237		},
238		{
239			name:                 "nested",
240			affectableProperties: []string{"multilib.lib32.cflags"},
241			factoryProps: struct {
242				Multilib struct {
243					Lib32 struct {
244						Cflags string
245					}
246				}
247			}{},
248			want: "*struct { Multilib struct { Lib32 struct { Cflags string } } }",
249		},
250		{
251			name: "complex",
252			affectableProperties: []string{
253				"cflags",
254				"multilib.lib32.cflags",
255				"multilib.lib32.ldflags",
256				"multilib.lib64.cflags",
257				"multilib.lib64.ldflags",
258				"zflags",
259			},
260			factoryProps: struct {
261				Cflags   string
262				Multilib struct {
263					Lib32 struct {
264						Cflags  string
265						Ldflags string
266					}
267					Lib64 struct {
268						Cflags  string
269						Ldflags string
270					}
271				}
272				Zflags string
273			}{},
274			want: "*struct { Cflags string; Multilib struct { Lib32 struct { Cflags string; Ldflags string }; Lib64 struct { Cflags string; Ldflags string } }; Zflags string }",
275		},
276	}
277	for _, tt := range tests {
278		t.Run(tt.name, func(t *testing.T) {
279			typ := createAffectablePropertiesType(tt.affectableProperties, []interface{}{tt.factoryProps})
280			got := ""
281			if typ != nil {
282				got = typ.String()
283			}
284			if !reflect.DeepEqual(got, tt.want) {
285				t.Errorf("createAffectablePropertiesType() = %v, want %v", got, tt.want)
286			}
287		})
288	}
289}
290
291type properties struct {
292	A *string
293	B bool
294	C []string
295}
296
297type varProps struct {
298	A                  *string
299	B                  bool
300	C                  []string
301	Conditions_default *properties
302}
303
304type boolSoongConfigVars struct {
305	Bool_var interface{}
306}
307
308type stringSoongConfigVars struct {
309	String_var interface{}
310}
311
312type valueSoongConfigVars struct {
313	My_value_var interface{}
314}
315
316type listProperties struct {
317	C []string
318}
319
320type listVarProps struct {
321	C                  []string
322	Conditions_default *listProperties
323}
324
325type listSoongConfigVars struct {
326	List_var interface{}
327}
328
329func Test_PropertiesToApply_Bool(t *testing.T) {
330	mt, _ := newModuleType(&ModuleTypeProperties{
331		Module_type:      "foo",
332		Config_namespace: "bar",
333		Bool_variables:   []string{"bool_var"},
334		Properties:       []string{"a", "b"},
335	})
336	boolVarPositive := &properties{
337		A: proptools.StringPtr("A"),
338		B: true,
339	}
340	conditionsDefault := &properties{
341		A: proptools.StringPtr("default"),
342		B: false,
343	}
344	actualProps := &struct {
345		Soong_config_variables boolSoongConfigVars
346	}{
347		Soong_config_variables: boolSoongConfigVars{
348			Bool_var: &varProps{
349				A:                  boolVarPositive.A,
350				B:                  boolVarPositive.B,
351				Conditions_default: conditionsDefault,
352			},
353		},
354	}
355	props := reflect.ValueOf(actualProps)
356
357	testCases := []struct {
358		name      string
359		config    SoongConfig
360		wantProps []interface{}
361	}{
362		{
363			name:      "no_vendor_config",
364			config:    Config(map[string]string{}),
365			wantProps: []interface{}{conditionsDefault},
366		},
367		{
368			name:      "vendor_config_false",
369			config:    Config(map[string]string{"bool_var": "n"}),
370			wantProps: []interface{}{conditionsDefault},
371		},
372		{
373			name:      "bool_var_true",
374			config:    Config(map[string]string{"bool_var": "y"}),
375			wantProps: []interface{}{boolVarPositive},
376		},
377	}
378
379	for _, tc := range testCases {
380		gotProps, err := PropertiesToApply(mt, props, tc.config)
381		if err != nil {
382			t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err)
383		}
384
385		if !reflect.DeepEqual(gotProps, tc.wantProps) {
386			t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps)
387		}
388	}
389}
390
391func Test_PropertiesToApply_List(t *testing.T) {
392	mt, _ := newModuleType(&ModuleTypeProperties{
393		Module_type:      "foo",
394		Config_namespace: "bar",
395		List_variables:   []string{"my_list_var"},
396		Properties:       []string{"c"},
397	})
398	conditionsDefault := &listProperties{
399		C: []string{"default"},
400	}
401	actualProps := &struct {
402		Soong_config_variables listSoongConfigVars
403	}{
404		Soong_config_variables: listSoongConfigVars{
405			List_var: &listVarProps{
406				C:                  []string{"A=%s", "B=%s"},
407				Conditions_default: conditionsDefault,
408			},
409		},
410	}
411	props := reflect.ValueOf(actualProps)
412
413	testCases := []struct {
414		name      string
415		config    SoongConfig
416		wantProps []interface{}
417	}{
418		{
419			name:      "no_vendor_config",
420			config:    Config(map[string]string{}),
421			wantProps: []interface{}{conditionsDefault},
422		},
423		{
424			name:   "value_var_set",
425			config: Config(map[string]string{"my_list_var": "hello there"}),
426			wantProps: []interface{}{&listProperties{
427				C: []string{"A=hello", "A=there", "B=hello", "B=there"},
428			}},
429		},
430	}
431
432	for _, tc := range testCases {
433		gotProps, err := PropertiesToApply(mt, props, tc.config)
434		if err != nil {
435			t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err)
436		}
437
438		if !reflect.DeepEqual(gotProps, tc.wantProps) {
439			t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps)
440		}
441	}
442}
443
444func Test_PropertiesToApply_Value(t *testing.T) {
445	mt, _ := newModuleType(&ModuleTypeProperties{
446		Module_type:      "foo",
447		Config_namespace: "bar",
448		Value_variables:  []string{"my_value_var"},
449		Properties:       []string{"a", "b"},
450	})
451	conditionsDefault := &properties{
452		A: proptools.StringPtr("default"),
453		B: false,
454	}
455	actualProps := &struct {
456		Soong_config_variables valueSoongConfigVars
457	}{
458		Soong_config_variables: valueSoongConfigVars{
459			My_value_var: &varProps{
460				A:                  proptools.StringPtr("A=%s"),
461				B:                  true,
462				Conditions_default: conditionsDefault,
463			},
464		},
465	}
466	props := reflect.ValueOf(actualProps)
467
468	testCases := []struct {
469		name      string
470		config    SoongConfig
471		wantProps []interface{}
472	}{
473		{
474			name:      "no_vendor_config",
475			config:    Config(map[string]string{}),
476			wantProps: []interface{}{conditionsDefault},
477		},
478		{
479			name:   "value_var_set",
480			config: Config(map[string]string{"my_value_var": "Hello"}),
481			wantProps: []interface{}{&properties{
482				A: proptools.StringPtr("A=Hello"),
483				B: true,
484			}},
485		},
486	}
487
488	for _, tc := range testCases {
489		gotProps, err := PropertiesToApply(mt, props, tc.config)
490		if err != nil {
491			t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err)
492		}
493
494		if !reflect.DeepEqual(gotProps, tc.wantProps) {
495			t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps)
496		}
497	}
498}
499
500func Test_PropertiesToApply_Value_Nested(t *testing.T) {
501	mt, _ := newModuleType(&ModuleTypeProperties{
502		Module_type:      "foo",
503		Config_namespace: "bar",
504		Value_variables:  []string{"my_value_var"},
505		Properties:       []string{"a.b"},
506	})
507	type properties struct {
508		A struct {
509			B string
510		}
511	}
512	conditionsDefault := &properties{
513		A: struct{ B string }{
514			B: "default",
515		},
516	}
517	type valueVarProps struct {
518		A struct {
519			B string
520		}
521		Conditions_default *properties
522	}
523	actualProps := &struct {
524		Soong_config_variables valueSoongConfigVars
525	}{
526		Soong_config_variables: valueSoongConfigVars{
527			My_value_var: &valueVarProps{
528				A: struct{ B string }{
529					B: "A.B=%s",
530				},
531				Conditions_default: conditionsDefault,
532			},
533		},
534	}
535	props := reflect.ValueOf(actualProps)
536
537	testCases := []struct {
538		name      string
539		config    SoongConfig
540		wantProps []interface{}
541	}{
542		{
543			name:      "no_vendor_config",
544			config:    Config(map[string]string{}),
545			wantProps: []interface{}{conditionsDefault},
546		},
547		{
548			name:   "value_var_set",
549			config: Config(map[string]string{"my_value_var": "Hello"}),
550			wantProps: []interface{}{&properties{
551				A: struct{ B string }{
552					B: "A.B=Hello",
553				},
554			}},
555		},
556	}
557
558	for _, tc := range testCases {
559		gotProps, err := PropertiesToApply(mt, props, tc.config)
560		if err != nil {
561			t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err)
562		}
563
564		if !reflect.DeepEqual(gotProps, tc.wantProps) {
565			t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps)
566		}
567	}
568}
569
570func Test_PropertiesToApply_String_Error(t *testing.T) {
571	mt, _ := newModuleType(&ModuleTypeProperties{
572		Module_type:      "foo",
573		Config_namespace: "bar",
574		Variables:        []string{"string_var"},
575		Properties:       []string{"a", "b"},
576	})
577	mt.Variables = append(mt.Variables, &stringVariable{
578		baseVariable: baseVariable{
579			variable: "string_var",
580		},
581		values: []string{"a", "b", "c"},
582	})
583	stringVarPositive := &properties{
584		A: proptools.StringPtr("A"),
585		B: true,
586	}
587	conditionsDefault := &properties{
588		A: proptools.StringPtr("default"),
589		B: false,
590	}
591	actualProps := &struct {
592		Soong_config_variables stringSoongConfigVars
593	}{
594		Soong_config_variables: stringSoongConfigVars{
595			String_var: &varProps{
596				A:                  stringVarPositive.A,
597				B:                  stringVarPositive.B,
598				Conditions_default: conditionsDefault,
599			},
600		},
601	}
602	props := reflect.ValueOf(actualProps)
603
604	_, err := PropertiesToApply(mt, props, Config(map[string]string{
605		"string_var": "x",
606	}))
607	expected := `Soong config property "string_var" must be one of [a b c], found "x"`
608	if err == nil {
609		t.Fatalf("Expected an error, got nil")
610	} else if err.Error() != expected {
611		t.Fatalf("Error message was not correct, expected %q, got %q", expected, err.Error())
612	}
613}
614