|  | // Copyright 2019 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" | 
|  | "path/filepath" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | type soongConfigTestDefaultsModule struct { | 
|  | ModuleBase | 
|  | DefaultsModuleBase | 
|  | } | 
|  |  | 
|  | func soongConfigTestDefaultsModuleFactory() Module { | 
|  | m := &soongConfigTestDefaultsModule{} | 
|  | m.AddProperties(&soongConfigTestModuleProperties{}) | 
|  | InitDefaultsModule(m) | 
|  | return m | 
|  | } | 
|  |  | 
|  | type soongConfigTestModule struct { | 
|  | ModuleBase | 
|  | DefaultableModuleBase | 
|  | props      soongConfigTestModuleProperties | 
|  | outputPath ModuleOutPath | 
|  | } | 
|  |  | 
|  | type soongConfigTestModuleProperties struct { | 
|  | Cflags []string | 
|  | } | 
|  |  | 
|  | func soongConfigTestModuleFactory() Module { | 
|  | m := &soongConfigTestModule{} | 
|  | m.AddProperties(&m.props) | 
|  | InitAndroidModule(m) | 
|  | InitDefaultableModule(m) | 
|  | return m | 
|  | } | 
|  |  | 
|  | func (t *soongConfigTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
|  | t.outputPath = PathForModuleOut(ctx, "test") | 
|  | } | 
|  |  | 
|  | var prepareForSoongConfigTestModule = FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
|  | ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory) | 
|  | ctx.RegisterModuleType("test", soongConfigTestModuleFactory) | 
|  | }) | 
|  |  | 
|  | func TestSoongConfigModule(t *testing.T) { | 
|  | configBp := ` | 
|  | soong_config_module_type { | 
|  | name: "acme_test", | 
|  | module_type: "test", | 
|  | config_namespace: "acme", | 
|  | variables: ["board", "feature1", "FEATURE3", "unused_string_var"], | 
|  | bool_variables: ["feature2", "unused_feature", "always_true"], | 
|  | value_variables: ["size", "unused_size"], | 
|  | properties: ["cflags", "srcs", "defaults"], | 
|  | } | 
|  |  | 
|  | soong_config_string_variable { | 
|  | name: "board", | 
|  | values: ["soc_a", "soc_b", "soc_c", "soc_d"], | 
|  | } | 
|  |  | 
|  | soong_config_string_variable { | 
|  | name: "unused_string_var", | 
|  | values: ["a", "b"], | 
|  | } | 
|  |  | 
|  | soong_config_bool_variable { | 
|  | name: "feature1", | 
|  | } | 
|  |  | 
|  | soong_config_bool_variable { | 
|  | name: "FEATURE3", | 
|  | } | 
|  | ` | 
|  |  | 
|  | importBp := ` | 
|  | soong_config_module_type_import { | 
|  | from: "SoongConfig.bp", | 
|  | module_types: ["acme_test"], | 
|  | } | 
|  | ` | 
|  |  | 
|  | bp := ` | 
|  | test_defaults { | 
|  | name: "foo_defaults", | 
|  | cflags: ["DEFAULT"], | 
|  | } | 
|  |  | 
|  | acme_test { | 
|  | name: "foo", | 
|  | cflags: ["-DGENERIC"], | 
|  | defaults: ["foo_defaults"], | 
|  | soong_config_variables: { | 
|  | board: { | 
|  | soc_a: { | 
|  | cflags: ["-DSOC_A"], | 
|  | }, | 
|  | soc_b: { | 
|  | cflags: ["-DSOC_B"], | 
|  | }, | 
|  | soc_c: {}, | 
|  | conditions_default: { | 
|  | cflags: ["-DSOC_CONDITIONS_DEFAULT"], | 
|  | }, | 
|  | }, | 
|  | size: { | 
|  | cflags: ["-DSIZE=%s"], | 
|  | conditions_default: { | 
|  | cflags: ["-DSIZE=CONDITIONS_DEFAULT"], | 
|  | }, | 
|  | }, | 
|  | feature1: { | 
|  | conditions_default: { | 
|  | cflags: ["-DF1_CONDITIONS_DEFAULT"], | 
|  | }, | 
|  | cflags: ["-DFEATURE1"], | 
|  | }, | 
|  | feature2: { | 
|  | cflags: ["-DFEATURE2"], | 
|  | conditions_default: { | 
|  | cflags: ["-DF2_CONDITIONS_DEFAULT"], | 
|  | }, | 
|  | }, | 
|  | FEATURE3: { | 
|  | cflags: ["-DFEATURE3"], | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | test_defaults { | 
|  | name: "foo_defaults_a", | 
|  | cflags: ["DEFAULT_A"], | 
|  | } | 
|  |  | 
|  | test_defaults { | 
|  | name: "foo_defaults_b", | 
|  | cflags: ["DEFAULT_B"], | 
|  | } | 
|  |  | 
|  | test_defaults { | 
|  | name: "foo_defaults_always_true", | 
|  | cflags: ["DEFAULT_ALWAYS_TRUE"], | 
|  | } | 
|  |  | 
|  | acme_test { | 
|  | name: "foo_with_defaults", | 
|  | cflags: ["-DGENERIC"], | 
|  | defaults: ["foo_defaults"], | 
|  | soong_config_variables: { | 
|  | board: { | 
|  | soc_a: { | 
|  | cflags: ["-DSOC_A"], | 
|  | defaults: ["foo_defaults_a"], | 
|  | }, | 
|  | soc_b: { | 
|  | cflags: ["-DSOC_B"], | 
|  | defaults: ["foo_defaults_b"], | 
|  | }, | 
|  | soc_c: {}, | 
|  | }, | 
|  | size: { | 
|  | cflags: ["-DSIZE=%s"], | 
|  | }, | 
|  | feature1: { | 
|  | cflags: ["-DFEATURE1"], | 
|  | }, | 
|  | feature2: { | 
|  | cflags: ["-DFEATURE2"], | 
|  | }, | 
|  | FEATURE3: { | 
|  | cflags: ["-DFEATURE3"], | 
|  | }, | 
|  | always_true: { | 
|  | defaults: ["foo_defaults_always_true"], | 
|  | conditions_default: { | 
|  | // verify that conditions_default is skipped if the | 
|  | // soong config variable is true by specifying a | 
|  | // non-existent module in conditions_default | 
|  | defaults: ["//nonexistent:defaults"], | 
|  | } | 
|  | }, | 
|  | }, | 
|  | } | 
|  | ` | 
|  |  | 
|  | fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer { | 
|  | return FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
|  | variables.VendorVars = vars | 
|  | }) | 
|  | } | 
|  |  | 
|  | run := func(t *testing.T, bp string, fs MockFS) { | 
|  | testCases := []struct { | 
|  | name                     string | 
|  | preparer                 FixturePreparer | 
|  | fooExpectedFlags         []string | 
|  | fooDefaultsExpectedFlags []string | 
|  | }{ | 
|  | { | 
|  | name: "withValues", | 
|  | preparer: fixtureForVendorVars(map[string]map[string]string{ | 
|  | "acme": { | 
|  | "board":    "soc_a", | 
|  | "size":     "42", | 
|  | "feature1": "true", | 
|  | "feature2": "false", | 
|  | // FEATURE3 unset | 
|  | "unused_feature":    "true", // unused | 
|  | "unused_size":       "1",    // unused | 
|  | "unused_string_var": "a",    // unused | 
|  | "always_true":       "true", | 
|  | }, | 
|  | }), | 
|  | fooExpectedFlags: []string{ | 
|  | "DEFAULT", | 
|  | "-DGENERIC", | 
|  | "-DF2_CONDITIONS_DEFAULT", | 
|  | "-DSIZE=42", | 
|  | "-DSOC_A", | 
|  | "-DFEATURE1", | 
|  | }, | 
|  | fooDefaultsExpectedFlags: []string{ | 
|  | "DEFAULT_A", | 
|  | "DEFAULT_ALWAYS_TRUE", | 
|  | "DEFAULT", | 
|  | "-DGENERIC", | 
|  | "-DSIZE=42", | 
|  | "-DSOC_A", | 
|  | "-DFEATURE1", | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name: "empty_prop_for_string_var", | 
|  | preparer: fixtureForVendorVars(map[string]map[string]string{ | 
|  | "acme": { | 
|  | "board":       "soc_c", | 
|  | "always_true": "true", | 
|  | }}), | 
|  | fooExpectedFlags: []string{ | 
|  | "DEFAULT", | 
|  | "-DGENERIC", | 
|  | "-DF2_CONDITIONS_DEFAULT", | 
|  | "-DSIZE=CONDITIONS_DEFAULT", | 
|  | "-DF1_CONDITIONS_DEFAULT", | 
|  | }, | 
|  | fooDefaultsExpectedFlags: []string{ | 
|  | "DEFAULT_ALWAYS_TRUE", | 
|  | "DEFAULT", | 
|  | "-DGENERIC", | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name: "unused_string_var", | 
|  | preparer: fixtureForVendorVars(map[string]map[string]string{ | 
|  | "acme": { | 
|  | "board":       "soc_d", | 
|  | "always_true": "true", | 
|  | }}), | 
|  | fooExpectedFlags: []string{ | 
|  | "DEFAULT", | 
|  | "-DGENERIC", | 
|  | "-DF2_CONDITIONS_DEFAULT", | 
|  | "-DSIZE=CONDITIONS_DEFAULT", | 
|  | "-DSOC_CONDITIONS_DEFAULT", // foo does not contain a prop "soc_d", so we use the default | 
|  | "-DF1_CONDITIONS_DEFAULT", | 
|  | }, | 
|  | fooDefaultsExpectedFlags: []string{ | 
|  | "DEFAULT_ALWAYS_TRUE", | 
|  | "DEFAULT", | 
|  | "-DGENERIC", | 
|  | }, | 
|  | }, | 
|  |  | 
|  | { | 
|  | name: "conditions_default", | 
|  | preparer: fixtureForVendorVars(map[string]map[string]string{ | 
|  | "acme": { | 
|  | "always_true": "true", | 
|  | }}), | 
|  | fooExpectedFlags: []string{ | 
|  | "DEFAULT", | 
|  | "-DGENERIC", | 
|  | "-DF2_CONDITIONS_DEFAULT", | 
|  | "-DSIZE=CONDITIONS_DEFAULT", | 
|  | "-DSOC_CONDITIONS_DEFAULT", | 
|  | "-DF1_CONDITIONS_DEFAULT", | 
|  | }, | 
|  | fooDefaultsExpectedFlags: []string{ | 
|  | "DEFAULT_ALWAYS_TRUE", | 
|  | "DEFAULT", | 
|  | "-DGENERIC", | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | for _, tc := range testCases { | 
|  | t.Run(tc.name, func(t *testing.T) { | 
|  | result := GroupFixturePreparers( | 
|  | tc.preparer, | 
|  | PrepareForTestWithDefaults, | 
|  | PrepareForTestWithSoongConfigModuleBuildComponents, | 
|  | prepareForSoongConfigTestModule, | 
|  | fs.AddToFixture(), | 
|  | FixtureWithRootAndroidBp(bp), | 
|  | ).RunTest(t) | 
|  |  | 
|  | foo := result.ModuleForTests("foo", "").Module().(*soongConfigTestModule) | 
|  | AssertDeepEquals(t, "foo cflags", tc.fooExpectedFlags, foo.props.Cflags) | 
|  |  | 
|  | fooDefaults := result.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule) | 
|  | AssertDeepEquals(t, "foo_with_defaults cflags", tc.fooDefaultsExpectedFlags, fooDefaults.props.Cflags) | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | t.Run("single file", func(t *testing.T) { | 
|  | run(t, configBp+bp, nil) | 
|  | }) | 
|  |  | 
|  | t.Run("import", func(t *testing.T) { | 
|  | run(t, importBp+bp, map[string][]byte{ | 
|  | "SoongConfig.bp": []byte(configBp), | 
|  | }) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestNonExistentPropertyInSoongConfigModule(t *testing.T) { | 
|  | bp := ` | 
|  | soong_config_module_type { | 
|  | name: "acme_test", | 
|  | module_type: "test", | 
|  | config_namespace: "acme", | 
|  | bool_variables: ["feature1"], | 
|  | properties: ["made_up_property"], | 
|  | } | 
|  |  | 
|  | acme_test { | 
|  | name: "foo", | 
|  | cflags: ["-DGENERIC"], | 
|  | soong_config_variables: { | 
|  | feature1: { | 
|  | made_up_property: true, | 
|  | }, | 
|  | }, | 
|  | } | 
|  | ` | 
|  |  | 
|  | fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer { | 
|  | return FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
|  | variables.VendorVars = vars | 
|  | }) | 
|  | } | 
|  |  | 
|  | GroupFixturePreparers( | 
|  | fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}), | 
|  | PrepareForTestWithDefaults, | 
|  | PrepareForTestWithSoongConfigModuleBuildComponents, | 
|  | prepareForSoongConfigTestModule, | 
|  | FixtureWithRootAndroidBp(bp), | 
|  | ).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{ | 
|  | // TODO(b/171232169): improve the error message for non-existent properties | 
|  | `unrecognized property "soong_config_variables`, | 
|  | })).RunTest(t) | 
|  | } | 
|  |  | 
|  | func TestDuplicateStringValueInSoongConfigStringVariable(t *testing.T) { | 
|  | bp := ` | 
|  | soong_config_string_variable { | 
|  | name: "board", | 
|  | values: ["soc_a", "soc_b", "soc_c", "soc_a"], | 
|  | } | 
|  |  | 
|  | soong_config_module_type { | 
|  | name: "acme_test", | 
|  | module_type: "test", | 
|  | config_namespace: "acme", | 
|  | variables: ["board"], | 
|  | properties: ["cflags", "srcs", "defaults"], | 
|  | } | 
|  | ` | 
|  |  | 
|  | fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer { | 
|  | return FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
|  | variables.VendorVars = vars | 
|  | }) | 
|  | } | 
|  |  | 
|  | GroupFixturePreparers( | 
|  | fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}), | 
|  | PrepareForTestWithDefaults, | 
|  | PrepareForTestWithSoongConfigModuleBuildComponents, | 
|  | prepareForSoongConfigTestModule, | 
|  | FixtureWithRootAndroidBp(bp), | 
|  | ).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{ | 
|  | // TODO(b/171232169): improve the error message for non-existent properties | 
|  | `Android.bp: soong_config_string_variable: values property error: duplicate value: "soc_a"`, | 
|  | })).RunTest(t) | 
|  | } | 
|  |  | 
|  | type soongConfigTestSingletonModule struct { | 
|  | SingletonModuleBase | 
|  | props soongConfigTestSingletonModuleProperties | 
|  | } | 
|  |  | 
|  | type soongConfigTestSingletonModuleProperties struct { | 
|  | Fragments []struct { | 
|  | Apex   string | 
|  | Module string | 
|  | } | 
|  | } | 
|  |  | 
|  | func soongConfigTestSingletonModuleFactory() SingletonModule { | 
|  | m := &soongConfigTestSingletonModule{} | 
|  | m.AddProperties(&m.props) | 
|  | InitAndroidModule(m) | 
|  | return m | 
|  | } | 
|  |  | 
|  | func (t *soongConfigTestSingletonModule) GenerateAndroidBuildActions(ModuleContext) {} | 
|  |  | 
|  | func (t *soongConfigTestSingletonModule) GenerateSingletonBuildActions(SingletonContext) {} | 
|  |  | 
|  | var prepareForSoongConfigTestSingletonModule = FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
|  | ctx.RegisterSingletonModuleType("test_singleton", soongConfigTestSingletonModuleFactory) | 
|  | }) | 
|  |  | 
|  | func TestSoongConfigModuleSingletonModule(t *testing.T) { | 
|  | bp := ` | 
|  | soong_config_module_type { | 
|  | name: "acme_test_singleton", | 
|  | module_type: "test_singleton", | 
|  | config_namespace: "acme", | 
|  | bool_variables: ["coyote"], | 
|  | properties: ["fragments"], | 
|  | } | 
|  |  | 
|  | acme_test_singleton { | 
|  | name: "wiley", | 
|  | fragments: [ | 
|  | { | 
|  | apex: "com.android.acme", | 
|  | module: "road-runner", | 
|  | }, | 
|  | ], | 
|  | soong_config_variables: { | 
|  | coyote: { | 
|  | fragments: [ | 
|  | { | 
|  | apex: "com.android.acme", | 
|  | module: "wiley", | 
|  | }, | 
|  | ], | 
|  | }, | 
|  | }, | 
|  | } | 
|  | ` | 
|  |  | 
|  | for _, test := range []struct { | 
|  | coyote            bool | 
|  | expectedFragments string | 
|  | }{ | 
|  | { | 
|  | coyote:            false, | 
|  | expectedFragments: "[{Apex:com.android.acme Module:road-runner}]", | 
|  | }, | 
|  | { | 
|  | coyote:            true, | 
|  | expectedFragments: "[{Apex:com.android.acme Module:road-runner} {Apex:com.android.acme Module:wiley}]", | 
|  | }, | 
|  | } { | 
|  | t.Run(fmt.Sprintf("coyote:%t", test.coyote), func(t *testing.T) { | 
|  | result := GroupFixturePreparers( | 
|  | PrepareForTestWithSoongConfigModuleBuildComponents, | 
|  | prepareForSoongConfigTestSingletonModule, | 
|  | FixtureWithRootAndroidBp(bp), | 
|  | FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
|  | variables.VendorVars = map[string]map[string]string{ | 
|  | "acme": { | 
|  | "coyote": fmt.Sprintf("%t", test.coyote), | 
|  | }, | 
|  | } | 
|  | }), | 
|  | ).RunTest(t) | 
|  |  | 
|  | // Make sure that the singleton was created. | 
|  | result.SingletonForTests("test_singleton") | 
|  | m := result.ModuleForTests("wiley", "").module.(*soongConfigTestSingletonModule) | 
|  | AssertStringEquals(t, "fragments", test.expectedFragments, fmt.Sprintf("%+v", m.props.Fragments)) | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestSoongConfigModuleTrace(t *testing.T) { | 
|  | bp := ` | 
|  | soong_config_module_type { | 
|  | name: "acme_test", | 
|  | module_type: "test", | 
|  | config_namespace: "acme", | 
|  | variables: ["board", "feature1", "FEATURE3", "unused_string_var"], | 
|  | bool_variables: ["feature2", "unused_feature", "always_true"], | 
|  | value_variables: ["size", "unused_size"], | 
|  | properties: ["cflags", "srcs", "defaults"], | 
|  | } | 
|  |  | 
|  | soong_config_module_type { | 
|  | name: "acme_test_defaults", | 
|  | module_type: "test_defaults", | 
|  | config_namespace: "acme", | 
|  | variables: ["board", "feature1", "FEATURE3", "unused_string_var"], | 
|  | bool_variables: ["feature2", "unused_feature", "always_true"], | 
|  | value_variables: ["size", "unused_size"], | 
|  | properties: ["cflags", "srcs", "defaults"], | 
|  | } | 
|  |  | 
|  | soong_config_string_variable { | 
|  | name: "board", | 
|  | values: ["soc_a", "soc_b", "soc_c"], | 
|  | } | 
|  |  | 
|  | soong_config_string_variable { | 
|  | name: "unused_string_var", | 
|  | values: ["a", "b"], | 
|  | } | 
|  |  | 
|  | soong_config_bool_variable { | 
|  | name: "feature1", | 
|  | } | 
|  |  | 
|  | soong_config_bool_variable { | 
|  | name: "FEATURE3", | 
|  | } | 
|  |  | 
|  | test_defaults { | 
|  | name: "test_defaults", | 
|  | cflags: ["DEFAULT"], | 
|  | } | 
|  |  | 
|  | test { | 
|  | name: "normal", | 
|  | defaults: ["test_defaults"], | 
|  | } | 
|  |  | 
|  | acme_test { | 
|  | name: "board_1", | 
|  | defaults: ["test_defaults"], | 
|  | soong_config_variables: { | 
|  | board: { | 
|  | soc_a: { | 
|  | cflags: ["-DSOC_A"], | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | acme_test { | 
|  | name: "board_2", | 
|  | defaults: ["test_defaults"], | 
|  | soong_config_variables: { | 
|  | board: { | 
|  | soc_a: { | 
|  | cflags: ["-DSOC_A"], | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | acme_test { | 
|  | name: "size", | 
|  | defaults: ["test_defaults"], | 
|  | soong_config_variables: { | 
|  | size: { | 
|  | cflags: ["-DSIZE=%s"], | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | acme_test { | 
|  | name: "board_and_size", | 
|  | defaults: ["test_defaults"], | 
|  | soong_config_variables: { | 
|  | board: { | 
|  | soc_a: { | 
|  | cflags: ["-DSOC_A"], | 
|  | }, | 
|  | }, | 
|  | size: { | 
|  | cflags: ["-DSIZE=%s"], | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | acme_test_defaults { | 
|  | name: "board_defaults", | 
|  | soong_config_variables: { | 
|  | board: { | 
|  | soc_a: { | 
|  | cflags: ["-DSOC_A"], | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | acme_test_defaults { | 
|  | name: "size_defaults", | 
|  | soong_config_variables: { | 
|  | size: { | 
|  | cflags: ["-DSIZE=%s"], | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | test { | 
|  | name: "board_and_size_with_defaults", | 
|  | defaults: ["board_defaults", "size_defaults"], | 
|  | } | 
|  | ` | 
|  |  | 
|  | fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer { | 
|  | return FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
|  | variables.VendorVars = vars | 
|  | }) | 
|  | } | 
|  |  | 
|  | preparer := fixtureForVendorVars(map[string]map[string]string{ | 
|  | "acme": { | 
|  | "board":    "soc_a", | 
|  | "size":     "42", | 
|  | "feature1": "true", | 
|  | "feature2": "false", | 
|  | // FEATURE3 unset | 
|  | "unused_feature":    "true", // unused | 
|  | "unused_size":       "1",    // unused | 
|  | "unused_string_var": "a",    // unused | 
|  | "always_true":       "true", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | t.Run("soong config trace hash", func(t *testing.T) { | 
|  | result := GroupFixturePreparers( | 
|  | preparer, | 
|  | PrepareForTestWithDefaults, | 
|  | PrepareForTestWithSoongConfigModuleBuildComponents, | 
|  | prepareForSoongConfigTestModule, | 
|  | FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
|  | ctx.FinalDepsMutators(registerSoongConfigTraceMutator) | 
|  | }), | 
|  | FixtureWithRootAndroidBp(bp), | 
|  | ).RunTest(t) | 
|  |  | 
|  | // Hashes of modules not using soong config should be empty | 
|  | normal := result.ModuleForTests("normal", "").Module().(*soongConfigTestModule) | 
|  | AssertDeepEquals(t, "normal hash", normal.base().commonProperties.SoongConfigTraceHash, "") | 
|  | AssertDeepEquals(t, "normal hash out", normal.outputPath.RelativeToTop().String(), "out/soong/.intermediates/normal/test") | 
|  |  | 
|  | board1 := result.ModuleForTests("board_1", "").Module().(*soongConfigTestModule) | 
|  | board2 := result.ModuleForTests("board_2", "").Module().(*soongConfigTestModule) | 
|  | size := result.ModuleForTests("size", "").Module().(*soongConfigTestModule) | 
|  |  | 
|  | // Trace mutator sets soong config trace hash correctly | 
|  | board1Hash := board1.base().commonProperties.SoongConfigTrace.hash() | 
|  | board1Output := board1.outputPath.RelativeToTop().String() | 
|  | AssertDeepEquals(t, "board hash calc", board1Hash, board1.base().commonProperties.SoongConfigTraceHash) | 
|  | AssertDeepEquals(t, "board hash path", board1Output, filepath.Join("out/soong/.intermediates/board_1", board1Hash, "test")) | 
|  |  | 
|  | sizeHash := size.base().commonProperties.SoongConfigTrace.hash() | 
|  | sizeOutput := size.outputPath.RelativeToTop().String() | 
|  | AssertDeepEquals(t, "size hash calc", sizeHash, size.base().commonProperties.SoongConfigTraceHash) | 
|  | AssertDeepEquals(t, "size hash path", sizeOutput, filepath.Join("out/soong/.intermediates/size", sizeHash, "test")) | 
|  |  | 
|  | // Trace should be identical for modules using the same set of variables | 
|  | AssertDeepEquals(t, "board trace", board1.base().commonProperties.SoongConfigTrace, board2.base().commonProperties.SoongConfigTrace) | 
|  | AssertDeepEquals(t, "board hash", board1.base().commonProperties.SoongConfigTraceHash, board2.base().commonProperties.SoongConfigTraceHash) | 
|  |  | 
|  | // Trace hash should be different for different sets of soong variables | 
|  | AssertBoolEquals(t, "board hash not equal to size hash", board1.base().commonProperties.SoongConfigTraceHash == size.commonProperties.SoongConfigTraceHash, false) | 
|  |  | 
|  | boardSize := result.ModuleForTests("board_and_size", "").Module().(*soongConfigTestModule) | 
|  | boardSizeDefaults := result.ModuleForTests("board_and_size_with_defaults", "").Module() | 
|  |  | 
|  | // Trace should propagate | 
|  | AssertDeepEquals(t, "board_size hash calc", boardSize.base().commonProperties.SoongConfigTrace.hash(), boardSize.base().commonProperties.SoongConfigTraceHash) | 
|  | AssertDeepEquals(t, "board_size trace", boardSize.base().commonProperties.SoongConfigTrace, boardSizeDefaults.base().commonProperties.SoongConfigTrace) | 
|  | AssertDeepEquals(t, "board_size hash", boardSize.base().commonProperties.SoongConfigTraceHash, boardSizeDefaults.base().commonProperties.SoongConfigTraceHash) | 
|  | }) | 
|  | } |