xref: /aosp_15_r20/build/soong/android/selects_test.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2024 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	"reflect"
20	"testing"
21
22	"github.com/google/blueprint"
23	"github.com/google/blueprint/proptools"
24)
25
26func TestSelects(t *testing.T) {
27	testCases := []struct {
28		name           string
29		bp             string
30		fs             MockFS
31		provider       selectsTestProvider
32		providers      map[string]selectsTestProvider
33		vendorVars     map[string]map[string]string
34		vendorVarTypes map[string]map[string]string
35		expectedError  string
36	}{
37		{
38			name: "basic string list",
39			bp: `
40			my_module_type {
41				name: "foo",
42				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
43					"a": ["a.cpp"],
44					"b": ["b.cpp"],
45					default: ["c.cpp"],
46				}),
47			}
48			`,
49			provider: selectsTestProvider{
50				my_string_list: &[]string{"c.cpp"},
51			},
52		},
53		{
54			name: "basic string",
55			bp: `
56			my_module_type {
57				name: "foo",
58				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
59					"a": "a.cpp",
60					"b": "b.cpp",
61					default: "c.cpp",
62				}),
63			}
64			`,
65			provider: selectsTestProvider{
66				my_string: proptools.StringPtr("c.cpp"),
67			},
68		},
69		{
70			name: "basic bool",
71			bp: `
72			my_module_type {
73				name: "foo",
74				my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
75					"a": true,
76					"b": false,
77					default: true,
78				}),
79			}
80			`,
81			provider: selectsTestProvider{
82				my_bool: proptools.BoolPtr(true),
83			},
84		},
85		{
86			name: "basic paths",
87			bp: `
88			my_module_type {
89				name: "foo",
90				my_paths: select(soong_config_variable("my_namespace", "my_variable"), {
91					"a": ["foo.txt"],
92					"b": ["bar.txt"],
93					default: ["baz.txt"],
94				}),
95			}
96			`,
97			provider: selectsTestProvider{
98				my_paths: &[]string{"baz.txt"},
99			},
100		},
101		{
102			name: "Expression in select",
103			bp: `
104			my_module_type {
105				name: "foo",
106				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
107					"a": "foo" + "bar",
108					default: "baz",
109				}),
110			}
111			`,
112			provider: selectsTestProvider{
113				my_string: proptools.StringPtr("foobar"),
114			},
115			vendorVars: map[string]map[string]string{
116				"my_namespace": {
117					"my_variable": "a",
118				},
119			},
120		},
121		{
122			name: "paths with module references",
123			bp: `
124			my_module_type {
125				name: "foo",
126				my_paths: select(soong_config_variable("my_namespace", "my_variable"), {
127					"a": [":a"],
128					"b": [":b"],
129					default: [":c"],
130				}),
131			}
132			`,
133			expectedError: `"foo" depends on undefined module "c"`,
134		},
135		{
136			name: "Select type doesn't match property type",
137			bp: `
138			my_module_type {
139				name: "foo",
140				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
141					"a": false,
142					"b": true,
143					default: true,
144				}),
145			}
146			`,
147			expectedError: `can't assign bool value to string property`,
148		},
149		{
150			name: "String list non-default",
151			bp: `
152			my_module_type {
153				name: "foo",
154				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
155					"a": ["a.cpp"],
156					"b": ["b.cpp"],
157					default: ["c.cpp"],
158				}),
159			}
160			`,
161			provider: selectsTestProvider{
162				my_string_list: &[]string{"a.cpp"},
163			},
164			vendorVars: map[string]map[string]string{
165				"my_namespace": {
166					"my_variable": "a",
167				},
168			},
169		},
170		{
171			name: "String list append",
172			bp: `
173			my_module_type {
174				name: "foo",
175				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
176					"a": ["a.cpp"],
177					"b": ["b.cpp"],
178					default: ["c.cpp"],
179				}) + select(soong_config_variable("my_namespace", "my_variable_2"), {
180					"a2": ["a2.cpp"],
181					"b2": ["b2.cpp"],
182					default: ["c2.cpp"],
183				}),
184			}
185			`,
186			provider: selectsTestProvider{
187				my_string_list: &[]string{"a.cpp", "c2.cpp"},
188			},
189			vendorVars: map[string]map[string]string{
190				"my_namespace": {
191					"my_variable": "a",
192				},
193			},
194		},
195		{
196			name: "String list prepend literal",
197			bp: `
198			my_module_type {
199				name: "foo",
200				my_string_list: ["literal.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), {
201					"a2": ["a2.cpp"],
202					"b2": ["b2.cpp"],
203					default: ["c2.cpp"],
204				}),
205			}
206			`,
207			provider: selectsTestProvider{
208				my_string_list: &[]string{"literal.cpp", "c2.cpp"},
209			},
210		},
211		{
212			name: "String list append literal",
213			bp: `
214			my_module_type {
215				name: "foo",
216				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
217					"a2": ["a2.cpp"],
218					"b2": ["b2.cpp"],
219					default: ["c2.cpp"],
220				}) + ["literal.cpp"],
221			}
222			`,
223			provider: selectsTestProvider{
224				my_string_list: &[]string{"c2.cpp", "literal.cpp"},
225			},
226		},
227		{
228			name: "true + false = true",
229			bp: `
230			my_module_type {
231				name: "foo",
232				my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
233					"a": true,
234					"b": false,
235					default: true,
236				}) + false,
237			}
238			`,
239			provider: selectsTestProvider{
240				my_bool: proptools.BoolPtr(true),
241			},
242		},
243		{
244			name: "false + false = false",
245			bp: `
246			my_module_type {
247				name: "foo",
248				my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
249					"a": true,
250					"b": false,
251					default: true,
252				}) + false,
253			}
254			`,
255			vendorVars: map[string]map[string]string{
256				"my_namespace": {
257					"my_variable": "b",
258				},
259			},
260			provider: selectsTestProvider{
261				my_bool: proptools.BoolPtr(false),
262			},
263		},
264		{
265			name: "Append string",
266			bp: `
267			my_module_type {
268				name: "foo",
269				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
270					"a": "a",
271					"b": "b",
272					default: "c",
273				}) + ".cpp",
274			}
275			`,
276			provider: selectsTestProvider{
277				my_string: proptools.StringPtr("c.cpp"),
278			},
279		},
280		{
281			name: "Select on arch",
282			bp: `
283			my_module_type {
284				name: "foo",
285				my_string: select(arch(), {
286					"x86": "my_x86",
287					"x86_64": "my_x86_64",
288					"arm": "my_arm",
289					"arm64": "my_arm64",
290					default: "my_default",
291				}),
292			}
293			`,
294			provider: selectsTestProvider{
295				my_string: proptools.StringPtr("my_arm64"),
296			},
297		},
298		{
299			name: "Select on os",
300			bp: `
301			my_module_type {
302				name: "foo",
303				my_string: select(os(), {
304					"android": "my_android",
305					"linux": "my_linux",
306					default: "my_default",
307				}),
308			}
309			`,
310			provider: selectsTestProvider{
311				my_string: proptools.StringPtr("my_android"),
312			},
313		},
314		{
315			name: "Unset value",
316			bp: `
317			my_module_type {
318				name: "foo",
319				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
320					"a": unset,
321					"b": "b",
322					default: "c",
323				})
324			}
325			`,
326			vendorVars: map[string]map[string]string{
327				"my_namespace": {
328					"my_variable": "a",
329				},
330			},
331			provider: selectsTestProvider{},
332		},
333		{
334			name: "Unset value on different branch",
335			bp: `
336			my_module_type {
337				name: "foo",
338				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
339					"a": unset,
340					"b": "b",
341					default: "c",
342				})
343			}
344			`,
345			provider: selectsTestProvider{
346				my_string: proptools.StringPtr("c"),
347			},
348		},
349		{
350			name: "unset + unset = unset",
351			bp: `
352			my_module_type {
353				name: "foo",
354				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
355					"foo": "bar",
356					default: unset,
357				}) + select(soong_config_variable("my_namespace", "my_variable2"), {
358					"baz": "qux",
359					default: unset,
360				})
361			}
362			`,
363			provider: selectsTestProvider{},
364		},
365		{
366			name: "unset + string = string",
367			bp: `
368			my_module_type {
369				name: "foo",
370				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
371					"foo": "bar",
372					default: unset,
373				}) + select(soong_config_variable("my_namespace", "my_variable2"), {
374					default: "a",
375				})
376			}
377			`,
378			provider: selectsTestProvider{
379				my_string: proptools.StringPtr("a"),
380			},
381		},
382		{
383			name: "unset + bool = bool",
384			bp: `
385			my_module_type {
386				name: "foo",
387				my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
388					"a": true,
389					default: unset,
390				}) + select(soong_config_variable("my_namespace", "my_variable2"), {
391					default: true,
392				})
393			}
394			`,
395			provider: selectsTestProvider{
396				my_bool: proptools.BoolPtr(true),
397			},
398		},
399		{
400			name: "defaults with lists are appended",
401			bp: `
402			my_module_type {
403				name: "foo",
404				defaults: ["bar"],
405				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
406					"a": ["a1"],
407					default: ["b1"],
408				}),
409			}
410			my_defaults {
411				name: "bar",
412				my_string_list: select(soong_config_variable("my_namespace", "my_variable2"), {
413					"a": ["a2"],
414					default: ["b2"],
415				}),
416			}
417			`,
418			provider: selectsTestProvider{
419				my_string_list: &[]string{"b2", "b1"},
420			},
421		},
422		{
423			name: "defaults applied to multiple modules",
424			bp: `
425			my_module_type {
426				name: "foo2",
427				defaults: ["bar"],
428				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
429					"a": ["a1"],
430					default: ["b1"],
431				}),
432			}
433			my_module_type {
434				name: "foo",
435				defaults: ["bar"],
436				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
437					"a": ["a1"],
438					default: ["b1"],
439				}),
440			}
441			my_defaults {
442				name: "bar",
443				my_string_list: select(soong_config_variable("my_namespace", "my_variable2"), {
444					"a": ["a2"],
445					default: ["b2"],
446				}),
447			}
448			`,
449			providers: map[string]selectsTestProvider{
450				"foo": {
451					my_string_list: &[]string{"b2", "b1"},
452				},
453				"foo2": {
454					my_string_list: &[]string{"b2", "b1"},
455				},
456			},
457		},
458		{
459			name: "Replacing string list",
460			bp: `
461			my_module_type {
462				name: "foo",
463				defaults: ["bar"],
464				replacing_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
465					"a": ["a1"],
466					default: ["b1"],
467				}),
468			}
469			my_defaults {
470				name: "bar",
471				replacing_string_list: select(soong_config_variable("my_namespace", "my_variable2"), {
472					"a": ["a2"],
473					default: ["b2"],
474				}),
475			}
476			`,
477			provider: selectsTestProvider{
478				replacing_string_list: &[]string{"b1"},
479			},
480		},
481		{
482			name: "Multi-condition string 1",
483			bp: `
484			my_module_type {
485				name: "foo",
486				my_string: select((
487					soong_config_variable("my_namespace", "my_variable"),
488					soong_config_variable("my_namespace", "my_variable2"),
489				), {
490					("a", "b"): "a+b",
491					("a", default): "a+default",
492					(default, default): "default",
493				}),
494			}
495			`,
496			vendorVars: map[string]map[string]string{
497				"my_namespace": {
498					"my_variable":  "a",
499					"my_variable2": "b",
500				},
501			},
502			provider: selectsTestProvider{
503				my_string: proptools.StringPtr("a+b"),
504			},
505		},
506		{
507			name: "Multi-condition string 2",
508			bp: `
509			my_module_type {
510				name: "foo",
511				my_string: select((
512					soong_config_variable("my_namespace", "my_variable"),
513					soong_config_variable("my_namespace", "my_variable2"),
514				), {
515					("a", "b"): "a+b",
516					("a", default): "a+default",
517					(default, default): "default",
518				}),
519			}
520			`,
521			vendorVars: map[string]map[string]string{
522				"my_namespace": {
523					"my_variable":  "a",
524					"my_variable2": "c",
525				},
526			},
527			provider: selectsTestProvider{
528				my_string: proptools.StringPtr("a+default"),
529			},
530		},
531		{
532			name: "Multi-condition string 3",
533			bp: `
534			my_module_type {
535				name: "foo",
536				my_string: select((
537					soong_config_variable("my_namespace", "my_variable"),
538					soong_config_variable("my_namespace", "my_variable2"),
539				), {
540					("a", "b"): "a+b",
541					("a", default): "a+default",
542					(default, default): "default",
543				}),
544			}
545			`,
546			vendorVars: map[string]map[string]string{
547				"my_namespace": {
548					"my_variable":  "c",
549					"my_variable2": "b",
550				},
551			},
552			provider: selectsTestProvider{
553				my_string: proptools.StringPtr("default"),
554			},
555		},
556		{
557			name: "Unhandled string value",
558			bp: `
559			my_module_type {
560				name: "foo",
561				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
562					"foo": "a",
563					"bar": "b",
564				}),
565			}
566			`,
567			vendorVars: map[string]map[string]string{
568				"my_namespace": {
569					"my_variable": "baz",
570				},
571			},
572			expectedError: `my_string: soong_config_variable\("my_namespace", "my_variable"\) had value "baz", which was not handled by the select statement`,
573		},
574		{
575			name: "Select on boolean",
576			bp: `
577			my_module_type {
578				name: "foo",
579				my_string: select(boolean_var_for_testing(), {
580					true: "t",
581					false: "f",
582				}),
583			}
584			`,
585			vendorVars: map[string]map[string]string{
586				"boolean_var": {
587					"for_testing": "true",
588				},
589			},
590			provider: selectsTestProvider{
591				my_string: proptools.StringPtr("t"),
592			},
593		},
594		{
595			name: "Select on boolean soong config variable",
596			bp: `
597			my_module_type {
598				name: "foo",
599				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
600					true: "t",
601					false: "f",
602				}),
603			}
604			`,
605			vendorVars: map[string]map[string]string{
606				"my_namespace": {
607					"my_variable": "true",
608				},
609			},
610			vendorVarTypes: map[string]map[string]string{
611				"my_namespace": {
612					"my_variable": "bool",
613				},
614			},
615			provider: selectsTestProvider{
616				my_string: proptools.StringPtr("t"),
617			},
618		},
619		{
620			name: "Select on boolean false",
621			bp: `
622			my_module_type {
623				name: "foo",
624				my_string: select(boolean_var_for_testing(), {
625					true: "t",
626					false: "f",
627				}),
628			}
629			`,
630			vendorVars: map[string]map[string]string{
631				"boolean_var": {
632					"for_testing": "false",
633				},
634			},
635			provider: selectsTestProvider{
636				my_string: proptools.StringPtr("f"),
637			},
638		},
639		{
640			name: "Select on boolean undefined",
641			bp: `
642			my_module_type {
643				name: "foo",
644				my_string: select(boolean_var_for_testing(), {
645					true: "t",
646					false: "f",
647				}),
648			}
649			`,
650			expectedError: `my_string: boolean_var_for_testing\(\) had value undefined, which was not handled by the select statement`,
651		},
652		{
653			name: "Select on boolean undefined with default",
654			bp: `
655			my_module_type {
656				name: "foo",
657				my_string: select(boolean_var_for_testing(), {
658					true: "t",
659					false: "f",
660					default: "default",
661				}),
662			}
663			`,
664			provider: selectsTestProvider{
665				my_string: proptools.StringPtr("default"),
666			},
667		},
668		{
669			name: "Mismatched condition types",
670			bp: `
671			my_module_type {
672				name: "foo",
673				my_string: select(boolean_var_for_testing(), {
674					"true": "t",
675					"false": "f",
676					default: "default",
677				}),
678			}
679			`,
680			vendorVars: map[string]map[string]string{
681				"boolean_var": {
682					"for_testing": "false",
683				},
684			},
685			expectedError: "Expected all branches of a select on condition boolean_var_for_testing\\(\\) to have type bool, found string",
686		},
687		{
688			name: "Assigning select to nonconfigurable bool",
689			bp: `
690			my_module_type {
691				name: "foo",
692				my_nonconfigurable_bool: select(arch(), {
693					"x86_64": true,
694					default: false,
695				}),
696			}
697			`,
698			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_bool"`,
699		},
700		{
701			name: "Assigning select to nonconfigurable string",
702			bp: `
703			my_module_type {
704				name: "foo",
705				my_nonconfigurable_string: select(arch(), {
706					"x86_64": "x86!",
707					default: "unknown!",
708				}),
709			}
710			`,
711			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string"`,
712		},
713		{
714			name: "Assigning appended selects to nonconfigurable string",
715			bp: `
716			my_module_type {
717				name: "foo",
718				my_nonconfigurable_string: select(arch(), {
719					"x86_64": "x86!",
720					default: "unknown!",
721				}) + select(os(), {
722					"darwin": "_darwin!",
723					default: "unknown!",
724				}),
725			}
726			`,
727			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string"`,
728		},
729		{
730			name: "Assigning select to nonconfigurable string list",
731			bp: `
732			my_module_type {
733				name: "foo",
734				my_nonconfigurable_string_list: select(arch(), {
735					"x86_64": ["foo", "bar"],
736					default: ["baz", "qux"],
737				}),
738			}
739			`,
740			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string_list"`,
741		},
742		{
743			name: "Select in variable",
744			bp: `
745			my_second_variable = ["after.cpp"]
746			my_variable = select(soong_config_variable("my_namespace", "my_variable"), {
747				"a": ["a.cpp"],
748				"b": ["b.cpp"],
749				default: ["c.cpp"],
750			}) + my_second_variable
751			my_module_type {
752				name: "foo",
753				my_string_list: ["before.cpp"] + my_variable,
754			}
755			`,
756			provider: selectsTestProvider{
757				my_string_list: &[]string{"before.cpp", "a.cpp", "after.cpp"},
758			},
759			vendorVars: map[string]map[string]string{
760				"my_namespace": {
761					"my_variable": "a",
762				},
763			},
764		},
765		{
766			name: "Soong config value variable on configurable property",
767			bp: `
768			soong_config_module_type {
769				name: "soong_config_my_module_type",
770				module_type: "my_module_type",
771				config_namespace: "my_namespace",
772				value_variables: ["my_variable"],
773				properties: ["my_string", "my_string_list"],
774			}
775
776			soong_config_my_module_type {
777				name: "foo",
778				my_string_list: ["before.cpp"],
779				soong_config_variables: {
780					my_variable: {
781						my_string_list: ["after_%s.cpp"],
782						my_string: "%s.cpp",
783					},
784				},
785			}
786			`,
787			provider: selectsTestProvider{
788				my_string:      proptools.StringPtr("foo.cpp"),
789				my_string_list: &[]string{"before.cpp", "after_foo.cpp"},
790			},
791			vendorVars: map[string]map[string]string{
792				"my_namespace": {
793					"my_variable": "foo",
794				},
795			},
796		},
797		{
798			name: "Property appending with variable",
799			bp: `
800			my_variable = ["b.cpp"]
801			my_module_type {
802				name: "foo",
803				my_string_list: ["a.cpp"] + my_variable + select(soong_config_variable("my_namespace", "my_variable"), {
804					"a": ["a.cpp"],
805					"b": ["b.cpp"],
806					default: ["c.cpp"],
807				}),
808			}
809			`,
810			provider: selectsTestProvider{
811				my_string_list: &[]string{"a.cpp", "b.cpp", "c.cpp"},
812			},
813		},
814		{
815			name: "Test AppendSimpleValue",
816			bp: `
817			my_module_type {
818				name: "foo",
819				my_string_list: ["a.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), {
820					"a": ["a.cpp"],
821					"b": ["b.cpp"],
822					default: ["c.cpp"],
823				}),
824			}
825			`,
826			vendorVars: map[string]map[string]string{
827				"selects_test": {
828					"append_to_string_list": "foo.cpp",
829				},
830			},
831			provider: selectsTestProvider{
832				my_string_list: &[]string{"a.cpp", "c.cpp", "foo.cpp"},
833			},
834		},
835		{
836			name: "Arch variant bool",
837			bp: `
838			my_variable = ["b.cpp"]
839			my_module_type {
840				name: "foo",
841				arch_variant_configurable_bool: false,
842				target: {
843					bionic_arm64: {
844						enabled: true,
845					},
846				},
847			}
848			`,
849			provider: selectsTestProvider{
850				arch_variant_configurable_bool: proptools.BoolPtr(false),
851			},
852		},
853		{
854			name: "Simple string binding",
855			bp: `
856			my_module_type {
857				name: "foo",
858				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
859					any @ my_binding: "hello " + my_binding,
860					default: "goodbye",
861				})
862			}
863			`,
864			vendorVars: map[string]map[string]string{
865				"my_namespace": {
866					"my_variable": "world!",
867				},
868			},
869			provider: selectsTestProvider{
870				my_string: proptools.StringPtr("hello world!"),
871			},
872		},
873		{
874			name: "Any branch with binding not taken",
875			bp: `
876			my_module_type {
877				name: "foo",
878				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
879					any @ my_binding: "hello " + my_binding,
880					default: "goodbye",
881				})
882			}
883			`,
884			provider: selectsTestProvider{
885				my_string: proptools.StringPtr("goodbye"),
886			},
887		},
888		{
889			name: "Any branch without binding",
890			bp: `
891			my_module_type {
892				name: "foo",
893				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
894					any: "hello",
895					default: "goodbye",
896				})
897			}
898			`,
899			vendorVars: map[string]map[string]string{
900				"my_namespace": {
901					"my_variable": "world!",
902				},
903			},
904			provider: selectsTestProvider{
905				my_string: proptools.StringPtr("hello"),
906			},
907		},
908		{
909			name: "Binding conflicts with file-level variable",
910			bp: `
911			my_binding = "asdf"
912			my_module_type {
913				name: "foo",
914				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
915					any @ my_binding: "hello",
916					default: "goodbye",
917				})
918			}
919			`,
920			vendorVars: map[string]map[string]string{
921				"my_namespace": {
922					"my_variable": "world!",
923				},
924			},
925			expectedError: "variable already set in inherited scope, previous assignment",
926		},
927		{
928			name: "Binding in combination with file-level variable",
929			bp: `
930			my_var = " there "
931			my_module_type {
932				name: "foo",
933				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
934					any @ my_binding: "hello" + my_var + my_binding,
935					default: "goodbye",
936				})
937			}
938			`,
939			vendorVars: map[string]map[string]string{
940				"my_namespace": {
941					"my_variable": "world!",
942				},
943			},
944			provider: selectsTestProvider{
945				my_string: proptools.StringPtr("hello there world!"),
946			},
947		},
948		{
949			name: "Bindings in subdirectory inherits variable",
950			fs: map[string][]byte{
951				"Android.bp": []byte(`
952my_var = "abcd"
953`),
954				"directoryB/Android.bp": []byte(`
955my_module_type {
956	name: "foo",
957	my_string: select(soong_config_variable("my_namespace", "variable_a"), {
958		any @ my_binding: my_var + my_binding,
959		default: "",
960	}),
961}
962`),
963			},
964			vendorVars: map[string]map[string]string{
965				"my_namespace": {
966					"variable_a": "e",
967				},
968			},
969			provider: selectsTestProvider{
970				my_string: proptools.StringPtr("abcde"),
971			},
972		},
973		{
974			name: "Cannot modify variable after referenced by select",
975			bp: `
976my_var = "foo"
977my_module_type {
978	name: "foo",
979	my_string: select(soong_config_variable("my_namespace", "variable_a"), {
980		"a": my_var,
981		default: "",
982	}),
983}
984my_var += "bar"
985`,
986			vendorVars: map[string]map[string]string{
987				"my_namespace": {
988					"variable_a": "b", // notably not the value that causes my_var to be referenced
989				},
990			},
991			expectedError: `modified variable "my_var" with \+= after referencing`,
992		},
993		{
994			name: "Cannot shadow variable with binding",
995			bp: `
996my_var = "foo"
997my_module_type {
998	name: "foo",
999	my_string: select(soong_config_variable("my_namespace", "variable_a"), {
1000		any @ my_var: my_var,
1001		default: "",
1002	}),
1003}
1004`,
1005			vendorVars: map[string]map[string]string{
1006				"my_namespace": {
1007					"variable_a": "a",
1008				},
1009			},
1010			expectedError: `variable already set in inherited scope, previous assignment:`,
1011		},
1012		{
1013			name: "Basic string list postprocessor",
1014			bp: `
1015my_defaults {
1016	name: "defaults_a",
1017	my_string_list: ["a", "b", "c"],
1018	string_list_postprocessor_add_to_elements: "1",
1019}
1020my_defaults {
1021	name: "defaults_b",
1022	my_string_list: ["d", "e", "f"],
1023	string_list_postprocessor_add_to_elements: "2",
1024}
1025my_module_type {
1026	name: "foo",
1027	defaults: ["defaults_a", "defaults_b"],
1028}
1029`,
1030			provider: selectsTestProvider{
1031				my_string_list: &[]string{"d2", "e2", "f2", "a1", "b1", "c1"},
1032			},
1033		},
1034		{
1035			name: "string list variables",
1036			bp: `
1037my_module_type {
1038	name: "foo",
1039	my_string_list: ["a"] + select(soong_config_variable("my_namespace", "my_var"), {
1040		any @ my_var: my_var,
1041		default: [],
1042	}),
1043}
1044`,
1045			vendorVars: map[string]map[string]string{
1046				"my_namespace": {
1047					"my_var": "b c",
1048				},
1049			},
1050			vendorVarTypes: map[string]map[string]string{
1051				"my_namespace": {
1052					"my_var": "string_list",
1053				},
1054			},
1055			provider: selectsTestProvider{
1056				my_string_list: &[]string{"a", "b", "c"},
1057			},
1058		},
1059		{
1060			name: "string list variables don't match string matchers",
1061			bp: `
1062my_module_type {
1063	name: "foo",
1064	my_string_list: ["a"] + select(soong_config_variable("my_namespace", "my_var"), {
1065		"foo": ["b"],
1066		default: [],
1067	}),
1068}
1069`,
1070			vendorVars: map[string]map[string]string{
1071				"my_namespace": {
1072					"my_var": "b c",
1073				},
1074			},
1075			vendorVarTypes: map[string]map[string]string{
1076				"my_namespace": {
1077					"my_var": "string_list",
1078				},
1079			},
1080			expectedError: `Expected all branches of a select on condition soong_config_variable\("my_namespace", "my_var"\) to have type string_list, found string`,
1081		},
1082	}
1083
1084	for _, tc := range testCases {
1085		t.Run(tc.name, func(t *testing.T) {
1086			fs := tc.fs
1087			if fs == nil {
1088				fs = make(MockFS)
1089			}
1090			if tc.bp != "" {
1091				fs["Android.bp"] = []byte(tc.bp)
1092			}
1093			fixtures := GroupFixturePreparers(
1094				PrepareForTestWithDefaults,
1095				PrepareForTestWithArchMutator,
1096				PrepareForTestWithSoongConfigModuleBuildComponents,
1097				FixtureRegisterWithContext(func(ctx RegistrationContext) {
1098					ctx.RegisterModuleType("my_module_type", newSelectsMockModule)
1099					ctx.RegisterModuleType("my_defaults", newSelectsMockModuleDefaults)
1100				}),
1101				FixtureModifyProductVariables(func(variables FixtureProductVariables) {
1102					variables.VendorVars = tc.vendorVars
1103					variables.VendorVarTypes = tc.vendorVarTypes
1104				}),
1105				FixtureMergeMockFs(fs),
1106			)
1107			if tc.expectedError != "" {
1108				fixtures = fixtures.ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(tc.expectedError))
1109			}
1110			result := fixtures.RunTest(t)
1111
1112			if tc.expectedError == "" {
1113				if len(tc.providers) == 0 {
1114					tc.providers = map[string]selectsTestProvider{
1115						"foo": tc.provider,
1116					}
1117				}
1118
1119				for moduleName := range tc.providers {
1120					expected := tc.providers[moduleName]
1121					m := result.ModuleForTests(moduleName, "android_arm64_armv8-a")
1122					p, _ := OtherModuleProvider(result.testContext.OtherModuleProviderAdaptor(), m.Module(), selectsTestProviderKey)
1123					if !reflect.DeepEqual(p, expected) {
1124						t.Errorf("Expected:\n  %q\ngot:\n  %q", expected.String(), p.String())
1125					}
1126				}
1127			}
1128		})
1129	}
1130}
1131
1132type selectsTestProvider struct {
1133	my_bool                        *bool
1134	my_string                      *string
1135	my_string_list                 *[]string
1136	my_paths                       *[]string
1137	replacing_string_list          *[]string
1138	arch_variant_configurable_bool *bool
1139	my_nonconfigurable_bool        *bool
1140	my_nonconfigurable_string      *string
1141	my_nonconfigurable_string_list []string
1142}
1143
1144func (p *selectsTestProvider) String() string {
1145	myBoolStr := "nil"
1146	if p.my_bool != nil {
1147		myBoolStr = fmt.Sprintf("%t", *p.my_bool)
1148	}
1149	myStringStr := "nil"
1150	if p.my_string != nil {
1151		myStringStr = *p.my_string
1152	}
1153	myNonconfigurableStringStr := "nil"
1154	if p.my_nonconfigurable_string != nil {
1155		myNonconfigurableStringStr = *p.my_nonconfigurable_string
1156	}
1157	return fmt.Sprintf(`selectsTestProvider {
1158	my_bool: %v,
1159	my_string: %s,
1160    my_string_list: %s,
1161    my_paths: %s,
1162	replacing_string_list %s,
1163	arch_variant_configurable_bool %v
1164	my_nonconfigurable_bool: %v,
1165	my_nonconfigurable_string: %s,
1166	my_nonconfigurable_string_list: %s,
1167}`,
1168		myBoolStr,
1169		myStringStr,
1170		p.my_string_list,
1171		p.my_paths,
1172		p.replacing_string_list,
1173		p.arch_variant_configurable_bool,
1174		p.my_nonconfigurable_bool,
1175		myNonconfigurableStringStr,
1176		p.my_nonconfigurable_string_list,
1177	)
1178}
1179
1180var selectsTestProviderKey = blueprint.NewProvider[selectsTestProvider]()
1181
1182type selectsMockModuleProperties struct {
1183	My_bool                        proptools.Configurable[bool]
1184	My_string                      proptools.Configurable[string]
1185	My_string_list                 proptools.Configurable[[]string]
1186	My_paths                       proptools.Configurable[[]string] `android:"path"`
1187	Replacing_string_list          proptools.Configurable[[]string] `android:"replace_instead_of_append,arch_variant"`
1188	Arch_variant_configurable_bool proptools.Configurable[bool]     `android:"replace_instead_of_append,arch_variant"`
1189	My_nonconfigurable_bool        *bool
1190	My_nonconfigurable_string      *string
1191	My_nonconfigurable_string_list []string
1192}
1193
1194type selectsMockModule struct {
1195	ModuleBase
1196	DefaultableModuleBase
1197	properties selectsMockModuleProperties
1198}
1199
1200func optionalToPtr[T any](o proptools.ConfigurableOptional[T]) *T {
1201	if o.IsEmpty() {
1202		return nil
1203	}
1204	x := o.Get()
1205	return &x
1206}
1207
1208func (p *selectsMockModule) GenerateAndroidBuildActions(ctx ModuleContext) {
1209	toAppend := ctx.Config().VendorConfig("selects_test").String("append_to_string_list")
1210	if toAppend != "" {
1211		p.properties.My_string_list.AppendSimpleValue([]string{toAppend})
1212	}
1213	SetProvider(ctx, selectsTestProviderKey, selectsTestProvider{
1214		my_bool:                        optionalToPtr(p.properties.My_bool.Get(ctx)),
1215		my_string:                      optionalToPtr(p.properties.My_string.Get(ctx)),
1216		my_string_list:                 optionalToPtr(p.properties.My_string_list.Get(ctx)),
1217		my_paths:                       optionalToPtr(p.properties.My_paths.Get(ctx)),
1218		replacing_string_list:          optionalToPtr(p.properties.Replacing_string_list.Get(ctx)),
1219		arch_variant_configurable_bool: optionalToPtr(p.properties.Arch_variant_configurable_bool.Get(ctx)),
1220		my_nonconfigurable_bool:        p.properties.My_nonconfigurable_bool,
1221		my_nonconfigurable_string:      p.properties.My_nonconfigurable_string,
1222		my_nonconfigurable_string_list: p.properties.My_nonconfigurable_string_list,
1223	})
1224}
1225
1226func newSelectsMockModule() Module {
1227	m := &selectsMockModule{}
1228	m.AddProperties(&m.properties)
1229	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibFirst)
1230	InitDefaultableModule(m)
1231	return m
1232}
1233
1234type selectsMockDefaultsProperties struct {
1235	String_list_postprocessor_add_to_elements string
1236}
1237
1238type selectsMockModuleDefaults struct {
1239	ModuleBase
1240	DefaultsModuleBase
1241	myProperties       selectsMockModuleProperties
1242	defaultsProperties selectsMockDefaultsProperties
1243}
1244
1245func (d *selectsMockModuleDefaults) GenerateAndroidBuildActions(ctx ModuleContext) {
1246}
1247
1248func newSelectsMockModuleDefaults() Module {
1249	module := &selectsMockModuleDefaults{}
1250
1251	module.AddProperties(
1252		&module.myProperties,
1253		&module.defaultsProperties,
1254	)
1255
1256	InitDefaultsModule(module)
1257
1258	AddLoadHook(module, func(lhc LoadHookContext) {
1259		if module.defaultsProperties.String_list_postprocessor_add_to_elements != "" {
1260			module.myProperties.My_string_list.AddPostProcessor(func(x []string) []string {
1261				for i := range x {
1262					x[i] = x[i] + module.defaultsProperties.String_list_postprocessor_add_to_elements
1263				}
1264				return x
1265			})
1266		}
1267	})
1268
1269	return module
1270}
1271