// Copyright 2024 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package blueprint import ( "fmt" "slices" "strings" "testing" ) func testTransition(bp string) (*Context, []error) { ctx := newContext() ctx.MockFileSystem(map[string][]byte{ "Android.bp": []byte(bp), }) ctx.RegisterBottomUpMutator("deps", depsMutator) ctx.RegisterTransitionMutator("transition", transitionTestMutator{}) ctx.RegisterBottomUpMutator("post_transition_deps", postTransitionDepsMutator) ctx.RegisterModuleType("transition_module", newTransitionModule) _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil) if len(errs) > 0 { return nil, errs } _, errs = ctx.ResolveDependencies(nil) if len(errs) > 0 { return nil, errs } return ctx, nil } func assertNoErrors(t *testing.T, errs []error) { t.Helper() if len(errs) > 0 { t.Errorf("unexpected errors:") for _, err := range errs { t.Errorf(" %s", err) } t.FailNow() } } const testTransitionBp = ` transition_module { name: "A", deps: ["B", "C"], split: ["b", "a"], } transition_module { name: "B", deps: ["C"], outgoing: "c", %s } transition_module { name: "C", deps: ["D"], } transition_module { name: "D", incoming: "d", deps: ["E"], } transition_module { name: "E", } transition_module { name: "F" } ` func getTransitionModule(ctx *Context, name, variant string) *transitionModule { group := ctx.moduleGroupFromName(name, nil) module := group.moduleOrAliasByVariantName(variant).module() return module.logicModule.(*transitionModule) } func checkTransitionVariants(t *testing.T, ctx *Context, name string, expectedVariants []string) { t.Helper() group := ctx.moduleGroupFromName(name, nil) var gotVariants []string for _, variant := range group.modules { gotVariants = append(gotVariants, variant.moduleOrAliasVariant().variations["transition"]) } if !slices.Equal(expectedVariants, gotVariants) { t.Errorf("expected variants of %q to be %q, got %q", name, expectedVariants, gotVariants) } } func checkTransitionDeps(t *testing.T, ctx *Context, m Module, expected ...string) { t.Helper() var got []string ctx.VisitDirectDeps(m, func(m Module) { got = append(got, ctx.ModuleName(m)+"("+ctx.ModuleSubDir(m)+")") }) if !slices.Equal(got, expected) { t.Errorf("unexpected %q dependencies, got %q expected %q", ctx.ModuleName(m), got, expected) } } func checkTransitionMutate(t *testing.T, m *transitionModule, variant string) { t.Helper() if m.properties.Mutated != variant { t.Errorf("unexpected mutated property in %q, expected %q got %q", m.Name(), variant, m.properties.Mutated) } } func TestTransition(t *testing.T) { ctx, errs := testTransition(fmt.Sprintf(testTransitionBp, "")) assertNoErrors(t, errs) // Module A uses Split to create a and b variants checkTransitionVariants(t, ctx, "A", []string{"b", "a"}) // Module B inherits a and b variants from A checkTransitionVariants(t, ctx, "B", []string{"", "a", "b"}) // Module C inherits a and b variants from A, but gets an outgoing c variant from B checkTransitionVariants(t, ctx, "C", []string{"", "a", "b", "c"}) // Module D always has incoming variant d checkTransitionVariants(t, ctx, "D", []string{"", "d"}) // Module E inherits d from D checkTransitionVariants(t, ctx, "E", []string{"", "d"}) // Module F is untouched checkTransitionVariants(t, ctx, "F", []string{""}) A_a := getTransitionModule(ctx, "A", "a") A_b := getTransitionModule(ctx, "A", "b") B_a := getTransitionModule(ctx, "B", "a") B_b := getTransitionModule(ctx, "B", "b") C_a := getTransitionModule(ctx, "C", "a") C_b := getTransitionModule(ctx, "C", "b") C_c := getTransitionModule(ctx, "C", "c") D_d := getTransitionModule(ctx, "D", "d") E_d := getTransitionModule(ctx, "E", "d") F := getTransitionModule(ctx, "F", "") checkTransitionDeps(t, ctx, A_a, "B(a)", "C(a)") checkTransitionDeps(t, ctx, A_b, "B(b)", "C(b)") checkTransitionDeps(t, ctx, B_a, "C(c)") checkTransitionDeps(t, ctx, B_b, "C(c)") checkTransitionDeps(t, ctx, C_a, "D(d)") checkTransitionDeps(t, ctx, C_b, "D(d)") checkTransitionDeps(t, ctx, C_c, "D(d)") checkTransitionDeps(t, ctx, D_d, "E(d)") checkTransitionDeps(t, ctx, E_d) checkTransitionDeps(t, ctx, F) checkTransitionMutate(t, A_a, "a") checkTransitionMutate(t, A_b, "b") checkTransitionMutate(t, B_a, "a") checkTransitionMutate(t, B_b, "b") checkTransitionMutate(t, C_a, "a") checkTransitionMutate(t, C_b, "b") checkTransitionMutate(t, C_c, "c") checkTransitionMutate(t, D_d, "d") checkTransitionMutate(t, E_d, "d") checkTransitionMutate(t, F, "") } func TestPostTransitionDeps(t *testing.T) { ctx, errs := testTransition(fmt.Sprintf(testTransitionBp, `post_transition_deps: ["C", "D:late", "E:d", "F"],`)) assertNoErrors(t, errs) // Module A uses Split to create a and b variants checkTransitionVariants(t, ctx, "A", []string{"b", "a"}) // Module B inherits a and b variants from A checkTransitionVariants(t, ctx, "B", []string{"", "a", "b"}) // Module C inherits a and b variants from A, but gets an outgoing c variant from B checkTransitionVariants(t, ctx, "C", []string{"", "a", "b", "c"}) // Module D always has incoming variant d checkTransitionVariants(t, ctx, "D", []string{"", "d"}) // Module E inherits d from D checkTransitionVariants(t, ctx, "E", []string{"", "d"}) // Module F is untouched checkTransitionVariants(t, ctx, "F", []string{""}) A_a := getTransitionModule(ctx, "A", "a") A_b := getTransitionModule(ctx, "A", "b") B_a := getTransitionModule(ctx, "B", "a") B_b := getTransitionModule(ctx, "B", "b") C_a := getTransitionModule(ctx, "C", "a") C_b := getTransitionModule(ctx, "C", "b") C_c := getTransitionModule(ctx, "C", "c") D_d := getTransitionModule(ctx, "D", "d") E_d := getTransitionModule(ctx, "E", "d") F := getTransitionModule(ctx, "F", "") checkTransitionDeps(t, ctx, A_a, "B(a)", "C(a)") checkTransitionDeps(t, ctx, A_b, "B(b)", "C(b)") // Verify post-mutator dependencies added to B. The first C(c) is a pre-mutator dependency. // C(c) was added by C and rewritten by OutgoingTransition on B // D(d) was added by D:late and rewritten by IncomingTransition on D // E(d) was added by E:d // F() was added by F, and ignored the existing variation on B checkTransitionDeps(t, ctx, B_a, "C(c)", "C(c)", "D(d)", "E(d)", "F()") checkTransitionDeps(t, ctx, B_b, "C(c)", "C(c)", "D(d)", "E(d)", "F()") checkTransitionDeps(t, ctx, C_a, "D(d)") checkTransitionDeps(t, ctx, C_b, "D(d)") checkTransitionDeps(t, ctx, C_c, "D(d)") checkTransitionDeps(t, ctx, D_d, "E(d)") checkTransitionDeps(t, ctx, E_d) checkTransitionDeps(t, ctx, F) checkTransitionMutate(t, A_a, "a") checkTransitionMutate(t, A_b, "b") checkTransitionMutate(t, B_a, "a") checkTransitionMutate(t, B_b, "b") checkTransitionMutate(t, C_a, "a") checkTransitionMutate(t, C_b, "b") checkTransitionMutate(t, C_c, "c") checkTransitionMutate(t, D_d, "d") checkTransitionMutate(t, E_d, "d") checkTransitionMutate(t, F, "") } func TestPostTransitionDepsMissingVariant(t *testing.T) { // TODO: eventually this will create the missing variant on demand _, errs := testTransition(fmt.Sprintf(testTransitionBp, `post_transition_deps: ["E:missing"],`)) expectedError := `Android.bp:8:4: dependency "E" of "B" missing variant: transition:missing available variants: transition: transition:d` if len(errs) != 1 || errs[0].Error() != expectedError { t.Errorf("expected error %q, got %q", expectedError, errs) } } type transitionTestMutator struct{} func (transitionTestMutator) Split(ctx BaseModuleContext) []string { if split := ctx.Module().(*transitionModule).properties.Split; len(split) > 0 { return split } return []string{""} } func (transitionTestMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string { if outgoing := ctx.Module().(*transitionModule).properties.Outgoing; outgoing != nil { return *outgoing } return sourceVariation } func (transitionTestMutator) IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string { if incoming := ctx.Module().(*transitionModule).properties.Incoming; incoming != nil { return *incoming } return incomingVariation } func (transitionTestMutator) Mutate(ctx BottomUpMutatorContext, variation string) { ctx.Module().(*transitionModule).properties.Mutated = variation } type transitionModule struct { SimpleName properties struct { Deps []string Post_transition_deps []string Split []string Outgoing *string Incoming *string Mutated string `blueprint:"mutated"` } } func newTransitionModule() (Module, []interface{}) { m := &transitionModule{} return m, []interface{}{&m.properties, &m.SimpleName.Properties} } func (f *transitionModule) GenerateBuildActions(ModuleContext) { } func (f *transitionModule) Deps() []string { return f.properties.Deps } func (f *transitionModule) IgnoreDeps() []string { return nil } func postTransitionDepsMutator(mctx BottomUpMutatorContext) { if m, ok := mctx.Module().(*transitionModule); ok { for _, dep := range m.properties.Post_transition_deps { module, variation, _ := strings.Cut(dep, ":") var variations []Variation if variation != "" { variations = append(variations, Variation{"transition", variation}) } mctx.AddVariationDependencies(variations, walkerDepsTag{follow: true}, module) } } }