• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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