1// Copyright 2024 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	"fmt"
19	"slices"
20	"strings"
21	"testing"
22)
23
24func testTransition(bp string) (*Context, []error) {
25	ctx := newContext()
26	ctx.MockFileSystem(map[string][]byte{
27		"Android.bp": []byte(bp),
28	})
29
30	ctx.RegisterBottomUpMutator("deps", depsMutator)
31	ctx.RegisterTransitionMutator("transition", transitionTestMutator{})
32	ctx.RegisterBottomUpMutator("post_transition_deps", postTransitionDepsMutator)
33
34	ctx.RegisterModuleType("transition_module", newTransitionModule)
35	_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
36	if len(errs) > 0 {
37		return nil, errs
38	}
39
40	_, errs = ctx.ResolveDependencies(nil)
41	if len(errs) > 0 {
42		return nil, errs
43	}
44
45	return ctx, nil
46}
47
48func assertNoErrors(t *testing.T, errs []error) {
49	t.Helper()
50	if len(errs) > 0 {
51		t.Errorf("unexpected errors:")
52		for _, err := range errs {
53			t.Errorf("  %s", err)
54		}
55		t.FailNow()
56	}
57}
58
59const testTransitionBp = `
60			transition_module {
61			    name: "A",
62			    deps: ["B", "C"],
63				split: ["b", "a"],
64			}
65
66			transition_module {
67				name: "B",
68				deps: ["C"],
69				outgoing: "c",
70				%s
71			}
72
73			transition_module {
74				name: "C",
75				deps: ["D"],
76			}
77
78			transition_module {
79				name: "D",
80				incoming: "d",
81				deps: ["E"],
82			}
83
84			transition_module {
85				name: "E",
86			}
87
88			transition_module {
89				name: "F",
90			}
91
92			transition_module {
93				name: "G",
94				outgoing: "h",
95				%s
96			}
97
98			transition_module {
99				name: "H",
100				split: ["h"],
101			}
102		`
103
104func getTransitionModule(ctx *Context, name, variant string) *transitionModule {
105	group := ctx.moduleGroupFromName(name, nil)
106	module := group.moduleOrAliasByVariantName(variant).module()
107	return module.logicModule.(*transitionModule)
108}
109
110func checkTransitionVariants(t *testing.T, ctx *Context, name string, expectedVariants []string) {
111	t.Helper()
112	group := ctx.moduleGroupFromName(name, nil)
113	var gotVariants []string
114	for _, variant := range group.modules {
115		gotVariants = append(gotVariants, variant.moduleOrAliasVariant().variations["transition"])
116	}
117	if !slices.Equal(expectedVariants, gotVariants) {
118		t.Errorf("expected variants of %q to be %q, got %q", name, expectedVariants, gotVariants)
119	}
120}
121
122func checkTransitionDeps(t *testing.T, ctx *Context, m Module, expected ...string) {
123	t.Helper()
124	var got []string
125	ctx.VisitDirectDeps(m, func(m Module) {
126		got = append(got, ctx.ModuleName(m)+"("+ctx.ModuleSubDir(m)+")")
127	})
128	if !slices.Equal(got, expected) {
129		t.Errorf("unexpected %q dependencies, got %q expected %q",
130			ctx.ModuleName(m), got, expected)
131	}
132}
133
134func checkTransitionMutate(t *testing.T, m *transitionModule, variant string) {
135	t.Helper()
136	if m.properties.Mutated != variant {
137		t.Errorf("unexpected mutated property in %q, expected %q got %q", m.Name(), variant, m.properties.Mutated)
138	}
139}
140
141func TestTransition(t *testing.T) {
142	ctx, errs := testTransition(fmt.Sprintf(testTransitionBp, "", ""))
143	assertNoErrors(t, errs)
144
145	// Module A uses Split to create a and b variants
146	checkTransitionVariants(t, ctx, "A", []string{"b", "a"})
147	// Module B inherits a and b variants from A
148	checkTransitionVariants(t, ctx, "B", []string{"", "a", "b"})
149	// Module C inherits a and b variants from A, but gets an outgoing c variant from B
150	checkTransitionVariants(t, ctx, "C", []string{"", "a", "b", "c"})
151	// Module D always has incoming variant d
152	checkTransitionVariants(t, ctx, "D", []string{"", "d"})
153	// Module E inherits d from D
154	checkTransitionVariants(t, ctx, "E", []string{"", "d"})
155	// Module F is untouched
156	checkTransitionVariants(t, ctx, "F", []string{""})
157
158	A_a := getTransitionModule(ctx, "A", "a")
159	A_b := getTransitionModule(ctx, "A", "b")
160	B_a := getTransitionModule(ctx, "B", "a")
161	B_b := getTransitionModule(ctx, "B", "b")
162	C_a := getTransitionModule(ctx, "C", "a")
163	C_b := getTransitionModule(ctx, "C", "b")
164	C_c := getTransitionModule(ctx, "C", "c")
165	D_d := getTransitionModule(ctx, "D", "d")
166	E_d := getTransitionModule(ctx, "E", "d")
167	F := getTransitionModule(ctx, "F", "")
168	G := getTransitionModule(ctx, "G", "")
169	H_h := getTransitionModule(ctx, "H", "h")
170
171	checkTransitionDeps(t, ctx, A_a, "B(a)", "C(a)")
172	checkTransitionDeps(t, ctx, A_b, "B(b)", "C(b)")
173	checkTransitionDeps(t, ctx, B_a, "C(c)")
174	checkTransitionDeps(t, ctx, B_b, "C(c)")
175	checkTransitionDeps(t, ctx, C_a, "D(d)")
176	checkTransitionDeps(t, ctx, C_b, "D(d)")
177	checkTransitionDeps(t, ctx, C_c, "D(d)")
178	checkTransitionDeps(t, ctx, D_d, "E(d)")
179	checkTransitionDeps(t, ctx, E_d)
180	checkTransitionDeps(t, ctx, F)
181	checkTransitionDeps(t, ctx, G)
182	checkTransitionDeps(t, ctx, H_h)
183
184	checkTransitionMutate(t, A_a, "a")
185	checkTransitionMutate(t, A_b, "b")
186	checkTransitionMutate(t, B_a, "a")
187	checkTransitionMutate(t, B_b, "b")
188	checkTransitionMutate(t, C_a, "a")
189	checkTransitionMutate(t, C_b, "b")
190	checkTransitionMutate(t, C_c, "c")
191	checkTransitionMutate(t, D_d, "d")
192	checkTransitionMutate(t, E_d, "d")
193	checkTransitionMutate(t, F, "")
194	checkTransitionMutate(t, G, "")
195	checkTransitionMutate(t, H_h, "h")
196}
197
198func TestPostTransitionDeps(t *testing.T) {
199	ctx, errs := testTransition(fmt.Sprintf(testTransitionBp,
200		`post_transition_deps: ["C", "D:late", "E:d", "F"],`,
201		`post_transition_deps: ["H"],`))
202	assertNoErrors(t, errs)
203
204	// Module A uses Split to create a and b variants
205	checkTransitionVariants(t, ctx, "A", []string{"b", "a"})
206	// Module B inherits a and b variants from A
207	checkTransitionVariants(t, ctx, "B", []string{"", "a", "b"})
208	// Module C inherits a and b variants from A, but gets an outgoing c variant from B
209	checkTransitionVariants(t, ctx, "C", []string{"", "a", "b", "c"})
210	// Module D always has incoming variant d
211	checkTransitionVariants(t, ctx, "D", []string{"", "d"})
212	// Module E inherits d from D
213	checkTransitionVariants(t, ctx, "E", []string{"", "d"})
214	// Module F is untouched
215	checkTransitionVariants(t, ctx, "F", []string{""})
216
217	A_a := getTransitionModule(ctx, "A", "a")
218	A_b := getTransitionModule(ctx, "A", "b")
219	B_a := getTransitionModule(ctx, "B", "a")
220	B_b := getTransitionModule(ctx, "B", "b")
221	C_a := getTransitionModule(ctx, "C", "a")
222	C_b := getTransitionModule(ctx, "C", "b")
223	C_c := getTransitionModule(ctx, "C", "c")
224	D_d := getTransitionModule(ctx, "D", "d")
225	E_d := getTransitionModule(ctx, "E", "d")
226	F := getTransitionModule(ctx, "F", "")
227	G := getTransitionModule(ctx, "G", "")
228	H_h := getTransitionModule(ctx, "H", "h")
229
230	checkTransitionDeps(t, ctx, A_a, "B(a)", "C(a)")
231	checkTransitionDeps(t, ctx, A_b, "B(b)", "C(b)")
232	// Verify post-mutator dependencies added to B.  The first C(c) is a pre-mutator dependency.
233	//  C(c) was added by C and rewritten by OutgoingTransition on B
234	//  D(d) was added by D:late and rewritten by IncomingTransition on D
235	//  E(d) was added by E:d
236	//  F() was added by F, and ignored the existing variation on B
237	checkTransitionDeps(t, ctx, B_a, "C(c)", "C(c)", "D(d)", "E(d)", "F()")
238	checkTransitionDeps(t, ctx, B_b, "C(c)", "C(c)", "D(d)", "E(d)", "F()")
239	checkTransitionDeps(t, ctx, C_a, "D(d)")
240	checkTransitionDeps(t, ctx, C_b, "D(d)")
241	checkTransitionDeps(t, ctx, C_c, "D(d)")
242	checkTransitionDeps(t, ctx, D_d, "E(d)")
243	checkTransitionDeps(t, ctx, E_d)
244	checkTransitionDeps(t, ctx, F)
245	checkTransitionDeps(t, ctx, G, "H(h)")
246	checkTransitionDeps(t, ctx, H_h)
247
248	checkTransitionMutate(t, A_a, "a")
249	checkTransitionMutate(t, A_b, "b")
250	checkTransitionMutate(t, B_a, "a")
251	checkTransitionMutate(t, B_b, "b")
252	checkTransitionMutate(t, C_a, "a")
253	checkTransitionMutate(t, C_b, "b")
254	checkTransitionMutate(t, C_c, "c")
255	checkTransitionMutate(t, D_d, "d")
256	checkTransitionMutate(t, E_d, "d")
257	checkTransitionMutate(t, F, "")
258	checkTransitionMutate(t, G, "")
259	checkTransitionMutate(t, H_h, "h")
260}
261
262func TestPostTransitionDepsMissingVariant(t *testing.T) {
263	// TODO: eventually this will create the missing variant on demand
264	_, errs := testTransition(fmt.Sprintf(testTransitionBp,
265		`post_transition_deps: ["E:missing"],`, ""))
266	expectedError := `Android.bp:8:4: dependency "E" of "B" missing variant:
267  transition:missing
268available variants:
269  transition:
270  transition:d`
271	if len(errs) != 1 || errs[0].Error() != expectedError {
272		t.Errorf("expected error %q, got %q", expectedError, errs)
273	}
274}
275
276type transitionTestMutator struct{}
277
278func (transitionTestMutator) Split(ctx BaseModuleContext) []string {
279	if split := ctx.Module().(*transitionModule).properties.Split; len(split) > 0 {
280		return split
281	}
282	return []string{""}
283}
284
285func (transitionTestMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string {
286	if outgoing := ctx.Module().(*transitionModule).properties.Outgoing; outgoing != nil {
287		return *outgoing
288	}
289	return sourceVariation
290}
291
292func (transitionTestMutator) IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string {
293	if incoming := ctx.Module().(*transitionModule).properties.Incoming; incoming != nil {
294		return *incoming
295	}
296	return incomingVariation
297}
298
299func (transitionTestMutator) Mutate(ctx BottomUpMutatorContext, variation string) {
300	ctx.Module().(*transitionModule).properties.Mutated = variation
301}
302
303type transitionModule struct {
304	SimpleName
305	properties struct {
306		Deps                 []string
307		Post_transition_deps []string
308		Split                []string
309		Outgoing             *string
310		Incoming             *string
311
312		Mutated string `blueprint:"mutated"`
313	}
314}
315
316func newTransitionModule() (Module, []interface{}) {
317	m := &transitionModule{}
318	return m, []interface{}{&m.properties, &m.SimpleName.Properties}
319}
320
321func (f *transitionModule) GenerateBuildActions(ModuleContext) {
322}
323
324func (f *transitionModule) Deps() []string {
325	return f.properties.Deps
326}
327
328func (f *transitionModule) IgnoreDeps() []string {
329	return nil
330}
331
332func postTransitionDepsMutator(mctx BottomUpMutatorContext) {
333	if m, ok := mctx.Module().(*transitionModule); ok {
334		for _, dep := range m.properties.Post_transition_deps {
335			module, variation, _ := strings.Cut(dep, ":")
336			var variations []Variation
337			if variation != "" {
338				variations = append(variations, Variation{"transition", variation})
339			}
340			mctx.AddVariationDependencies(variations, walkerDepsTag{follow: true}, module)
341		}
342	}
343}
344