// 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 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) } }