1// Copyright 2019 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	"reflect"
19	"strings"
20	"testing"
21)
22
23type moduleCtxTestModule struct {
24	SimpleName
25}
26
27func newModuleCtxTestModule() (Module, []interface{}) {
28	m := &moduleCtxTestModule{}
29	return m, []interface{}{&m.SimpleName.Properties}
30}
31
32func (f *moduleCtxTestModule) GenerateBuildActions(ModuleContext) {
33}
34
35func noAliasMutator(name string) func(ctx BottomUpMutatorContext) {
36	return func(ctx BottomUpMutatorContext) {
37		if ctx.ModuleName() == name {
38			ctx.CreateVariations("a", "b")
39		}
40	}
41}
42
43func aliasMutator(name string) func(ctx BottomUpMutatorContext) {
44	return func(ctx BottomUpMutatorContext) {
45		if ctx.ModuleName() == name {
46			ctx.CreateVariations("a", "b")
47			ctx.AliasVariation("b")
48		}
49	}
50}
51
52func createAliasMutator(name string) func(ctx BottomUpMutatorContext) {
53	return func(ctx BottomUpMutatorContext) {
54		if ctx.ModuleName() == name {
55			ctx.CreateVariations("a", "b")
56			ctx.CreateAliasVariation("c", "a")
57			ctx.CreateAliasVariation("d", "b")
58			ctx.CreateAliasVariation("e", "a")
59		}
60	}
61}
62
63func addVariantDepsMutator(variants []Variation, tag DependencyTag, from, to string) func(ctx BottomUpMutatorContext) {
64	return func(ctx BottomUpMutatorContext) {
65		if ctx.ModuleName() == from {
66			ctx.AddVariationDependencies(variants, tag, to)
67		}
68	}
69}
70
71func addVariantDepsResultMutator(variants []Variation, tag DependencyTag, from, to string, results map[string][]Module) func(ctx BottomUpMutatorContext) {
72	return func(ctx BottomUpMutatorContext) {
73		if ctx.ModuleName() == from {
74			ret := ctx.AddVariationDependencies(variants, tag, to)
75			results[ctx.ModuleName()] = ret
76		}
77	}
78}
79
80func TestAliasVariation(t *testing.T) {
81	runWithFailures := func(ctx *Context, expectedErr string) {
82		t.Helper()
83		bp := `
84			test {
85				name: "foo",
86			}
87
88			test {
89				name: "bar",
90			}
91		`
92
93		mockFS := map[string][]byte{
94			"Blueprints": []byte(bp),
95		}
96
97		ctx.MockFileSystem(mockFS)
98
99		_, errs := ctx.ParseFileList(".", []string{"Blueprints"}, nil)
100		if len(errs) > 0 {
101			t.Errorf("unexpected parse errors:")
102			for _, err := range errs {
103				t.Errorf("  %s", err)
104			}
105		}
106
107		_, errs = ctx.ResolveDependencies(nil)
108		if len(errs) > 0 {
109			if expectedErr == "" {
110				t.Errorf("unexpected dep errors:")
111				for _, err := range errs {
112					t.Errorf("  %s", err)
113				}
114			} else {
115				for _, err := range errs {
116					if strings.Contains(err.Error(), expectedErr) {
117						continue
118					} else {
119						t.Errorf("unexpected dep error: %s", err)
120					}
121				}
122			}
123		} else if expectedErr != "" {
124			t.Errorf("missing dep error: %s", expectedErr)
125		}
126	}
127
128	run := func(ctx *Context) {
129		t.Helper()
130		runWithFailures(ctx, "")
131	}
132
133	t.Run("simple", func(t *testing.T) {
134		// Creates a module "bar" with variants "a" and "b" and alias "" -> "b".
135		// Tests a dependency from "foo" to "bar" variant "b" through alias "".
136		ctx := NewContext()
137		ctx.RegisterModuleType("test", newModuleCtxTestModule)
138		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
139		ctx.RegisterBottomUpMutator("2", addVariantDepsMutator(nil, nil, "foo", "bar"))
140
141		run(ctx)
142
143		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
144		barB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("b")
145
146		if g, w := foo.forwardDeps, []*moduleInfo{barB}; !reflect.DeepEqual(g, w) {
147			t.Fatalf("expected foo deps to be %q, got %q", w, g)
148		}
149	})
150
151	t.Run("chained", func(t *testing.T) {
152		// Creates a module "bar" with variants "a_a", "a_b", "b_a" and "b_b" and aliases "" -> "b_b",
153		// "a" -> "a_b", and "b" -> "b_b".
154		// Tests a dependency from "foo" to "bar" variant "b_b" through alias "".
155		ctx := NewContext()
156		ctx.RegisterModuleType("test", newModuleCtxTestModule)
157		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
158		ctx.RegisterBottomUpMutator("2", aliasMutator("bar"))
159		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
160
161		run(ctx)
162
163		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
164		barBB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("b_b")
165
166		if g, w := foo.forwardDeps, []*moduleInfo{barBB}; !reflect.DeepEqual(g, w) {
167			t.Fatalf("expected foo deps to be %q, got %q", w, g)
168		}
169	})
170
171	t.Run("chained2", func(t *testing.T) {
172		// Creates a module "bar" with variants "a_a", "a_b", "b_a" and "b_b" and aliases "" -> "b_b",
173		// "a" -> "a_b", and "b" -> "b_b".
174		// Tests a dependency from "foo" to "bar" variant "a_b" through alias "a".
175		ctx := NewContext()
176		ctx.RegisterModuleType("test", newModuleCtxTestModule)
177		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
178		ctx.RegisterBottomUpMutator("2", aliasMutator("bar"))
179		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "a"}}, nil, "foo", "bar"))
180
181		run(ctx)
182
183		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
184		barAB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("a_b")
185
186		if g, w := foo.forwardDeps, []*moduleInfo{barAB}; !reflect.DeepEqual(g, w) {
187			t.Fatalf("expected foo deps to be %q, got %q", w, g)
188		}
189	})
190
191	t.Run("removed dangling alias", func(t *testing.T) {
192		// Creates a module "bar" with variants "a" and "b" and aliases "" -> "b", then splits the variants into
193		// "a_a", "a_b", "b_a" and "b_b" without creating new aliases.
194		// Tests a dependency from "foo" to removed "bar" alias "" fails.
195		ctx := NewContext()
196		ctx.RegisterModuleType("test", newModuleCtxTestModule)
197		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
198		ctx.RegisterBottomUpMutator("2", noAliasMutator("bar"))
199		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
200
201		runWithFailures(ctx, `dependency "bar" of "foo" missing variant:`+"\n  \n"+
202			"available variants:"+
203			"\n  1:a,2:a\n  1:a,2:b\n  1:b,2:a\n  1:b,2:b")
204	})
205}
206
207func TestCreateAliasVariations(t *testing.T) {
208	runWithFailures := func(ctx *Context, expectedErr string) {
209		t.Helper()
210		bp := `
211			test {
212				name: "foo",
213			}
214
215			test {
216				name: "bar",
217			}
218		`
219
220		mockFS := map[string][]byte{
221			"Blueprints": []byte(bp),
222		}
223
224		ctx.MockFileSystem(mockFS)
225
226		_, errs := ctx.ParseFileList(".", []string{"Blueprints"}, nil)
227		if len(errs) > 0 {
228			t.Errorf("unexpected parse errors:")
229			for _, err := range errs {
230				t.Errorf("  %s", err)
231			}
232		}
233
234		_, errs = ctx.ResolveDependencies(nil)
235		if len(errs) > 0 {
236			if expectedErr == "" {
237				t.Errorf("unexpected dep errors:")
238				for _, err := range errs {
239					t.Errorf("  %s", err)
240				}
241			} else {
242				for _, err := range errs {
243					if strings.Contains(err.Error(), expectedErr) {
244						continue
245					} else {
246						t.Errorf("unexpected dep error: %s", err)
247					}
248				}
249			}
250		} else if expectedErr != "" {
251			t.Errorf("missing dep error: %s", expectedErr)
252		}
253	}
254
255	run := func(ctx *Context) {
256		t.Helper()
257		runWithFailures(ctx, "")
258	}
259
260	t.Run("simple", func(t *testing.T) {
261		// Creates a module "bar" with variants "a" and "b" and alias "c" -> "a", "d" -> "b", and "e" -> "a".
262		// Tests a dependency from "foo" to "bar" variant "b" through alias "d".
263		ctx := NewContext()
264		ctx.RegisterModuleType("test", newModuleCtxTestModule)
265		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
266		ctx.RegisterBottomUpMutator("2", addVariantDepsMutator([]Variation{{"1", "d"}}, nil, "foo", "bar"))
267
268		run(ctx)
269
270		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
271		barB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("b")
272
273		if g, w := foo.forwardDeps, []*moduleInfo{barB}; !reflect.DeepEqual(g, w) {
274			t.Fatalf("expected foo deps to be %q, got %q", w, g)
275		}
276	})
277
278	t.Run("chained", func(t *testing.T) {
279		// Creates a module "bar" with variants "a_a", "a_b", "b_a" and "b_b" and aliases "c" -> "a_b",
280		// "d" -> "b_b", and "d" -> "a_b".
281		// Tests a dependency from "foo" to "bar" variant "b_b" through alias "d".
282		ctx := NewContext()
283		ctx.RegisterModuleType("test", newModuleCtxTestModule)
284		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
285		ctx.RegisterBottomUpMutator("2", aliasMutator("bar"))
286		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "d"}}, nil, "foo", "bar"))
287
288		run(ctx)
289
290		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
291		barBB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("b_b")
292
293		if g, w := foo.forwardDeps, []*moduleInfo{barBB}; !reflect.DeepEqual(g, w) {
294			t.Fatalf("expected foo deps to be %q, got %q", w, g)
295		}
296	})
297
298	t.Run("removed dangling alias", func(t *testing.T) {
299		// Creates a module "bar" with variants "a" and "b" and alias "c" -> "a", "d" -> "b", and "e" -> "a",
300		// then splits the variants into "a_a", "a_b", "b_a" and "b_b" without creating new aliases.
301		// Tests a dependency from "foo" to removed "bar" alias "d" fails.
302		ctx := NewContext()
303		ctx.RegisterModuleType("test", newModuleCtxTestModule)
304		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
305		ctx.RegisterBottomUpMutator("2", noAliasMutator("bar"))
306		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "d"}}, nil, "foo", "bar"))
307
308		runWithFailures(ctx, `dependency "bar" of "foo" missing variant:`+"\n  1:d\n"+
309			"available variants:"+
310			"\n  1:a,2:a\n  1:a,2:b\n  1:b,2:a\n  1:b,2:b")
311	})
312}
313
314func expectedErrors(t *testing.T, errs []error, expectedMessages ...string) {
315	t.Helper()
316	if len(errs) != len(expectedMessages) {
317		t.Errorf("expected %d error, found: %q", len(expectedMessages), errs)
318	} else {
319		for i, expected := range expectedMessages {
320			err := errs[i]
321			if err.Error() != expected {
322				t.Errorf("expected error %q found %q", expected, err)
323			}
324		}
325	}
326}
327
328func TestAddVariationDependencies(t *testing.T) {
329	runWithFailures := func(ctx *Context, expectedErr string) {
330		t.Helper()
331		bp := `
332			test {
333				name: "foo",
334			}
335
336			test {
337				name: "bar",
338			}
339		`
340
341		mockFS := map[string][]byte{
342			"Blueprints": []byte(bp),
343		}
344
345		ctx.MockFileSystem(mockFS)
346
347		_, errs := ctx.ParseFileList(".", []string{"Blueprints"}, nil)
348		if len(errs) > 0 {
349			t.Errorf("unexpected parse errors:")
350			for _, err := range errs {
351				t.Errorf("  %s", err)
352			}
353		}
354
355		_, errs = ctx.ResolveDependencies(nil)
356		if len(errs) > 0 {
357			if expectedErr == "" {
358				t.Errorf("unexpected dep errors:")
359				for _, err := range errs {
360					t.Errorf("  %s", err)
361				}
362			} else {
363				for _, err := range errs {
364					if strings.Contains(err.Error(), expectedErr) {
365						continue
366					} else {
367						t.Errorf("unexpected dep error: %s", err)
368					}
369				}
370			}
371		} else if expectedErr != "" {
372			t.Errorf("missing dep error: %s", expectedErr)
373		}
374	}
375
376	run := func(ctx *Context) {
377		t.Helper()
378		runWithFailures(ctx, "")
379	}
380
381	t.Run("parallel", func(t *testing.T) {
382		ctx := NewContext()
383		ctx.RegisterModuleType("test", newModuleCtxTestModule)
384		results := make(map[string][]Module)
385		depsMutator := addVariantDepsResultMutator(nil, nil, "foo", "bar", results)
386		ctx.RegisterBottomUpMutator("deps", depsMutator).Parallel()
387
388		run(ctx)
389
390		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
391		bar := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("")
392
393		if g, w := foo.forwardDeps, []*moduleInfo{bar}; !reflect.DeepEqual(g, w) {
394			t.Fatalf("expected foo deps to be %q, got %q", w, g)
395		}
396
397		if g, w := results["foo"], []Module{bar.logicModule}; !reflect.DeepEqual(g, w) {
398			t.Fatalf("expected AddVariationDependencies return value to be %q, got %q", w, g)
399		}
400	})
401
402	t.Run("non-parallel", func(t *testing.T) {
403		ctx := NewContext()
404		ctx.RegisterModuleType("test", newModuleCtxTestModule)
405		results := make(map[string][]Module)
406		depsMutator := addVariantDepsResultMutator(nil, nil, "foo", "bar", results)
407		ctx.RegisterBottomUpMutator("deps", depsMutator)
408		run(ctx)
409
410		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
411		bar := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("")
412
413		if g, w := foo.forwardDeps, []*moduleInfo{bar}; !reflect.DeepEqual(g, w) {
414			t.Fatalf("expected foo deps to be %q, got %q", w, g)
415		}
416
417		if g, w := results["foo"], []Module{nil}; !reflect.DeepEqual(g, w) {
418			t.Fatalf("expected AddVariationDependencies return value to be %q, got %q", w, g)
419		}
420	})
421
422	t.Run("missing", func(t *testing.T) {
423		ctx := NewContext()
424		ctx.RegisterModuleType("test", newModuleCtxTestModule)
425		results := make(map[string][]Module)
426		depsMutator := addVariantDepsResultMutator(nil, nil, "foo", "baz", results)
427		ctx.RegisterBottomUpMutator("deps", depsMutator).Parallel()
428		runWithFailures(ctx, `"foo" depends on undefined module "baz"`)
429
430		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
431
432		if g, w := foo.forwardDeps, []*moduleInfo(nil); !reflect.DeepEqual(g, w) {
433			t.Fatalf("expected foo deps to be %q, got %q", w, g)
434		}
435
436		if g, w := results["foo"], []Module{nil}; !reflect.DeepEqual(g, w) {
437			t.Fatalf("expected AddVariationDependencies return value to be %q, got %q", w, g)
438		}
439	})
440
441	t.Run("allow missing", func(t *testing.T) {
442		ctx := NewContext()
443		ctx.SetAllowMissingDependencies(true)
444		ctx.RegisterModuleType("test", newModuleCtxTestModule)
445		results := make(map[string][]Module)
446		depsMutator := addVariantDepsResultMutator(nil, nil, "foo", "baz", results)
447		ctx.RegisterBottomUpMutator("deps", depsMutator).Parallel()
448		run(ctx)
449
450		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
451
452		if g, w := foo.forwardDeps, []*moduleInfo(nil); !reflect.DeepEqual(g, w) {
453			t.Fatalf("expected foo deps to be %q, got %q", w, g)
454		}
455
456		if g, w := results["foo"], []Module{nil}; !reflect.DeepEqual(g, w) {
457			t.Fatalf("expected AddVariationDependencies return value to be %q, got %q", w, g)
458		}
459	})
460
461}
462
463func TestCheckBlueprintSyntax(t *testing.T) {
464	factories := map[string]ModuleFactory{
465		"test": newModuleCtxTestModule,
466	}
467
468	t.Run("valid", func(t *testing.T) {
469		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
470test {
471	name: "test",
472}
473`)
474		expectedErrors(t, errs)
475	})
476
477	t.Run("syntax error", func(t *testing.T) {
478		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
479test {
480	name: "test",
481
482`)
483
484		expectedErrors(t, errs, `path/Blueprint:5:1: expected "}", found EOF`)
485	})
486
487	t.Run("unknown module type", func(t *testing.T) {
488		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
489test2 {
490	name: "test",
491}
492`)
493
494		expectedErrors(t, errs, `path/Blueprint:2:1: unrecognized module type "test2"`)
495	})
496
497	t.Run("unknown property name", func(t *testing.T) {
498		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
499test {
500	nam: "test",
501}
502`)
503
504		expectedErrors(t, errs, `path/Blueprint:3:5: unrecognized property "nam"`)
505	})
506
507	t.Run("invalid property type", func(t *testing.T) {
508		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
509test {
510	name: false,
511}
512`)
513
514		expectedErrors(t, errs, `path/Blueprint:3:8: can't assign bool value to string property "name"`)
515	})
516
517	t.Run("multiple failures", func(t *testing.T) {
518		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
519test {
520	name: false,
521}
522
523test2 {
524	name: false,
525}
526`)
527
528		expectedErrors(t, errs,
529			`path/Blueprint:3:8: can't assign bool value to string property "name"`,
530			`path/Blueprint:6:1: unrecognized module type "test2"`,
531		)
532	})
533}
534