// Copyright 2017 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 ( "errors" "path/filepath" "reflect" "testing" "github.com/google/blueprint" ) func TestDependingOnModuleInSameNamespace(t *testing.T) { ctx := setupTest(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", } test_module { name: "b", deps: ["a"], } `, }, ) a := getModule(ctx, "a") b := getModule(ctx, "b") if !dependsOn(ctx, b, a) { t.Errorf("module b does not depend on module a in the same namespace") } } func TestDependingOnModuleInRootNamespace(t *testing.T) { ctx := setupTest(t, map[string]string{ ".": ` test_module { name: "b", deps: ["a"], } test_module { name: "a", } `, }, ) a := getModule(ctx, "a") b := getModule(ctx, "b") if !dependsOn(ctx, b, a) { t.Errorf("module b in root namespace does not depend on module a in the root namespace") } } func TestImplicitlyImportRootNamespace(t *testing.T) { _ = setupTest(t, map[string]string{ ".": ` test_module { name: "a", } `, "dir1": ` soong_namespace { } test_module { name: "b", deps: ["a"], } `, }, ) // setupTest will report any errors } func TestDependingOnBlueprintModuleInRootNamespace(t *testing.T) { _ = setupTest(t, map[string]string{ ".": ` blueprint_test_module { name: "a", } `, "dir1": ` soong_namespace { } blueprint_test_module { name: "b", deps: ["a"], } `, }, ) // setupTest will report any errors } func TestDependingOnModuleInImportedNamespace(t *testing.T) { ctx := setupTest(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", } `, "dir2": ` soong_namespace { imports: ["dir1"], } test_module { name: "b", deps: ["a"], } `, }, ) a := getModule(ctx, "a") b := getModule(ctx, "b") if !dependsOn(ctx, b, a) { t.Errorf("module b does not depend on module a in the same namespace") } } func TestDependingOnModuleInNonImportedNamespace(t *testing.T) { _, errs := setupTestExpectErrs(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", } `, "dir2": ` soong_namespace { } test_module { name: "a", } `, "dir3": ` soong_namespace { } test_module { name: "b", deps: ["a"], } `, }, ) expectedErrors := []error{ errors.New( `dir3/Android.bp:4:4: "b" depends on undefined module "a" Module "b" is defined in namespace "dir3" which can read these 2 namespaces: ["dir3" "."] Module "a" can be found in these namespaces: ["dir1" "dir2"]`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } func TestDependingOnModuleByFullyQualifiedReference(t *testing.T) { ctx := setupTest(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", } `, "dir2": ` soong_namespace { } test_module { name: "b", deps: ["//dir1:a"], } `, }, ) a := getModule(ctx, "a") b := getModule(ctx, "b") if !dependsOn(ctx, b, a) { t.Errorf("module b does not depend on module a") } } func TestSameNameInTwoNamespaces(t *testing.T) { ctx := setupTest(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", id: "1", } test_module { name: "b", deps: ["a"], id: "2", } `, "dir2": ` soong_namespace { } test_module { name: "a", id:"3", } test_module { name: "b", deps: ["a"], id:"4", } `, }, ) one := findModuleById(ctx, "1") two := findModuleById(ctx, "2") three := findModuleById(ctx, "3") four := findModuleById(ctx, "4") if !dependsOn(ctx, two, one) { t.Fatalf("Module 2 does not depend on module 1 in its namespace") } if dependsOn(ctx, two, three) { t.Fatalf("Module 2 depends on module 3 in another namespace") } if !dependsOn(ctx, four, three) { t.Fatalf("Module 4 does not depend on module 3 in its namespace") } if dependsOn(ctx, four, one) { t.Fatalf("Module 4 depends on module 1 in another namespace") } } func TestSearchOrder(t *testing.T) { ctx := setupTest(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", id: "1", } `, "dir2": ` soong_namespace { } test_module { name: "a", id:"2", } test_module { name: "b", id:"3", } `, "dir3": ` soong_namespace { } test_module { name: "a", id:"4", } test_module { name: "b", id:"5", } test_module { name: "c", id:"6", } `, ".": ` test_module { name: "a", id: "7", } test_module { name: "b", id: "8", } test_module { name: "c", id: "9", } test_module { name: "d", id: "10", } `, "dir4": ` soong_namespace { imports: ["dir1", "dir2", "dir3"] } test_module { name: "test_me", id:"0", deps: ["a", "b", "c", "d"], } `, }, ) testMe := findModuleById(ctx, "0") if !dependsOn(ctx, testMe, findModuleById(ctx, "1")) { t.Errorf("test_me doesn't depend on id 1") } if !dependsOn(ctx, testMe, findModuleById(ctx, "3")) { t.Errorf("test_me doesn't depend on id 3") } if !dependsOn(ctx, testMe, findModuleById(ctx, "6")) { t.Errorf("test_me doesn't depend on id 6") } if !dependsOn(ctx, testMe, findModuleById(ctx, "10")) { t.Errorf("test_me doesn't depend on id 10") } if numDeps(ctx, testMe) != 4 { t.Errorf("num dependencies of test_me = %v, not 4\n", numDeps(ctx, testMe)) } } func TestTwoNamespacesCanImportEachOther(t *testing.T) { _ = setupTest(t, map[string]string{ "dir1": ` soong_namespace { imports: ["dir2"] } test_module { name: "a", } test_module { name: "c", deps: ["b"], } `, "dir2": ` soong_namespace { imports: ["dir1"], } test_module { name: "b", deps: ["a"], } `, }, ) // setupTest will report any errors } func TestImportingNonexistentNamespace(t *testing.T) { _, errs := setupTestExpectErrs(t, map[string]string{ "dir1": ` soong_namespace { imports: ["a_nonexistent_namespace"] } test_module { name: "a", deps: ["a_nonexistent_module"] } `, }, ) // should complain about the missing namespace and not complain about the unresolvable dependency expectedErrors := []error{ errors.New(`dir1/Android.bp:2:4: module "soong_namespace": namespace a_nonexistent_namespace does not exist`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } func TestNamespacesDontInheritParentNamespaces(t *testing.T) { _, errs := setupTestExpectErrs(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", } `, "dir1/subdir1": ` soong_namespace { } test_module { name: "b", deps: ["a"], } `, }, ) expectedErrors := []error{ errors.New(`dir1/subdir1/Android.bp:4:4: "b" depends on undefined module "a" Module "b" is defined in namespace "dir1/subdir1" which can read these 2 namespaces: ["dir1/subdir1" "."] Module "a" can be found in these namespaces: ["dir1"]`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } func TestModulesDoReceiveParentNamespace(t *testing.T) { _ = setupTest(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", } `, "dir1/subdir": ` test_module { name: "b", deps: ["a"], } `, }, ) // setupTest will report any errors } func TestNamespaceImportsNotTransitive(t *testing.T) { _, errs := setupTestExpectErrs(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", } `, "dir2": ` soong_namespace { imports: ["dir1"], } test_module { name: "b", deps: ["a"], } `, "dir3": ` soong_namespace { imports: ["dir2"], } test_module { name: "c", deps: ["a"], } `, }, ) expectedErrors := []error{ errors.New(`dir3/Android.bp:5:4: "c" depends on undefined module "a" Module "c" is defined in namespace "dir3" which can read these 3 namespaces: ["dir3" "dir2" "."] Module "a" can be found in these namespaces: ["dir1"]`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } func TestTwoNamepacesInSameDir(t *testing.T) { _, errs := setupTestExpectErrs(t, map[string]string{ "dir1": ` soong_namespace { } soong_namespace { } `, }, ) expectedErrors := []error{ errors.New(`dir1/Android.bp:4:4: namespace dir1 already exists`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } func TestNamespaceNotAtTopOfFile(t *testing.T) { _, errs := setupTestExpectErrs(t, map[string]string{ "dir1": ` test_module { name: "a" } soong_namespace { } `, }, ) expectedErrors := []error{ errors.New(`dir1/Android.bp:5:4: a namespace must be the first module in the file`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } func TestTwoModulesWithSameNameInSameNamespace(t *testing.T) { _, errs := setupTestExpectErrs(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a" } test_module { name: "a" } `, }, ) expectedErrors := []error{ errors.New(`dir1/Android.bp:7:4: module "a" already defined dir1/Android.bp:4:4 <-- previous definition here`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } func TestDeclaringNamespaceInNonAndroidBpFile(t *testing.T) { _, errs := setupTestFromFiles(t, map[string][]byte{ "Android.bp": []byte(` build = ["include.bp"] `), "include.bp": []byte(` soong_namespace { } `), }, ) expectedErrors := []error{ errors.New(`include.bp:2:5: A namespace may only be declared in a file named Android.bp`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } // so that the generated .ninja file will have consistent names func TestConsistentNamespaceNames(t *testing.T) { ctx := setupTest(t, map[string]string{ "dir1": "soong_namespace{}", "dir2": "soong_namespace{}", "dir3": "soong_namespace{}", }) ns1, _ := ctx.NameResolver.namespaceAt("dir1") ns2, _ := ctx.NameResolver.namespaceAt("dir2") ns3, _ := ctx.NameResolver.namespaceAt("dir3") actualIds := []string{ns1.id, ns2.id, ns3.id} expectedIds := []string{"1", "2", "3"} if !reflect.DeepEqual(actualIds, expectedIds) { t.Errorf("Incorrect namespace ids.\nactual: %s\nexpected: %s\n", actualIds, expectedIds) } } // so that the generated .ninja file will have consistent names func TestRename(t *testing.T) { _ = setupTest(t, map[string]string{ "dir1": ` soong_namespace { } test_module { name: "a", deps: ["c"], } test_module { name: "b", rename: "c", } `}) // setupTest will report any errors } // some utils to support the tests func mockFiles(bps map[string]string) (files map[string][]byte) { files = make(map[string][]byte, len(bps)) files["Android.bp"] = []byte("") for dir, text := range bps { files[filepath.Join(dir, "Android.bp")] = []byte(text) } return files } func setupTestFromFiles(t *testing.T, bps MockFS) (ctx *TestContext, errs []error) { result := GroupFixturePreparers( FixtureModifyContext(func(ctx *TestContext) { ctx.RegisterModuleType("test_module", newTestModule) ctx.RegisterModuleType("soong_namespace", NamespaceFactory) ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule) ctx.PreArchMutators(RegisterNamespaceMutator) ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) { ctx.BottomUp("rename", renameMutator) }) }), bps.AddToFixture(), ). // Ignore errors for now so tests can check them later. ExtendWithErrorHandler(FixtureIgnoreErrors). RunTest(t) return result.TestContext, result.Errs } func setupTestExpectErrs(t *testing.T, bps map[string]string) (ctx *TestContext, errs []error) { files := make(map[string][]byte, len(bps)) files["Android.bp"] = []byte("") for dir, text := range bps { files[filepath.Join(dir, "Android.bp")] = []byte(text) } return setupTestFromFiles(t, files) } func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) { t.Helper() ctx, errs := setupTestExpectErrs(t, bps) FailIfErrored(t, errs) return ctx } func dependsOn(ctx *TestContext, module TestingModule, possibleDependency TestingModule) bool { depends := false visit := func(dependency blueprint.Module) { if dependency == possibleDependency.module { depends = true } } ctx.VisitDirectDeps(module.module, visit) return depends } func numDeps(ctx *TestContext, module TestingModule) int { count := 0 visit := func(dependency blueprint.Module) { count++ } ctx.VisitDirectDeps(module.module, visit) return count } func getModule(ctx *TestContext, moduleName string) TestingModule { return ctx.ModuleForTests(moduleName, "") } func findModuleById(ctx *TestContext, id string) (module TestingModule) { visit := func(candidate blueprint.Module) { testModule, ok := candidate.(*testModule) if ok { if testModule.properties.Id == id { module = TestingModule{testModule} } } } ctx.VisitAllModules(visit) return module } type testModule struct { ModuleBase properties struct { Rename string Deps []string Id string } } func (m *testModule) DepsMutator(ctx BottomUpMutatorContext) { if m.properties.Rename != "" { ctx.Rename(m.properties.Rename) } for _, d := range m.properties.Deps { ctx.AddDependency(ctx.Module(), nil, d) } } func (m *testModule) GenerateAndroidBuildActions(ModuleContext) { } func renameMutator(ctx BottomUpMutatorContext) { if m, ok := ctx.Module().(*testModule); ok { if m.properties.Rename != "" { ctx.Rename(m.properties.Rename) } } } func newTestModule() Module { m := &testModule{} m.AddProperties(&m.properties) InitAndroidModule(m) return m } type blueprintTestModule struct { blueprint.SimpleName properties struct { Deps []string } } func (b *blueprintTestModule) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { return b.properties.Deps } func (b *blueprintTestModule) GenerateBuildActions(blueprint.ModuleContext) { } func newBlueprintTestModule() (blueprint.Module, []interface{}) { m := &blueprintTestModule{} return m, []interface{}{&m.properties, &m.SimpleName.Properties} }