WalkDeps - only record module visited when it has been recursed into

Previously, WalkDeps() would record that a module was visited after the
first time it encountered the module irrespective of whether it recursed
into or not. This change moves the recording so it happens only after it
has been recursed into.

Added TestWalkDepsDuplicates_IgnoreFirstPath to test the change. Without
the change the test fails because it does not visit E.

Test refactoring:
* A depsMutator was added instead of relying on blueprintDepsMutator to
  allow different tags to be used for different dependency types.
* Modified barModule and fooModule to support the new depsMutator and
  add support for another type of dependency that is ignored by the
  walking code.
* Extracted walkDependencyGraph() function to reuse common code.
This commit is contained in:
Paul Duffin 2020-04-02 10:51:33 +01:00
parent ef33c90fe3
commit 72bab1707e
2 changed files with 132 additions and 34 deletions

View file

@ -2619,8 +2619,8 @@ func (c *Context) walkDeps(topModule *moduleInfo, allowDuplicates bool,
} }
if recurse && !visited[dep.module] { if recurse && !visited[dep.module] {
walk(dep.module) walk(dep.module)
}
visited[dep.module] = true visited[dep.module] = true
}
if visitUp != nil { if visitUp != nil {
visitUp(dep, module) visitUp(dep, module)
} }

View file

@ -31,10 +31,39 @@ type Walker interface {
Walk() bool 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 { type fooModule struct {
SimpleName SimpleName
properties struct { properties struct {
Deps []string Deps []string
Ignored_deps []string
Foo string Foo string
} }
} }
@ -47,10 +76,14 @@ func newFooModule() (Module, []interface{}) {
func (f *fooModule) GenerateBuildActions(ModuleContext) { func (f *fooModule) GenerateBuildActions(ModuleContext) {
} }
func (f *fooModule) DynamicDependencies(ctx DynamicDependerModuleContext) []string { func (f *fooModule) Deps() []string {
return f.properties.Deps return f.properties.Deps
} }
func (f *fooModule) IgnoreDeps() []string {
return f.properties.Ignored_deps
}
func (f *fooModule) Foo() string { func (f *fooModule) Foo() string {
return f.properties.Foo return f.properties.Foo
} }
@ -63,6 +96,7 @@ type barModule struct {
SimpleName SimpleName
properties struct { properties struct {
Deps []string Deps []string
Ignored_deps []string
Bar bool Bar bool
} }
} }
@ -72,10 +106,14 @@ func newBarModule() (Module, []interface{}) {
return m, []interface{}{&m.properties, &m.SimpleName.Properties} return m, []interface{}{&m.properties, &m.SimpleName.Properties}
} }
func (b *barModule) DynamicDependencies(ctx DynamicDependerModuleContext) []string { func (b *barModule) Deps() []string {
return b.properties.Deps return b.properties.Deps
} }
func (b *barModule) IgnoreDeps() []string {
return b.properties.Ignored_deps
}
func (b *barModule) GenerateBuildActions(ModuleContext) { func (b *barModule) GenerateBuildActions(ModuleContext) {
} }
@ -87,6 +125,19 @@ func (b *barModule) Walk() bool {
return false 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) { func TestContextParse(t *testing.T) {
ctx := NewContext() ctx := NewContext()
ctx.RegisterModuleType("foo_module", newFooModule) ctx.RegisterModuleType("foo_module", newFooModule)
@ -168,6 +219,7 @@ func TestWalkDeps(t *testing.T) {
ctx.RegisterModuleType("foo_module", newFooModule) ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule) ctx.RegisterModuleType("bar_module", newBarModule)
ctx.RegisterBottomUpMutator("deps", depsMutator)
_, errs := ctx.ParseBlueprintsFiles("Blueprints", nil) _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
if len(errs) > 0 { if len(errs) > 0 {
t.Errorf("unexpected parse errors:") t.Errorf("unexpected parse errors:")
@ -186,20 +238,8 @@ func TestWalkDeps(t *testing.T) {
t.FailNow() t.FailNow()
} }
var outputDown string
var outputUp string
topModule := ctx.moduleGroupFromName("A", nil).modules[0] topModule := ctx.moduleGroupFromName("A", nil).modules[0]
ctx.walkDeps(topModule, false, outputDown, outputUp := walkDependencyGraph(ctx, topModule, false)
func(dep depInfo, parent *moduleInfo) bool {
outputDown += ctx.ModuleName(dep.module.logicModule)
if dep.module.logicModule.(Walker).Walk() {
return true
}
return false
},
func(dep depInfo, parent *moduleInfo) {
outputUp += ctx.ModuleName(dep.module.logicModule)
})
if outputDown != "BCEFG" { if outputDown != "BCEFG" {
t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEFG", outputDown) t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEFG", outputDown)
} }
@ -260,6 +300,7 @@ func TestWalkDepsDuplicates(t *testing.T) {
ctx.RegisterModuleType("foo_module", newFooModule) ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule) ctx.RegisterModuleType("bar_module", newBarModule)
ctx.RegisterBottomUpMutator("deps", depsMutator)
_, errs := ctx.ParseBlueprintsFiles("Blueprints", nil) _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
if len(errs) > 0 { if len(errs) > 0 {
t.Errorf("unexpected parse errors:") t.Errorf("unexpected parse errors:")
@ -278,20 +319,8 @@ func TestWalkDepsDuplicates(t *testing.T) {
t.FailNow() t.FailNow()
} }
var outputDown string
var outputUp string
topModule := ctx.moduleGroupFromName("A", nil).modules[0] topModule := ctx.moduleGroupFromName("A", nil).modules[0]
ctx.walkDeps(topModule, true, outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
func(dep depInfo, parent *moduleInfo) bool {
outputDown += ctx.ModuleName(dep.module.logicModule)
if dep.module.logicModule.(Walker).Walk() {
return true
}
return false
},
func(dep depInfo, parent *moduleInfo) {
outputUp += ctx.ModuleName(dep.module.logicModule)
})
if outputDown != "BCEGHFGG" { if outputDown != "BCEGHFGG" {
t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEGHFGG", outputDown) t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEGHFGG", outputDown)
} }
@ -300,6 +329,75 @@ func TestWalkDepsDuplicates(t *testing.T) {
} }
} }
// - 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{
"Blueprints": []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("Blueprints", 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[0]
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) { func TestCreateModule(t *testing.T) {
ctx := newContext() ctx := newContext()
ctx.MockFileSystem(map[string][]byte{ ctx.MockFileSystem(map[string][]byte{
@ -312,7 +410,7 @@ func TestCreateModule(t *testing.T) {
}) })
ctx.RegisterTopDownMutator("create", createTestMutator) ctx.RegisterTopDownMutator("create", createTestMutator)
ctx.RegisterBottomUpMutator("deps", blueprintDepsMutator) ctx.RegisterBottomUpMutator("deps", depsMutator)
ctx.RegisterModuleType("foo_module", newFooModule) ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule) ctx.RegisterModuleType("bar_module", newBarModule)