platform_build_blueprint/transition_test.go
Colin Cross 5ac6648829 Apply outgoing transitions when adding dependencies
TransitionMutators can always return "" from OutgoingVariation in
order to implement the equivalent of CreateLocalVariations - a
variation that doesn't affect which variation of dependencies is
selected.  The same need applies when creating the dependency after
the TransitionMutator.  Call OutgoingVariation on the parent module
when adding new dependencies unless the variation was explicitly
requested.

Bug: 319288033
Test: TestPostTransitionDeps
Flag: NONE
Change-Id: Iadb72a5a3f19579a6c1d34e3c7169ee9e2b53210
2024-05-08 15:22:21 -07:00

319 lines
9.8 KiB
Go

// 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: ["a", "b"],
}
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{"a", "b"})
// 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{"a", "b"})
// 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)
}
}
}