| // Copyright 2024 Google Inc. All rights reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | package android | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"reflect" | 
 | 	"testing" | 
 |  | 
 | 	"github.com/google/blueprint" | 
 | 	"github.com/google/blueprint/proptools" | 
 | ) | 
 |  | 
 | func TestSelects(t *testing.T) { | 
 | 	testCases := []struct { | 
 | 		name           string | 
 | 		bp             string | 
 | 		fs             MockFS | 
 | 		provider       selectsTestProvider | 
 | 		providers      map[string]selectsTestProvider | 
 | 		vendorVars     map[string]map[string]string | 
 | 		vendorVarTypes map[string]map[string]string | 
 | 		expectedError  string | 
 | 	}{ | 
 | 		{ | 
 | 			name: "basic string list", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a.cpp"], | 
 | 					"b": ["b.cpp"], | 
 | 					default: ["c.cpp"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"c.cpp"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "basic string", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": "a.cpp", | 
 | 					"b": "b.cpp", | 
 | 					default: "c.cpp", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("c.cpp"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "basic bool", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_bool: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": true, | 
 | 					"b": false, | 
 | 					default: true, | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_bool: proptools.BoolPtr(true), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "basic paths", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_paths: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["foo.txt"], | 
 | 					"b": ["bar.txt"], | 
 | 					default: ["baz.txt"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_paths: &[]string{"baz.txt"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Expression in select", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": "foo" + "bar", | 
 | 					default: "baz", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("foobar"), | 
 | 			}, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "a", | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "paths with module references", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_paths: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": [":a"], | 
 | 					"b": [":b"], | 
 | 					default: [":c"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			expectedError: `"foo" depends on undefined module "c"`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select type doesn't match property type", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": false, | 
 | 					"b": true, | 
 | 					default: true, | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			expectedError: `can't assign bool value to string property`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "String list non-default", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a.cpp"], | 
 | 					"b": ["b.cpp"], | 
 | 					default: ["c.cpp"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"a.cpp"}, | 
 | 			}, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "a", | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "String list append", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a.cpp"], | 
 | 					"b": ["b.cpp"], | 
 | 					default: ["c.cpp"], | 
 | 				}) + select(soong_config_variable("my_namespace", "my_variable_2"), { | 
 | 					"a2": ["a2.cpp"], | 
 | 					"b2": ["b2.cpp"], | 
 | 					default: ["c2.cpp"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"a.cpp", "c2.cpp"}, | 
 | 			}, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "a", | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "String list prepend literal", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: ["literal.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a2": ["a2.cpp"], | 
 | 					"b2": ["b2.cpp"], | 
 | 					default: ["c2.cpp"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"literal.cpp", "c2.cpp"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "String list append literal", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a2": ["a2.cpp"], | 
 | 					"b2": ["b2.cpp"], | 
 | 					default: ["c2.cpp"], | 
 | 				}) + ["literal.cpp"], | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"c2.cpp", "literal.cpp"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "true + false = true", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_bool: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": true, | 
 | 					"b": false, | 
 | 					default: true, | 
 | 				}) + false, | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_bool: proptools.BoolPtr(true), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "false + false = false", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_bool: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": true, | 
 | 					"b": false, | 
 | 					default: true, | 
 | 				}) + false, | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "b", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_bool: proptools.BoolPtr(false), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Append string", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": "a", | 
 | 					"b": "b", | 
 | 					default: "c", | 
 | 				}) + ".cpp", | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("c.cpp"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select on arch", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(arch(), { | 
 | 					"x86": "my_x86", | 
 | 					"x86_64": "my_x86_64", | 
 | 					"arm": "my_arm", | 
 | 					"arm64": "my_arm64", | 
 | 					default: "my_default", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("my_arm64"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select on os", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(os(), { | 
 | 					"android": "my_android", | 
 | 					"linux": "my_linux", | 
 | 					default: "my_default", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("my_android"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Unset value", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": unset, | 
 | 					"b": "b", | 
 | 					default: "c", | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "a", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Unset value on different branch", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": unset, | 
 | 					"b": "b", | 
 | 					default: "c", | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("c"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "unset + unset = unset", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"foo": "bar", | 
 | 					default: unset, | 
 | 				}) + select(soong_config_variable("my_namespace", "my_variable2"), { | 
 | 					"baz": "qux", | 
 | 					default: unset, | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "unset + string = string", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"foo": "bar", | 
 | 					default: unset, | 
 | 				}) + select(soong_config_variable("my_namespace", "my_variable2"), { | 
 | 					default: "a", | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("a"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "unset + bool = bool", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_bool: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": true, | 
 | 					default: unset, | 
 | 				}) + select(soong_config_variable("my_namespace", "my_variable2"), { | 
 | 					default: true, | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_bool: proptools.BoolPtr(true), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "defaults with lists are appended", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				defaults: ["bar"], | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a1"], | 
 | 					default: ["b1"], | 
 | 				}), | 
 | 			} | 
 | 			my_defaults { | 
 | 				name: "bar", | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable2"), { | 
 | 					"a": ["a2"], | 
 | 					default: ["b2"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"b2", "b1"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "defaults applied to multiple modules", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo2", | 
 | 				defaults: ["bar"], | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a1"], | 
 | 					default: ["b1"], | 
 | 				}), | 
 | 			} | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				defaults: ["bar"], | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a1"], | 
 | 					default: ["b1"], | 
 | 				}), | 
 | 			} | 
 | 			my_defaults { | 
 | 				name: "bar", | 
 | 				my_string_list: select(soong_config_variable("my_namespace", "my_variable2"), { | 
 | 					"a": ["a2"], | 
 | 					default: ["b2"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			providers: map[string]selectsTestProvider{ | 
 | 				"foo": { | 
 | 					my_string_list: &[]string{"b2", "b1"}, | 
 | 				}, | 
 | 				"foo2": { | 
 | 					my_string_list: &[]string{"b2", "b1"}, | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Replacing string list", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				defaults: ["bar"], | 
 | 				replacing_string_list: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a1"], | 
 | 					default: ["b1"], | 
 | 				}), | 
 | 			} | 
 | 			my_defaults { | 
 | 				name: "bar", | 
 | 				replacing_string_list: select(soong_config_variable("my_namespace", "my_variable2"), { | 
 | 					"a": ["a2"], | 
 | 					default: ["b2"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				replacing_string_list: &[]string{"b1"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Multi-condition string 1", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(( | 
 | 					soong_config_variable("my_namespace", "my_variable"), | 
 | 					soong_config_variable("my_namespace", "my_variable2"), | 
 | 				), { | 
 | 					("a", "b"): "a+b", | 
 | 					("a", default): "a+default", | 
 | 					(default, default): "default", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable":  "a", | 
 | 					"my_variable2": "b", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("a+b"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Multi-condition string 2", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(( | 
 | 					soong_config_variable("my_namespace", "my_variable"), | 
 | 					soong_config_variable("my_namespace", "my_variable2"), | 
 | 				), { | 
 | 					("a", "b"): "a+b", | 
 | 					("a", default): "a+default", | 
 | 					(default, default): "default", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable":  "a", | 
 | 					"my_variable2": "c", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("a+default"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Multi-condition string 3", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(( | 
 | 					soong_config_variable("my_namespace", "my_variable"), | 
 | 					soong_config_variable("my_namespace", "my_variable2"), | 
 | 				), { | 
 | 					("a", "b"): "a+b", | 
 | 					("a", default): "a+default", | 
 | 					(default, default): "default", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable":  "c", | 
 | 					"my_variable2": "b", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("default"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Unhandled string value", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"foo": "a", | 
 | 					"bar": "b", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "baz", | 
 | 				}, | 
 | 			}, | 
 | 			expectedError: `my_string: soong_config_variable\("my_namespace", "my_variable"\) had value "baz", which was not handled by the select statement`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select on boolean", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(boolean_var_for_testing(), { | 
 | 					true: "t", | 
 | 					false: "f", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"boolean_var": { | 
 | 					"for_testing": "true", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("t"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select on boolean soong config variable", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					true: "t", | 
 | 					false: "f", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "true", | 
 | 				}, | 
 | 			}, | 
 | 			vendorVarTypes: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "bool", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("t"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select on boolean false", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(boolean_var_for_testing(), { | 
 | 					true: "t", | 
 | 					false: "f", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"boolean_var": { | 
 | 					"for_testing": "false", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("f"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select on boolean undefined", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(boolean_var_for_testing(), { | 
 | 					true: "t", | 
 | 					false: "f", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			expectedError: `my_string: boolean_var_for_testing\(\) had value undefined, which was not handled by the select statement`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select on boolean undefined with default", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(boolean_var_for_testing(), { | 
 | 					true: "t", | 
 | 					false: "f", | 
 | 					default: "default", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("default"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Mismatched condition types", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(boolean_var_for_testing(), { | 
 | 					"true": "t", | 
 | 					"false": "f", | 
 | 					default: "default", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"boolean_var": { | 
 | 					"for_testing": "false", | 
 | 				}, | 
 | 			}, | 
 | 			expectedError: "Expected all branches of a select on condition boolean_var_for_testing\\(\\) to have type bool, found string", | 
 | 		}, | 
 | 		{ | 
 | 			name: "Assigning select to nonconfigurable bool", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_nonconfigurable_bool: select(arch(), { | 
 | 					"x86_64": true, | 
 | 					default: false, | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_bool"`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Assigning select to nonconfigurable string", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_nonconfigurable_string: select(arch(), { | 
 | 					"x86_64": "x86!", | 
 | 					default: "unknown!", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string"`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Assigning appended selects to nonconfigurable string", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_nonconfigurable_string: select(arch(), { | 
 | 					"x86_64": "x86!", | 
 | 					default: "unknown!", | 
 | 				}) + select(os(), { | 
 | 					"darwin": "_darwin!", | 
 | 					default: "unknown!", | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string"`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Assigning select to nonconfigurable string list", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_nonconfigurable_string_list: select(arch(), { | 
 | 					"x86_64": ["foo", "bar"], | 
 | 					default: ["baz", "qux"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string_list"`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Select in variable", | 
 | 			bp: ` | 
 | 			my_second_variable = ["after.cpp"] | 
 | 			my_variable = select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 				"a": ["a.cpp"], | 
 | 				"b": ["b.cpp"], | 
 | 				default: ["c.cpp"], | 
 | 			}) + my_second_variable | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: ["before.cpp"] + my_variable, | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"before.cpp", "a.cpp", "after.cpp"}, | 
 | 			}, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "a", | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Soong config value variable on configurable property", | 
 | 			bp: ` | 
 | 			soong_config_module_type { | 
 | 				name: "soong_config_my_module_type", | 
 | 				module_type: "my_module_type", | 
 | 				config_namespace: "my_namespace", | 
 | 				value_variables: ["my_variable"], | 
 | 				properties: ["my_string", "my_string_list"], | 
 | 			} | 
 |  | 
 | 			soong_config_my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: ["before.cpp"], | 
 | 				soong_config_variables: { | 
 | 					my_variable: { | 
 | 						my_string_list: ["after_%s.cpp"], | 
 | 						my_string: "%s.cpp", | 
 | 					}, | 
 | 				}, | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string:      proptools.StringPtr("foo.cpp"), | 
 | 				my_string_list: &[]string{"before.cpp", "after_foo.cpp"}, | 
 | 			}, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "foo", | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Property appending with variable", | 
 | 			bp: ` | 
 | 			my_variable = ["b.cpp"] | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: ["a.cpp"] + my_variable + select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a.cpp"], | 
 | 					"b": ["b.cpp"], | 
 | 					default: ["c.cpp"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"a.cpp", "b.cpp", "c.cpp"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Test AppendSimpleValue", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string_list: ["a.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					"a": ["a.cpp"], | 
 | 					"b": ["b.cpp"], | 
 | 					default: ["c.cpp"], | 
 | 				}), | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"selects_test": { | 
 | 					"append_to_string_list": "foo.cpp", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"a.cpp", "c.cpp", "foo.cpp"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Arch variant bool", | 
 | 			bp: ` | 
 | 			my_variable = ["b.cpp"] | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				arch_variant_configurable_bool: false, | 
 | 				target: { | 
 | 					bionic_arm64: { | 
 | 						enabled: true, | 
 | 					}, | 
 | 				}, | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				arch_variant_configurable_bool: proptools.BoolPtr(false), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Simple string binding", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					any @ my_binding: "hello " + my_binding, | 
 | 					default: "goodbye", | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "world!", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("hello world!"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Any branch with binding not taken", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					any @ my_binding: "hello " + my_binding, | 
 | 					default: "goodbye", | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("goodbye"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Any branch without binding", | 
 | 			bp: ` | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					any: "hello", | 
 | 					default: "goodbye", | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "world!", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("hello"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Binding conflicts with file-level variable", | 
 | 			bp: ` | 
 | 			my_binding = "asdf" | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					any @ my_binding: "hello", | 
 | 					default: "goodbye", | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "world!", | 
 | 				}, | 
 | 			}, | 
 | 			expectedError: "variable already set in inherited scope, previous assignment", | 
 | 		}, | 
 | 		{ | 
 | 			name: "Binding in combination with file-level variable", | 
 | 			bp: ` | 
 | 			my_var = " there " | 
 | 			my_module_type { | 
 | 				name: "foo", | 
 | 				my_string: select(soong_config_variable("my_namespace", "my_variable"), { | 
 | 					any @ my_binding: "hello" + my_var + my_binding, | 
 | 					default: "goodbye", | 
 | 				}) | 
 | 			} | 
 | 			`, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"my_variable": "world!", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("hello there world!"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Bindings in subdirectory inherits variable", | 
 | 			fs: map[string][]byte{ | 
 | 				"Android.bp": []byte(` | 
 | my_var = "abcd" | 
 | `), | 
 | 				"directoryB/Android.bp": []byte(` | 
 | my_module_type { | 
 | 	name: "foo", | 
 | 	my_string: select(soong_config_variable("my_namespace", "variable_a"), { | 
 | 		any @ my_binding: my_var + my_binding, | 
 | 		default: "", | 
 | 	}), | 
 | } | 
 | `), | 
 | 			}, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"variable_a": "e", | 
 | 				}, | 
 | 			}, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string: proptools.StringPtr("abcde"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Cannot modify variable after referenced by select", | 
 | 			bp: ` | 
 | my_var = "foo" | 
 | my_module_type { | 
 | 	name: "foo", | 
 | 	my_string: select(soong_config_variable("my_namespace", "variable_a"), { | 
 | 		"a": my_var, | 
 | 		default: "", | 
 | 	}), | 
 | } | 
 | my_var += "bar" | 
 | `, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"variable_a": "b", // notably not the value that causes my_var to be referenced | 
 | 				}, | 
 | 			}, | 
 | 			expectedError: `modified variable "my_var" with \+= after referencing`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Cannot shadow variable with binding", | 
 | 			bp: ` | 
 | my_var = "foo" | 
 | my_module_type { | 
 | 	name: "foo", | 
 | 	my_string: select(soong_config_variable("my_namespace", "variable_a"), { | 
 | 		any @ my_var: my_var, | 
 | 		default: "", | 
 | 	}), | 
 | } | 
 | `, | 
 | 			vendorVars: map[string]map[string]string{ | 
 | 				"my_namespace": { | 
 | 					"variable_a": "a", | 
 | 				}, | 
 | 			}, | 
 | 			expectedError: `variable already set in inherited scope, previous assignment:`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Basic string list postprocessor", | 
 | 			bp: ` | 
 | my_defaults { | 
 | 	name: "defaults_a", | 
 | 	my_string_list: ["a", "b", "c"], | 
 | 	string_list_postprocessor_add_to_elements: "1", | 
 | } | 
 | my_defaults { | 
 | 	name: "defaults_b", | 
 | 	my_string_list: ["d", "e", "f"], | 
 | 	string_list_postprocessor_add_to_elements: "2", | 
 | } | 
 | my_module_type { | 
 | 	name: "foo", | 
 | 	defaults: ["defaults_a", "defaults_b"], | 
 | } | 
 | `, | 
 | 			provider: selectsTestProvider{ | 
 | 				my_string_list: &[]string{"d2", "e2", "f2", "a1", "b1", "c1"}, | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	for _, tc := range testCases { | 
 | 		t.Run(tc.name, func(t *testing.T) { | 
 | 			fs := tc.fs | 
 | 			if fs == nil { | 
 | 				fs = make(MockFS) | 
 | 			} | 
 | 			if tc.bp != "" { | 
 | 				fs["Android.bp"] = []byte(tc.bp) | 
 | 			} | 
 | 			fixtures := GroupFixturePreparers( | 
 | 				PrepareForTestWithDefaults, | 
 | 				PrepareForTestWithArchMutator, | 
 | 				PrepareForTestWithSoongConfigModuleBuildComponents, | 
 | 				FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
 | 					ctx.RegisterModuleType("my_module_type", newSelectsMockModule) | 
 | 					ctx.RegisterModuleType("my_defaults", newSelectsMockModuleDefaults) | 
 | 				}), | 
 | 				FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
 | 					variables.VendorVars = tc.vendorVars | 
 | 					variables.VendorVarTypes = tc.vendorVarTypes | 
 | 				}), | 
 | 				FixtureMergeMockFs(fs), | 
 | 			) | 
 | 			if tc.expectedError != "" { | 
 | 				fixtures = fixtures.ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(tc.expectedError)) | 
 | 			} | 
 | 			result := fixtures.RunTest(t) | 
 |  | 
 | 			if tc.expectedError == "" { | 
 | 				if len(tc.providers) == 0 { | 
 | 					tc.providers = map[string]selectsTestProvider{ | 
 | 						"foo": tc.provider, | 
 | 					} | 
 | 				} | 
 |  | 
 | 				for moduleName := range tc.providers { | 
 | 					expected := tc.providers[moduleName] | 
 | 					m := result.ModuleForTests(moduleName, "android_arm64_armv8-a") | 
 | 					p, _ := OtherModuleProvider(result.testContext.OtherModuleProviderAdaptor(), m.Module(), selectsTestProviderKey) | 
 | 					if !reflect.DeepEqual(p, expected) { | 
 | 						t.Errorf("Expected:\n  %q\ngot:\n  %q", expected.String(), p.String()) | 
 | 					} | 
 | 				} | 
 | 			} | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | type selectsTestProvider struct { | 
 | 	my_bool                        *bool | 
 | 	my_string                      *string | 
 | 	my_string_list                 *[]string | 
 | 	my_paths                       *[]string | 
 | 	replacing_string_list          *[]string | 
 | 	arch_variant_configurable_bool *bool | 
 | 	my_nonconfigurable_bool        *bool | 
 | 	my_nonconfigurable_string      *string | 
 | 	my_nonconfigurable_string_list []string | 
 | } | 
 |  | 
 | func (p *selectsTestProvider) String() string { | 
 | 	myBoolStr := "nil" | 
 | 	if p.my_bool != nil { | 
 | 		myBoolStr = fmt.Sprintf("%t", *p.my_bool) | 
 | 	} | 
 | 	myStringStr := "nil" | 
 | 	if p.my_string != nil { | 
 | 		myStringStr = *p.my_string | 
 | 	} | 
 | 	myNonconfigurableStringStr := "nil" | 
 | 	if p.my_nonconfigurable_string != nil { | 
 | 		myNonconfigurableStringStr = *p.my_nonconfigurable_string | 
 | 	} | 
 | 	return fmt.Sprintf(`selectsTestProvider { | 
 | 	my_bool: %v, | 
 | 	my_string: %s, | 
 |     my_string_list: %s, | 
 |     my_paths: %s, | 
 | 	replacing_string_list %s, | 
 | 	arch_variant_configurable_bool %v | 
 | 	my_nonconfigurable_bool: %v, | 
 | 	my_nonconfigurable_string: %s, | 
 | 	my_nonconfigurable_string_list: %s, | 
 | }`, | 
 | 		myBoolStr, | 
 | 		myStringStr, | 
 | 		p.my_string_list, | 
 | 		p.my_paths, | 
 | 		p.replacing_string_list, | 
 | 		p.arch_variant_configurable_bool, | 
 | 		p.my_nonconfigurable_bool, | 
 | 		myNonconfigurableStringStr, | 
 | 		p.my_nonconfigurable_string_list, | 
 | 	) | 
 | } | 
 |  | 
 | var selectsTestProviderKey = blueprint.NewProvider[selectsTestProvider]() | 
 |  | 
 | type selectsMockModuleProperties struct { | 
 | 	My_bool                        proptools.Configurable[bool] | 
 | 	My_string                      proptools.Configurable[string] | 
 | 	My_string_list                 proptools.Configurable[[]string] | 
 | 	My_paths                       proptools.Configurable[[]string] `android:"path"` | 
 | 	Replacing_string_list          proptools.Configurable[[]string] `android:"replace_instead_of_append,arch_variant"` | 
 | 	Arch_variant_configurable_bool proptools.Configurable[bool]     `android:"replace_instead_of_append,arch_variant"` | 
 | 	My_nonconfigurable_bool        *bool | 
 | 	My_nonconfigurable_string      *string | 
 | 	My_nonconfigurable_string_list []string | 
 | } | 
 |  | 
 | type selectsMockModule struct { | 
 | 	ModuleBase | 
 | 	DefaultableModuleBase | 
 | 	properties selectsMockModuleProperties | 
 | } | 
 |  | 
 | func optionalToPtr[T any](o proptools.ConfigurableOptional[T]) *T { | 
 | 	if o.IsEmpty() { | 
 | 		return nil | 
 | 	} | 
 | 	x := o.Get() | 
 | 	return &x | 
 | } | 
 |  | 
 | func (p *selectsMockModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
 | 	toAppend := ctx.Config().VendorConfig("selects_test").String("append_to_string_list") | 
 | 	if toAppend != "" { | 
 | 		p.properties.My_string_list.AppendSimpleValue([]string{toAppend}) | 
 | 	} | 
 | 	SetProvider(ctx, selectsTestProviderKey, selectsTestProvider{ | 
 | 		my_bool:                        optionalToPtr(p.properties.My_bool.Get(ctx)), | 
 | 		my_string:                      optionalToPtr(p.properties.My_string.Get(ctx)), | 
 | 		my_string_list:                 optionalToPtr(p.properties.My_string_list.Get(ctx)), | 
 | 		my_paths:                       optionalToPtr(p.properties.My_paths.Get(ctx)), | 
 | 		replacing_string_list:          optionalToPtr(p.properties.Replacing_string_list.Get(ctx)), | 
 | 		arch_variant_configurable_bool: optionalToPtr(p.properties.Arch_variant_configurable_bool.Get(ctx)), | 
 | 		my_nonconfigurable_bool:        p.properties.My_nonconfigurable_bool, | 
 | 		my_nonconfigurable_string:      p.properties.My_nonconfigurable_string, | 
 | 		my_nonconfigurable_string_list: p.properties.My_nonconfigurable_string_list, | 
 | 	}) | 
 | } | 
 |  | 
 | func newSelectsMockModule() Module { | 
 | 	m := &selectsMockModule{} | 
 | 	m.AddProperties(&m.properties) | 
 | 	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibFirst) | 
 | 	InitDefaultableModule(m) | 
 | 	return m | 
 | } | 
 |  | 
 | type selectsMockDefaultsProperties struct { | 
 | 	String_list_postprocessor_add_to_elements string | 
 | } | 
 |  | 
 | type selectsMockModuleDefaults struct { | 
 | 	ModuleBase | 
 | 	DefaultsModuleBase | 
 | 	myProperties       selectsMockModuleProperties | 
 | 	defaultsProperties selectsMockDefaultsProperties | 
 | } | 
 |  | 
 | func (d *selectsMockModuleDefaults) GenerateAndroidBuildActions(ctx ModuleContext) { | 
 | } | 
 |  | 
 | func newSelectsMockModuleDefaults() Module { | 
 | 	module := &selectsMockModuleDefaults{} | 
 |  | 
 | 	module.AddProperties( | 
 | 		&module.myProperties, | 
 | 		&module.defaultsProperties, | 
 | 	) | 
 |  | 
 | 	InitDefaultsModule(module) | 
 |  | 
 | 	AddLoadHook(module, func(lhc LoadHookContext) { | 
 | 		if module.defaultsProperties.String_list_postprocessor_add_to_elements != "" { | 
 | 			module.myProperties.My_string_list.AddPostProcessor(func(x []string) []string { | 
 | 				for i := range x { | 
 | 					x[i] = x[i] + module.defaultsProperties.String_list_postprocessor_add_to_elements | 
 | 				} | 
 | 				return x | 
 | 			}) | 
 | 		} | 
 | 	}) | 
 |  | 
 | 	return module | 
 | } |