Home | History | Annotate | Download | only in blueprint
      1 // Copyright 2014 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package blueprint
     16 
     17 import (
     18 	"bytes"
     19 	"errors"
     20 	"fmt"
     21 	"reflect"
     22 	"strings"
     23 	"sync"
     24 	"testing"
     25 	"time"
     26 
     27 	"github.com/google/blueprint/parser"
     28 )
     29 
     30 type Walker interface {
     31 	Walk() bool
     32 }
     33 
     34 type fooModule struct {
     35 	SimpleName
     36 	properties struct {
     37 		Deps []string
     38 		Foo  string
     39 	}
     40 }
     41 
     42 func newFooModule() (Module, []interface{}) {
     43 	m := &fooModule{}
     44 	return m, []interface{}{&m.properties, &m.SimpleName.Properties}
     45 }
     46 
     47 func (f *fooModule) GenerateBuildActions(ModuleContext) {
     48 }
     49 
     50 func (f *fooModule) DynamicDependencies(ctx DynamicDependerModuleContext) []string {
     51 	return f.properties.Deps
     52 }
     53 
     54 func (f *fooModule) Foo() string {
     55 	return f.properties.Foo
     56 }
     57 
     58 func (f *fooModule) Walk() bool {
     59 	return true
     60 }
     61 
     62 type barModule struct {
     63 	SimpleName
     64 	properties struct {
     65 		Deps []string
     66 		Bar  bool
     67 	}
     68 }
     69 
     70 func newBarModule() (Module, []interface{}) {
     71 	m := &barModule{}
     72 	return m, []interface{}{&m.properties, &m.SimpleName.Properties}
     73 }
     74 
     75 func (b *barModule) DynamicDependencies(ctx DynamicDependerModuleContext) []string {
     76 	return b.properties.Deps
     77 }
     78 
     79 func (b *barModule) GenerateBuildActions(ModuleContext) {
     80 }
     81 
     82 func (b *barModule) Bar() bool {
     83 	return b.properties.Bar
     84 }
     85 
     86 func (b *barModule) Walk() bool {
     87 	return false
     88 }
     89 
     90 func TestContextParse(t *testing.T) {
     91 	ctx := NewContext()
     92 	ctx.RegisterModuleType("foo_module", newFooModule)
     93 	ctx.RegisterModuleType("bar_module", newBarModule)
     94 
     95 	r := bytes.NewBufferString(`
     96 		foo_module {
     97 	        name: "MyFooModule",
     98 			deps: ["MyBarModule"],
     99 		}
    100 
    101 		bar_module {
    102 	        name: "MyBarModule",
    103 		}
    104 	`)
    105 
    106 	_, _, errs := ctx.parseOne(".", "Blueprint", r, parser.NewScope(nil), nil)
    107 	if len(errs) > 0 {
    108 		t.Errorf("unexpected parse errors:")
    109 		for _, err := range errs {
    110 			t.Errorf("  %s", err)
    111 		}
    112 		t.FailNow()
    113 	}
    114 
    115 	_, errs = ctx.ResolveDependencies(nil)
    116 	if len(errs) > 0 {
    117 		t.Errorf("unexpected dep errors:")
    118 		for _, err := range errs {
    119 			t.Errorf("  %s", err)
    120 		}
    121 		t.FailNow()
    122 	}
    123 }
    124 
    125 // |---B===D       - represents a non-walkable edge
    126 // A               = represents a walkable edge
    127 // |===C---E===G
    128 //     |       |   A should not be visited because it's the root node.
    129 //     |===F===|   B, D and E should not be walked.
    130 func TestWalkDeps(t *testing.T) {
    131 	ctx := NewContext()
    132 	ctx.MockFileSystem(map[string][]byte{
    133 		"Blueprints": []byte(`
    134 			foo_module {
    135 			    name: "A",
    136 			    deps: ["B", "C"],
    137 			}
    138 			
    139 			bar_module {
    140 			    name: "B",
    141 			    deps: ["D"],
    142 			}
    143 			
    144 			foo_module {
    145 			    name: "C",
    146 			    deps: ["E", "F"],
    147 			}
    148 			
    149 			foo_module {
    150 			    name: "D",
    151 			}
    152 			
    153 			bar_module {
    154 			    name: "E",
    155 			    deps: ["G"],
    156 			}
    157 			
    158 			foo_module {
    159 			    name: "F",
    160 			    deps: ["G"],
    161 			}
    162 			
    163 			foo_module {
    164 			    name: "G",
    165 			}
    166 		`),
    167 	})
    168 
    169 	ctx.RegisterModuleType("foo_module", newFooModule)
    170 	ctx.RegisterModuleType("bar_module", newBarModule)
    171 	_, errs := ctx.ParseBlueprintsFiles("Blueprints")
    172 	if len(errs) > 0 {
    173 		t.Errorf("unexpected parse errors:")
    174 		for _, err := range errs {
    175 			t.Errorf("  %s", err)
    176 		}
    177 		t.FailNow()
    178 	}
    179 
    180 	_, errs = ctx.ResolveDependencies(nil)
    181 	if len(errs) > 0 {
    182 		t.Errorf("unexpected dep errors:")
    183 		for _, err := range errs {
    184 			t.Errorf("  %s", err)
    185 		}
    186 		t.FailNow()
    187 	}
    188 
    189 	var outputDown string
    190 	var outputUp string
    191 	topModule := ctx.modulesFromName("A", nil)[0]
    192 	ctx.walkDeps(topModule,
    193 		func(dep depInfo, parent *moduleInfo) bool {
    194 			if dep.module.logicModule.(Walker).Walk() {
    195 				outputDown += ctx.ModuleName(dep.module.logicModule)
    196 				return true
    197 			}
    198 			return false
    199 		},
    200 		func(dep depInfo, parent *moduleInfo) {
    201 			if dep.module.logicModule.(Walker).Walk() {
    202 				outputUp += ctx.ModuleName(dep.module.logicModule)
    203 			}
    204 		})
    205 	if outputDown != "CFG" {
    206 		t.Fatalf("unexpected walkDeps behaviour: %s\ndown should be: CFG", outputDown)
    207 	}
    208 	if outputUp != "GFC" {
    209 		t.Fatalf("unexpected walkDeps behaviour: %s\nup should be: GFC", outputUp)
    210 	}
    211 }
    212 
    213 func TestCreateModule(t *testing.T) {
    214 	ctx := newContext()
    215 	ctx.MockFileSystem(map[string][]byte{
    216 		"Blueprints": []byte(`
    217 			foo_module {
    218 			    name: "A",
    219 			    deps: ["B", "C"],
    220 			}
    221 		`),
    222 	})
    223 
    224 	ctx.RegisterTopDownMutator("create", createTestMutator)
    225 	ctx.RegisterBottomUpMutator("deps", blueprintDepsMutator)
    226 
    227 	ctx.RegisterModuleType("foo_module", newFooModule)
    228 	ctx.RegisterModuleType("bar_module", newBarModule)
    229 	_, errs := ctx.ParseBlueprintsFiles("Blueprints")
    230 	if len(errs) > 0 {
    231 		t.Errorf("unexpected parse errors:")
    232 		for _, err := range errs {
    233 			t.Errorf("  %s", err)
    234 		}
    235 		t.FailNow()
    236 	}
    237 
    238 	_, errs = ctx.ResolveDependencies(nil)
    239 	if len(errs) > 0 {
    240 		t.Errorf("unexpected dep errors:")
    241 		for _, err := range errs {
    242 			t.Errorf("  %s", err)
    243 		}
    244 		t.FailNow()
    245 	}
    246 
    247 	a := ctx.modulesFromName("A", nil)[0].logicModule.(*fooModule)
    248 	b := ctx.modulesFromName("B", nil)[0].logicModule.(*barModule)
    249 	c := ctx.modulesFromName("C", nil)[0].logicModule.(*barModule)
    250 	d := ctx.modulesFromName("D", nil)[0].logicModule.(*fooModule)
    251 
    252 	checkDeps := func(m Module, expected string) {
    253 		var deps []string
    254 		ctx.VisitDirectDeps(m, func(m Module) {
    255 			deps = append(deps, ctx.ModuleName(m))
    256 		})
    257 		got := strings.Join(deps, ",")
    258 		if got != expected {
    259 			t.Errorf("unexpected %q dependencies, got %q expected %q",
    260 				ctx.ModuleName(m), got, expected)
    261 		}
    262 	}
    263 
    264 	checkDeps(a, "B,C")
    265 	checkDeps(b, "D")
    266 	checkDeps(c, "D")
    267 	checkDeps(d, "")
    268 }
    269 
    270 func createTestMutator(ctx TopDownMutatorContext) {
    271 	type props struct {
    272 		Name string
    273 		Deps []string
    274 	}
    275 
    276 	ctx.CreateModule(newBarModule, &props{
    277 		Name: "B",
    278 		Deps: []string{"D"},
    279 	})
    280 
    281 	ctx.CreateModule(newBarModule, &props{
    282 		Name: "C",
    283 		Deps: []string{"D"},
    284 	})
    285 
    286 	ctx.CreateModule(newFooModule, &props{
    287 		Name: "D",
    288 	})
    289 }
    290 
    291 func TestWalkFileOrder(t *testing.T) {
    292 	// Run the test once to see how long it normally takes
    293 	start := time.Now()
    294 	doTestWalkFileOrder(t, time.Duration(0))
    295 	duration := time.Since(start)
    296 
    297 	// Run the test again, but put enough of a sleep into each visitor to detect ordering
    298 	// problems if they exist
    299 	doTestWalkFileOrder(t, duration)
    300 }
    301 
    302 // test that WalkBlueprintsFiles calls asyncVisitor in the right order
    303 func doTestWalkFileOrder(t *testing.T, sleepDuration time.Duration) {
    304 	// setup mock context
    305 	ctx := newContext()
    306 	mockFiles := map[string][]byte{
    307 		"Blueprints": []byte(`
    308 			sample_module {
    309 			    name: "a",
    310 			}
    311 		`),
    312 		"dir1/Blueprints": []byte(`
    313 			sample_module {
    314 			    name: "b",
    315 			}
    316 		`),
    317 		"dir1/dir2/Blueprints": []byte(`
    318 			sample_module {
    319 			    name: "c",
    320 			}
    321 		`),
    322 	}
    323 	ctx.MockFileSystem(mockFiles)
    324 
    325 	// prepare to monitor the visit order
    326 	visitOrder := []string{}
    327 	visitLock := sync.Mutex{}
    328 	correctVisitOrder := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
    329 
    330 	// sleep longer when processing the earlier files
    331 	chooseSleepDuration := func(fileName string) (duration time.Duration) {
    332 		duration = time.Duration(0)
    333 		for i := len(correctVisitOrder) - 1; i >= 0; i-- {
    334 			if fileName == correctVisitOrder[i] {
    335 				return duration
    336 			}
    337 			duration = duration + sleepDuration
    338 		}
    339 		panic("unrecognized file name " + fileName)
    340 	}
    341 
    342 	visitor := func(file *parser.File) {
    343 		time.Sleep(chooseSleepDuration(file.Name))
    344 		visitLock.Lock()
    345 		defer visitLock.Unlock()
    346 		visitOrder = append(visitOrder, file.Name)
    347 	}
    348 	keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
    349 
    350 	// visit the blueprints files
    351 	ctx.WalkBlueprintsFiles(".", keys, visitor)
    352 
    353 	// check the order
    354 	if !reflect.DeepEqual(visitOrder, correctVisitOrder) {
    355 		t.Errorf("Incorrect visit order; expected %v, got %v", correctVisitOrder, visitOrder)
    356 	}
    357 }
    358 
    359 // test that WalkBlueprintsFiles reports syntax errors
    360 func TestWalkingWithSyntaxError(t *testing.T) {
    361 	// setup mock context
    362 	ctx := newContext()
    363 	mockFiles := map[string][]byte{
    364 		"Blueprints": []byte(`
    365 			sample_module {
    366 			    name: "a" "b",
    367 			}
    368 		`),
    369 		"dir1/Blueprints": []byte(`
    370 			sample_module {
    371 			    name: "b",
    372 		`),
    373 		"dir1/dir2/Blueprints": []byte(`
    374 			sample_module {
    375 			    name: "c",
    376 			}
    377 		`),
    378 	}
    379 	ctx.MockFileSystem(mockFiles)
    380 
    381 	keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
    382 
    383 	// visit the blueprints files
    384 	_, errs := ctx.WalkBlueprintsFiles(".", keys, func(file *parser.File) {})
    385 
    386 	expectedErrs := []error{
    387 		errors.New(`Blueprints:3:18: expected "}", found String`),
    388 		errors.New(`dir1/Blueprints:4:3: expected "}", found EOF`),
    389 	}
    390 	if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) {
    391 		t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
    392 	}
    393 
    394 }
    395