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