| // 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{ |
| `unrecognized property "soong_config_variables.feature1.made_up_property`, |
| })).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) |
| }) |
| } |