• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 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 android
16
17import (
18	"android/soong/bazel"
19	"testing"
20)
21
22func TestExpandVars(t *testing.T) {
23	android_arm64_config := TestConfig("out", nil, "", nil)
24	android_arm64_config.BuildOS = Android
25	android_arm64_config.BuildArch = Arm64
26
27	testCases := []struct {
28		description     string
29		config          Config
30		stringScope     ExportedStringVariables
31		stringListScope ExportedStringListVariables
32		configVars      ExportedConfigDependingVariables
33		toExpand        string
34		expectedValues  []string
35	}{
36		{
37			description:    "no expansion for non-interpolated value",
38			toExpand:       "foo",
39			expectedValues: []string{"foo"},
40		},
41		{
42			description: "single level expansion for string var",
43			stringScope: ExportedStringVariables{
44				"foo": "bar",
45			},
46			toExpand:       "${foo}",
47			expectedValues: []string{"bar"},
48		},
49		{
50			description: "single level expansion with short-name for string var",
51			stringScope: ExportedStringVariables{
52				"foo": "bar",
53			},
54			toExpand:       "${config.foo}",
55			expectedValues: []string{"bar"},
56		},
57		{
58			description: "single level expansion string list var",
59			stringListScope: ExportedStringListVariables{
60				"foo": []string{"bar"},
61			},
62			toExpand:       "${foo}",
63			expectedValues: []string{"bar"},
64		},
65		{
66			description: "mixed level expansion for string list var",
67			stringScope: ExportedStringVariables{
68				"foo": "${bar}",
69				"qux": "hello",
70			},
71			stringListScope: ExportedStringListVariables{
72				"bar": []string{"baz", "${qux}"},
73			},
74			toExpand:       "${foo}",
75			expectedValues: []string{"baz hello"},
76		},
77		{
78			description: "double level expansion",
79			stringListScope: ExportedStringListVariables{
80				"foo": []string{"${bar}"},
81				"bar": []string{"baz"},
82			},
83			toExpand:       "${foo}",
84			expectedValues: []string{"baz"},
85		},
86		{
87			description: "double level expansion with a literal",
88			stringListScope: ExportedStringListVariables{
89				"a": []string{"${b}", "c"},
90				"b": []string{"d"},
91			},
92			toExpand:       "${a}",
93			expectedValues: []string{"d c"},
94		},
95		{
96			description: "double level expansion, with two variables in a string",
97			stringListScope: ExportedStringListVariables{
98				"a": []string{"${b} ${c}"},
99				"b": []string{"d"},
100				"c": []string{"e"},
101			},
102			toExpand:       "${a}",
103			expectedValues: []string{"d e"},
104		},
105		{
106			description: "triple level expansion with two variables in a string",
107			stringListScope: ExportedStringListVariables{
108				"a": []string{"${b} ${c}"},
109				"b": []string{"${c}", "${d}"},
110				"c": []string{"${d}"},
111				"d": []string{"foo"},
112			},
113			toExpand:       "${a}",
114			expectedValues: []string{"foo foo foo"},
115		},
116		{
117			description: "expansion with config depending vars",
118			configVars: ExportedConfigDependingVariables{
119				"a": func(c Config) string { return c.BuildOS.String() },
120				"b": func(c Config) string { return c.BuildArch.String() },
121			},
122			config:         android_arm64_config,
123			toExpand:       "${a}-${b}",
124			expectedValues: []string{"android-arm64"},
125		},
126		{
127			description: "double level multi type expansion",
128			stringListScope: ExportedStringListVariables{
129				"platform": []string{"${os}-${arch}"},
130				"const":    []string{"const"},
131			},
132			configVars: ExportedConfigDependingVariables{
133				"os":   func(c Config) string { return c.BuildOS.String() },
134				"arch": func(c Config) string { return c.BuildArch.String() },
135				"foo":  func(c Config) string { return "foo" },
136			},
137			config:         android_arm64_config,
138			toExpand:       "${const}/${platform}/${foo}",
139			expectedValues: []string{"const/android-arm64/foo"},
140		},
141	}
142
143	for _, testCase := range testCases {
144		t.Run(testCase.description, func(t *testing.T) {
145			output, _ := expandVar(testCase.config, testCase.toExpand, testCase.stringScope, testCase.stringListScope, testCase.configVars)
146			if len(output) != len(testCase.expectedValues) {
147				t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output))
148			}
149			for i, actual := range output {
150				expectedValue := testCase.expectedValues[i]
151				if actual != expectedValue {
152					t.Errorf("Actual value '%s' doesn't match expected value '%s'", actual, expectedValue)
153				}
154			}
155		})
156	}
157}
158
159func TestBazelToolchainVars(t *testing.T) {
160	testCases := []struct {
161		name        string
162		config      Config
163		vars        ExportedVariables
164		expectedOut string
165	}{
166		{
167			name: "exports strings",
168			vars: ExportedVariables{
169				exportedStringVars: ExportedStringVariables{
170					"a": "b",
171					"c": "d",
172				},
173			},
174			expectedOut: bazel.GeneratedBazelFileWarning + `
175
176_a = "b"
177
178_c = "d"
179
180constants = struct(
181    a = _a,
182    c = _c,
183)`,
184		},
185		{
186			name: "exports string lists",
187			vars: ExportedVariables{
188				exportedStringListVars: ExportedStringListVariables{
189					"a": []string{"b1", "b2"},
190					"c": []string{"d1", "d2"},
191				},
192			},
193			expectedOut: bazel.GeneratedBazelFileWarning + `
194
195_a = [
196    "b1",
197    "b2",
198]
199
200_c = [
201    "d1",
202    "d2",
203]
204
205constants = struct(
206    a = _a,
207    c = _c,
208)`,
209		},
210		{
211			name: "exports string lists dicts",
212			vars: ExportedVariables{
213				exportedStringListDictVars: ExportedStringListDictVariables{
214					"a": map[string][]string{"b1": {"b2"}},
215					"c": map[string][]string{"d1": {"d2"}},
216				},
217			},
218			expectedOut: bazel.GeneratedBazelFileWarning + `
219
220_a = {
221    "b1": ["b2"],
222}
223
224_c = {
225    "d1": ["d2"],
226}
227
228constants = struct(
229    a = _a,
230    c = _c,
231)`,
232		},
233		{
234			name: "exports dict with var refs",
235			vars: ExportedVariables{
236				exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{
237					"a": map[string]string{"b1": "${b2}"},
238					"c": map[string]string{"d1": "${config.d2}"},
239				},
240			},
241			expectedOut: bazel.GeneratedBazelFileWarning + `
242
243_a = {
244    "b1": _b2,
245}
246
247_c = {
248    "d1": _d2,
249}
250
251constants = struct(
252    a = _a,
253    c = _c,
254)`,
255		},
256		{
257			name: "sorts across types with variable references last",
258			vars: ExportedVariables{
259				exportedStringVars: ExportedStringVariables{
260					"b": "b-val",
261					"d": "d-val",
262				},
263				exportedStringListVars: ExportedStringListVariables{
264					"c": []string{"c-val"},
265					"e": []string{"e-val"},
266				},
267				exportedStringListDictVars: ExportedStringListDictVariables{
268					"a": map[string][]string{"a1": {"a2"}},
269					"f": map[string][]string{"f1": {"f2"}},
270				},
271				exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{
272					"aa": map[string]string{"b1": "${b}"},
273					"cc": map[string]string{"d1": "${config.d}"},
274				},
275			},
276			expectedOut: bazel.GeneratedBazelFileWarning + `
277
278_a = {
279    "a1": ["a2"],
280}
281
282_b = "b-val"
283
284_c = ["c-val"]
285
286_d = "d-val"
287
288_e = ["e-val"]
289
290_f = {
291    "f1": ["f2"],
292}
293
294_aa = {
295    "b1": _b,
296}
297
298_cc = {
299    "d1": _d,
300}
301
302constants = struct(
303    a = _a,
304    b = _b,
305    c = _c,
306    d = _d,
307    e = _e,
308    f = _f,
309    aa = _aa,
310    cc = _cc,
311)`,
312		},
313	}
314
315	for _, tc := range testCases {
316		t.Run(tc.name, func(t *testing.T) {
317			out := BazelToolchainVars(tc.config, tc.vars)
318			if out != tc.expectedOut {
319				t.Errorf("Expected \n%s, got \n%s", tc.expectedOut, out)
320			}
321		})
322	}
323}
324
325func TestSplitStringKeepingQuotedSubstring(t *testing.T) {
326	testCases := []struct {
327		description string
328		s           string
329		delimiter   byte
330		split       []string
331	}{
332		{
333			description: "empty string returns single empty string",
334			s:           "",
335			delimiter:   ' ',
336			split: []string{
337				"",
338			},
339		},
340		{
341			description: "string with single space returns two empty strings",
342			s:           " ",
343			delimiter:   ' ',
344			split: []string{
345				"",
346				"",
347			},
348		},
349		{
350			description: "string with two spaces returns three empty strings",
351			s:           "  ",
352			delimiter:   ' ',
353			split: []string{
354				"",
355				"",
356				"",
357			},
358		},
359		{
360			description: "string with four words returns four word string",
361			s:           "hello world with words",
362			delimiter:   ' ',
363			split: []string{
364				"hello",
365				"world",
366				"with",
367				"words",
368			},
369		},
370		{
371			description: "string with words and nested quote returns word strings and quote string",
372			s:           `hello "world with" words`,
373			delimiter:   ' ',
374			split: []string{
375				"hello",
376				`"world with"`,
377				"words",
378			},
379		},
380		{
381			description: "string with escaped quote inside real quotes",
382			s:           `hello \"world "with\" words"`,
383			delimiter:   ' ',
384			split: []string{
385				"hello",
386				`"world`,
387				`"with" words"`,
388			},
389		},
390		{
391			description: "string with words and escaped quotes returns word strings",
392			s:           `hello \"world with\" words`,
393			delimiter:   ' ',
394			split: []string{
395				"hello",
396				`"world`,
397				`with"`,
398				"words",
399			},
400		},
401		{
402			description: "string which is single quoted substring returns only substring",
403			s:           `"hello world with words"`,
404			delimiter:   ' ',
405			split: []string{
406				`"hello world with words"`,
407			},
408		},
409		{
410			description: "string starting with quote returns quoted string",
411			s:           `"hello world with" words`,
412			delimiter:   ' ',
413			split: []string{
414				`"hello world with"`,
415				"words",
416			},
417		},
418		{
419			description: "string with starting quote and no ending quote returns quote to end of string",
420			s:           `hello "world with words`,
421			delimiter:   ' ',
422			split: []string{
423				"hello",
424				`"world with words`,
425			},
426		},
427		{
428			description: "quoted string is treated as a single \"word\" unless separated by delimiter",
429			s:           `hello "world"with words`,
430			delimiter:   ' ',
431			split: []string{
432				"hello",
433				`"world"with`,
434				"words",
435			},
436		},
437	}
438
439	for _, tc := range testCases {
440		t.Run(tc.description, func(t *testing.T) {
441			split := splitStringKeepingQuotedSubstring(tc.s, tc.delimiter)
442			if len(split) != len(tc.split) {
443				t.Fatalf("number of split string elements (%d) differs from expected (%d): split string (%v), expected (%v)",
444					len(split), len(tc.split), split, tc.split,
445				)
446			}
447			for i := range split {
448				if split[i] != tc.split[i] {
449					t.Errorf("split string element (%d), %v, differs from expected, %v", i, split[i], tc.split[i])
450				}
451			}
452		})
453	}
454}
455