| // Copyright 2014 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 blueprint |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "path/filepath" |
| "reflect" |
| "strings" |
| "sync" |
| "testing" |
| "time" |
| |
| "github.com/google/blueprint/parser" |
| ) |
| |
| type Walker interface { |
| Walk() bool |
| } |
| |
| func walkDependencyGraph(ctx *Context, topModule *moduleInfo, allowDuplicates bool) (string, string) { |
| var outputDown string |
| var outputUp string |
| ctx.walkDeps(topModule, allowDuplicates, |
| func(dep depInfo, parent *moduleInfo) bool { |
| outputDown += ctx.ModuleName(dep.module.logicModule) |
| if tag, ok := dep.tag.(walkerDepsTag); ok { |
| if !tag.follow { |
| return false |
| } |
| } |
| if dep.module.logicModule.(Walker).Walk() { |
| return true |
| } |
| |
| return false |
| }, |
| func(dep depInfo, parent *moduleInfo) { |
| outputUp += ctx.ModuleName(dep.module.logicModule) |
| }) |
| return outputDown, outputUp |
| } |
| |
| type depsProvider interface { |
| Deps() []string |
| IgnoreDeps() []string |
| } |
| |
| type fooModule struct { |
| SimpleName |
| properties struct { |
| Deps []string |
| Ignored_deps []string |
| Foo string |
| } |
| } |
| |
| func newFooModule() (Module, []interface{}) { |
| m := &fooModule{} |
| return m, []interface{}{&m.properties, &m.SimpleName.Properties} |
| } |
| |
| func (f *fooModule) GenerateBuildActions(ModuleContext) { |
| } |
| |
| func (f *fooModule) Deps() []string { |
| return f.properties.Deps |
| } |
| |
| func (f *fooModule) IgnoreDeps() []string { |
| return f.properties.Ignored_deps |
| } |
| |
| func (f *fooModule) Foo() string { |
| return f.properties.Foo |
| } |
| |
| func (f *fooModule) Walk() bool { |
| return true |
| } |
| |
| type barModule struct { |
| SimpleName |
| properties struct { |
| Deps []string |
| Ignored_deps []string |
| Bar bool |
| } |
| } |
| |
| func newBarModule() (Module, []interface{}) { |
| m := &barModule{} |
| return m, []interface{}{&m.properties, &m.SimpleName.Properties} |
| } |
| |
| func (b *barModule) Deps() []string { |
| return b.properties.Deps |
| } |
| |
| func (b *barModule) IgnoreDeps() []string { |
| return b.properties.Ignored_deps |
| } |
| |
| func (b *barModule) GenerateBuildActions(ModuleContext) { |
| } |
| |
| func (b *barModule) Bar() bool { |
| return b.properties.Bar |
| } |
| |
| func (b *barModule) Walk() bool { |
| return false |
| } |
| |
| type walkerDepsTag struct { |
| BaseDependencyTag |
| // True if the dependency should be followed, false otherwise. |
| follow bool |
| } |
| |
| func depsMutator(mctx BottomUpMutatorContext) { |
| if m, ok := mctx.Module().(depsProvider); ok { |
| mctx.AddDependency(mctx.Module(), walkerDepsTag{follow: false}, m.IgnoreDeps()...) |
| mctx.AddDependency(mctx.Module(), walkerDepsTag{follow: true}, m.Deps()...) |
| } |
| } |
| |
| func TestContextParse(t *testing.T) { |
| ctx := NewContext() |
| ctx.RegisterModuleType("foo_module", newFooModule) |
| ctx.RegisterModuleType("bar_module", newBarModule) |
| |
| r := bytes.NewBufferString(` |
| foo_module { |
| name: "MyFooModule", |
| deps: ["MyBarModule"], |
| } |
| |
| bar_module { |
| name: "MyBarModule", |
| } |
| `) |
| |
| _, _, errs := ctx.parseOne(".", "Blueprint", r, parser.NewScope(nil), nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected parse errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| _, errs = ctx.ResolveDependencies(nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected dep errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| } |
| |
| // > |===B---D - represents a non-walkable edge |
| // > A = represents a walkable edge |
| // > |===C===E---G |
| // > | | A should not be visited because it's the root node. |
| // > |===F===| B, D and E should not be walked. |
| func TestWalkDeps(t *testing.T) { |
| ctx := NewContext() |
| ctx.MockFileSystem(map[string][]byte{ |
| "Android.bp": []byte(` |
| foo_module { |
| name: "A", |
| deps: ["B", "C"], |
| } |
| |
| bar_module { |
| name: "B", |
| deps: ["D"], |
| } |
| |
| foo_module { |
| name: "C", |
| deps: ["E", "F"], |
| } |
| |
| foo_module { |
| name: "D", |
| } |
| |
| bar_module { |
| name: "E", |
| deps: ["G"], |
| } |
| |
| foo_module { |
| name: "F", |
| deps: ["G"], |
| } |
| |
| foo_module { |
| name: "G", |
| } |
| `), |
| }) |
| |
| ctx.RegisterModuleType("foo_module", newFooModule) |
| ctx.RegisterModuleType("bar_module", newBarModule) |
| ctx.RegisterBottomUpMutator("deps", depsMutator) |
| _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected parse errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| _, errs = ctx.ResolveDependencies(nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected dep errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule() |
| outputDown, outputUp := walkDependencyGraph(ctx, topModule, false) |
| if outputDown != "BCEFG" { |
| t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEFG", outputDown) |
| } |
| if outputUp != "BEGFC" { |
| t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BEGFC", outputUp) |
| } |
| } |
| |
| // > |===B---D - represents a non-walkable edge |
| // > A = represents a walkable edge |
| // > |===C===E===\ A should not be visited because it's the root node. |
| // > | | B, D should not be walked. |
| // > |===F===G===H G should be visited multiple times |
| // > \===/ H should only be visited once |
| func TestWalkDepsDuplicates(t *testing.T) { |
| ctx := NewContext() |
| ctx.MockFileSystem(map[string][]byte{ |
| "Android.bp": []byte(` |
| foo_module { |
| name: "A", |
| deps: ["B", "C"], |
| } |
| |
| bar_module { |
| name: "B", |
| deps: ["D"], |
| } |
| |
| foo_module { |
| name: "C", |
| deps: ["E", "F"], |
| } |
| |
| foo_module { |
| name: "D", |
| } |
| |
| foo_module { |
| name: "E", |
| deps: ["G"], |
| } |
| |
| foo_module { |
| name: "F", |
| deps: ["G", "G"], |
| } |
| |
| foo_module { |
| name: "G", |
| deps: ["H"], |
| } |
| |
| foo_module { |
| name: "H", |
| } |
| `), |
| }) |
| |
| ctx.RegisterModuleType("foo_module", newFooModule) |
| ctx.RegisterModuleType("bar_module", newBarModule) |
| ctx.RegisterBottomUpMutator("deps", depsMutator) |
| _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected parse errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| _, errs = ctx.ResolveDependencies(nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected dep errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule() |
| outputDown, outputUp := walkDependencyGraph(ctx, topModule, true) |
| if outputDown != "BCEGHFGG" { |
| t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEGHFGG", outputDown) |
| } |
| if outputUp != "BHGEGGFC" { |
| t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BHGEGGFC", outputUp) |
| } |
| } |
| |
| // > - represents a non-walkable edge |
| // > A = represents a walkable edge |
| // > |===B-------\ A should not be visited because it's the root node. |
| // > | | B -> D should not be walked. |
| // > |===C===D===E B -> C -> D -> E should be walked |
| func TestWalkDepsDuplicates_IgnoreFirstPath(t *testing.T) { |
| ctx := NewContext() |
| ctx.MockFileSystem(map[string][]byte{ |
| "Android.bp": []byte(` |
| foo_module { |
| name: "A", |
| deps: ["B"], |
| } |
| |
| foo_module { |
| name: "B", |
| deps: ["C"], |
| ignored_deps: ["D"], |
| } |
| |
| foo_module { |
| name: "C", |
| deps: ["D"], |
| } |
| |
| foo_module { |
| name: "D", |
| deps: ["E"], |
| } |
| |
| foo_module { |
| name: "E", |
| } |
| `), |
| }) |
| |
| ctx.RegisterModuleType("foo_module", newFooModule) |
| ctx.RegisterModuleType("bar_module", newBarModule) |
| ctx.RegisterBottomUpMutator("deps", depsMutator) |
| _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected parse errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| _, errs = ctx.ResolveDependencies(nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected dep errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule() |
| outputDown, outputUp := walkDependencyGraph(ctx, topModule, true) |
| expectedDown := "BDCDE" |
| if outputDown != expectedDown { |
| t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: %s", outputDown, expectedDown) |
| } |
| expectedUp := "DEDCB" |
| if outputUp != expectedUp { |
| t.Errorf("unexpected walkDeps behaviour: %s\nup should be: %s", outputUp, expectedUp) |
| } |
| } |
| |
| func TestCreateModule(t *testing.T) { |
| ctx := newContext() |
| ctx.MockFileSystem(map[string][]byte{ |
| "Android.bp": []byte(` |
| foo_module { |
| name: "A", |
| deps: ["B", "C"], |
| } |
| `), |
| }) |
| |
| ctx.RegisterTopDownMutator("create", createTestMutator) |
| ctx.RegisterBottomUpMutator("deps", depsMutator) |
| |
| ctx.RegisterModuleType("foo_module", newFooModule) |
| ctx.RegisterModuleType("bar_module", newBarModule) |
| _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected parse errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| _, errs = ctx.ResolveDependencies(nil) |
| if len(errs) > 0 { |
| t.Errorf("unexpected dep errors:") |
| for _, err := range errs { |
| t.Errorf(" %s", err) |
| } |
| t.FailNow() |
| } |
| |
| a := ctx.moduleGroupFromName("A", nil).modules.firstModule().logicModule.(*fooModule) |
| b := ctx.moduleGroupFromName("B", nil).modules.firstModule().logicModule.(*barModule) |
| c := ctx.moduleGroupFromName("C", nil).modules.firstModule().logicModule.(*barModule) |
| d := ctx.moduleGroupFromName("D", nil).modules.firstModule().logicModule.(*fooModule) |
| |
| checkDeps := func(m Module, expected string) { |
| var deps []string |
| ctx.VisitDirectDeps(m, func(m Module) { |
| deps = append(deps, ctx.ModuleName(m)) |
| }) |
| got := strings.Join(deps, ",") |
| if got != expected { |
| t.Errorf("unexpected %q dependencies, got %q expected %q", |
| ctx.ModuleName(m), got, expected) |
| } |
| } |
| |
| checkDeps(a, "B,C") |
| checkDeps(b, "D") |
| checkDeps(c, "D") |
| checkDeps(d, "") |
| } |
| |
| func createTestMutator(ctx TopDownMutatorContext) { |
| type props struct { |
| Name string |
| Deps []string |
| } |
| |
| ctx.CreateModule(newBarModule, "new_bar", &props{ |
| Name: "B", |
| Deps: []string{"D"}, |
| }) |
| |
| ctx.CreateModule(newBarModule, "new_bar", &props{ |
| Name: "C", |
| Deps: []string{"D"}, |
| }) |
| |
| ctx.CreateModule(newFooModule, "new_foo", &props{ |
| Name: "D", |
| }) |
| } |
| |
| func TestWalkFileOrder(t *testing.T) { |
| // Run the test once to see how long it normally takes |
| start := time.Now() |
| doTestWalkFileOrder(t, time.Duration(0)) |
| duration := time.Since(start) |
| |
| // Run the test again, but put enough of a sleep into each visitor to detect ordering |
| // problems if they exist |
| doTestWalkFileOrder(t, duration) |
| } |
| |
| // test that WalkBlueprintsFiles calls asyncVisitor in the right order |
| func doTestWalkFileOrder(t *testing.T, sleepDuration time.Duration) { |
| // setup mock context |
| ctx := newContext() |
| mockFiles := map[string][]byte{ |
| "Android.bp": []byte(` |
| sample_module { |
| name: "a", |
| } |
| `), |
| "dir1/Android.bp": []byte(` |
| sample_module { |
| name: "b", |
| } |
| `), |
| "dir1/dir2/Android.bp": []byte(` |
| sample_module { |
| name: "c", |
| } |
| `), |
| } |
| ctx.MockFileSystem(mockFiles) |
| |
| // prepare to monitor the visit order |
| visitOrder := []string{} |
| visitLock := sync.Mutex{} |
| correctVisitOrder := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"} |
| |
| // sleep longer when processing the earlier files |
| chooseSleepDuration := func(fileName string) (duration time.Duration) { |
| duration = time.Duration(0) |
| for i := len(correctVisitOrder) - 1; i >= 0; i-- { |
| if fileName == correctVisitOrder[i] { |
| return duration |
| } |
| duration = duration + sleepDuration |
| } |
| panic("unrecognized file name " + fileName) |
| } |
| |
| visitor := func(file *parser.File) { |
| time.Sleep(chooseSleepDuration(file.Name)) |
| visitLock.Lock() |
| defer visitLock.Unlock() |
| visitOrder = append(visitOrder, file.Name) |
| } |
| keys := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"} |
| |
| // visit the blueprints files |
| ctx.WalkBlueprintsFiles(".", keys, visitor) |
| |
| // check the order |
| if !reflect.DeepEqual(visitOrder, correctVisitOrder) { |
| t.Errorf("Incorrect visit order; expected %v, got %v", correctVisitOrder, visitOrder) |
| } |
| } |
| |
| // test that WalkBlueprintsFiles reports syntax errors |
| func TestWalkingWithSyntaxError(t *testing.T) { |
| // setup mock context |
| ctx := newContext() |
| mockFiles := map[string][]byte{ |
| "Android.bp": []byte(` |
| sample_module { |
| name: "a" "b", |
| } |
| `), |
| "dir1/Android.bp": []byte(` |
| sample_module { |
| name: "b", |
| `), |
| "dir1/dir2/Android.bp": []byte(` |
| sample_module { |
| name: "c", |
| } |
| `), |
| } |
| ctx.MockFileSystem(mockFiles) |
| |
| keys := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"} |
| |
| // visit the blueprints files |
| _, errs := ctx.WalkBlueprintsFiles(".", keys, func(file *parser.File) {}) |
| |
| expectedErrs := []error{ |
| errors.New(`Android.bp:3:18: expected "}", found String`), |
| errors.New(`dir1/Android.bp:4:3: expected "}", found EOF`), |
| } |
| if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) { |
| t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs) |
| } |
| |
| } |
| |
| func TestParseFailsForModuleWithoutName(t *testing.T) { |
| ctx := NewContext() |
| ctx.MockFileSystem(map[string][]byte{ |
| "Android.bp": []byte(` |
| foo_module { |
| name: "A", |
| } |
| |
| bar_module { |
| deps: ["A"], |
| } |
| `), |
| }) |
| ctx.RegisterModuleType("foo_module", newFooModule) |
| ctx.RegisterModuleType("bar_module", newBarModule) |
| |
| _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil) |
| |
| expectedErrs := []error{ |
| errors.New(`Android.bp:6:4: property 'name' is missing from a module`), |
| } |
| if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) { |
| t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs) |
| } |
| } |
| |
| func Test_findVariant(t *testing.T) { |
| module := &moduleInfo{ |
| variant: variant{ |
| name: "normal_local", |
| variations: variationMap{ |
| "normal": "normal", |
| "local": "local", |
| }, |
| dependencyVariations: variationMap{ |
| "normal": "normal", |
| }, |
| }, |
| } |
| |
| type alias struct { |
| variant variant |
| target int |
| } |
| |
| makeDependencyGroup := func(in ...interface{}) *moduleGroup { |
| group := &moduleGroup{ |
| name: "dep", |
| } |
| for _, x := range in { |
| switch m := x.(type) { |
| case *moduleInfo: |
| m.group = group |
| group.modules = append(group.modules, m) |
| case alias: |
| // aliases may need to target modules that haven't been processed |
| // yet, put an empty alias in for now. |
| group.modules = append(group.modules, nil) |
| default: |
| t.Fatalf("unexpected type %T", x) |
| } |
| } |
| |
| for i, x := range in { |
| switch m := x.(type) { |
| case *moduleInfo: |
| // already added in the first pass |
| case alias: |
| group.modules[i] = &moduleAlias{ |
| variant: m.variant, |
| target: group.modules[m.target].moduleOrAliasTarget(), |
| } |
| default: |
| t.Fatalf("unexpected type %T", x) |
| } |
| } |
| |
| return group |
| } |
| |
| tests := []struct { |
| name string |
| possibleDeps *moduleGroup |
| variations []Variation |
| far bool |
| reverse bool |
| want string |
| }{ |
| { |
| name: "AddVariationDependencies(nil)", |
| // A dependency that matches the non-local variations of the module |
| possibleDeps: makeDependencyGroup( |
| &moduleInfo{ |
| variant: variant{ |
| name: "normal", |
| variations: variationMap{ |
| "normal": "normal", |
| }, |
| }, |
| }, |
| ), |
| variations: nil, |
| far: false, |
| reverse: false, |
| want: "normal", |
| }, |
| { |
| name: "AddVariationDependencies(nil) to alias", |
| // A dependency with an alias that matches the non-local variations of the module |
| possibleDeps: makeDependencyGroup( |
| alias{ |
| variant: variant{ |
| name: "normal", |
| variations: variationMap{ |
| "normal": "normal", |
| }, |
| }, |
| target: 1, |
| }, |
| &moduleInfo{ |
| variant: variant{ |
| name: "normal_a", |
| variations: variationMap{ |
| "normal": "normal", |
| "a": "a", |
| }, |
| }, |
| }, |
| ), |
| variations: nil, |
| far: false, |
| reverse: false, |
| want: "normal_a", |
| }, |
| { |
| name: "AddVariationDependencies(a)", |
| // A dependency with local variations |
| possibleDeps: makeDependencyGroup( |
| &moduleInfo{ |
| variant: variant{ |
| name: "normal_a", |
| variations: variationMap{ |
| "normal": "normal", |
| "a": "a", |
| }, |
| }, |
| }, |
| ), |
| variations: []Variation{{"a", "a"}}, |
| far: false, |
| reverse: false, |
| want: "normal_a", |
| }, |
| { |
| name: "AddFarVariationDependencies(far)", |
| // A dependency with far variations |
| possibleDeps: makeDependencyGroup( |
| &moduleInfo{ |
| variant: variant{ |
| name: "", |
| variations: nil, |
| }, |
| }, |
| &moduleInfo{ |
| variant: variant{ |
| name: "far", |
| variations: variationMap{ |
| "far": "far", |
| }, |
| }, |
| }, |
| ), |
| variations: []Variation{{"far", "far"}}, |
| far: true, |
| reverse: false, |
| want: "far", |
| }, |
| { |
| name: "AddFarVariationDependencies(far) to alias", |
| // A dependency with far variations and aliases |
| possibleDeps: makeDependencyGroup( |
| alias{ |
| variant: variant{ |
| name: "far", |
| variations: variationMap{ |
| "far": "far", |
| }, |
| }, |
| target: 2, |
| }, |
| &moduleInfo{ |
| variant: variant{ |
| name: "far_a", |
| variations: variationMap{ |
| "far": "far", |
| "a": "a", |
| }, |
| }, |
| }, |
| &moduleInfo{ |
| variant: variant{ |
| name: "far_b", |
| variations: variationMap{ |
| "far": "far", |
| "b": "b", |
| }, |
| }, |
| }, |
| ), |
| variations: []Variation{{"far", "far"}}, |
| far: true, |
| reverse: false, |
| want: "far_b", |
| }, |
| { |
| name: "AddFarVariationDependencies(far, b) to missing", |
| // A dependency with far variations and aliases |
| possibleDeps: makeDependencyGroup( |
| alias{ |
| variant: variant{ |
| name: "far", |
| variations: variationMap{ |
| "far": "far", |
| }, |
| }, |
| target: 1, |
| }, |
| &moduleInfo{ |
| variant: variant{ |
| name: "far_a", |
| variations: variationMap{ |
| "far": "far", |
| "a": "a", |
| }, |
| }, |
| }, |
| ), |
| variations: []Variation{{"far", "far"}, {"a", "b"}}, |
| far: true, |
| reverse: false, |
| want: "nil", |
| }, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| got, _ := findVariant(module, tt.possibleDeps, tt.variations, tt.far, tt.reverse) |
| if g, w := got == nil, tt.want == "nil"; g != w { |
| t.Fatalf("findVariant() got = %v, want %v", got, tt.want) |
| } |
| if got != nil { |
| if g, w := got.String(), fmt.Sprintf("module %q variant %q", "dep", tt.want); g != w { |
| t.Errorf("findVariant() got = %v, want %v", g, w) |
| } |
| } |
| }) |
| } |
| } |
| |
| func Test_parallelVisit(t *testing.T) { |
| addDep := func(from, to *moduleInfo) { |
| from.directDeps = append(from.directDeps, depInfo{to, nil}) |
| from.forwardDeps = append(from.forwardDeps, to) |
| to.reverseDeps = append(to.reverseDeps, from) |
| } |
| |
| create := func(name string) *moduleInfo { |
| m := &moduleInfo{ |
| group: &moduleGroup{ |
| name: name, |
| }, |
| } |
| m.group.modules = modulesOrAliases{m} |
| return m |
| } |
| moduleA := create("A") |
| moduleB := create("B") |
| moduleC := create("C") |
| moduleD := create("D") |
| moduleE := create("E") |
| moduleF := create("F") |
| moduleG := create("G") |
| |
| // A depends on B, B depends on C. Nothing depends on D through G, and they don't depend on |
| // anything. |
| addDep(moduleA, moduleB) |
| addDep(moduleB, moduleC) |
| |
| t.Run("no modules", func(t *testing.T) { |
| errs := parallelVisit(nil, bottomUpVisitorImpl{}, 1, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| panic("unexpected call to visitor") |
| }) |
| if errs != nil { |
| t.Errorf("expected no errors, got %q", errs) |
| } |
| }) |
| t.Run("bottom up", func(t *testing.T) { |
| order := "" |
| errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 1, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| order += module.group.name |
| return false |
| }) |
| if errs != nil { |
| t.Errorf("expected no errors, got %q", errs) |
| } |
| if g, w := order, "CBA"; g != w { |
| t.Errorf("expected order %q, got %q", w, g) |
| } |
| }) |
| t.Run("pause", func(t *testing.T) { |
| order := "" |
| errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 1, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| if module == moduleC { |
| // Pause module C on module D |
| unpause := make(chan struct{}) |
| pause <- pauseSpec{moduleC, moduleD, unpause} |
| <-unpause |
| } |
| order += module.group.name |
| return false |
| }) |
| if errs != nil { |
| t.Errorf("expected no errors, got %q", errs) |
| } |
| if g, w := order, "DCBA"; g != w { |
| t.Errorf("expected order %q, got %q", w, g) |
| } |
| }) |
| t.Run("cancel", func(t *testing.T) { |
| order := "" |
| errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 1, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| order += module.group.name |
| // Cancel in module B |
| return module == moduleB |
| }) |
| if errs != nil { |
| t.Errorf("expected no errors, got %q", errs) |
| } |
| if g, w := order, "CB"; g != w { |
| t.Errorf("expected order %q, got %q", w, g) |
| } |
| }) |
| t.Run("pause and cancel", func(t *testing.T) { |
| order := "" |
| errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 1, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| if module == moduleC { |
| // Pause module C on module D |
| unpause := make(chan struct{}) |
| pause <- pauseSpec{moduleC, moduleD, unpause} |
| <-unpause |
| } |
| order += module.group.name |
| // Cancel in module D |
| return module == moduleD |
| }) |
| if errs != nil { |
| t.Errorf("expected no errors, got %q", errs) |
| } |
| if g, w := order, "D"; g != w { |
| t.Errorf("expected order %q, got %q", w, g) |
| } |
| }) |
| t.Run("parallel", func(t *testing.T) { |
| order := "" |
| errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| order += module.group.name |
| return false |
| }) |
| if errs != nil { |
| t.Errorf("expected no errors, got %q", errs) |
| } |
| if g, w := order, "CBA"; g != w { |
| t.Errorf("expected order %q, got %q", w, g) |
| } |
| }) |
| t.Run("pause existing", func(t *testing.T) { |
| order := "" |
| errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| if module == moduleA { |
| // Pause module A on module B (an existing dependency) |
| unpause := make(chan struct{}) |
| pause <- pauseSpec{moduleA, moduleB, unpause} |
| <-unpause |
| } |
| order += module.group.name |
| return false |
| }) |
| if errs != nil { |
| t.Errorf("expected no errors, got %q", errs) |
| } |
| if g, w := order, "CBA"; g != w { |
| t.Errorf("expected order %q, got %q", w, g) |
| } |
| }) |
| t.Run("cycle", func(t *testing.T) { |
| errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| if module == moduleC { |
| // Pause module C on module A (a dependency cycle) |
| unpause := make(chan struct{}) |
| pause <- pauseSpec{moduleC, moduleA, unpause} |
| <-unpause |
| } |
| return false |
| }) |
| want := []string{ |
| `encountered dependency cycle`, |
| `module "C" depends on module "A"`, |
| `module "A" depends on module "B"`, |
| `module "B" depends on module "C"`, |
| } |
| for i := range want { |
| if len(errs) <= i { |
| t.Errorf("missing error %s", want[i]) |
| } else if !strings.Contains(errs[i].Error(), want[i]) { |
| t.Errorf("expected error %s, got %s", want[i], errs[i]) |
| } |
| } |
| if len(errs) > len(want) { |
| for _, err := range errs[len(want):] { |
| t.Errorf("unexpected error %s", err.Error()) |
| } |
| } |
| }) |
| t.Run("pause cycle", func(t *testing.T) { |
| errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 3, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| if module == moduleC { |
| // Pause module C on module D |
| unpause := make(chan struct{}) |
| pause <- pauseSpec{moduleC, moduleD, unpause} |
| <-unpause |
| } |
| if module == moduleD { |
| // Pause module D on module C (a pause cycle) |
| unpause := make(chan struct{}) |
| pause <- pauseSpec{moduleD, moduleC, unpause} |
| <-unpause |
| } |
| return false |
| }) |
| want := []string{ |
| `encountered dependency cycle`, |
| `module "D" depends on module "C"`, |
| `module "C" depends on module "D"`, |
| } |
| for i := range want { |
| if len(errs) <= i { |
| t.Errorf("missing error %s", want[i]) |
| } else if !strings.Contains(errs[i].Error(), want[i]) { |
| t.Errorf("expected error %s, got %s", want[i], errs[i]) |
| } |
| } |
| if len(errs) > len(want) { |
| for _, err := range errs[len(want):] { |
| t.Errorf("unexpected error %s", err.Error()) |
| } |
| } |
| }) |
| t.Run("pause cycle with deps", func(t *testing.T) { |
| pauseDeps := map[*moduleInfo]*moduleInfo{ |
| // F and G form a pause cycle |
| moduleF: moduleG, |
| moduleG: moduleF, |
| // D depends on E which depends on the pause cycle, making E the first alphabetical |
| // entry in pauseMap, which is not part of the cycle. |
| moduleD: moduleE, |
| moduleE: moduleF, |
| } |
| errs := parallelVisit([]*moduleInfo{moduleD, moduleE, moduleF, moduleG}, bottomUpVisitorImpl{}, 4, |
| func(module *moduleInfo, pause chan<- pauseSpec) bool { |
| if dep, ok := pauseDeps[module]; ok { |
| unpause := make(chan struct{}) |
| pause <- pauseSpec{module, dep, unpause} |
| <-unpause |
| } |
| return false |
| }) |
| want := []string{ |
| `encountered dependency cycle`, |
| `module "G" depends on module "F"`, |
| `module "F" depends on module "G"`, |
| } |
| for i := range want { |
| if len(errs) <= i { |
| t.Errorf("missing error %s", want[i]) |
| } else if !strings.Contains(errs[i].Error(), want[i]) { |
| t.Errorf("expected error %s, got %s", want[i], errs[i]) |
| } |
| } |
| if len(errs) > len(want) { |
| for _, err := range errs[len(want):] { |
| t.Errorf("unexpected error %s", err.Error()) |
| } |
| } |
| }) |
| } |
| |
| func TestPackageIncludes(t *testing.T) { |
| dir1_foo_bp := ` |
| blueprint_package_includes { |
| match_all: ["use_dir1"], |
| } |
| foo_module { |
| name: "foo", |
| } |
| ` |
| dir2_foo_bp := ` |
| blueprint_package_includes { |
| match_all: ["use_dir2"], |
| } |
| foo_module { |
| name: "foo", |
| } |
| ` |
| mockFs := map[string][]byte{ |
| "dir1/Android.bp": []byte(dir1_foo_bp), |
| "dir2/Android.bp": []byte(dir2_foo_bp), |
| } |
| testCases := []struct { |
| desc string |
| includeTags []string |
| expectedDir string |
| expectedErr string |
| }{ |
| { |
| desc: "use_dir1 is set, use dir1 foo", |
| includeTags: []string{"use_dir1"}, |
| expectedDir: "dir1", |
| }, |
| { |
| desc: "use_dir2 is set, use dir2 foo", |
| includeTags: []string{"use_dir2"}, |
| expectedDir: "dir2", |
| }, |
| { |
| desc: "duplicate module error if both use_dir1 and use_dir2 are set", |
| includeTags: []string{"use_dir1", "use_dir2"}, |
| expectedDir: "", |
| expectedErr: `module "foo" already defined`, |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.desc, func(t *testing.T) { |
| ctx := NewContext() |
| // Register mock FS |
| ctx.MockFileSystem(mockFs) |
| // Register module types |
| ctx.RegisterModuleType("foo_module", newFooModule) |
| RegisterPackageIncludesModuleType(ctx) |
| // Add include tags for test case |
| ctx.AddIncludeTags(tc.includeTags...) |
| // Run test |
| _, actualErrs := ctx.ParseFileList(".", []string{"dir1/Android.bp", "dir2/Android.bp"}, nil) |
| // Evaluate |
| if !strings.Contains(fmt.Sprintf("%s", actualErrs), fmt.Sprintf("%s", tc.expectedErr)) { |
| t.Errorf("Expected errors: %s, got errors: %s\n", tc.expectedErr, actualErrs) |
| } |
| if tc.expectedErr != "" { |
| return // expectedDir check not necessary |
| } |
| actualBpFile := ctx.moduleGroupFromName("foo", nil).modules.firstModule().relBlueprintsFile |
| if tc.expectedDir != filepath.Dir(actualBpFile) { |
| t.Errorf("Expected foo from %s, got %s\n", tc.expectedDir, filepath.Dir(actualBpFile)) |
| } |
| }) |
| } |
| |
| } |
| |
| func TestDeduplicateOrderOnlyDeps(t *testing.T) { |
| outputs := func(names ...string) []ninjaString { |
| r := make([]ninjaString, len(names)) |
| for i, name := range names { |
| r[i] = literalNinjaString(name) |
| } |
| return r |
| } |
| b := func(output string, inputs []string, orderOnlyDeps []string) *buildDef { |
| return &buildDef{ |
| Outputs: outputs(output), |
| Inputs: outputs(inputs...), |
| OrderOnly: outputs(orderOnlyDeps...), |
| } |
| } |
| m := func(bs ...*buildDef) *moduleInfo { |
| return &moduleInfo{actionDefs: localBuildActions{buildDefs: bs}} |
| } |
| type testcase struct { |
| modules []*moduleInfo |
| expectedPhonys []*buildDef |
| conversions map[string][]ninjaString |
| } |
| testCases := []testcase{{ |
| modules: []*moduleInfo{ |
| m(b("A", nil, []string{"d"})), |
| m(b("B", nil, []string{"d"})), |
| }, |
| expectedPhonys: []*buildDef{ |
| b("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ", []string{"d"}, nil), |
| }, |
| conversions: map[string][]ninjaString{ |
| "A": outputs("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ"), |
| "B": outputs("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ"), |
| }, |
| }, { |
| modules: []*moduleInfo{ |
| m(b("A", nil, []string{"a"})), |
| m(b("B", nil, []string{"b"})), |
| }, |
| }, { |
| modules: []*moduleInfo{ |
| m(b("A", nil, []string{"a"})), |
| m(b("B", nil, []string{"b"})), |
| m(b("C", nil, []string{"a"})), |
| }, |
| expectedPhonys: []*buildDef{b("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs", []string{"a"}, nil)}, |
| conversions: map[string][]ninjaString{ |
| "A": outputs("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs"), |
| "B": outputs("b"), |
| "C": outputs("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs"), |
| }, |
| }, { |
| modules: []*moduleInfo{ |
| m(b("A", nil, []string{"a", "b"}), |
| b("B", nil, []string{"a", "b"})), |
| m(b("C", nil, []string{"a", "c"}), |
| b("D", nil, []string{"a", "c"})), |
| }, |
| expectedPhonys: []*buildDef{ |
| b("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM", []string{"a", "b"}, nil), |
| b("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E", []string{"a", "c"}, nil)}, |
| conversions: map[string][]ninjaString{ |
| "A": outputs("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM"), |
| "B": outputs("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM"), |
| "C": outputs("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E"), |
| "D": outputs("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E"), |
| }, |
| }} |
| for index, tc := range testCases { |
| t.Run(fmt.Sprintf("TestCase-%d", index), func(t *testing.T) { |
| ctx := NewContext() |
| actualPhonys := ctx.deduplicateOrderOnlyDeps(tc.modules) |
| if len(actualPhonys.variables) != 0 { |
| t.Errorf("No variables expected but found %v", actualPhonys.variables) |
| } |
| if len(actualPhonys.rules) != 0 { |
| t.Errorf("No rules expected but found %v", actualPhonys.rules) |
| } |
| if e, a := len(tc.expectedPhonys), len(actualPhonys.buildDefs); e != a { |
| t.Errorf("Expected %d build statements but got %d", e, a) |
| } |
| for i := 0; i < len(tc.expectedPhonys); i++ { |
| a := actualPhonys.buildDefs[i] |
| e := tc.expectedPhonys[i] |
| if !reflect.DeepEqual(e.Outputs, a.Outputs) { |
| t.Errorf("phonys expected %v but actualPhonys %v", e.Outputs, a.Outputs) |
| } |
| if !reflect.DeepEqual(e.Inputs, a.Inputs) { |
| t.Errorf("phonys expected %v but actualPhonys %v", e.Inputs, a.Inputs) |
| } |
| } |
| find := func(k string) *buildDef { |
| for _, m := range tc.modules { |
| for _, b := range m.actionDefs.buildDefs { |
| if reflect.DeepEqual(b.Outputs, outputs(k)) { |
| return b |
| } |
| } |
| } |
| return nil |
| } |
| for k, conversion := range tc.conversions { |
| actual := find(k) |
| if actual == nil { |
| t.Errorf("Couldn't find %s", k) |
| } |
| if !reflect.DeepEqual(actual.OrderOnly, conversion) { |
| t.Errorf("expected %s.OrderOnly = %v but got %v", k, conversion, actual.OrderOnly) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestSourceRootDirAllowed(t *testing.T) { |
| type pathCase struct { |
| path string |
| decidingPrefix string |
| allowed bool |
| } |
| testcases := []struct { |
| desc string |
| rootDirs []string |
| pathCases []pathCase |
| }{ |
| { |
| desc: "simple case", |
| rootDirs: []string{ |
| "a", |
| "b/c/d", |
| "-c", |
| "-d/c/a", |
| "c/some_single_file", |
| }, |
| pathCases: []pathCase{ |
| { |
| path: "a", |
| decidingPrefix: "a", |
| allowed: true, |
| }, |
| { |
| path: "a/b/c", |
| decidingPrefix: "a", |
| allowed: true, |
| }, |
| { |
| path: "b", |
| decidingPrefix: "", |
| allowed: true, |
| }, |
| { |
| path: "b/c/d/a", |
| decidingPrefix: "b/c/d", |
| allowed: true, |
| }, |
| { |
| path: "c", |
| decidingPrefix: "c", |
| allowed: false, |
| }, |
| { |
| path: "c/a/b", |
| decidingPrefix: "c", |
| allowed: false, |
| }, |
| { |
| path: "c/some_single_file", |
| decidingPrefix: "c/some_single_file", |
| allowed: true, |
| }, |
| { |
| path: "d/c/a/abc", |
| decidingPrefix: "d/c/a", |
| allowed: false, |
| }, |
| }, |
| }, |
| { |
| desc: "root directory order matters", |
| rootDirs: []string{ |
| "-a", |
| "a/c/some_allowed_file", |
| "a/b/d/some_allowed_file", |
| "a/b", |
| "a/c", |
| "-a/b/d", |
| }, |
| pathCases: []pathCase{ |
| { |
| path: "a", |
| decidingPrefix: "a", |
| allowed: false, |
| }, |
| { |
| path: "a/some_disallowed_file", |
| decidingPrefix: "a", |
| allowed: false, |
| }, |
| { |
| path: "a/c/some_allowed_file", |
| decidingPrefix: "a/c/some_allowed_file", |
| allowed: true, |
| }, |
| { |
| path: "a/b/d/some_allowed_file", |
| decidingPrefix: "a/b/d/some_allowed_file", |
| allowed: true, |
| }, |
| { |
| path: "a/b/c", |
| decidingPrefix: "a/b", |
| allowed: true, |
| }, |
| { |
| path: "a/b/c/some_allowed_file", |
| decidingPrefix: "a/b", |
| allowed: true, |
| }, |
| { |
| path: "a/b/d", |
| decidingPrefix: "a/b/d", |
| allowed: false, |
| }, |
| }, |
| }, |
| } |
| for _, tc := range testcases { |
| dirs := SourceRootDirs{} |
| dirs.Add(tc.rootDirs...) |
| for _, pc := range tc.pathCases { |
| t.Run(fmt.Sprintf("%s: %s", tc.desc, pc.path), func(t *testing.T) { |
| allowed, decidingPrefix := dirs.SourceRootDirAllowed(pc.path) |
| if allowed != pc.allowed { |
| if pc.allowed { |
| t.Errorf("expected path %q to be allowed, but was not; root allowlist: %q", pc.path, tc.rootDirs) |
| } else { |
| t.Errorf("path %q was allowed unexpectedly; root allowlist: %q", pc.path, tc.rootDirs) |
| } |
| } |
| if decidingPrefix != pc.decidingPrefix { |
| t.Errorf("expected decidingPrefix to be %q, but got %q", pc.decidingPrefix, decidingPrefix) |
| } |
| }) |
| } |
| } |
| } |
| |
| func TestSourceRootDirs(t *testing.T) { |
| root_foo_bp := ` |
| foo_module { |
| name: "foo", |
| deps: ["foo_dir1", "foo_dir_ignored_special_case"], |
| } |
| ` |
| dir1_foo_bp := ` |
| foo_module { |
| name: "foo_dir1", |
| deps: ["foo_dir_ignored"], |
| } |
| ` |
| dir_ignored_foo_bp := ` |
| foo_module { |
| name: "foo_dir_ignored", |
| } |
| ` |
| dir_ignored_special_case_foo_bp := ` |
| foo_module { |
| name: "foo_dir_ignored_special_case", |
| } |
| ` |
| mockFs := map[string][]byte{ |
| "Android.bp": []byte(root_foo_bp), |
| "dir1/Android.bp": []byte(dir1_foo_bp), |
| "dir_ignored/Android.bp": []byte(dir_ignored_foo_bp), |
| "dir_ignored/special_case/Android.bp": []byte(dir_ignored_special_case_foo_bp), |
| } |
| fileList := []string{} |
| for f := range mockFs { |
| fileList = append(fileList, f) |
| } |
| testCases := []struct { |
| sourceRootDirs []string |
| expectedModuleDefs []string |
| unexpectedModuleDefs []string |
| expectedErrs []string |
| }{ |
| { |
| sourceRootDirs: []string{}, |
| expectedModuleDefs: []string{ |
| "foo", |
| "foo_dir1", |
| "foo_dir_ignored", |
| "foo_dir_ignored_special_case", |
| }, |
| }, |
| { |
| sourceRootDirs: []string{"-", ""}, |
| unexpectedModuleDefs: []string{ |
| "foo", |
| "foo_dir1", |
| "foo_dir_ignored", |
| "foo_dir_ignored_special_case", |
| }, |
| }, |
| { |
| sourceRootDirs: []string{"-"}, |
| unexpectedModuleDefs: []string{ |
| "foo", |
| "foo_dir1", |
| "foo_dir_ignored", |
| "foo_dir_ignored_special_case", |
| }, |
| }, |
| { |
| sourceRootDirs: []string{"dir1"}, |
| expectedModuleDefs: []string{ |
| "foo", |
| "foo_dir1", |
| "foo_dir_ignored", |
| "foo_dir_ignored_special_case", |
| }, |
| }, |
| { |
| sourceRootDirs: []string{"-dir1"}, |
| expectedModuleDefs: []string{ |
| "foo", |
| "foo_dir_ignored", |
| "foo_dir_ignored_special_case", |
| }, |
| unexpectedModuleDefs: []string{ |
| "foo_dir1", |
| }, |
| expectedErrs: []string{ |
| `Android.bp:2:2: module "foo" depends on skipped module "foo_dir1"; "foo_dir1" was defined in files(s) [dir1/Android.bp], but was skipped for reason(s) ["dir1/Android.bp" is a descendant of "dir1", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]`, |
| }, |
| }, |
| { |
| sourceRootDirs: []string{"-", "dir1"}, |
| expectedModuleDefs: []string{ |
| "foo_dir1", |
| }, |
| unexpectedModuleDefs: []string{ |
| "foo", |
| "foo_dir_ignored", |
| "foo_dir_ignored_special_case", |
| }, |
| expectedErrs: []string{ |
| `dir1/Android.bp:2:2: module "foo_dir1" depends on skipped module "foo_dir_ignored"; "foo_dir_ignored" was defined in files(s) [dir_ignored/Android.bp], but was skipped for reason(s) ["dir_ignored/Android.bp" is a descendant of "", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]`, |
| }, |
| }, |
| { |
| sourceRootDirs: []string{"-", "dir1", "dir_ignored/special_case/Android.bp"}, |
| expectedModuleDefs: []string{ |
| "foo_dir1", |
| "foo_dir_ignored_special_case", |
| }, |
| unexpectedModuleDefs: []string{ |
| "foo", |
| "foo_dir_ignored", |
| }, |
| expectedErrs: []string{ |
| "dir1/Android.bp:2:2: module \"foo_dir1\" depends on skipped module \"foo_dir_ignored\"; \"foo_dir_ignored\" was defined in files(s) [dir_ignored/Android.bp], but was skipped for reason(s) [\"dir_ignored/Android.bp\" is a descendant of \"\", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]", |
| }, |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(fmt.Sprintf(`source root dirs are %q`, tc.sourceRootDirs), func(t *testing.T) { |
| ctx := NewContext() |
| ctx.MockFileSystem(mockFs) |
| ctx.RegisterModuleType("foo_module", newFooModule) |
| ctx.RegisterBottomUpMutator("deps", depsMutator) |
| ctx.AddSourceRootDirs(tc.sourceRootDirs...) |
| RegisterPackageIncludesModuleType(ctx) |
| ctx.ParseFileList(".", fileList, nil) |
| _, actualErrs := ctx.ResolveDependencies(nil) |
| |
| stringErrs := []string(nil) |
| for _, err := range actualErrs { |
| stringErrs = append(stringErrs, err.Error()) |
| } |
| if !reflect.DeepEqual(tc.expectedErrs, stringErrs) { |
| t.Errorf("expected to find errors %v; got %v", tc.expectedErrs, stringErrs) |
| } |
| for _, modName := range tc.expectedModuleDefs { |
| allMods := ctx.moduleGroupFromName(modName, nil) |
| if allMods == nil || len(allMods.modules) != 1 { |
| mods := modulesOrAliases{} |
| if allMods != nil { |
| mods = allMods.modules |
| } |
| t.Errorf("expected to find one definition for module %q, but got %v", modName, mods) |
| } |
| } |
| |
| for _, modName := range tc.unexpectedModuleDefs { |
| allMods := ctx.moduleGroupFromName(modName, nil) |
| if allMods != nil { |
| t.Errorf("expected to find no definitions for module %q, but got %v", modName, allMods.modules) |
| } |
| } |
| }) |
| } |
| } |