|  | // Copyright 2015 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 ( | 
|  | "github.com/google/blueprint" | 
|  | "path/filepath" | 
|  | "runtime" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | func TestSrcIsModule(t *testing.T) { | 
|  | type args struct { | 
|  | s string | 
|  | } | 
|  | tests := []struct { | 
|  | name       string | 
|  | args       args | 
|  | wantModule string | 
|  | }{ | 
|  | { | 
|  | name: "file", | 
|  | args: args{ | 
|  | s: "foo", | 
|  | }, | 
|  | wantModule: "", | 
|  | }, | 
|  | { | 
|  | name: "module", | 
|  | args: args{ | 
|  | s: ":foo", | 
|  | }, | 
|  | wantModule: "foo", | 
|  | }, | 
|  | { | 
|  | name: "tag", | 
|  | args: args{ | 
|  | s: ":foo{.bar}", | 
|  | }, | 
|  | wantModule: "foo{.bar}", | 
|  | }, | 
|  | { | 
|  | name: "extra colon", | 
|  | args: args{ | 
|  | s: ":foo:bar", | 
|  | }, | 
|  | wantModule: "foo:bar", | 
|  | }, | 
|  | { | 
|  | name: "fully qualified", | 
|  | args: args{ | 
|  | s: "//foo:bar", | 
|  | }, | 
|  | wantModule: "//foo:bar", | 
|  | }, | 
|  | { | 
|  | name: "fully qualified with tag", | 
|  | args: args{ | 
|  | s: "//foo:bar{.tag}", | 
|  | }, | 
|  | wantModule: "//foo:bar{.tag}", | 
|  | }, | 
|  | { | 
|  | name: "invalid unqualified name", | 
|  | args: args{ | 
|  | s: ":foo/bar", | 
|  | }, | 
|  | wantModule: "", | 
|  | }, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | t.Run(tt.name, func(t *testing.T) { | 
|  | if gotModule := SrcIsModule(tt.args.s); gotModule != tt.wantModule { | 
|  | t.Errorf("SrcIsModule() = %v, want %v", gotModule, tt.wantModule) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestSrcIsModuleWithTag(t *testing.T) { | 
|  | type args struct { | 
|  | s string | 
|  | } | 
|  | tests := []struct { | 
|  | name       string | 
|  | args       args | 
|  | wantModule string | 
|  | wantTag    string | 
|  | }{ | 
|  | { | 
|  | name: "file", | 
|  | args: args{ | 
|  | s: "foo", | 
|  | }, | 
|  | wantModule: "", | 
|  | wantTag:    "", | 
|  | }, | 
|  | { | 
|  | name: "module", | 
|  | args: args{ | 
|  | s: ":foo", | 
|  | }, | 
|  | wantModule: "foo", | 
|  | wantTag:    "", | 
|  | }, | 
|  | { | 
|  | name: "tag", | 
|  | args: args{ | 
|  | s: ":foo{.bar}", | 
|  | }, | 
|  | wantModule: "foo", | 
|  | wantTag:    ".bar", | 
|  | }, | 
|  | { | 
|  | name: "empty tag", | 
|  | args: args{ | 
|  | s: ":foo{}", | 
|  | }, | 
|  | wantModule: "foo", | 
|  | wantTag:    "", | 
|  | }, | 
|  | { | 
|  | name: "extra colon", | 
|  | args: args{ | 
|  | s: ":foo:bar", | 
|  | }, | 
|  | wantModule: "foo:bar", | 
|  | }, | 
|  | { | 
|  | name: "invalid tag", | 
|  | args: args{ | 
|  | s: ":foo{.bar", | 
|  | }, | 
|  | wantModule: "foo{.bar", | 
|  | }, | 
|  | { | 
|  | name: "invalid tag 2", | 
|  | args: args{ | 
|  | s: ":foo.bar}", | 
|  | }, | 
|  | wantModule: "foo.bar}", | 
|  | }, | 
|  | { | 
|  | name: "fully qualified", | 
|  | args: args{ | 
|  | s: "//foo:bar", | 
|  | }, | 
|  | wantModule: "//foo:bar", | 
|  | }, | 
|  | { | 
|  | name: "fully qualified with tag", | 
|  | args: args{ | 
|  | s: "//foo:bar{.tag}", | 
|  | }, | 
|  | wantModule: "//foo:bar", | 
|  | wantTag:    ".tag", | 
|  | }, | 
|  | { | 
|  | name: "invalid unqualified name", | 
|  | args: args{ | 
|  | s: ":foo/bar", | 
|  | }, | 
|  | wantModule: "", | 
|  | }, | 
|  | { | 
|  | name: "invalid unqualified name with tag", | 
|  | args: args{ | 
|  | s: ":foo/bar{.tag}", | 
|  | }, | 
|  | wantModule: "", | 
|  | }, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | t.Run(tt.name, func(t *testing.T) { | 
|  | gotModule, gotTag := SrcIsModuleWithTag(tt.args.s) | 
|  | if gotModule != tt.wantModule { | 
|  | t.Errorf("SrcIsModuleWithTag() gotModule = %v, want %v", gotModule, tt.wantModule) | 
|  | } | 
|  | if gotTag != tt.wantTag { | 
|  | t.Errorf("SrcIsModuleWithTag() gotTag = %v, want %v", gotTag, tt.wantTag) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | type depsModule struct { | 
|  | ModuleBase | 
|  | props struct { | 
|  | Deps []string | 
|  | } | 
|  | } | 
|  |  | 
|  | func (m *depsModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
|  | outputFile := PathForModuleOut(ctx, ctx.ModuleName()) | 
|  | ctx.Build(pctx, BuildParams{ | 
|  | Rule:   Touch, | 
|  | Output: outputFile, | 
|  | }) | 
|  | installFile := ctx.InstallFile(PathForModuleInstall(ctx), ctx.ModuleName(), outputFile) | 
|  | ctx.InstallSymlink(PathForModuleInstall(ctx, "symlinks"), ctx.ModuleName(), installFile) | 
|  | } | 
|  |  | 
|  | func (m *depsModule) DepsMutator(ctx BottomUpMutatorContext) { | 
|  | ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...) | 
|  | } | 
|  |  | 
|  | func depsModuleFactory() Module { | 
|  | m := &depsModule{} | 
|  | m.AddProperties(&m.props) | 
|  | InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon) | 
|  | return m | 
|  | } | 
|  |  | 
|  | var prepareForModuleTests = FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
|  | ctx.RegisterModuleType("deps", depsModuleFactory) | 
|  | }) | 
|  |  | 
|  | func TestErrorDependsOnDisabledModule(t *testing.T) { | 
|  | bp := ` | 
|  | deps { | 
|  | name: "foo", | 
|  | deps: ["bar"], | 
|  | } | 
|  | deps { | 
|  | name: "bar", | 
|  | enabled: false, | 
|  | } | 
|  | ` | 
|  |  | 
|  | prepareForModuleTests. | 
|  | ExtendWithErrorHandler(FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": depends on disabled module "bar"`)). | 
|  | RunTestWithBp(t, bp) | 
|  | } | 
|  |  | 
|  | func TestValidateCorrectBuildParams(t *testing.T) { | 
|  | config := TestConfig(t.TempDir(), nil, "", nil) | 
|  | pathContext := PathContextForTesting(config) | 
|  | bparams := convertBuildParams(BuildParams{ | 
|  | // Test with Output | 
|  | Output:        PathForOutput(pathContext, "undeclared_symlink"), | 
|  | SymlinkOutput: PathForOutput(pathContext, "undeclared_symlink"), | 
|  | }) | 
|  |  | 
|  | err := validateBuildParams(bparams) | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | } | 
|  |  | 
|  | bparams = convertBuildParams(BuildParams{ | 
|  | // Test with ImplicitOutput | 
|  | ImplicitOutput: PathForOutput(pathContext, "undeclared_symlink"), | 
|  | SymlinkOutput:  PathForOutput(pathContext, "undeclared_symlink"), | 
|  | }) | 
|  |  | 
|  | err = validateBuildParams(bparams) | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestValidateIncorrectBuildParams(t *testing.T) { | 
|  | config := TestConfig(t.TempDir(), nil, "", nil) | 
|  | pathContext := PathContextForTesting(config) | 
|  | params := BuildParams{ | 
|  | Output:          PathForOutput(pathContext, "regular_output"), | 
|  | Outputs:         PathsForOutput(pathContext, []string{"out1", "out2"}), | 
|  | ImplicitOutput:  PathForOutput(pathContext, "implicit_output"), | 
|  | ImplicitOutputs: PathsForOutput(pathContext, []string{"i_out1", "_out2"}), | 
|  | SymlinkOutput:   PathForOutput(pathContext, "undeclared_symlink"), | 
|  | } | 
|  |  | 
|  | bparams := convertBuildParams(params) | 
|  | err := validateBuildParams(bparams) | 
|  | if err != nil { | 
|  | FailIfNoMatchingErrors(t, "undeclared_symlink is not a declared output or implicit output", []error{err}) | 
|  | } else { | 
|  | t.Errorf("Expected build params to fail validation: %+v", bparams) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestDistErrorChecking(t *testing.T) { | 
|  | bp := ` | 
|  | deps { | 
|  | name: "foo", | 
|  | dist: { | 
|  | dest: "../invalid-dest", | 
|  | dir: "../invalid-dir", | 
|  | suffix: "invalid/suffix", | 
|  | }, | 
|  | dists: [ | 
|  | { | 
|  | dest: "../invalid-dest0", | 
|  | dir: "../invalid-dir0", | 
|  | suffix: "invalid/suffix0", | 
|  | }, | 
|  | { | 
|  | dest: "../invalid-dest1", | 
|  | dir: "../invalid-dir1", | 
|  | suffix: "invalid/suffix1", | 
|  | }, | 
|  | ], | 
|  | } | 
|  | ` | 
|  |  | 
|  | expectedErrs := []string{ | 
|  | "\\QAndroid.bp:5:13: module \"foo\": dist.dest: Path is outside directory: ../invalid-dest\\E", | 
|  | "\\QAndroid.bp:6:12: module \"foo\": dist.dir: Path is outside directory: ../invalid-dir\\E", | 
|  | "\\QAndroid.bp:7:15: module \"foo\": dist.suffix: Suffix may not contain a '/' character.\\E", | 
|  | "\\QAndroid.bp:11:15: module \"foo\": dists[0].dest: Path is outside directory: ../invalid-dest0\\E", | 
|  | "\\QAndroid.bp:12:14: module \"foo\": dists[0].dir: Path is outside directory: ../invalid-dir0\\E", | 
|  | "\\QAndroid.bp:13:17: module \"foo\": dists[0].suffix: Suffix may not contain a '/' character.\\E", | 
|  | "\\QAndroid.bp:16:15: module \"foo\": dists[1].dest: Path is outside directory: ../invalid-dest1\\E", | 
|  | "\\QAndroid.bp:17:14: module \"foo\": dists[1].dir: Path is outside directory: ../invalid-dir1\\E", | 
|  | "\\QAndroid.bp:18:17: module \"foo\": dists[1].suffix: Suffix may not contain a '/' character.\\E", | 
|  | } | 
|  |  | 
|  | prepareForModuleTests. | 
|  | ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(expectedErrs)). | 
|  | RunTestWithBp(t, bp) | 
|  | } | 
|  |  | 
|  | func TestInstall(t *testing.T) { | 
|  | if runtime.GOOS != "linux" { | 
|  | t.Skip("requires linux") | 
|  | } | 
|  | bp := ` | 
|  | deps { | 
|  | name: "foo", | 
|  | deps: ["bar"], | 
|  | } | 
|  |  | 
|  | deps { | 
|  | name: "bar", | 
|  | deps: ["baz", "qux"], | 
|  | } | 
|  |  | 
|  | deps { | 
|  | name: "baz", | 
|  | deps: ["qux"], | 
|  | } | 
|  |  | 
|  | deps { | 
|  | name: "qux", | 
|  | } | 
|  | ` | 
|  |  | 
|  | result := GroupFixturePreparers( | 
|  | prepareForModuleTests, | 
|  | PrepareForTestWithArchMutator, | 
|  | ).RunTestWithBp(t, bp) | 
|  |  | 
|  | module := func(name string, host bool) TestingModule { | 
|  | variant := "android_common" | 
|  | if host { | 
|  | variant = result.Config.BuildOSCommonTarget.String() | 
|  | } | 
|  | return result.ModuleForTests(name, variant) | 
|  | } | 
|  |  | 
|  | outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) } | 
|  |  | 
|  | installRule := func(name string) TestingBuildParams { | 
|  | return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system", name)) | 
|  | } | 
|  |  | 
|  | symlinkRule := func(name string) TestingBuildParams { | 
|  | return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system/symlinks", name)) | 
|  | } | 
|  |  | 
|  | hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) } | 
|  |  | 
|  | hostInstallRule := func(name string) TestingBuildParams { | 
|  | return module(name, true).Output(filepath.Join("out/soong/host/linux-x86", name)) | 
|  | } | 
|  |  | 
|  | hostSymlinkRule := func(name string) TestingBuildParams { | 
|  | return module(name, true).Output(filepath.Join("out/soong/host/linux-x86/symlinks", name)) | 
|  | } | 
|  |  | 
|  | assertInputs := func(params TestingBuildParams, inputs ...Path) { | 
|  | t.Helper() | 
|  | AssertArrayString(t, "expected inputs", Paths(inputs).Strings(), | 
|  | append(PathsIfNonNil(params.Input), params.Inputs...).Strings()) | 
|  | } | 
|  |  | 
|  | assertImplicits := func(params TestingBuildParams, implicits ...Path) { | 
|  | t.Helper() | 
|  | AssertArrayString(t, "expected implicit dependencies", Paths(implicits).Strings(), | 
|  | append(PathsIfNonNil(params.Implicit), params.Implicits...).Strings()) | 
|  | } | 
|  |  | 
|  | assertOrderOnlys := func(params TestingBuildParams, orderonlys ...Path) { | 
|  | t.Helper() | 
|  | AssertArrayString(t, "expected orderonly dependencies", Paths(orderonlys).Strings(), | 
|  | params.OrderOnly.Strings()) | 
|  | } | 
|  |  | 
|  | // Check host install rule dependencies | 
|  | assertInputs(hostInstallRule("foo"), hostOutputRule("foo").Output) | 
|  | assertImplicits(hostInstallRule("foo"), | 
|  | hostInstallRule("bar").Output, | 
|  | hostSymlinkRule("bar").Output, | 
|  | hostInstallRule("baz").Output, | 
|  | hostSymlinkRule("baz").Output, | 
|  | hostInstallRule("qux").Output, | 
|  | hostSymlinkRule("qux").Output, | 
|  | ) | 
|  | assertOrderOnlys(hostInstallRule("foo")) | 
|  |  | 
|  | // Check host symlink rule dependencies.  Host symlinks must use a normal dependency, not an | 
|  | // order-only dependency, so that the tool gets updated when the symlink is depended on. | 
|  | assertInputs(hostSymlinkRule("foo"), hostInstallRule("foo").Output) | 
|  | assertImplicits(hostSymlinkRule("foo")) | 
|  | assertOrderOnlys(hostSymlinkRule("foo")) | 
|  |  | 
|  | // Check device install rule dependencies | 
|  | assertInputs(installRule("foo"), outputRule("foo").Output) | 
|  | assertImplicits(installRule("foo")) | 
|  | assertOrderOnlys(installRule("foo"), | 
|  | installRule("bar").Output, | 
|  | symlinkRule("bar").Output, | 
|  | installRule("baz").Output, | 
|  | symlinkRule("baz").Output, | 
|  | installRule("qux").Output, | 
|  | symlinkRule("qux").Output, | 
|  | ) | 
|  |  | 
|  | // Check device symlink rule dependencies.  Device symlinks could use an order-only dependency, | 
|  | // but the current implementation uses a normal dependency. | 
|  | assertInputs(symlinkRule("foo"), installRule("foo").Output) | 
|  | assertImplicits(symlinkRule("foo")) | 
|  | assertOrderOnlys(symlinkRule("foo")) | 
|  | } | 
|  |  | 
|  | func TestInstallKatiEnabled(t *testing.T) { | 
|  | if runtime.GOOS != "linux" { | 
|  | t.Skip("requires linux") | 
|  | } | 
|  | bp := ` | 
|  | deps { | 
|  | name: "foo", | 
|  | deps: ["bar"], | 
|  | } | 
|  |  | 
|  | deps { | 
|  | name: "bar", | 
|  | deps: ["baz", "qux"], | 
|  | } | 
|  |  | 
|  | deps { | 
|  | name: "baz", | 
|  | deps: ["qux"], | 
|  | } | 
|  |  | 
|  | deps { | 
|  | name: "qux", | 
|  | } | 
|  | ` | 
|  |  | 
|  | result := GroupFixturePreparers( | 
|  | prepareForModuleTests, | 
|  | PrepareForTestWithArchMutator, | 
|  | FixtureModifyConfig(SetKatiEnabledForTests), | 
|  | PrepareForTestWithMakevars, | 
|  | ).RunTestWithBp(t, bp) | 
|  |  | 
|  | rules := result.InstallMakeRulesForTesting(t) | 
|  |  | 
|  | module := func(name string, host bool) TestingModule { | 
|  | variant := "android_common" | 
|  | if host { | 
|  | variant = result.Config.BuildOSCommonTarget.String() | 
|  | } | 
|  | return result.ModuleForTests(name, variant) | 
|  | } | 
|  |  | 
|  | outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) } | 
|  |  | 
|  | ruleForOutput := func(output string) InstallMakeRule { | 
|  | for _, rule := range rules { | 
|  | if rule.Target == output { | 
|  | return rule | 
|  | } | 
|  | } | 
|  | t.Fatalf("no make install rule for %s", output) | 
|  | return InstallMakeRule{} | 
|  | } | 
|  |  | 
|  | installRule := func(name string) InstallMakeRule { | 
|  | return ruleForOutput(filepath.Join("out/target/product/test_device/system", name)) | 
|  | } | 
|  |  | 
|  | symlinkRule := func(name string) InstallMakeRule { | 
|  | return ruleForOutput(filepath.Join("out/target/product/test_device/system/symlinks", name)) | 
|  | } | 
|  |  | 
|  | hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) } | 
|  |  | 
|  | hostInstallRule := func(name string) InstallMakeRule { | 
|  | return ruleForOutput(filepath.Join("out/host/linux-x86", name)) | 
|  | } | 
|  |  | 
|  | hostSymlinkRule := func(name string) InstallMakeRule { | 
|  | return ruleForOutput(filepath.Join("out/host/linux-x86/symlinks", name)) | 
|  | } | 
|  |  | 
|  | assertDeps := func(rule InstallMakeRule, deps ...string) { | 
|  | t.Helper() | 
|  | AssertArrayString(t, "expected inputs", deps, rule.Deps) | 
|  | } | 
|  |  | 
|  | assertOrderOnlys := func(rule InstallMakeRule, orderonlys ...string) { | 
|  | t.Helper() | 
|  | AssertArrayString(t, "expected orderonly dependencies", orderonlys, rule.OrderOnlyDeps) | 
|  | } | 
|  |  | 
|  | // Check host install rule dependencies | 
|  | assertDeps(hostInstallRule("foo"), | 
|  | hostOutputRule("foo").Output.String(), | 
|  | hostInstallRule("bar").Target, | 
|  | hostSymlinkRule("bar").Target, | 
|  | hostInstallRule("baz").Target, | 
|  | hostSymlinkRule("baz").Target, | 
|  | hostInstallRule("qux").Target, | 
|  | hostSymlinkRule("qux").Target, | 
|  | ) | 
|  | assertOrderOnlys(hostInstallRule("foo")) | 
|  |  | 
|  | // Check host symlink rule dependencies.  Host symlinks must use a normal dependency, not an | 
|  | // order-only dependency, so that the tool gets updated when the symlink is depended on. | 
|  | assertDeps(hostSymlinkRule("foo"), hostInstallRule("foo").Target) | 
|  | assertOrderOnlys(hostSymlinkRule("foo")) | 
|  |  | 
|  | // Check device install rule dependencies | 
|  | assertDeps(installRule("foo"), outputRule("foo").Output.String()) | 
|  | assertOrderOnlys(installRule("foo"), | 
|  | installRule("bar").Target, | 
|  | symlinkRule("bar").Target, | 
|  | installRule("baz").Target, | 
|  | symlinkRule("baz").Target, | 
|  | installRule("qux").Target, | 
|  | symlinkRule("qux").Target, | 
|  | ) | 
|  |  | 
|  | // Check device symlink rule dependencies.  Device symlinks could use an order-only dependency, | 
|  | // but the current implementation uses a normal dependency. | 
|  | assertDeps(symlinkRule("foo"), installRule("foo").Target) | 
|  | assertOrderOnlys(symlinkRule("foo")) | 
|  | } | 
|  |  | 
|  | type PropsTestModuleEmbedded struct { | 
|  | Embedded_prop *string | 
|  | } | 
|  |  | 
|  | type StructInSlice struct { | 
|  | G string | 
|  | H bool | 
|  | I []string | 
|  | } | 
|  |  | 
|  | type propsTestModule struct { | 
|  | ModuleBase | 
|  | DefaultableModuleBase | 
|  | props struct { | 
|  | A string `android:"arch_variant"` | 
|  | B *bool | 
|  | C []string | 
|  | } | 
|  | otherProps struct { | 
|  | PropsTestModuleEmbedded | 
|  |  | 
|  | D      *int64 | 
|  | Nested struct { | 
|  | E *string | 
|  | } | 
|  | F *string `blueprint:"mutated"` | 
|  |  | 
|  | Slice_of_struct []StructInSlice | 
|  | } | 
|  | } | 
|  |  | 
|  | func propsTestModuleFactory() Module { | 
|  | module := &propsTestModule{} | 
|  | module.AddProperties(&module.props, &module.otherProps) | 
|  | InitAndroidArchModule(module, HostAndDeviceSupported, MultilibBoth) | 
|  | InitDefaultableModule(module) | 
|  | return module | 
|  | } | 
|  |  | 
|  | type propsTestModuleDefaults struct { | 
|  | ModuleBase | 
|  | DefaultsModuleBase | 
|  | } | 
|  |  | 
|  | func propsTestModuleDefaultsFactory() Module { | 
|  | defaults := &propsTestModuleDefaults{} | 
|  | module := propsTestModule{} | 
|  | defaults.AddProperties(&module.props, &module.otherProps) | 
|  | InitDefaultsModule(defaults) | 
|  | return defaults | 
|  | } | 
|  |  | 
|  | func (p *propsTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
|  | str := "abc" | 
|  | p.otherProps.F = &str | 
|  | } | 
|  |  | 
|  | func TestUsedProperties(t *testing.T) { | 
|  | testCases := []struct { | 
|  | desc          string | 
|  | bp            string | 
|  | expectedProps []propInfo | 
|  | }{ | 
|  | { | 
|  | desc: "only name", | 
|  | bp: `test { | 
|  | name: "foo", | 
|  | } | 
|  | `, | 
|  | expectedProps: []propInfo{ | 
|  | propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | desc: "some props", | 
|  | bp: `test { | 
|  | name: "foo", | 
|  | a: "abc", | 
|  | b: true, | 
|  | d: 123, | 
|  | } | 
|  | `, | 
|  | expectedProps: []propInfo{ | 
|  | propInfo{Name: "A", Type: "string", Value: "abc"}, | 
|  | propInfo{Name: "B", Type: "bool", Value: "true"}, | 
|  | propInfo{Name: "D", Type: "int64", Value: "123"}, | 
|  | propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | desc: "unused non-pointer prop", | 
|  | bp: `test { | 
|  | name: "foo", | 
|  | b: true, | 
|  | d: 123, | 
|  | } | 
|  | `, | 
|  | expectedProps: []propInfo{ | 
|  | // for non-pointer cannot distinguish between unused and intentionally set to empty | 
|  | propInfo{Name: "A", Type: "string", Value: ""}, | 
|  | propInfo{Name: "B", Type: "bool", Value: "true"}, | 
|  | propInfo{Name: "D", Type: "int64", Value: "123"}, | 
|  | propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | desc: "nested props", | 
|  | bp: `test { | 
|  | name: "foo", | 
|  | nested: { | 
|  | e: "abc", | 
|  | } | 
|  | } | 
|  | `, | 
|  | expectedProps: []propInfo{ | 
|  | propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
|  | propInfo{Name: "Nested.E", Type: "string", Value: "abc"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | desc: "arch props", | 
|  | bp: `test { | 
|  | name: "foo", | 
|  | arch: { | 
|  | x86_64: { | 
|  | a: "abc", | 
|  | }, | 
|  | } | 
|  | } | 
|  | `, | 
|  | expectedProps: []propInfo{ | 
|  | propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "abc"}, | 
|  | propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | desc: "embedded props", | 
|  | bp: `test { | 
|  | name: "foo", | 
|  | embedded_prop: "a", | 
|  | } | 
|  | `, | 
|  | expectedProps: []propInfo{ | 
|  | propInfo{Name: "Embedded_prop", Type: "string", Value: "a"}, | 
|  | propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | desc: "struct slice", | 
|  | bp: `test { | 
|  | name: "foo", | 
|  | slice_of_struct: [ | 
|  | { | 
|  | g: "abc", | 
|  | h: false, | 
|  | i: ["baz"], | 
|  | }, | 
|  | { | 
|  | g: "def", | 
|  | h: true, | 
|  | i: [], | 
|  | }, | 
|  | ] | 
|  | } | 
|  | `, | 
|  | expectedProps: []propInfo{ | 
|  | propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
|  | propInfo{Name: "Slice_of_struct", Type: "struct slice", Values: []string{ | 
|  | `android.StructInSlice{G: abc, H: false, I: [baz]}`, | 
|  | `android.StructInSlice{G: def, H: true, I: []}`, | 
|  | }}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | desc: "defaults", | 
|  | bp: ` | 
|  | test_defaults { | 
|  | name: "foo_defaults", | 
|  | a: "a", | 
|  | b: true, | 
|  | c: ["default_c"], | 
|  | embedded_prop:"a", | 
|  | arch: { | 
|  | x86_64: { | 
|  | a: "x86_64 a", | 
|  | }, | 
|  | }, | 
|  | } | 
|  | test { | 
|  | name: "foo", | 
|  | defaults: ["foo_defaults"], | 
|  | c: ["c"], | 
|  | nested: { | 
|  | e: "nested e", | 
|  | }, | 
|  | target: { | 
|  | linux: { | 
|  | a: "a", | 
|  | }, | 
|  | }, | 
|  | } | 
|  | `, | 
|  | expectedProps: []propInfo{ | 
|  | propInfo{Name: "A", Type: "string", Value: "a"}, | 
|  | propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "x86_64 a"}, | 
|  | propInfo{Name: "B", Type: "bool", Value: "true"}, | 
|  | propInfo{Name: "C", Type: "string slice", Values: []string{"default_c", "c"}}, | 
|  | propInfo{Name: "Defaults", Type: "string slice", Values: []string{"foo_defaults"}}, | 
|  | propInfo{Name: "Embedded_prop", Type: "string", Value: "a"}, | 
|  | propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
|  | propInfo{Name: "Nested.E", Type: "string", Value: "nested e"}, | 
|  | propInfo{Name: "Target.Linux.A", Type: "string", Value: "a"}, | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | for _, tc := range testCases { | 
|  | t.Run(tc.desc, func(t *testing.T) { | 
|  | result := GroupFixturePreparers( | 
|  | PrepareForTestWithAllowMissingDependencies, | 
|  | PrepareForTestWithDefaults, | 
|  | FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
|  | ctx.RegisterModuleType("test", propsTestModuleFactory) | 
|  | ctx.RegisterModuleType("test_defaults", propsTestModuleDefaultsFactory) | 
|  | }), | 
|  | FixtureWithRootAndroidBp(tc.bp), | 
|  | ).RunTest(t) | 
|  |  | 
|  | foo := result.ModuleForTests("foo", "").Module().base() | 
|  |  | 
|  | AssertDeepEquals(t, "foo ", tc.expectedProps, foo.propertiesWithValues()) | 
|  |  | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestSortedUniqueNamedPaths(t *testing.T) { | 
|  | type np struct { | 
|  | path, name string | 
|  | } | 
|  | makePaths := func(l []np) NamedPaths { | 
|  | result := make(NamedPaths, 0, len(l)) | 
|  | for _, p := range l { | 
|  | result = append(result, NamedPath{PathForTesting(p.path), p.name}) | 
|  | } | 
|  | return result | 
|  | } | 
|  |  | 
|  | tests := []struct { | 
|  | name        string | 
|  | in          []np | 
|  | expectedOut []np | 
|  | }{ | 
|  | { | 
|  | name:        "empty", | 
|  | in:          []np{}, | 
|  | expectedOut: []np{}, | 
|  | }, | 
|  | { | 
|  | name: "all_same", | 
|  | in: []np{ | 
|  | {"a.txt", "A"}, | 
|  | {"a.txt", "A"}, | 
|  | {"a.txt", "A"}, | 
|  | {"a.txt", "A"}, | 
|  | {"a.txt", "A"}, | 
|  | }, | 
|  | expectedOut: []np{ | 
|  | {"a.txt", "A"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name: "same_path_different_names", | 
|  | in: []np{ | 
|  | {"a.txt", "C"}, | 
|  | {"a.txt", "A"}, | 
|  | {"a.txt", "D"}, | 
|  | {"a.txt", "B"}, | 
|  | {"a.txt", "E"}, | 
|  | }, | 
|  | expectedOut: []np{ | 
|  | {"a.txt", "A"}, | 
|  | {"a.txt", "B"}, | 
|  | {"a.txt", "C"}, | 
|  | {"a.txt", "D"}, | 
|  | {"a.txt", "E"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name: "different_paths_same_name", | 
|  | in: []np{ | 
|  | {"b/b.txt", "A"}, | 
|  | {"a/a.txt", "A"}, | 
|  | {"a/txt", "A"}, | 
|  | {"b", "A"}, | 
|  | {"a/b/d", "A"}, | 
|  | }, | 
|  | expectedOut: []np{ | 
|  | {"a/a.txt", "A"}, | 
|  | {"a/b/d", "A"}, | 
|  | {"a/txt", "A"}, | 
|  | {"b/b.txt", "A"}, | 
|  | {"b", "A"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name: "all_different", | 
|  | in: []np{ | 
|  | {"b/b.txt", "A"}, | 
|  | {"a/a.txt", "B"}, | 
|  | {"a/txt", "D"}, | 
|  | {"b", "C"}, | 
|  | {"a/b/d", "E"}, | 
|  | }, | 
|  | expectedOut: []np{ | 
|  | {"a/a.txt", "B"}, | 
|  | {"a/b/d", "E"}, | 
|  | {"a/txt", "D"}, | 
|  | {"b/b.txt", "A"}, | 
|  | {"b", "C"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name: "some_different", | 
|  | in: []np{ | 
|  | {"b/b.txt", "A"}, | 
|  | {"a/a.txt", "B"}, | 
|  | {"a/txt", "D"}, | 
|  | {"a/b/d", "E"}, | 
|  | {"b", "C"}, | 
|  | {"a/a.txt", "B"}, | 
|  | {"a/b/d", "E"}, | 
|  | }, | 
|  | expectedOut: []np{ | 
|  | {"a/a.txt", "B"}, | 
|  | {"a/b/d", "E"}, | 
|  | {"a/txt", "D"}, | 
|  | {"b/b.txt", "A"}, | 
|  | {"b", "C"}, | 
|  | }, | 
|  | }, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | t.Run(tt.name, func(t *testing.T) { | 
|  | actual := SortedUniqueNamedPaths(makePaths(tt.in)) | 
|  | expected := makePaths(tt.expectedOut) | 
|  | t.Logf("actual: %v", actual) | 
|  | t.Logf("expected: %v", expected) | 
|  | AssertDeepEquals(t, "SortedUniqueNamedPaths ", expected, actual) | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestSetAndroidMkEntriesWithTestOptions(t *testing.T) { | 
|  | tests := []struct { | 
|  | name        string | 
|  | testOptions CommonTestOptions | 
|  | expected    map[string][]string | 
|  | }{ | 
|  | { | 
|  | name:        "empty", | 
|  | testOptions: CommonTestOptions{}, | 
|  | expected:    map[string][]string{}, | 
|  | }, | 
|  | { | 
|  | name: "is unit test", | 
|  | testOptions: CommonTestOptions{ | 
|  | Unit_test: boolPtr(true), | 
|  | }, | 
|  | expected: map[string][]string{ | 
|  | "LOCAL_IS_UNIT_TEST": []string{"true"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name: "is not unit test", | 
|  | testOptions: CommonTestOptions{ | 
|  | Unit_test: boolPtr(false), | 
|  | }, | 
|  | expected: map[string][]string{}, | 
|  | }, | 
|  | { | 
|  | name: "empty tag", | 
|  | testOptions: CommonTestOptions{ | 
|  | Tags: []string{}, | 
|  | }, | 
|  | expected: map[string][]string{}, | 
|  | }, | 
|  | { | 
|  | name: "single tag", | 
|  | testOptions: CommonTestOptions{ | 
|  | Tags: []string{"tag1"}, | 
|  | }, | 
|  | expected: map[string][]string{ | 
|  | "LOCAL_TEST_OPTIONS_TAGS": []string{"tag1"}, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name: "multiple tag", | 
|  | testOptions: CommonTestOptions{ | 
|  | Tags: []string{"tag1", "tag2", "tag3"}, | 
|  | }, | 
|  | expected: map[string][]string{ | 
|  | "LOCAL_TEST_OPTIONS_TAGS": []string{"tag1", "tag2", "tag3"}, | 
|  | }, | 
|  | }, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | t.Run(tt.name, func(t *testing.T) { | 
|  | actualEntries := AndroidMkEntries{ | 
|  | EntryMap: map[string][]string{}, | 
|  | } | 
|  | tt.testOptions.SetAndroidMkEntries(&actualEntries) | 
|  | actual := actualEntries.EntryMap | 
|  | t.Logf("actual: %v", actual) | 
|  | t.Logf("expected: %v", tt.expected) | 
|  | AssertDeepEquals(t, "TestProcessCommonTestOptions ", tt.expected, actual) | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | type fakeBlueprintModule struct{} | 
|  |  | 
|  | func (fakeBlueprintModule) Name() string { return "foo" } | 
|  |  | 
|  | func (fakeBlueprintModule) GenerateBuildActions(blueprint.ModuleContext) {} | 
|  |  | 
|  | type sourceProducerTestModule struct { | 
|  | fakeBlueprintModule | 
|  | source Path | 
|  | } | 
|  |  | 
|  | func (s sourceProducerTestModule) Srcs() Paths { return Paths{s.source} } | 
|  |  | 
|  | type outputFileProducerTestModule struct { | 
|  | fakeBlueprintModule | 
|  | output map[string]Path | 
|  | error  map[string]error | 
|  | } | 
|  |  | 
|  | func (o outputFileProducerTestModule) OutputFiles(tag string) (Paths, error) { | 
|  | return PathsIfNonNil(o.output[tag]), o.error[tag] | 
|  | } | 
|  |  | 
|  | type pathContextAddMissingDependenciesWrapper struct { | 
|  | PathContext | 
|  | missingDeps []string | 
|  | } | 
|  |  | 
|  | func (p *pathContextAddMissingDependenciesWrapper) AddMissingDependencies(deps []string) { | 
|  | p.missingDeps = append(p.missingDeps, deps...) | 
|  | } | 
|  | func (p *pathContextAddMissingDependenciesWrapper) OtherModuleName(module blueprint.Module) string { | 
|  | return module.Name() | 
|  | } | 
|  |  | 
|  | func TestOutputFileForModule(t *testing.T) { | 
|  | testcases := []struct { | 
|  | name        string | 
|  | module      blueprint.Module | 
|  | tag         string | 
|  | env         map[string]string | 
|  | config      func(*config) | 
|  | expected    string | 
|  | missingDeps []string | 
|  | }{ | 
|  | { | 
|  | name:     "SourceFileProducer", | 
|  | module:   &sourceProducerTestModule{source: PathForTesting("foo.txt")}, | 
|  | expected: "foo.txt", | 
|  | }, | 
|  | { | 
|  | name:     "OutputFileProducer", | 
|  | module:   &outputFileProducerTestModule{output: map[string]Path{"": PathForTesting("foo.txt")}}, | 
|  | expected: "foo.txt", | 
|  | }, | 
|  | { | 
|  | name:     "OutputFileProducer_tag", | 
|  | module:   &outputFileProducerTestModule{output: map[string]Path{"foo": PathForTesting("foo.txt")}}, | 
|  | tag:      "foo", | 
|  | expected: "foo.txt", | 
|  | }, | 
|  | { | 
|  | name: "OutputFileProducer_AllowMissingDependencies", | 
|  | config: func(config *config) { | 
|  | config.TestProductVariables.Allow_missing_dependencies = boolPtr(true) | 
|  | }, | 
|  | module:      &outputFileProducerTestModule{}, | 
|  | missingDeps: []string{"foo"}, | 
|  | expected:    "missing_output_file/foo", | 
|  | }, | 
|  | } | 
|  | for _, tt := range testcases { | 
|  | config := TestConfig(buildDir, tt.env, "", nil) | 
|  | if tt.config != nil { | 
|  | tt.config(config.config) | 
|  | } | 
|  | ctx := &pathContextAddMissingDependenciesWrapper{ | 
|  | PathContext: PathContextForTesting(config), | 
|  | } | 
|  | got := OutputFileForModule(ctx, tt.module, tt.tag) | 
|  | AssertPathRelativeToTopEquals(t, "expected source path", tt.expected, got) | 
|  | AssertArrayString(t, "expected missing deps", tt.missingDeps, ctx.missingDeps) | 
|  | } | 
|  | } |