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 15package blueprint 16 17import ( 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 30type Walker interface { 31 Walk() bool 32} 33 34type fooModule struct { 35 SimpleName 36 properties struct { 37 Deps []string 38 Foo string 39 } 40} 41 42func newFooModule() (Module, []interface{}) { 43 m := &fooModule{} 44 return m, []interface{}{&m.properties, &m.SimpleName.Properties} 45} 46 47func (f *fooModule) GenerateBuildActions(ModuleContext) { 48} 49 50func (f *fooModule) DynamicDependencies(ctx DynamicDependerModuleContext) []string { 51 return f.properties.Deps 52} 53 54func (f *fooModule) Foo() string { 55 return f.properties.Foo 56} 57 58func (f *fooModule) Walk() bool { 59 return true 60} 61 62type barModule struct { 63 SimpleName 64 properties struct { 65 Deps []string 66 Bar bool 67 } 68} 69 70func newBarModule() (Module, []interface{}) { 71 m := &barModule{} 72 return m, []interface{}{&m.properties, &m.SimpleName.Properties} 73} 74 75func (b *barModule) DynamicDependencies(ctx DynamicDependerModuleContext) []string { 76 return b.properties.Deps 77} 78 79func (b *barModule) GenerateBuildActions(ModuleContext) { 80} 81 82func (b *barModule) Bar() bool { 83 return b.properties.Bar 84} 85 86func (b *barModule) Walk() bool { 87 return false 88} 89 90func 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. 130func 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, false, 193 func(dep depInfo, parent *moduleInfo) bool { 194 outputDown += ctx.ModuleName(dep.module.logicModule) 195 if dep.module.logicModule.(Walker).Walk() { 196 return true 197 } 198 return false 199 }, 200 func(dep depInfo, parent *moduleInfo) { 201 outputUp += ctx.ModuleName(dep.module.logicModule) 202 }) 203 if outputDown != "BCEFG" { 204 t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEFG", outputDown) 205 } 206 if outputUp != "BEGFC" { 207 t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BEGFC", outputUp) 208 } 209} 210 211// |===B---D - represents a non-walkable edge 212// A = represents a walkable edge 213// |===C===E===\ A should not be visited because it's the root node. 214// | | B, D should not be walked. 215// |===F===G===H G should be visited multiple times 216// \===/ H should only be visited once 217func TestWalkDepsDuplicates(t *testing.T) { 218 ctx := NewContext() 219 ctx.MockFileSystem(map[string][]byte{ 220 "Blueprints": []byte(` 221 foo_module { 222 name: "A", 223 deps: ["B", "C"], 224 } 225 226 bar_module { 227 name: "B", 228 deps: ["D"], 229 } 230 231 foo_module { 232 name: "C", 233 deps: ["E", "F"], 234 } 235 236 foo_module { 237 name: "D", 238 } 239 240 foo_module { 241 name: "E", 242 deps: ["G"], 243 } 244 245 foo_module { 246 name: "F", 247 deps: ["G", "G"], 248 } 249 250 foo_module { 251 name: "G", 252 deps: ["H"], 253 } 254 255 foo_module { 256 name: "H", 257 } 258 `), 259 }) 260 261 ctx.RegisterModuleType("foo_module", newFooModule) 262 ctx.RegisterModuleType("bar_module", newBarModule) 263 _, errs := ctx.ParseBlueprintsFiles("Blueprints") 264 if len(errs) > 0 { 265 t.Errorf("unexpected parse errors:") 266 for _, err := range errs { 267 t.Errorf(" %s", err) 268 } 269 t.FailNow() 270 } 271 272 _, errs = ctx.ResolveDependencies(nil) 273 if len(errs) > 0 { 274 t.Errorf("unexpected dep errors:") 275 for _, err := range errs { 276 t.Errorf(" %s", err) 277 } 278 t.FailNow() 279 } 280 281 var outputDown string 282 var outputUp string 283 topModule := ctx.modulesFromName("A", nil)[0] 284 ctx.walkDeps(topModule, true, 285 func(dep depInfo, parent *moduleInfo) bool { 286 outputDown += ctx.ModuleName(dep.module.logicModule) 287 if dep.module.logicModule.(Walker).Walk() { 288 return true 289 } 290 return false 291 }, 292 func(dep depInfo, parent *moduleInfo) { 293 outputUp += ctx.ModuleName(dep.module.logicModule) 294 }) 295 if outputDown != "BCEGHFGG" { 296 t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEGHFGG", outputDown) 297 } 298 if outputUp != "BHGEGGFC" { 299 t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BHGEGGFC", outputUp) 300 } 301} 302 303func TestCreateModule(t *testing.T) { 304 ctx := newContext() 305 ctx.MockFileSystem(map[string][]byte{ 306 "Blueprints": []byte(` 307 foo_module { 308 name: "A", 309 deps: ["B", "C"], 310 } 311 `), 312 }) 313 314 ctx.RegisterTopDownMutator("create", createTestMutator) 315 ctx.RegisterBottomUpMutator("deps", blueprintDepsMutator) 316 317 ctx.RegisterModuleType("foo_module", newFooModule) 318 ctx.RegisterModuleType("bar_module", newBarModule) 319 _, errs := ctx.ParseBlueprintsFiles("Blueprints") 320 if len(errs) > 0 { 321 t.Errorf("unexpected parse errors:") 322 for _, err := range errs { 323 t.Errorf(" %s", err) 324 } 325 t.FailNow() 326 } 327 328 _, errs = ctx.ResolveDependencies(nil) 329 if len(errs) > 0 { 330 t.Errorf("unexpected dep errors:") 331 for _, err := range errs { 332 t.Errorf(" %s", err) 333 } 334 t.FailNow() 335 } 336 337 a := ctx.modulesFromName("A", nil)[0].logicModule.(*fooModule) 338 b := ctx.modulesFromName("B", nil)[0].logicModule.(*barModule) 339 c := ctx.modulesFromName("C", nil)[0].logicModule.(*barModule) 340 d := ctx.modulesFromName("D", nil)[0].logicModule.(*fooModule) 341 342 checkDeps := func(m Module, expected string) { 343 var deps []string 344 ctx.VisitDirectDeps(m, func(m Module) { 345 deps = append(deps, ctx.ModuleName(m)) 346 }) 347 got := strings.Join(deps, ",") 348 if got != expected { 349 t.Errorf("unexpected %q dependencies, got %q expected %q", 350 ctx.ModuleName(m), got, expected) 351 } 352 } 353 354 checkDeps(a, "B,C") 355 checkDeps(b, "D") 356 checkDeps(c, "D") 357 checkDeps(d, "") 358} 359 360func createTestMutator(ctx TopDownMutatorContext) { 361 type props struct { 362 Name string 363 Deps []string 364 } 365 366 ctx.CreateModule(newBarModule, &props{ 367 Name: "B", 368 Deps: []string{"D"}, 369 }) 370 371 ctx.CreateModule(newBarModule, &props{ 372 Name: "C", 373 Deps: []string{"D"}, 374 }) 375 376 ctx.CreateModule(newFooModule, &props{ 377 Name: "D", 378 }) 379} 380 381func TestWalkFileOrder(t *testing.T) { 382 // Run the test once to see how long it normally takes 383 start := time.Now() 384 doTestWalkFileOrder(t, time.Duration(0)) 385 duration := time.Since(start) 386 387 // Run the test again, but put enough of a sleep into each visitor to detect ordering 388 // problems if they exist 389 doTestWalkFileOrder(t, duration) 390} 391 392// test that WalkBlueprintsFiles calls asyncVisitor in the right order 393func doTestWalkFileOrder(t *testing.T, sleepDuration time.Duration) { 394 // setup mock context 395 ctx := newContext() 396 mockFiles := map[string][]byte{ 397 "Blueprints": []byte(` 398 sample_module { 399 name: "a", 400 } 401 `), 402 "dir1/Blueprints": []byte(` 403 sample_module { 404 name: "b", 405 } 406 `), 407 "dir1/dir2/Blueprints": []byte(` 408 sample_module { 409 name: "c", 410 } 411 `), 412 } 413 ctx.MockFileSystem(mockFiles) 414 415 // prepare to monitor the visit order 416 visitOrder := []string{} 417 visitLock := sync.Mutex{} 418 correctVisitOrder := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"} 419 420 // sleep longer when processing the earlier files 421 chooseSleepDuration := func(fileName string) (duration time.Duration) { 422 duration = time.Duration(0) 423 for i := len(correctVisitOrder) - 1; i >= 0; i-- { 424 if fileName == correctVisitOrder[i] { 425 return duration 426 } 427 duration = duration + sleepDuration 428 } 429 panic("unrecognized file name " + fileName) 430 } 431 432 visitor := func(file *parser.File) { 433 time.Sleep(chooseSleepDuration(file.Name)) 434 visitLock.Lock() 435 defer visitLock.Unlock() 436 visitOrder = append(visitOrder, file.Name) 437 } 438 keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"} 439 440 // visit the blueprints files 441 ctx.WalkBlueprintsFiles(".", keys, visitor) 442 443 // check the order 444 if !reflect.DeepEqual(visitOrder, correctVisitOrder) { 445 t.Errorf("Incorrect visit order; expected %v, got %v", correctVisitOrder, visitOrder) 446 } 447} 448 449// test that WalkBlueprintsFiles reports syntax errors 450func TestWalkingWithSyntaxError(t *testing.T) { 451 // setup mock context 452 ctx := newContext() 453 mockFiles := map[string][]byte{ 454 "Blueprints": []byte(` 455 sample_module { 456 name: "a" "b", 457 } 458 `), 459 "dir1/Blueprints": []byte(` 460 sample_module { 461 name: "b", 462 `), 463 "dir1/dir2/Blueprints": []byte(` 464 sample_module { 465 name: "c", 466 } 467 `), 468 } 469 ctx.MockFileSystem(mockFiles) 470 471 keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"} 472 473 // visit the blueprints files 474 _, errs := ctx.WalkBlueprintsFiles(".", keys, func(file *parser.File) {}) 475 476 expectedErrs := []error{ 477 errors.New(`Blueprints:3:18: expected "}", found String`), 478 errors.New(`dir1/Blueprints:4:3: expected "}", found EOF`), 479 } 480 if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) { 481 t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs) 482 } 483 484} 485 486func TestParseFailsForModuleWithoutName(t *testing.T) { 487 ctx := NewContext() 488 ctx.MockFileSystem(map[string][]byte{ 489 "Blueprints": []byte(` 490 foo_module { 491 name: "A", 492 } 493 494 bar_module { 495 deps: ["A"], 496 } 497 `), 498 }) 499 ctx.RegisterModuleType("foo_module", newFooModule) 500 ctx.RegisterModuleType("bar_module", newBarModule) 501 502 _, errs := ctx.ParseBlueprintsFiles("Blueprints") 503 504 expectedErrs := []error{ 505 errors.New(`Blueprints:6:4: property 'name' is missing from a module`), 506 } 507 if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) { 508 t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs) 509 } 510} 511