xref: /aosp_15_r20/build/soong/android/soong_config_modules_test.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2019 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	"fmt"
19	"testing"
20)
21
22type soongConfigTestDefaultsModule struct {
23	ModuleBase
24	DefaultsModuleBase
25}
26
27func soongConfigTestDefaultsModuleFactory() Module {
28	m := &soongConfigTestDefaultsModule{}
29	m.AddProperties(&soongConfigTestModuleProperties{})
30	InitDefaultsModule(m)
31	return m
32}
33
34type soongConfigTestModule struct {
35	ModuleBase
36	DefaultableModuleBase
37	props      soongConfigTestModuleProperties
38	outputPath ModuleOutPath
39}
40
41type soongConfigTestModuleProperties struct {
42	Cflags []string
43}
44
45func soongConfigTestModuleFactory() Module {
46	m := &soongConfigTestModule{}
47	m.AddProperties(&m.props)
48	InitAndroidModule(m)
49	InitDefaultableModule(m)
50	return m
51}
52
53func (t *soongConfigTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
54	t.outputPath = PathForModuleOut(ctx, "test")
55}
56
57var prepareForSoongConfigTestModule = FixtureRegisterWithContext(func(ctx RegistrationContext) {
58	ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
59	ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
60})
61
62func TestSoongConfigModule(t *testing.T) {
63	configBp := `
64		soong_config_module_type {
65			name: "acme_test",
66			module_type: "test",
67			config_namespace: "acme",
68			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
69			bool_variables: ["feature2", "unused_feature", "always_true"],
70			value_variables: ["size", "unused_size"],
71			properties: ["cflags", "srcs", "defaults"],
72		}
73
74		soong_config_string_variable {
75			name: "board",
76			values: ["soc_a", "soc_b", "soc_c", "soc_d"],
77		}
78
79		soong_config_string_variable {
80			name: "unused_string_var",
81			values: ["a", "b"],
82		}
83
84		soong_config_bool_variable {
85			name: "feature1",
86		}
87
88		soong_config_bool_variable {
89			name: "FEATURE3",
90		}
91	`
92
93	importBp := `
94		soong_config_module_type_import {
95			from: "SoongConfig.bp",
96			module_types: ["acme_test"],
97		}
98	`
99
100	bp := `
101		test_defaults {
102			name: "foo_defaults",
103			cflags: ["DEFAULT"],
104		}
105
106		acme_test {
107			name: "foo",
108			cflags: ["-DGENERIC"],
109			defaults: ["foo_defaults"],
110			soong_config_variables: {
111				board: {
112					soc_a: {
113						cflags: ["-DSOC_A"],
114					},
115					soc_b: {
116						cflags: ["-DSOC_B"],
117					},
118					soc_c: {},
119					conditions_default: {
120						cflags: ["-DSOC_CONDITIONS_DEFAULT"],
121					},
122				},
123				size: {
124					cflags: ["-DSIZE=%s"],
125					conditions_default: {
126						cflags: ["-DSIZE=CONDITIONS_DEFAULT"],
127					},
128				},
129				feature1: {
130					  conditions_default: {
131						  cflags: ["-DF1_CONDITIONS_DEFAULT"],
132					  },
133					cflags: ["-DFEATURE1"],
134				},
135				feature2: {
136					cflags: ["-DFEATURE2"],
137					 conditions_default: {
138						 cflags: ["-DF2_CONDITIONS_DEFAULT"],
139					 },
140				},
141				FEATURE3: {
142					cflags: ["-DFEATURE3"],
143				},
144			},
145		}
146
147		test_defaults {
148			name: "foo_defaults_a",
149			cflags: ["DEFAULT_A"],
150		}
151
152		test_defaults {
153			name: "foo_defaults_b",
154			cflags: ["DEFAULT_B"],
155		}
156
157		test_defaults {
158			name: "foo_defaults_always_true",
159			cflags: ["DEFAULT_ALWAYS_TRUE"],
160		}
161
162		acme_test {
163			name: "foo_with_defaults",
164			cflags: ["-DGENERIC"],
165			defaults: ["foo_defaults"],
166			soong_config_variables: {
167				board: {
168					soc_a: {
169						cflags: ["-DSOC_A"],
170						defaults: ["foo_defaults_a"],
171					},
172					soc_b: {
173						cflags: ["-DSOC_B"],
174						defaults: ["foo_defaults_b"],
175					},
176					soc_c: {},
177				},
178				size: {
179					cflags: ["-DSIZE=%s"],
180				},
181				feature1: {
182					cflags: ["-DFEATURE1"],
183				},
184				feature2: {
185					cflags: ["-DFEATURE2"],
186				},
187				FEATURE3: {
188					cflags: ["-DFEATURE3"],
189				},
190				always_true: {
191					defaults: ["foo_defaults_always_true"],
192					conditions_default: {
193						// verify that conditions_default is skipped if the
194						// soong config variable is true by specifying a
195						// non-existent module in conditions_default
196						defaults: ["//nonexistent:defaults"],
197					}
198				},
199			},
200		}
201    `
202
203	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
204		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
205			variables.VendorVars = vars
206		})
207	}
208
209	run := func(t *testing.T, bp string, fs MockFS) {
210		testCases := []struct {
211			name                     string
212			preparer                 FixturePreparer
213			fooExpectedFlags         []string
214			fooDefaultsExpectedFlags []string
215		}{
216			{
217				name: "withValues",
218				preparer: fixtureForVendorVars(map[string]map[string]string{
219					"acme": {
220						"board":    "soc_a",
221						"size":     "42",
222						"feature1": "true",
223						"feature2": "false",
224						// FEATURE3 unset
225						"unused_feature":    "true", // unused
226						"unused_size":       "1",    // unused
227						"unused_string_var": "a",    // unused
228						"always_true":       "true",
229					},
230				}),
231				fooExpectedFlags: []string{
232					"DEFAULT",
233					"-DGENERIC",
234					"-DF2_CONDITIONS_DEFAULT",
235					"-DSIZE=42",
236					"-DSOC_A",
237					"-DFEATURE1",
238				},
239				fooDefaultsExpectedFlags: []string{
240					"DEFAULT_A",
241					"DEFAULT_ALWAYS_TRUE",
242					"DEFAULT",
243					"-DGENERIC",
244					"-DSIZE=42",
245					"-DSOC_A",
246					"-DFEATURE1",
247				},
248			},
249			{
250				name: "empty_prop_for_string_var",
251				preparer: fixtureForVendorVars(map[string]map[string]string{
252					"acme": {
253						"board":       "soc_c",
254						"always_true": "true",
255					}}),
256				fooExpectedFlags: []string{
257					"DEFAULT",
258					"-DGENERIC",
259					"-DF2_CONDITIONS_DEFAULT",
260					"-DSIZE=CONDITIONS_DEFAULT",
261					"-DF1_CONDITIONS_DEFAULT",
262				},
263				fooDefaultsExpectedFlags: []string{
264					"DEFAULT_ALWAYS_TRUE",
265					"DEFAULT",
266					"-DGENERIC",
267				},
268			},
269			{
270				name: "unused_string_var",
271				preparer: fixtureForVendorVars(map[string]map[string]string{
272					"acme": {
273						"board":       "soc_d",
274						"always_true": "true",
275					}}),
276				fooExpectedFlags: []string{
277					"DEFAULT",
278					"-DGENERIC",
279					"-DF2_CONDITIONS_DEFAULT",
280					"-DSIZE=CONDITIONS_DEFAULT",
281					"-DSOC_CONDITIONS_DEFAULT", // foo does not contain a prop "soc_d", so we use the default
282					"-DF1_CONDITIONS_DEFAULT",
283				},
284				fooDefaultsExpectedFlags: []string{
285					"DEFAULT_ALWAYS_TRUE",
286					"DEFAULT",
287					"-DGENERIC",
288				},
289			},
290
291			{
292				name: "conditions_default",
293				preparer: fixtureForVendorVars(map[string]map[string]string{
294					"acme": {
295						"always_true": "true",
296					}}),
297				fooExpectedFlags: []string{
298					"DEFAULT",
299					"-DGENERIC",
300					"-DF2_CONDITIONS_DEFAULT",
301					"-DSIZE=CONDITIONS_DEFAULT",
302					"-DSOC_CONDITIONS_DEFAULT",
303					"-DF1_CONDITIONS_DEFAULT",
304				},
305				fooDefaultsExpectedFlags: []string{
306					"DEFAULT_ALWAYS_TRUE",
307					"DEFAULT",
308					"-DGENERIC",
309				},
310			},
311		}
312
313		for _, tc := range testCases {
314			t.Run(tc.name, func(t *testing.T) {
315				result := GroupFixturePreparers(
316					tc.preparer,
317					PrepareForTestWithDefaults,
318					PrepareForTestWithSoongConfigModuleBuildComponents,
319					prepareForSoongConfigTestModule,
320					fs.AddToFixture(),
321					FixtureWithRootAndroidBp(bp),
322				).RunTest(t)
323
324				foo := result.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
325				AssertDeepEquals(t, "foo cflags", tc.fooExpectedFlags, foo.props.Cflags)
326
327				fooDefaults := result.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule)
328				AssertDeepEquals(t, "foo_with_defaults cflags", tc.fooDefaultsExpectedFlags, fooDefaults.props.Cflags)
329			})
330		}
331	}
332
333	t.Run("single file", func(t *testing.T) {
334		run(t, configBp+bp, nil)
335	})
336
337	t.Run("import", func(t *testing.T) {
338		run(t, importBp+bp, map[string][]byte{
339			"SoongConfig.bp": []byte(configBp),
340		})
341	})
342}
343
344func TestNonExistentPropertyInSoongConfigModule(t *testing.T) {
345	bp := `
346		soong_config_module_type {
347			name: "acme_test",
348			module_type: "test",
349			config_namespace: "acme",
350			bool_variables: ["feature1"],
351			properties: ["made_up_property"],
352		}
353
354		acme_test {
355			name: "foo",
356			cflags: ["-DGENERIC"],
357			soong_config_variables: {
358				feature1: {
359					made_up_property: true,
360				},
361			},
362		}
363    `
364
365	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
366		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
367			variables.VendorVars = vars
368		})
369	}
370
371	GroupFixturePreparers(
372		fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
373		PrepareForTestWithDefaults,
374		PrepareForTestWithSoongConfigModuleBuildComponents,
375		prepareForSoongConfigTestModule,
376		FixtureWithRootAndroidBp(bp),
377	).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
378		`unrecognized property "soong_config_variables.feature1.made_up_property`,
379	})).RunTest(t)
380}
381
382func TestDuplicateStringValueInSoongConfigStringVariable(t *testing.T) {
383	bp := `
384		soong_config_string_variable {
385			name: "board",
386			values: ["soc_a", "soc_b", "soc_c", "soc_a"],
387		}
388
389		soong_config_module_type {
390			name: "acme_test",
391			module_type: "test",
392			config_namespace: "acme",
393			variables: ["board"],
394			properties: ["cflags", "srcs", "defaults"],
395		}
396    `
397
398	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
399		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
400			variables.VendorVars = vars
401		})
402	}
403
404	GroupFixturePreparers(
405		fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
406		PrepareForTestWithDefaults,
407		PrepareForTestWithSoongConfigModuleBuildComponents,
408		prepareForSoongConfigTestModule,
409		FixtureWithRootAndroidBp(bp),
410	).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
411		// TODO(b/171232169): improve the error message for non-existent properties
412		`Android.bp: soong_config_string_variable: values property error: duplicate value: "soc_a"`,
413	})).RunTest(t)
414}
415
416type soongConfigTestSingletonModule struct {
417	SingletonModuleBase
418	props soongConfigTestSingletonModuleProperties
419}
420
421type soongConfigTestSingletonModuleProperties struct {
422	Fragments []struct {
423		Apex   string
424		Module string
425	}
426}
427
428func soongConfigTestSingletonModuleFactory() SingletonModule {
429	m := &soongConfigTestSingletonModule{}
430	m.AddProperties(&m.props)
431	InitAndroidModule(m)
432	return m
433}
434
435func (t *soongConfigTestSingletonModule) GenerateAndroidBuildActions(ModuleContext) {}
436
437func (t *soongConfigTestSingletonModule) GenerateSingletonBuildActions(SingletonContext) {}
438
439var prepareForSoongConfigTestSingletonModule = FixtureRegisterWithContext(func(ctx RegistrationContext) {
440	ctx.RegisterSingletonModuleType("test_singleton", soongConfigTestSingletonModuleFactory)
441})
442
443func TestSoongConfigModuleSingletonModule(t *testing.T) {
444	bp := `
445		soong_config_module_type {
446			name: "acme_test_singleton",
447			module_type: "test_singleton",
448			config_namespace: "acme",
449			bool_variables: ["coyote"],
450			properties: ["fragments"],
451		}
452
453		acme_test_singleton {
454			name: "wiley",
455			fragments: [
456				{
457					apex: "com.android.acme",
458					module: "road-runner",
459				},
460			],
461			soong_config_variables: {
462				coyote: {
463					fragments: [
464						{
465							apex: "com.android.acme",
466							module: "wiley",
467						},
468					],
469				},
470			},
471		}
472	`
473
474	for _, test := range []struct {
475		coyote            bool
476		expectedFragments string
477	}{
478		{
479			coyote:            false,
480			expectedFragments: "[{Apex:com.android.acme Module:road-runner}]",
481		},
482		{
483			coyote:            true,
484			expectedFragments: "[{Apex:com.android.acme Module:road-runner} {Apex:com.android.acme Module:wiley}]",
485		},
486	} {
487		t.Run(fmt.Sprintf("coyote:%t", test.coyote), func(t *testing.T) {
488			result := GroupFixturePreparers(
489				PrepareForTestWithSoongConfigModuleBuildComponents,
490				prepareForSoongConfigTestSingletonModule,
491				FixtureWithRootAndroidBp(bp),
492				FixtureModifyProductVariables(func(variables FixtureProductVariables) {
493					variables.VendorVars = map[string]map[string]string{
494						"acme": {
495							"coyote": fmt.Sprintf("%t", test.coyote),
496						},
497					}
498				}),
499			).RunTest(t)
500
501			// Make sure that the singleton was created.
502			result.SingletonForTests("test_singleton")
503			m := result.ModuleForTests("wiley", "").module.(*soongConfigTestSingletonModule)
504			AssertStringEquals(t, "fragments", test.expectedFragments, fmt.Sprintf("%+v", m.props.Fragments))
505		})
506	}
507}
508