| // 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 ( | |
| "path/filepath" | |
| "runtime" | |
| "testing" | |
| "github.com/google/blueprint" | |
| ) | |
| 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 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 sourceProducerTestModule struct { | |
| ModuleBase | |
| props struct { | |
| // A represents the source file | |
| A string | |
| } | |
| } | |
| func sourceProducerTestModuleFactory() Module { | |
| module := &sourceProducerTestModule{} | |
| module.AddProperties(&module.props) | |
| InitAndroidModule(module) | |
| return module | |
| } | |
| func (s sourceProducerTestModule) GenerateAndroidBuildActions(ModuleContext) {} | |
| func (s sourceProducerTestModule) Srcs() Paths { return PathsForTesting(s.props.A) } | |
| type outputFilesTestModule struct { | |
| ModuleBase | |
| props struct { | |
| // A represents the tag | |
| A string | |
| // B represents the output file for tag A | |
| B string | |
| } | |
| } | |
| func outputFilesTestModuleFactory() Module { | |
| module := &outputFilesTestModule{} | |
| module.AddProperties(&module.props) | |
| InitAndroidModule(module) | |
| return module | |
| } | |
| func (o outputFilesTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { | |
| if o.props.A != "" || o.props.B != "" { | |
| ctx.SetOutputFiles(PathsForTesting(o.props.B), o.props.A) | |
| } | |
| // This is to simulate the case that some module uses an object to set its | |
| // OutputFilesProvider, but the object itself is empty. | |
| ctx.SetOutputFiles(Paths{}, "missing") | |
| } | |
| type pathContextAddMissingDependenciesWrapper struct { | |
| PathContext | |
| OtherModuleProviderContext | |
| 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 (p *pathContextAddMissingDependenciesWrapper) Module() Module { return nil } | |
| func (p *pathContextAddMissingDependenciesWrapper) GetOutputFiles() OutputFilesInfo { | |
| return OutputFilesInfo{} | |
| } | |
| func (p *pathContextAddMissingDependenciesWrapper) EqualModules(m1, m2 Module) bool { | |
| return m1 == m2 | |
| } | |
| func TestOutputFileForModule(t *testing.T) { | |
| testcases := []struct { | |
| name string | |
| bp string | |
| tag string | |
| expected string | |
| missingDeps []string | |
| env map[string]string | |
| config func(*config) | |
| }{ | |
| { | |
| name: "SourceFileProducer", | |
| bp: `spt_module { | |
| name: "test_module", | |
| a: "spt.txt", | |
| } | |
| `, | |
| tag: "", | |
| expected: "spt.txt", | |
| }, | |
| { | |
| name: "OutputFileProviderEmptyStringTag", | |
| bp: `oft_module { | |
| name: "test_module", | |
| a: "", | |
| b: "empty.txt", | |
| } | |
| `, | |
| tag: "", | |
| expected: "empty.txt", | |
| }, | |
| { | |
| name: "OutputFileProviderTag", | |
| bp: `oft_module { | |
| name: "test_module", | |
| a: "foo", | |
| b: "foo.txt", | |
| } | |
| `, | |
| tag: "foo", | |
| expected: "foo.txt", | |
| }, | |
| { | |
| name: "OutputFileAllowMissingDependencies", | |
| bp: `oft_module { | |
| name: "test_module", | |
| } | |
| `, | |
| tag: "missing", | |
| expected: "missing_output_file/test_module", | |
| missingDeps: []string{"test_module"}, | |
| config: func(config *config) { | |
| config.TestProductVariables.Allow_missing_dependencies = boolPtr(true) | |
| }, | |
| }, | |
| } | |
| for _, tt := range testcases { | |
| t.Run(tt.name, func(t *testing.T) { | |
| result := GroupFixturePreparers( | |
| PrepareForTestWithDefaults, | |
| FixtureRegisterWithContext(func(ctx RegistrationContext) { | |
| ctx.RegisterModuleType("spt_module", sourceProducerTestModuleFactory) | |
| ctx.RegisterModuleType("oft_module", outputFilesTestModuleFactory) | |
| }), | |
| FixtureWithRootAndroidBp(tt.bp), | |
| ).RunTest(t) | |
| config := TestConfig(buildDir, tt.env, tt.bp, nil) | |
| if tt.config != nil { | |
| tt.config(config.config) | |
| } | |
| ctx := &pathContextAddMissingDependenciesWrapper{ | |
| PathContext: PathContextForTesting(config), | |
| OtherModuleProviderContext: result.TestContext.OtherModuleProviderAdaptor(), | |
| } | |
| got := OutputFileForModule(ctx, result.ModuleForTests("test_module", "").Module(), tt.tag) | |
| AssertPathRelativeToTopEquals(t, "expected output path", tt.expected, got) | |
| AssertArrayString(t, "expected missing deps", tt.missingDeps, ctx.missingDeps) | |
| }) | |
| } | |
| } | |
| func TestVintfFragmentModulesChecksPartition(t *testing.T) { | |
| bp := ` | |
| vintf_fragment { | |
| name: "vintfModA", | |
| src: "test_vintf_file", | |
| vendor: true, | |
| } | |
| deps { | |
| name: "modA", | |
| vintf_fragment_modules: [ | |
| "vintfModA", | |
| ] | |
| } | |
| ` | |
| testPreparer := GroupFixturePreparers( | |
| PrepareForTestWithAndroidBuildComponents, | |
| prepareForModuleTests, | |
| ) | |
| testPreparer. | |
| ExtendWithErrorHandler(FixtureExpectsOneErrorPattern( | |
| "Module .+ and Vintf_fragment .+ are installed to different partitions.")). | |
| RunTestWithBp(t, bp) | |
| } |