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 noCreateAliasMutator(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 createAliasMutator(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 addVariantDepsMutator(variants []Variation, tag DependencyTag, from, to string) func(ctx BottomUpMutatorContext) {
53	return func(ctx BottomUpMutatorContext) {
54		if ctx.ModuleName() == from {
55			ctx.AddVariationDependencies(variants, tag, to)
56		}
57	}
58}
59
60func TestAliases(t *testing.T) {
61	runWithFailures := func(ctx *Context, expectedErr string) {
62		t.Helper()
63		bp := `
64			test {
65				name: "foo",
66			}
67
68			test {
69				name: "bar",
70			}
71		`
72
73		mockFS := map[string][]byte{
74			"Blueprints": []byte(bp),
75		}
76
77		ctx.MockFileSystem(mockFS)
78
79		_, errs := ctx.ParseFileList(".", []string{"Blueprints"}, nil)
80		if len(errs) > 0 {
81			t.Errorf("unexpected parse errors:")
82			for _, err := range errs {
83				t.Errorf("  %s", err)
84			}
85		}
86
87		_, errs = ctx.ResolveDependencies(nil)
88		if len(errs) > 0 {
89			if expectedErr == "" {
90				t.Errorf("unexpected dep errors:")
91				for _, err := range errs {
92					t.Errorf("  %s", err)
93				}
94			} else {
95				for _, err := range errs {
96					if strings.Contains(err.Error(), expectedErr) {
97						continue
98					} else {
99						t.Errorf("unexpected dep error: %s", err)
100					}
101				}
102			}
103		} else if expectedErr != "" {
104			t.Errorf("missing dep error: %s", expectedErr)
105		}
106	}
107
108	run := func(ctx *Context) {
109		t.Helper()
110		runWithFailures(ctx, "")
111	}
112
113	t.Run("simple", func(t *testing.T) {
114		// Creates a module "bar" with variants "a" and "b" and alias "" -> "b".
115		// Tests a dependency from "foo" to "bar" variant "b" through alias "".
116		ctx := NewContext()
117		ctx.RegisterModuleType("test", newModuleCtxTestModule)
118		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
119		ctx.RegisterBottomUpMutator("2", addVariantDepsMutator(nil, nil, "foo", "bar"))
120
121		run(ctx)
122
123		foo := ctx.moduleGroupFromName("foo", nil).modules[0]
124		barB := ctx.moduleGroupFromName("bar", nil).modules[1]
125
126		if g, w := barB.variantName, "b"; g != w {
127			t.Fatalf("expected bar.modules[1] variant to be %q, got %q", w, g)
128		}
129
130		if g, w := foo.forwardDeps, []*moduleInfo{barB}; !reflect.DeepEqual(g, w) {
131			t.Fatalf("expected foo deps to be %q, got %q", w, g)
132		}
133	})
134
135	t.Run("chained", func(t *testing.T) {
136		// Creates a module "bar" with variants "a_a", "a_b", "b_a" and "b_b" and aliases "" -> "b_b",
137		// "a" -> "a_b", and "b" -> "b_b".
138		// Tests a dependency from "foo" to "bar" variant "b_b" through alias "".
139		ctx := NewContext()
140		ctx.RegisterModuleType("test", newModuleCtxTestModule)
141		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
142		ctx.RegisterBottomUpMutator("2", createAliasMutator("bar"))
143		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
144
145		run(ctx)
146
147		foo := ctx.moduleGroupFromName("foo", nil).modules[0]
148		barBB := ctx.moduleGroupFromName("bar", nil).modules[3]
149
150		if g, w := barBB.variantName, "b_b"; g != w {
151			t.Fatalf("expected bar.modules[3] variant to be %q, got %q", w, g)
152		}
153
154		if g, w := foo.forwardDeps, []*moduleInfo{barBB}; !reflect.DeepEqual(g, w) {
155			t.Fatalf("expected foo deps to be %q, got %q", w, g)
156		}
157	})
158
159	t.Run("chained2", func(t *testing.T) {
160		// Creates a module "bar" with variants "a_a", "a_b", "b_a" and "b_b" and aliases "" -> "b_b",
161		// "a" -> "a_b", and "b" -> "b_b".
162		// Tests a dependency from "foo" to "bar" variant "a_b" through alias "a".
163		ctx := NewContext()
164		ctx.RegisterModuleType("test", newModuleCtxTestModule)
165		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
166		ctx.RegisterBottomUpMutator("2", createAliasMutator("bar"))
167		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "a"}}, nil, "foo", "bar"))
168
169		run(ctx)
170
171		foo := ctx.moduleGroupFromName("foo", nil).modules[0]
172		barAB := ctx.moduleGroupFromName("bar", nil).modules[1]
173
174		if g, w := barAB.variantName, "a_b"; g != w {
175			t.Fatalf("expected bar.modules[1] variant to be %q, got %q", w, g)
176		}
177
178		if g, w := foo.forwardDeps, []*moduleInfo{barAB}; !reflect.DeepEqual(g, w) {
179			t.Fatalf("expected foo deps to be %q, got %q", w, g)
180		}
181	})
182
183	t.Run("removed dangling alias", func(t *testing.T) {
184		// Creates a module "bar" with variants "a" and "b" and aliases "" -> "b", then splits the variants into
185		// "a_a", "a_b", "b_a" and "b_b" without creating new aliases.
186		// Tests a dependency from "foo" to removed "bar" alias "" fails.
187		ctx := NewContext()
188		ctx.RegisterModuleType("test", newModuleCtxTestModule)
189		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
190		ctx.RegisterBottomUpMutator("2", noCreateAliasMutator("bar"))
191		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
192
193		runWithFailures(ctx, `dependency "bar" of "foo" missing variant:`+"\n  \n"+
194			"available variants:"+
195			"\n  1:a, 2:a\n  1:a, 2:b\n  1:b, 2:a\n  1:b, 2:b")
196	})
197}
198
199func expectedErrors(t *testing.T, errs []error, expectedMessages ...string) {
200	t.Helper()
201	if len(errs) != len(expectedMessages) {
202		t.Errorf("expected %d error, found: %q", len(expectedMessages), errs)
203	} else {
204		for i, expected := range expectedMessages {
205			err := errs[i]
206			if err.Error() != expected {
207				t.Errorf("expected error %q found %q", expected, err)
208			}
209		}
210	}
211}
212
213func TestCheckBlueprintSyntax(t *testing.T) {
214	factories := map[string]ModuleFactory{
215		"test": newModuleCtxTestModule,
216	}
217
218	t.Run("valid", func(t *testing.T) {
219		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
220test {
221	name: "test",
222}
223`)
224		expectedErrors(t, errs)
225	})
226
227	t.Run("syntax error", func(t *testing.T) {
228		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
229test {
230	name: "test",
231
232`)
233
234		expectedErrors(t, errs, `path/Blueprint:5:1: expected "}", found EOF`)
235	})
236
237	t.Run("unknown module type", func(t *testing.T) {
238		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
239test2 {
240	name: "test",
241}
242`)
243
244		expectedErrors(t, errs, `path/Blueprint:2:1: unrecognized module type "test2"`)
245	})
246
247	t.Run("unknown property name", func(t *testing.T) {
248		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
249test {
250	nam: "test",
251}
252`)
253
254		expectedErrors(t, errs, `path/Blueprint:3:5: unrecognized property "nam"`)
255	})
256
257	t.Run("invalid property type", func(t *testing.T) {
258		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
259test {
260	name: false,
261}
262`)
263
264		expectedErrors(t, errs, `path/Blueprint:3:8: can't assign bool value to string property "name"`)
265	})
266
267	t.Run("multiple failures", func(t *testing.T) {
268		errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
269test {
270	name: false,
271}
272
273test2 {
274	name: false,
275}
276`)
277
278		expectedErrors(t, errs,
279			`path/Blueprint:3:8: can't assign bool value to string property "name"`,
280			`path/Blueprint:6:1: unrecognized module type "test2"`,
281		)
282	})
283}
284