blob: 3e49a18f552984eaad52f2ab991bb0ee086e1225 [file] [log] [blame]
Colin Cross8e0c5112015-01-23 14:15:10 -08001// 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
Jamie Gennis1bc967e2014-05-27 16:34:41 -070015package blueprint
16
17import (
18 "bytes"
Jeff Gastona7e408a2017-12-05 15:11:55 -080019 "errors"
20 "fmt"
Jeff Gaston656870f2017-11-29 18:37:31 -080021 "reflect"
Colin Crossaf4fd212017-07-28 14:32:36 -070022 "strings"
Jeff Gaston656870f2017-11-29 18:37:31 -080023 "sync"
Jamie Gennis1bc967e2014-05-27 16:34:41 -070024 "testing"
Jeff Gaston656870f2017-11-29 18:37:31 -080025 "time"
Jeff Gastonc3e28442017-08-09 15:13:12 -070026
27 "github.com/google/blueprint/parser"
Jamie Gennis1bc967e2014-05-27 16:34:41 -070028)
29
Yuchen Wuf9958462015-10-09 17:31:27 -070030type Walker interface {
31 Walk() bool
32}
33
Paul Duffin72bab172020-04-02 10:51:33 +010034func walkDependencyGraph(ctx *Context, topModule *moduleInfo, allowDuplicates bool) (string, string) {
35 var outputDown string
36 var outputUp string
37 ctx.walkDeps(topModule, allowDuplicates,
38 func(dep depInfo, parent *moduleInfo) bool {
39 outputDown += ctx.ModuleName(dep.module.logicModule)
40 if tag, ok := dep.tag.(walkerDepsTag); ok {
41 if !tag.follow {
42 return false
43 }
44 }
45 if dep.module.logicModule.(Walker).Walk() {
46 return true
47 }
48
49 return false
50 },
51 func(dep depInfo, parent *moduleInfo) {
52 outputUp += ctx.ModuleName(dep.module.logicModule)
53 })
54 return outputDown, outputUp
55}
56
57type depsProvider interface {
58 Deps() []string
59 IgnoreDeps() []string
60}
61
Jamie Gennis1bc967e2014-05-27 16:34:41 -070062type fooModule struct {
Colin Cross0b7e83e2016-05-17 14:58:05 -070063 SimpleName
Jamie Gennis1bc967e2014-05-27 16:34:41 -070064 properties struct {
Paul Duffin72bab172020-04-02 10:51:33 +010065 Deps []string
66 Ignored_deps []string
67 Foo string
Jamie Gennis1bc967e2014-05-27 16:34:41 -070068 }
69}
70
Jamie Gennis68540da2014-10-06 09:10:40 -070071func newFooModule() (Module, []interface{}) {
Jamie Gennis1bc967e2014-05-27 16:34:41 -070072 m := &fooModule{}
Colin Cross0b7e83e2016-05-17 14:58:05 -070073 return m, []interface{}{&m.properties, &m.SimpleName.Properties}
Jamie Gennis1bc967e2014-05-27 16:34:41 -070074}
75
76func (f *fooModule) GenerateBuildActions(ModuleContext) {
77}
78
Paul Duffin72bab172020-04-02 10:51:33 +010079func (f *fooModule) Deps() []string {
Colin Cross0b7e83e2016-05-17 14:58:05 -070080 return f.properties.Deps
81}
82
Paul Duffin72bab172020-04-02 10:51:33 +010083func (f *fooModule) IgnoreDeps() []string {
84 return f.properties.Ignored_deps
85}
86
Jamie Gennis1bc967e2014-05-27 16:34:41 -070087func (f *fooModule) Foo() string {
88 return f.properties.Foo
89}
90
Yuchen Wuf9958462015-10-09 17:31:27 -070091func (f *fooModule) Walk() bool {
92 return true
93}
94
Jamie Gennis1bc967e2014-05-27 16:34:41 -070095type barModule struct {
Colin Cross0b7e83e2016-05-17 14:58:05 -070096 SimpleName
Jamie Gennis1bc967e2014-05-27 16:34:41 -070097 properties struct {
Paul Duffin72bab172020-04-02 10:51:33 +010098 Deps []string
99 Ignored_deps []string
100 Bar bool
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700101 }
102}
103
Jamie Gennis68540da2014-10-06 09:10:40 -0700104func newBarModule() (Module, []interface{}) {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700105 m := &barModule{}
Colin Cross0b7e83e2016-05-17 14:58:05 -0700106 return m, []interface{}{&m.properties, &m.SimpleName.Properties}
107}
108
Paul Duffin72bab172020-04-02 10:51:33 +0100109func (b *barModule) Deps() []string {
Colin Cross0b7e83e2016-05-17 14:58:05 -0700110 return b.properties.Deps
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700111}
112
Paul Duffin72bab172020-04-02 10:51:33 +0100113func (b *barModule) IgnoreDeps() []string {
114 return b.properties.Ignored_deps
115}
116
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700117func (b *barModule) GenerateBuildActions(ModuleContext) {
118}
119
120func (b *barModule) Bar() bool {
121 return b.properties.Bar
122}
123
Yuchen Wuf9958462015-10-09 17:31:27 -0700124func (b *barModule) Walk() bool {
125 return false
126}
127
Paul Duffin72bab172020-04-02 10:51:33 +0100128type walkerDepsTag struct {
129 BaseDependencyTag
130 // True if the dependency should be followed, false otherwise.
131 follow bool
132}
133
134func depsMutator(mctx BottomUpMutatorContext) {
135 if m, ok := mctx.Module().(depsProvider); ok {
136 mctx.AddDependency(mctx.Module(), walkerDepsTag{follow: false}, m.IgnoreDeps()...)
137 mctx.AddDependency(mctx.Module(), walkerDepsTag{follow: true}, m.Deps()...)
138 }
139}
140
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700141func TestContextParse(t *testing.T) {
142 ctx := NewContext()
Jamie Gennis68540da2014-10-06 09:10:40 -0700143 ctx.RegisterModuleType("foo_module", newFooModule)
144 ctx.RegisterModuleType("bar_module", newBarModule)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700145
146 r := bytes.NewBufferString(`
147 foo_module {
Colin Cross0b7e83e2016-05-17 14:58:05 -0700148 name: "MyFooModule",
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700149 deps: ["MyBarModule"],
150 }
151
152 bar_module {
Colin Cross0b7e83e2016-05-17 14:58:05 -0700153 name: "MyBarModule",
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700154 }
155 `)
156
Jeff Gaston656870f2017-11-29 18:37:31 -0800157 _, _, errs := ctx.parseOne(".", "Blueprint", r, parser.NewScope(nil), nil)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700158 if len(errs) > 0 {
159 t.Errorf("unexpected parse errors:")
160 for _, err := range errs {
161 t.Errorf(" %s", err)
162 }
163 t.FailNow()
164 }
165
Colin Cross874a3462017-07-31 17:26:06 -0700166 _, errs = ctx.ResolveDependencies(nil)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700167 if len(errs) > 0 {
168 t.Errorf("unexpected dep errors:")
169 for _, err := range errs {
170 t.Errorf(" %s", err)
171 }
172 t.FailNow()
173 }
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700174}
Yuchen Wuf9958462015-10-09 17:31:27 -0700175
Colin Cross526e02f2018-06-21 13:31:53 -0700176// |===B---D - represents a non-walkable edge
Yuchen Wuf9958462015-10-09 17:31:27 -0700177// A = represents a walkable edge
Colin Cross526e02f2018-06-21 13:31:53 -0700178// |===C===E---G
Yuchen Wuf9958462015-10-09 17:31:27 -0700179// | | A should not be visited because it's the root node.
180// |===F===| B, D and E should not be walked.
181func TestWalkDeps(t *testing.T) {
182 ctx := NewContext()
Colin Crossd7b0f602016-06-02 15:30:20 -0700183 ctx.MockFileSystem(map[string][]byte{
184 "Blueprints": []byte(`
185 foo_module {
186 name: "A",
187 deps: ["B", "C"],
188 }
189
190 bar_module {
191 name: "B",
192 deps: ["D"],
193 }
194
195 foo_module {
196 name: "C",
197 deps: ["E", "F"],
198 }
199
200 foo_module {
201 name: "D",
202 }
203
204 bar_module {
205 name: "E",
206 deps: ["G"],
207 }
208
209 foo_module {
210 name: "F",
211 deps: ["G"],
212 }
213
214 foo_module {
215 name: "G",
216 }
217 `),
218 })
219
Yuchen Wuf9958462015-10-09 17:31:27 -0700220 ctx.RegisterModuleType("foo_module", newFooModule)
221 ctx.RegisterModuleType("bar_module", newBarModule)
Paul Duffin72bab172020-04-02 10:51:33 +0100222 ctx.RegisterBottomUpMutator("deps", depsMutator)
Colin Crossda70fd02019-12-30 18:40:09 -0800223 _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
Colin Crossd7b0f602016-06-02 15:30:20 -0700224 if len(errs) > 0 {
225 t.Errorf("unexpected parse errors:")
226 for _, err := range errs {
227 t.Errorf(" %s", err)
228 }
229 t.FailNow()
230 }
231
Colin Cross874a3462017-07-31 17:26:06 -0700232 _, errs = ctx.ResolveDependencies(nil)
Colin Crossd7b0f602016-06-02 15:30:20 -0700233 if len(errs) > 0 {
234 t.Errorf("unexpected dep errors:")
235 for _, err := range errs {
236 t.Errorf(" %s", err)
237 }
238 t.FailNow()
239 }
Yuchen Wuf9958462015-10-09 17:31:27 -0700240
Colin Cross5df74a82020-08-24 16:18:21 -0700241 topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
Paul Duffin72bab172020-04-02 10:51:33 +0100242 outputDown, outputUp := walkDependencyGraph(ctx, topModule, false)
Colin Cross526e02f2018-06-21 13:31:53 -0700243 if outputDown != "BCEFG" {
244 t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEFG", outputDown)
Colin Crossbafd5f52016-08-06 22:52:01 -0700245 }
Colin Cross526e02f2018-06-21 13:31:53 -0700246 if outputUp != "BEGFC" {
247 t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BEGFC", outputUp)
Colin Cross9607a9f2018-06-20 11:16:37 -0700248 }
249}
250
Colin Cross526e02f2018-06-21 13:31:53 -0700251// |===B---D - represents a non-walkable edge
252// A = represents a walkable edge
253// |===C===E===\ A should not be visited because it's the root node.
254// | | B, D should not be walked.
255// |===F===G===H G should be visited multiple times
256// \===/ H should only be visited once
Colin Cross9607a9f2018-06-20 11:16:37 -0700257func TestWalkDepsDuplicates(t *testing.T) {
258 ctx := NewContext()
259 ctx.MockFileSystem(map[string][]byte{
260 "Blueprints": []byte(`
261 foo_module {
262 name: "A",
263 deps: ["B", "C"],
264 }
265
266 bar_module {
267 name: "B",
268 deps: ["D"],
269 }
270
271 foo_module {
272 name: "C",
273 deps: ["E", "F"],
274 }
275
276 foo_module {
277 name: "D",
278 }
279
280 foo_module {
281 name: "E",
282 deps: ["G"],
283 }
284
285 foo_module {
286 name: "F",
287 deps: ["G", "G"],
288 }
289
290 foo_module {
291 name: "G",
Colin Cross526e02f2018-06-21 13:31:53 -0700292 deps: ["H"],
293 }
294
295 foo_module {
296 name: "H",
Colin Cross9607a9f2018-06-20 11:16:37 -0700297 }
298 `),
299 })
300
301 ctx.RegisterModuleType("foo_module", newFooModule)
302 ctx.RegisterModuleType("bar_module", newBarModule)
Paul Duffin72bab172020-04-02 10:51:33 +0100303 ctx.RegisterBottomUpMutator("deps", depsMutator)
Colin Crossda70fd02019-12-30 18:40:09 -0800304 _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
Colin Cross9607a9f2018-06-20 11:16:37 -0700305 if len(errs) > 0 {
306 t.Errorf("unexpected parse errors:")
307 for _, err := range errs {
308 t.Errorf(" %s", err)
309 }
310 t.FailNow()
311 }
312
313 _, errs = ctx.ResolveDependencies(nil)
314 if len(errs) > 0 {
315 t.Errorf("unexpected dep errors:")
316 for _, err := range errs {
317 t.Errorf(" %s", err)
318 }
319 t.FailNow()
320 }
321
Colin Cross5df74a82020-08-24 16:18:21 -0700322 topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
Paul Duffin72bab172020-04-02 10:51:33 +0100323 outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
Colin Cross526e02f2018-06-21 13:31:53 -0700324 if outputDown != "BCEGHFGG" {
325 t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEGHFGG", outputDown)
Colin Cross9607a9f2018-06-20 11:16:37 -0700326 }
Colin Cross526e02f2018-06-21 13:31:53 -0700327 if outputUp != "BHGEGGFC" {
328 t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BHGEGGFC", outputUp)
Yuchen Wuf9958462015-10-09 17:31:27 -0700329 }
330}
Colin Crossaf4fd212017-07-28 14:32:36 -0700331
Paul Duffin72bab172020-04-02 10:51:33 +0100332// - represents a non-walkable edge
333// A = represents a walkable edge
334// |===B-------\ A should not be visited because it's the root node.
335// | | B -> D should not be walked.
336// |===C===D===E B -> C -> D -> E should be walked
337func TestWalkDepsDuplicates_IgnoreFirstPath(t *testing.T) {
338 ctx := NewContext()
339 ctx.MockFileSystem(map[string][]byte{
340 "Blueprints": []byte(`
341 foo_module {
342 name: "A",
343 deps: ["B"],
344 }
345
346 foo_module {
347 name: "B",
348 deps: ["C"],
349 ignored_deps: ["D"],
350 }
351
352 foo_module {
353 name: "C",
354 deps: ["D"],
355 }
356
357 foo_module {
358 name: "D",
359 deps: ["E"],
360 }
361
362 foo_module {
363 name: "E",
364 }
365 `),
366 })
367
368 ctx.RegisterModuleType("foo_module", newFooModule)
369 ctx.RegisterModuleType("bar_module", newBarModule)
370 ctx.RegisterBottomUpMutator("deps", depsMutator)
371 _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
372 if len(errs) > 0 {
373 t.Errorf("unexpected parse errors:")
374 for _, err := range errs {
375 t.Errorf(" %s", err)
376 }
377 t.FailNow()
378 }
379
380 _, errs = ctx.ResolveDependencies(nil)
381 if len(errs) > 0 {
382 t.Errorf("unexpected dep errors:")
383 for _, err := range errs {
384 t.Errorf(" %s", err)
385 }
386 t.FailNow()
387 }
388
Colin Cross5df74a82020-08-24 16:18:21 -0700389 topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
Paul Duffin72bab172020-04-02 10:51:33 +0100390 outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
391 expectedDown := "BDCDE"
392 if outputDown != expectedDown {
393 t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: %s", outputDown, expectedDown)
394 }
395 expectedUp := "DEDCB"
396 if outputUp != expectedUp {
397 t.Errorf("unexpected walkDeps behaviour: %s\nup should be: %s", outputUp, expectedUp)
398 }
399}
400
Colin Crossaf4fd212017-07-28 14:32:36 -0700401func TestCreateModule(t *testing.T) {
402 ctx := newContext()
403 ctx.MockFileSystem(map[string][]byte{
404 "Blueprints": []byte(`
405 foo_module {
406 name: "A",
407 deps: ["B", "C"],
408 }
409 `),
410 })
411
412 ctx.RegisterTopDownMutator("create", createTestMutator)
Paul Duffin72bab172020-04-02 10:51:33 +0100413 ctx.RegisterBottomUpMutator("deps", depsMutator)
Colin Crossaf4fd212017-07-28 14:32:36 -0700414
415 ctx.RegisterModuleType("foo_module", newFooModule)
416 ctx.RegisterModuleType("bar_module", newBarModule)
Colin Crossda70fd02019-12-30 18:40:09 -0800417 _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
Colin Crossaf4fd212017-07-28 14:32:36 -0700418 if len(errs) > 0 {
419 t.Errorf("unexpected parse errors:")
420 for _, err := range errs {
421 t.Errorf(" %s", err)
422 }
423 t.FailNow()
424 }
425
Colin Cross874a3462017-07-31 17:26:06 -0700426 _, errs = ctx.ResolveDependencies(nil)
Colin Crossaf4fd212017-07-28 14:32:36 -0700427 if len(errs) > 0 {
428 t.Errorf("unexpected dep errors:")
429 for _, err := range errs {
430 t.Errorf(" %s", err)
431 }
432 t.FailNow()
433 }
434
Colin Cross5df74a82020-08-24 16:18:21 -0700435 a := ctx.moduleGroupFromName("A", nil).modules.firstModule().logicModule.(*fooModule)
436 b := ctx.moduleGroupFromName("B", nil).modules.firstModule().logicModule.(*barModule)
437 c := ctx.moduleGroupFromName("C", nil).modules.firstModule().logicModule.(*barModule)
438 d := ctx.moduleGroupFromName("D", nil).modules.firstModule().logicModule.(*fooModule)
Colin Crossaf4fd212017-07-28 14:32:36 -0700439
440 checkDeps := func(m Module, expected string) {
441 var deps []string
442 ctx.VisitDirectDeps(m, func(m Module) {
443 deps = append(deps, ctx.ModuleName(m))
444 })
445 got := strings.Join(deps, ",")
446 if got != expected {
447 t.Errorf("unexpected %q dependencies, got %q expected %q",
448 ctx.ModuleName(m), got, expected)
449 }
450 }
451
452 checkDeps(a, "B,C")
453 checkDeps(b, "D")
454 checkDeps(c, "D")
455 checkDeps(d, "")
456}
457
458func createTestMutator(ctx TopDownMutatorContext) {
459 type props struct {
460 Name string
461 Deps []string
462 }
463
464 ctx.CreateModule(newBarModule, &props{
465 Name: "B",
466 Deps: []string{"D"},
467 })
468
469 ctx.CreateModule(newBarModule, &props{
470 Name: "C",
471 Deps: []string{"D"},
472 })
473
474 ctx.CreateModule(newFooModule, &props{
475 Name: "D",
476 })
477}
Jeff Gaston656870f2017-11-29 18:37:31 -0800478
479func TestWalkFileOrder(t *testing.T) {
480 // Run the test once to see how long it normally takes
481 start := time.Now()
482 doTestWalkFileOrder(t, time.Duration(0))
483 duration := time.Since(start)
484
485 // Run the test again, but put enough of a sleep into each visitor to detect ordering
486 // problems if they exist
487 doTestWalkFileOrder(t, duration)
488}
489
490// test that WalkBlueprintsFiles calls asyncVisitor in the right order
491func doTestWalkFileOrder(t *testing.T, sleepDuration time.Duration) {
492 // setup mock context
493 ctx := newContext()
494 mockFiles := map[string][]byte{
495 "Blueprints": []byte(`
496 sample_module {
497 name: "a",
498 }
499 `),
500 "dir1/Blueprints": []byte(`
501 sample_module {
502 name: "b",
503 }
504 `),
505 "dir1/dir2/Blueprints": []byte(`
506 sample_module {
507 name: "c",
508 }
509 `),
510 }
511 ctx.MockFileSystem(mockFiles)
512
513 // prepare to monitor the visit order
514 visitOrder := []string{}
515 visitLock := sync.Mutex{}
516 correctVisitOrder := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
517
518 // sleep longer when processing the earlier files
519 chooseSleepDuration := func(fileName string) (duration time.Duration) {
520 duration = time.Duration(0)
521 for i := len(correctVisitOrder) - 1; i >= 0; i-- {
522 if fileName == correctVisitOrder[i] {
523 return duration
524 }
525 duration = duration + sleepDuration
526 }
527 panic("unrecognized file name " + fileName)
528 }
529
530 visitor := func(file *parser.File) {
531 time.Sleep(chooseSleepDuration(file.Name))
532 visitLock.Lock()
533 defer visitLock.Unlock()
534 visitOrder = append(visitOrder, file.Name)
535 }
536 keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
537
538 // visit the blueprints files
539 ctx.WalkBlueprintsFiles(".", keys, visitor)
540
541 // check the order
542 if !reflect.DeepEqual(visitOrder, correctVisitOrder) {
543 t.Errorf("Incorrect visit order; expected %v, got %v", correctVisitOrder, visitOrder)
544 }
545}
Jeff Gastona7e408a2017-12-05 15:11:55 -0800546
547// test that WalkBlueprintsFiles reports syntax errors
548func TestWalkingWithSyntaxError(t *testing.T) {
549 // setup mock context
550 ctx := newContext()
551 mockFiles := map[string][]byte{
552 "Blueprints": []byte(`
553 sample_module {
554 name: "a" "b",
555 }
556 `),
557 "dir1/Blueprints": []byte(`
558 sample_module {
559 name: "b",
560 `),
561 "dir1/dir2/Blueprints": []byte(`
562 sample_module {
563 name: "c",
564 }
565 `),
566 }
567 ctx.MockFileSystem(mockFiles)
568
569 keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
570
571 // visit the blueprints files
572 _, errs := ctx.WalkBlueprintsFiles(".", keys, func(file *parser.File) {})
573
574 expectedErrs := []error{
575 errors.New(`Blueprints:3:18: expected "}", found String`),
576 errors.New(`dir1/Blueprints:4:3: expected "}", found EOF`),
577 }
578 if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) {
579 t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
580 }
581
582}
Jaewoong Jungccc34942018-10-11 13:01:05 -0700583
584func TestParseFailsForModuleWithoutName(t *testing.T) {
585 ctx := NewContext()
586 ctx.MockFileSystem(map[string][]byte{
587 "Blueprints": []byte(`
588 foo_module {
589 name: "A",
590 }
591
592 bar_module {
593 deps: ["A"],
594 }
595 `),
596 })
597 ctx.RegisterModuleType("foo_module", newFooModule)
598 ctx.RegisterModuleType("bar_module", newBarModule)
599
Colin Crossda70fd02019-12-30 18:40:09 -0800600 _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
Jaewoong Jungccc34942018-10-11 13:01:05 -0700601
602 expectedErrs := []error{
603 errors.New(`Blueprints:6:4: property 'name' is missing from a module`),
604 }
605 if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) {
606 t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
607 }
608}
Colin Cross39644c02020-08-21 18:20:38 -0700609
610func Test_findVariant(t *testing.T) {
611 module := &moduleInfo{
612 variant: variant{
613 name: "normal_local",
614 variations: variationMap{
615 "normal": "normal",
616 "local": "local",
617 },
618 dependencyVariations: variationMap{
619 "normal": "normal",
620 },
621 },
622 }
623
624 type alias struct {
Colin Cross5df74a82020-08-24 16:18:21 -0700625 variant variant
626 target int
Colin Cross39644c02020-08-21 18:20:38 -0700627 }
628
Colin Cross5df74a82020-08-24 16:18:21 -0700629 makeDependencyGroup := func(in ...interface{}) *moduleGroup {
Colin Cross39644c02020-08-21 18:20:38 -0700630 group := &moduleGroup{
Colin Cross5df74a82020-08-24 16:18:21 -0700631 name: "dep",
632 }
633 for _, x := range in {
634 switch m := x.(type) {
635 case *moduleInfo:
636 m.group = group
637 group.modules = append(group.modules, m)
638 case alias:
639 // aliases may need to target modules that haven't been processed
640 // yet, put an empty alias in for now.
641 group.modules = append(group.modules, nil)
642 default:
643 t.Fatalf("unexpected type %T", x)
644 }
Colin Cross39644c02020-08-21 18:20:38 -0700645 }
646
Colin Cross5df74a82020-08-24 16:18:21 -0700647 for i, x := range in {
648 switch m := x.(type) {
649 case *moduleInfo:
650 // already added in the first pass
651 case alias:
652 group.modules[i] = &moduleAlias{
653 variant: m.variant,
654 target: group.modules[m.target].moduleOrAliasTarget(),
655 }
656 default:
657 t.Fatalf("unexpected type %T", x)
658 }
Colin Cross39644c02020-08-21 18:20:38 -0700659 }
660
Colin Cross39644c02020-08-21 18:20:38 -0700661 return group
662 }
663
664 tests := []struct {
665 name string
666 possibleDeps *moduleGroup
667 variations []Variation
668 far bool
669 reverse bool
670 want string
671 }{
672 {
673 name: "AddVariationDependencies(nil)",
674 // A dependency that matches the non-local variations of the module
Colin Cross5df74a82020-08-24 16:18:21 -0700675 possibleDeps: makeDependencyGroup(
676 &moduleInfo{
Colin Cross39644c02020-08-21 18:20:38 -0700677 variant: variant{
678 name: "normal",
679 variations: variationMap{
680 "normal": "normal",
681 },
682 },
683 },
Colin Cross5df74a82020-08-24 16:18:21 -0700684 ),
Colin Cross39644c02020-08-21 18:20:38 -0700685 variations: nil,
686 far: false,
687 reverse: false,
688 want: "normal",
689 },
690 {
691 name: "AddVariationDependencies(nil) to alias",
692 // A dependency with an alias that matches the non-local variations of the module
Colin Cross5df74a82020-08-24 16:18:21 -0700693 possibleDeps: makeDependencyGroup(
694 alias{
695 variant: variant{
696 name: "normal",
697 variations: variationMap{
698 "normal": "normal",
699 },
700 },
701 target: 1,
702 },
703 &moduleInfo{
Colin Cross39644c02020-08-21 18:20:38 -0700704 variant: variant{
705 name: "normal_a",
706 variations: variationMap{
707 "normal": "normal",
708 "a": "a",
709 },
710 },
711 },
Colin Cross5df74a82020-08-24 16:18:21 -0700712 ),
Colin Cross39644c02020-08-21 18:20:38 -0700713 variations: nil,
714 far: false,
715 reverse: false,
716 want: "normal_a",
717 },
718 {
719 name: "AddVariationDependencies(a)",
720 // A dependency with local variations
Colin Cross5df74a82020-08-24 16:18:21 -0700721 possibleDeps: makeDependencyGroup(
722 &moduleInfo{
Colin Cross39644c02020-08-21 18:20:38 -0700723 variant: variant{
724 name: "normal_a",
725 variations: variationMap{
726 "normal": "normal",
727 "a": "a",
728 },
729 },
730 },
Colin Cross5df74a82020-08-24 16:18:21 -0700731 ),
Colin Cross39644c02020-08-21 18:20:38 -0700732 variations: []Variation{{"a", "a"}},
733 far: false,
734 reverse: false,
735 want: "normal_a",
736 },
737 {
738 name: "AddFarVariationDependencies(far)",
739 // A dependency with far variations
Colin Cross5df74a82020-08-24 16:18:21 -0700740 possibleDeps: makeDependencyGroup(
741 &moduleInfo{
Colin Cross39644c02020-08-21 18:20:38 -0700742 variant: variant{
Colin Cross5dc67592020-08-24 14:46:13 -0700743 name: "",
744 variations: nil,
745 },
746 },
747 &moduleInfo{
748 variant: variant{
Colin Cross39644c02020-08-21 18:20:38 -0700749 name: "far",
750 variations: variationMap{
751 "far": "far",
752 },
753 },
754 },
Colin Cross5df74a82020-08-24 16:18:21 -0700755 ),
Colin Cross39644c02020-08-21 18:20:38 -0700756 variations: []Variation{{"far", "far"}},
757 far: true,
758 reverse: false,
759 want: "far",
760 },
761 {
762 name: "AddFarVariationDependencies(far) to alias",
763 // A dependency with far variations and aliases
Colin Cross5df74a82020-08-24 16:18:21 -0700764 possibleDeps: makeDependencyGroup(
765 alias{
766 variant: variant{
767 name: "far",
768 variations: variationMap{
769 "far": "far",
770 },
771 },
772 target: 2,
773 },
774 &moduleInfo{
Colin Cross39644c02020-08-21 18:20:38 -0700775 variant: variant{
776 name: "far_a",
777 variations: variationMap{
778 "far": "far",
779 "a": "a",
780 },
781 },
782 },
Colin Cross5df74a82020-08-24 16:18:21 -0700783 &moduleInfo{
784 variant: variant{
785 name: "far_b",
786 variations: variationMap{
787 "far": "far",
788 "b": "b",
789 },
Colin Cross39644c02020-08-21 18:20:38 -0700790 },
Colin Cross39644c02020-08-21 18:20:38 -0700791 },
Colin Cross5df74a82020-08-24 16:18:21 -0700792 ),
Colin Cross39644c02020-08-21 18:20:38 -0700793 variations: []Variation{{"far", "far"}},
794 far: true,
795 reverse: false,
Colin Cross5df74a82020-08-24 16:18:21 -0700796 want: "far_b",
Colin Cross39644c02020-08-21 18:20:38 -0700797 },
Colin Cross5dc67592020-08-24 14:46:13 -0700798 {
799 name: "AddFarVariationDependencies(far, b) to missing",
800 // A dependency with far variations and aliases
801 possibleDeps: makeDependencyGroup(
802 alias{
803 variant: variant{
804 name: "far",
805 variations: variationMap{
806 "far": "far",
807 },
808 },
809 target: 1,
810 },
811 &moduleInfo{
812 variant: variant{
813 name: "far_a",
814 variations: variationMap{
815 "far": "far",
816 "a": "a",
817 },
818 },
819 },
820 ),
821 variations: []Variation{{"far", "far"}, {"a", "b"}},
822 far: true,
823 reverse: false,
824 want: "nil",
825 },
Colin Cross39644c02020-08-21 18:20:38 -0700826 }
827 for _, tt := range tests {
828 t.Run(tt.name, func(t *testing.T) {
829 got, _ := findVariant(module, tt.possibleDeps, tt.variations, tt.far, tt.reverse)
Colin Cross5dc67592020-08-24 14:46:13 -0700830 if g, w := got == nil, tt.want == "nil"; g != w {
831 t.Fatalf("findVariant() got = %v, want %v", got, tt.want)
832 }
833 if got != nil {
834 if g, w := got.String(), fmt.Sprintf("module %q variant %q", "dep", tt.want); g != w {
835 t.Errorf("findVariant() got = %v, want %v", g, w)
836 }
Colin Cross39644c02020-08-21 18:20:38 -0700837 }
838 })
839 }
840}
Colin Crossc4773d92020-08-25 17:12:59 -0700841
842func Test_parallelVisit(t *testing.T) {
Colin Crossc4773d92020-08-25 17:12:59 -0700843 addDep := func(from, to *moduleInfo) {
844 from.directDeps = append(from.directDeps, depInfo{to, nil})
845 from.forwardDeps = append(from.forwardDeps, to)
846 to.reverseDeps = append(to.reverseDeps, from)
847 }
848
Colin Cross7d4958d2021-02-08 15:34:08 -0800849 create := func(name string) *moduleInfo {
850 m := &moduleInfo{
851 group: &moduleGroup{
852 name: name,
853 },
854 }
855 m.group.modules = modulesOrAliases{m}
856 return m
857 }
858 moduleA := create("A")
859 moduleB := create("B")
860 moduleC := create("C")
861 moduleD := create("D")
862 moduleE := create("E")
863 moduleF := create("F")
864 moduleG := create("G")
865
866 // A depends on B, B depends on C. Nothing depends on D through G, and they don't depend on
867 // anything.
Colin Crossc4773d92020-08-25 17:12:59 -0700868 addDep(moduleA, moduleB)
869 addDep(moduleB, moduleC)
870
871 t.Run("no modules", func(t *testing.T) {
872 errs := parallelVisit(nil, bottomUpVisitorImpl{}, 1,
873 func(module *moduleInfo, pause chan<- pauseSpec) bool {
874 panic("unexpected call to visitor")
875 })
876 if errs != nil {
877 t.Errorf("expected no errors, got %q", errs)
878 }
879 })
880 t.Run("bottom up", func(t *testing.T) {
881 order := ""
882 errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 1,
883 func(module *moduleInfo, pause chan<- pauseSpec) bool {
884 order += module.group.name
885 return false
886 })
887 if errs != nil {
888 t.Errorf("expected no errors, got %q", errs)
889 }
890 if g, w := order, "CBA"; g != w {
891 t.Errorf("expected order %q, got %q", w, g)
892 }
893 })
894 t.Run("pause", func(t *testing.T) {
895 order := ""
896 errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 1,
897 func(module *moduleInfo, pause chan<- pauseSpec) bool {
898 if module == moduleC {
899 // Pause module C on module D
900 unpause := make(chan struct{})
901 pause <- pauseSpec{moduleC, moduleD, unpause}
902 <-unpause
903 }
904 order += module.group.name
905 return false
906 })
907 if errs != nil {
908 t.Errorf("expected no errors, got %q", errs)
909 }
910 if g, w := order, "DCBA"; g != w {
911 t.Errorf("expected order %q, got %q", w, g)
912 }
913 })
914 t.Run("cancel", func(t *testing.T) {
915 order := ""
916 errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 1,
917 func(module *moduleInfo, pause chan<- pauseSpec) bool {
918 order += module.group.name
919 // Cancel in module B
920 return module == moduleB
921 })
922 if errs != nil {
923 t.Errorf("expected no errors, got %q", errs)
924 }
925 if g, w := order, "CB"; g != w {
926 t.Errorf("expected order %q, got %q", w, g)
927 }
928 })
929 t.Run("pause and cancel", func(t *testing.T) {
930 order := ""
931 errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 1,
932 func(module *moduleInfo, pause chan<- pauseSpec) bool {
933 if module == moduleC {
934 // Pause module C on module D
935 unpause := make(chan struct{})
936 pause <- pauseSpec{moduleC, moduleD, unpause}
937 <-unpause
938 }
939 order += module.group.name
940 // Cancel in module D
941 return module == moduleD
942 })
943 if errs != nil {
944 t.Errorf("expected no errors, got %q", errs)
945 }
946 if g, w := order, "D"; g != w {
947 t.Errorf("expected order %q, got %q", w, g)
948 }
949 })
950 t.Run("parallel", func(t *testing.T) {
951 order := ""
952 errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
953 func(module *moduleInfo, pause chan<- pauseSpec) bool {
954 order += module.group.name
955 return false
956 })
957 if errs != nil {
958 t.Errorf("expected no errors, got %q", errs)
959 }
960 if g, w := order, "CBA"; g != w {
961 t.Errorf("expected order %q, got %q", w, g)
962 }
963 })
964 t.Run("pause existing", func(t *testing.T) {
965 order := ""
966 errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
967 func(module *moduleInfo, pause chan<- pauseSpec) bool {
968 if module == moduleA {
969 // Pause module A on module B (an existing dependency)
970 unpause := make(chan struct{})
971 pause <- pauseSpec{moduleA, moduleB, unpause}
972 <-unpause
973 }
974 order += module.group.name
975 return false
976 })
977 if errs != nil {
978 t.Errorf("expected no errors, got %q", errs)
979 }
980 if g, w := order, "CBA"; g != w {
981 t.Errorf("expected order %q, got %q", w, g)
982 }
983 })
984 t.Run("cycle", func(t *testing.T) {
985 errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
986 func(module *moduleInfo, pause chan<- pauseSpec) bool {
987 if module == moduleC {
988 // Pause module C on module A (a dependency cycle)
989 unpause := make(chan struct{})
990 pause <- pauseSpec{moduleC, moduleA, unpause}
991 <-unpause
992 }
993 return false
994 })
995 want := []string{
996 `encountered dependency cycle`,
997 `"C" depends on "A"`,
998 `"A" depends on "B"`,
999 `"B" depends on "C"`,
1000 }
1001 for i := range want {
1002 if len(errs) <= i {
1003 t.Errorf("missing error %s", want[i])
1004 } else if !strings.Contains(errs[i].Error(), want[i]) {
1005 t.Errorf("expected error %s, got %s", want[i], errs[i])
1006 }
1007 }
1008 if len(errs) > len(want) {
1009 for _, err := range errs[len(want):] {
1010 t.Errorf("unexpected error %s", err.Error())
1011 }
1012 }
1013 })
1014 t.Run("pause cycle", func(t *testing.T) {
1015 errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 3,
1016 func(module *moduleInfo, pause chan<- pauseSpec) bool {
1017 if module == moduleC {
1018 // Pause module C on module D
1019 unpause := make(chan struct{})
1020 pause <- pauseSpec{moduleC, moduleD, unpause}
1021 <-unpause
1022 }
1023 if module == moduleD {
1024 // Pause module D on module C (a pause cycle)
1025 unpause := make(chan struct{})
1026 pause <- pauseSpec{moduleD, moduleC, unpause}
1027 <-unpause
1028 }
1029 return false
1030 })
1031 want := []string{
1032 `encountered dependency cycle`,
Colin Crossc4773d92020-08-25 17:12:59 -07001033 `"D" depends on "C"`,
Colin Cross7d4958d2021-02-08 15:34:08 -08001034 `"C" depends on "D"`,
1035 }
1036 for i := range want {
1037 if len(errs) <= i {
1038 t.Errorf("missing error %s", want[i])
1039 } else if !strings.Contains(errs[i].Error(), want[i]) {
1040 t.Errorf("expected error %s, got %s", want[i], errs[i])
1041 }
1042 }
1043 if len(errs) > len(want) {
1044 for _, err := range errs[len(want):] {
1045 t.Errorf("unexpected error %s", err.Error())
1046 }
1047 }
1048 })
1049 t.Run("pause cycle with deps", func(t *testing.T) {
1050 pauseDeps := map[*moduleInfo]*moduleInfo{
1051 // F and G form a pause cycle
1052 moduleF: moduleG,
1053 moduleG: moduleF,
1054 // D depends on E which depends on the pause cycle, making E the first alphabetical
1055 // entry in pauseMap, which is not part of the cycle.
1056 moduleD: moduleE,
1057 moduleE: moduleF,
1058 }
1059 errs := parallelVisit([]*moduleInfo{moduleD, moduleE, moduleF, moduleG}, bottomUpVisitorImpl{}, 4,
1060 func(module *moduleInfo, pause chan<- pauseSpec) bool {
1061 if dep, ok := pauseDeps[module]; ok {
1062 unpause := make(chan struct{})
1063 pause <- pauseSpec{module, dep, unpause}
1064 <-unpause
1065 }
1066 return false
1067 })
1068 want := []string{
1069 `encountered dependency cycle`,
1070 `"G" depends on "F"`,
1071 `"F" depends on "G"`,
Colin Crossc4773d92020-08-25 17:12:59 -07001072 }
1073 for i := range want {
1074 if len(errs) <= i {
1075 t.Errorf("missing error %s", want[i])
1076 } else if !strings.Contains(errs[i].Error(), want[i]) {
1077 t.Errorf("expected error %s, got %s", want[i], errs[i])
1078 }
1079 }
1080 if len(errs) > len(want) {
1081 for _, err := range errs[len(want):] {
1082 t.Errorf("unexpected error %s", err.Error())
1083 }
1084 }
1085 })
1086}