blob: 0541c06569a84fd61ce2b67b45059e5e907cc0ec [file] [log] [blame]
// 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"
"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{
"Blueprints": []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("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, 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{
"Blueprints": []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("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)
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{
"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) {
ctx := newContext()
ctx.MockFileSystem(map[string][]byte{
"Blueprints": []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("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()
}
a := ctx.moduleGroupFromName("A", nil).modules[0].logicModule.(*fooModule)
b := ctx.moduleGroupFromName("B", nil).modules[0].logicModule.(*barModule)
c := ctx.moduleGroupFromName("C", nil).modules[0].logicModule.(*barModule)
d := ctx.moduleGroupFromName("D", nil).modules[0].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, &props{
Name: "B",
Deps: []string{"D"},
})
ctx.CreateModule(newBarModule, &props{
Name: "C",
Deps: []string{"D"},
})
ctx.CreateModule(newFooModule, &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{
"Blueprints": []byte(`
sample_module {
name: "a",
}
`),
"dir1/Blueprints": []byte(`
sample_module {
name: "b",
}
`),
"dir1/dir2/Blueprints": []byte(`
sample_module {
name: "c",
}
`),
}
ctx.MockFileSystem(mockFiles)
// prepare to monitor the visit order
visitOrder := []string{}
visitLock := sync.Mutex{}
correctVisitOrder := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
// 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{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
// 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{
"Blueprints": []byte(`
sample_module {
name: "a" "b",
}
`),
"dir1/Blueprints": []byte(`
sample_module {
name: "b",
`),
"dir1/dir2/Blueprints": []byte(`
sample_module {
name: "c",
}
`),
}
ctx.MockFileSystem(mockFiles)
keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
// visit the blueprints files
_, errs := ctx.WalkBlueprintsFiles(".", keys, func(file *parser.File) {})
expectedErrs := []error{
errors.New(`Blueprints:3:18: expected "}", found String`),
errors.New(`dir1/Blueprints: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{
"Blueprints": []byte(`
foo_module {
name: "A",
}
bar_module {
deps: ["A"],
}
`),
})
ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule)
_, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
expectedErrs := []error{
errors.New(`Blueprints: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)
}
}