summaryrefslogtreecommitdiff
path: root/android/testing.go
diff options
context:
space:
mode:
Diffstat (limited to 'android/testing.go')
-rw-r--r--android/testing.go887
1 files changed, 766 insertions, 121 deletions
diff --git a/android/testing.go b/android/testing.go
index 90989ef53..b36f62cce 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -18,20 +18,23 @@ import (
"fmt"
"path/filepath"
"regexp"
+ "sort"
"strings"
+ "sync"
"testing"
"github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
)
-func NewTestContext() *TestContext {
+func NewTestContext(config Config) *TestContext {
namespaceExportFilter := func(namespace *Namespace) bool {
return true
}
nameResolver := NewNameResolver(namespaceExportFilter)
ctx := &TestContext{
- Context: &Context{blueprint.NewContext()},
+ Context: &Context{blueprint.NewContext(), config},
NameResolver: nameResolver,
}
@@ -39,20 +42,140 @@ func NewTestContext() *TestContext {
ctx.postDeps = append(ctx.postDeps, registerPathDepsMutator)
+ ctx.SetFs(ctx.config.fs)
+ if ctx.config.mockBpList != "" {
+ ctx.SetModuleListFile(ctx.config.mockBpList)
+ }
+
return ctx
}
-func NewTestArchContext() *TestContext {
- ctx := NewTestContext()
+var PrepareForTestWithArchMutator = GroupFixturePreparers(
+ // Configure architecture targets in the fixture config.
+ FixtureModifyConfig(modifyTestConfigToSupportArchMutator),
+
+ // Add the arch mutator to the context.
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PreDepsMutators(registerArchMutator)
+ }),
+)
+
+var PrepareForTestWithDefaults = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
+})
+
+var PrepareForTestWithComponentsMutator = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PreArchMutators(RegisterComponentsMutator)
+})
+
+var PrepareForTestWithPrebuilts = FixtureRegisterWithContext(RegisterPrebuiltMutators)
+
+var PrepareForTestWithOverrides = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
+})
+
+var PrepareForTestWithLicenses = GroupFixturePreparers(
+ FixtureRegisterWithContext(RegisterLicenseKindBuildComponents),
+ FixtureRegisterWithContext(RegisterLicenseBuildComponents),
+ FixtureRegisterWithContext(registerLicenseMutators),
+)
+
+func registerLicenseMutators(ctx RegistrationContext) {
+ ctx.PreArchMutators(RegisterLicensesPackageMapper)
+ ctx.PreArchMutators(RegisterLicensesPropertyGatherer)
+ ctx.PostDepsMutators(RegisterLicensesDependencyChecker)
+}
+
+var PrepareForTestWithLicenseDefaultModules = GroupFixturePreparers(
+ FixtureAddTextFile("build/soong/licenses/Android.bp", `
+ license {
+ name: "Android-Apache-2.0",
+ package_name: "Android",
+ license_kinds: ["SPDX-license-identifier-Apache-2.0"],
+ copyright_notice: "Copyright (C) The Android Open Source Project",
+ license_text: ["LICENSE"],
+ }
+
+ license_kind {
+ name: "SPDX-license-identifier-Apache-2.0",
+ conditions: ["notice"],
+ url: "https://spdx.org/licenses/Apache-2.0.html",
+ }
+
+ license_kind {
+ name: "legacy_unencumbered",
+ conditions: ["unencumbered"],
+ }
+ `),
+ FixtureAddFile("build/soong/licenses/LICENSE", nil),
+)
+
+// Test fixture preparer that will register most java build components.
+//
+// Singletons and mutators should only be added here if they are needed for a majority of java
+// module types, otherwise they should be added under a separate preparer to allow them to be
+// selected only when needed to reduce test execution time.
+//
+// Module types do not have much of an overhead unless they are used so this should include as many
+// module types as possible. The exceptions are those module types that require mutators and/or
+// singletons in order to function in which case they should be kept together in a separate
+// preparer.
+//
+// The mutators in this group were chosen because they are needed by the vast majority of tests.
+var PrepareForTestWithAndroidBuildComponents = GroupFixturePreparers(
+ // Sorted alphabetically as the actual order does not matter as tests automatically enforce the
+ // correct order.
+ PrepareForTestWithArchMutator,
+ PrepareForTestWithComponentsMutator,
+ PrepareForTestWithDefaults,
+ PrepareForTestWithFilegroup,
+ PrepareForTestWithOverrides,
+ PrepareForTestWithPackageModule,
+ PrepareForTestWithPrebuilts,
+ PrepareForTestWithVisibility,
+)
+
+// Prepares an integration test with all build components from the android package.
+//
+// This should only be used by tests that want to run with as much of the build enabled as possible.
+var PrepareForIntegrationTestWithAndroid = GroupFixturePreparers(
+ PrepareForTestWithAndroidBuildComponents,
+)
+
+// Prepares a test that may be missing dependencies by setting allow_missing_dependencies to
+// true.
+var PrepareForTestWithAllowMissingDependencies = GroupFixturePreparers(
+ FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+ variables.Allow_missing_dependencies = proptools.BoolPtr(true)
+ }),
+ FixtureModifyContext(func(ctx *TestContext) {
+ ctx.SetAllowMissingDependencies(true)
+ }),
+)
+
+// Prepares a test that disallows non-existent paths.
+var PrepareForTestDisallowNonExistentPaths = FixtureModifyConfig(func(config Config) {
+ config.TestAllowNonExistentPaths = false
+})
+
+func NewTestArchContext(config Config) *TestContext {
+ ctx := NewTestContext(config)
ctx.preDeps = append(ctx.preDeps, registerArchMutator)
return ctx
}
type TestContext struct {
*Context
- preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc
- NameResolver *NameResolver
- config Config
+ preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc
+ bp2buildPreArch, bp2buildDeps, bp2buildMutators []RegisterMutatorFunc
+ NameResolver *NameResolver
+
+ // The list of pre-singletons and singletons registered for the test.
+ preSingletons, singletons sortableComponents
+
+ // The order in which the pre-singletons, mutators and singletons will be run in this test
+ // context; for debugging.
+ preSingletonOrder, mutatorOrder, singletonOrder []string
}
func (ctx *TestContext) PreArchMutators(f RegisterMutatorFunc) {
@@ -76,16 +199,267 @@ func (ctx *TestContext) FinalDepsMutators(f RegisterMutatorFunc) {
ctx.finalDeps = append(ctx.finalDeps, f)
}
-func (ctx *TestContext) Register(config Config) {
- ctx.SetFs(config.fs)
- if config.mockBpList != "" {
- ctx.SetModuleListFile(config.mockBpList)
+func (ctx *TestContext) RegisterBp2BuildConfig(config Bp2BuildConfig) {
+ ctx.config.bp2buildPackageConfig = config
+}
+
+// RegisterBp2BuildMutator registers a BazelTargetModule mutator for converting a module
+// type to the equivalent Bazel target.
+func (ctx *TestContext) RegisterBp2BuildMutator(moduleType string, m func(TopDownMutatorContext)) {
+ f := func(ctx RegisterMutatorsContext) {
+ ctx.TopDown(moduleType, m)
+ }
+ ctx.config.bp2buildModuleTypeConfig[moduleType] = true
+ ctx.bp2buildMutators = append(ctx.bp2buildMutators, f)
+}
+
+// PreArchBp2BuildMutators adds mutators to be register for converting Android Blueprint modules
+// into Bazel BUILD targets that should run prior to deps and conversion.
+func (ctx *TestContext) PreArchBp2BuildMutators(f RegisterMutatorFunc) {
+ ctx.bp2buildPreArch = append(ctx.bp2buildPreArch, f)
+}
+
+// DepsBp2BuildMutators adds mutators to be register for converting Android Blueprint modules into
+// Bazel BUILD targets that should run prior to conversion to resolve dependencies.
+func (ctx *TestContext) DepsBp2BuildMutators(f RegisterMutatorFunc) {
+ ctx.bp2buildDeps = append(ctx.bp2buildDeps, f)
+}
+
+// registeredComponentOrder defines the order in which a sortableComponent type is registered at
+// runtime and provides support for reordering the components registered for a test in the same
+// way.
+type registeredComponentOrder struct {
+ // The name of the component type, used for error messages.
+ componentType string
+
+ // The names of the registered components in the order in which they were registered.
+ namesInOrder []string
+
+ // Maps from the component name to its position in the runtime ordering.
+ namesToIndex map[string]int
+
+ // A function that defines the order between two named components that can be used to sort a slice
+ // of component names into the same order as they appear in namesInOrder.
+ less func(string, string) bool
+}
+
+// registeredComponentOrderFromExistingOrder takes an existing slice of sortableComponents and
+// creates a registeredComponentOrder that contains a less function that can be used to sort a
+// subset of that list of names so it is in the same order as the original sortableComponents.
+func registeredComponentOrderFromExistingOrder(componentType string, existingOrder sortableComponents) registeredComponentOrder {
+ // Only the names from the existing order are needed for this so create a list of component names
+ // in the correct order.
+ namesInOrder := componentsToNames(existingOrder)
+
+ // Populate the map from name to position in the list.
+ nameToIndex := make(map[string]int)
+ for i, n := range namesInOrder {
+ nameToIndex[n] = i
+ }
+
+ // A function to use to map from a name to an index in the original order.
+ indexOf := func(name string) int {
+ index, ok := nameToIndex[name]
+ if !ok {
+ // Should never happen as tests that use components that are not known at runtime do not sort
+ // so should never use this function.
+ panic(fmt.Errorf("internal error: unknown %s %q should be one of %s", componentType, name, strings.Join(namesInOrder, ", ")))
+ }
+ return index
+ }
+
+ // The less function.
+ less := func(n1, n2 string) bool {
+ i1 := indexOf(n1)
+ i2 := indexOf(n2)
+ return i1 < i2
+ }
+
+ return registeredComponentOrder{
+ componentType: componentType,
+ namesInOrder: namesInOrder,
+ namesToIndex: nameToIndex,
+ less: less,
+ }
+}
+
+// componentsToNames maps from the slice of components to a slice of their names.
+func componentsToNames(components sortableComponents) []string {
+ names := make([]string, len(components))
+ for i, c := range components {
+ names[i] = c.componentName()
+ }
+ return names
+}
+
+// enforceOrdering enforces the supplied components are in the same order as is defined in this
+// object.
+//
+// If the supplied components contains any components that are not registered at runtime, i.e. test
+// specific components, then it is impossible to sort them into an order that both matches the
+// runtime and also preserves the implicit ordering defined in the test. In that case it will not
+// sort the components, instead it will just check that the components are in the correct order.
+//
+// Otherwise, this will sort the supplied components in place.
+func (o *registeredComponentOrder) enforceOrdering(components sortableComponents) {
+ // Check to see if the list of components contains any components that are
+ // not registered at runtime.
+ var unknownComponents []string
+ testOrder := componentsToNames(components)
+ for _, name := range testOrder {
+ if _, ok := o.namesToIndex[name]; !ok {
+ unknownComponents = append(unknownComponents, name)
+ break
+ }
}
- registerMutators(ctx.Context.Context, ctx.preArch, ctx.preDeps, ctx.postDeps, ctx.finalDeps)
- ctx.RegisterSingletonType("env", EnvSingleton)
+ // If the slice contains some unknown components then it is not possible to
+ // sort them into an order that matches the runtime while also preserving the
+ // order expected from the test, so in that case don't sort just check that
+ // the order of the known mutators does match.
+ if len(unknownComponents) > 0 {
+ // Check order.
+ o.checkTestOrder(testOrder, unknownComponents)
+ } else {
+ // Sort the components.
+ sort.Slice(components, func(i, j int) bool {
+ n1 := components[i].componentName()
+ n2 := components[j].componentName()
+ return o.less(n1, n2)
+ })
+ }
+}
+
+// checkTestOrder checks that the supplied testOrder matches the one defined by this object,
+// panicking if it does not.
+func (o *registeredComponentOrder) checkTestOrder(testOrder []string, unknownComponents []string) {
+ lastMatchingTest := -1
+ matchCount := 0
+ // Take a copy of the runtime order as it is modified during the comparison.
+ runtimeOrder := append([]string(nil), o.namesInOrder...)
+ componentType := o.componentType
+ for i, j := 0, 0; i < len(testOrder) && j < len(runtimeOrder); {
+ test := testOrder[i]
+ runtime := runtimeOrder[j]
+
+ if test == runtime {
+ testOrder[i] = test + fmt.Sprintf(" <-- matched with runtime %s %d", componentType, j)
+ runtimeOrder[j] = runtime + fmt.Sprintf(" <-- matched with test %s %d", componentType, i)
+ lastMatchingTest = i
+ i += 1
+ j += 1
+ matchCount += 1
+ } else if _, ok := o.namesToIndex[test]; !ok {
+ // The test component is not registered globally so assume it is the correct place, treat it
+ // as having matched and skip it.
+ i += 1
+ matchCount += 1
+ } else {
+ // Assume that the test list is in the same order as the runtime list but the runtime list
+ // contains some components that are not present in the tests. So, skip the runtime component
+ // to try and find the next one that matches the current test component.
+ j += 1
+ }
+ }
+
+ // If every item in the test order was either test specific or matched one in the runtime then
+ // it is in the correct order. Otherwise, it was not so fail.
+ if matchCount != len(testOrder) {
+ // The test component names were not all matched with a runtime component name so there must
+ // either be a component present in the test that is not present in the runtime or they must be
+ // in the wrong order.
+ testOrder[lastMatchingTest+1] = testOrder[lastMatchingTest+1] + " <--- unmatched"
+ panic(fmt.Errorf("the tests uses test specific components %q and so cannot be automatically sorted."+
+ " Unfortunately it uses %s components in the wrong order.\n"+
+ "test order:\n %s\n"+
+ "runtime order\n %s\n",
+ SortedUniqueStrings(unknownComponents),
+ componentType,
+ strings.Join(testOrder, "\n "),
+ strings.Join(runtimeOrder, "\n ")))
+ }
+}
+
+// registrationSorter encapsulates the information needed to ensure that the test mutators are
+// registered, and thereby executed, in the same order as they are at runtime.
+//
+// It MUST be populated lazily AFTER all package initialization has been done otherwise it will
+// only define the order for a subset of all the registered build components that are available for
+// the packages being tested.
+//
+// e.g if this is initialized during say the cc package initialization then any tests run in the
+// java package will not sort build components registered by the java package's init() functions.
+type registrationSorter struct {
+ // Used to ensure that this is only created once.
+ once sync.Once
+
+ // The order of pre-singletons
+ preSingletonOrder registeredComponentOrder
+
+ // The order of mutators
+ mutatorOrder registeredComponentOrder
+
+ // The order of singletons
+ singletonOrder registeredComponentOrder
+}
+
+// populate initializes this structure from globally registered build components.
+//
+// Only the first call has any effect.
+func (s *registrationSorter) populate() {
+ s.once.Do(func() {
+ // Create an ordering from the globally registered pre-singletons.
+ s.preSingletonOrder = registeredComponentOrderFromExistingOrder("pre-singleton", preSingletons)
+
+ // Created an ordering from the globally registered mutators.
+ globallyRegisteredMutators := collateGloballyRegisteredMutators()
+ s.mutatorOrder = registeredComponentOrderFromExistingOrder("mutator", globallyRegisteredMutators)
+
+ // Create an ordering from the globally registered singletons.
+ globallyRegisteredSingletons := collateGloballyRegisteredSingletons()
+ s.singletonOrder = registeredComponentOrderFromExistingOrder("singleton", globallyRegisteredSingletons)
+ })
+}
+
+// Provides support for enforcing the same order in which build components are registered globally
+// to the order in which they are registered during tests.
+//
+// MUST only be accessed via the globallyRegisteredComponentsOrder func.
+var globalRegistrationSorter registrationSorter
+
+// globallyRegisteredComponentsOrder returns the globalRegistrationSorter after ensuring it is
+// correctly populated.
+func globallyRegisteredComponentsOrder() *registrationSorter {
+ globalRegistrationSorter.populate()
+ return &globalRegistrationSorter
+}
+
+func (ctx *TestContext) Register() {
+ globalOrder := globallyRegisteredComponentsOrder()
+
+ // Ensure that the pre-singletons used in the test are in the same order as they are used at
+ // runtime.
+ globalOrder.preSingletonOrder.enforceOrdering(ctx.preSingletons)
+ ctx.preSingletons.registerAll(ctx.Context)
+
+ mutators := collateRegisteredMutators(ctx.preArch, ctx.preDeps, ctx.postDeps, ctx.finalDeps)
+ // Ensure that the mutators used in the test are in the same order as they are used at runtime.
+ globalOrder.mutatorOrder.enforceOrdering(mutators)
+ mutators.registerAll(ctx.Context)
- ctx.config = config
+ // Ensure that the singletons used in the test are in the same order as they are used at runtime.
+ globalOrder.singletonOrder.enforceOrdering(ctx.singletons)
+ ctx.singletons.registerAll(ctx.Context)
+
+ // Save the sorted components order away to make them easy to access while debugging.
+ ctx.preSingletonOrder = componentsToNames(preSingletons)
+ ctx.mutatorOrder = componentsToNames(mutators)
+ ctx.singletonOrder = componentsToNames(singletons)
+}
+
+// RegisterForBazelConversion prepares a test context for bp2build conversion.
+func (ctx *TestContext) RegisterForBazelConversion() {
+ RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch, ctx.bp2buildDeps, ctx.bp2buildMutators)
}
func (ctx *TestContext) ParseFileList(rootDir string, filePaths []string) (deps []string, errs []error) {
@@ -104,8 +478,18 @@ func (ctx *TestContext) RegisterModuleType(name string, factory ModuleFactory) {
ctx.Context.RegisterModuleType(name, ModuleFactoryAdaptor(factory))
}
+func (ctx *TestContext) RegisterSingletonModuleType(name string, factory SingletonModuleFactory) {
+ s, m := SingletonModuleFactoryAdaptor(name, factory)
+ ctx.RegisterSingletonType(name, s)
+ ctx.RegisterModuleType(name, m)
+}
+
func (ctx *TestContext) RegisterSingletonType(name string, factory SingletonFactory) {
- ctx.Context.RegisterSingletonType(name, SingletonFactoryAdaptor(factory))
+ ctx.singletons = append(ctx.singletons, newSingleton(name, factory))
+}
+
+func (ctx *TestContext) RegisterPreSingletonType(name string, factory SingletonFactory) {
+ ctx.preSingletons = append(ctx.preSingletons, newPreSingleton(name, factory))
}
func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
@@ -118,16 +502,27 @@ func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
if module == nil {
// find all the modules that do exist
- allModuleNames := []string{}
+ var allModuleNames []string
+ var allVariants []string
ctx.VisitAllModules(func(m blueprint.Module) {
- allModuleNames = append(allModuleNames, m.(Module).Name()+"("+ctx.ModuleSubDir(m)+")")
+ allModuleNames = append(allModuleNames, ctx.ModuleName(m))
+ if ctx.ModuleName(m) == name {
+ allVariants = append(allVariants, ctx.ModuleSubDir(m))
+ }
})
-
- panic(fmt.Errorf("failed to find module %q variant %q."+
- "\nall modules: %v", name, variant, allModuleNames))
+ sort.Strings(allModuleNames)
+ sort.Strings(allVariants)
+
+ if len(allVariants) == 0 {
+ panic(fmt.Errorf("failed to find module %q. All modules:\n %s",
+ name, strings.Join(allModuleNames, "\n ")))
+ } else {
+ panic(fmt.Errorf("failed to find module %q variant %q. All variants:\n %s",
+ name, variant, strings.Join(allVariants, "\n ")))
+ }
}
- return TestingModule{module}
+ return newTestingModule(ctx.config, module)
}
func (ctx *TestContext) ModuleVariantsForTests(name string) []string {
@@ -147,8 +542,8 @@ func (ctx *TestContext) SingletonForTests(name string) TestingSingleton {
n := ctx.SingletonName(s)
if n == name {
return TestingSingleton{
- singleton: s.(*singletonAdaptor).Singleton,
- provider: s.(testBuildProvider),
+ baseTestingComponent: newBaseTestingComponent(ctx.config, s.(testBuildProvider)),
+ singleton: s.(*singletonAdaptor).Singleton,
}
}
allSingletonNames = append(allSingletonNames, n)
@@ -158,6 +553,10 @@ func (ctx *TestContext) SingletonForTests(name string) TestingSingleton {
"\nall singletons: %v", name, allSingletonNames))
}
+func (ctx *TestContext) Config() Config {
+ return ctx.config
+}
+
type testBuildProvider interface {
BuildParamsForTests() []BuildParams
RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
@@ -166,60 +565,201 @@ type testBuildProvider interface {
type TestingBuildParams struct {
BuildParams
RuleParams blueprint.RuleParams
+
+ config Config
}
-func newTestingBuildParams(provider testBuildProvider, bparams BuildParams) TestingBuildParams {
+// RelativeToTop creates a new instance of this which has had any usages of the current test's
+// temporary and test specific build directory replaced with a path relative to the notional top.
+//
+// The parts of this structure which are changed are:
+// * BuildParams
+// * Args
+// * All Path, Paths, WritablePath and WritablePaths fields.
+//
+// * RuleParams
+// * Command
+// * Depfile
+// * Rspfile
+// * RspfileContent
+// * SymlinkOutputs
+// * CommandDeps
+// * CommandOrderOnly
+//
+// See PathRelativeToTop for more details.
+//
+// deprecated: this is no longer needed as TestingBuildParams are created in this form.
+func (p TestingBuildParams) RelativeToTop() TestingBuildParams {
+ // If this is not a valid params then just return it back. That will make it easy to use with the
+ // Maybe...() methods.
+ if p.Rule == nil {
+ return p
+ }
+ if p.config.config == nil {
+ return p
+ }
+ // Take a copy of the build params and replace any args that contains test specific temporary
+ // paths with paths relative to the top.
+ bparams := p.BuildParams
+ bparams.Depfile = normalizeWritablePathRelativeToTop(bparams.Depfile)
+ bparams.Output = normalizeWritablePathRelativeToTop(bparams.Output)
+ bparams.Outputs = bparams.Outputs.RelativeToTop()
+ bparams.SymlinkOutput = normalizeWritablePathRelativeToTop(bparams.SymlinkOutput)
+ bparams.SymlinkOutputs = bparams.SymlinkOutputs.RelativeToTop()
+ bparams.ImplicitOutput = normalizeWritablePathRelativeToTop(bparams.ImplicitOutput)
+ bparams.ImplicitOutputs = bparams.ImplicitOutputs.RelativeToTop()
+ bparams.Input = normalizePathRelativeToTop(bparams.Input)
+ bparams.Inputs = bparams.Inputs.RelativeToTop()
+ bparams.Implicit = normalizePathRelativeToTop(bparams.Implicit)
+ bparams.Implicits = bparams.Implicits.RelativeToTop()
+ bparams.OrderOnly = bparams.OrderOnly.RelativeToTop()
+ bparams.Validation = normalizePathRelativeToTop(bparams.Validation)
+ bparams.Validations = bparams.Validations.RelativeToTop()
+ bparams.Args = normalizeStringMapRelativeToTop(p.config, bparams.Args)
+
+ // Ditto for any fields in the RuleParams.
+ rparams := p.RuleParams
+ rparams.Command = normalizeStringRelativeToTop(p.config, rparams.Command)
+ rparams.Depfile = normalizeStringRelativeToTop(p.config, rparams.Depfile)
+ rparams.Rspfile = normalizeStringRelativeToTop(p.config, rparams.Rspfile)
+ rparams.RspfileContent = normalizeStringRelativeToTop(p.config, rparams.RspfileContent)
+ rparams.SymlinkOutputs = normalizeStringArrayRelativeToTop(p.config, rparams.SymlinkOutputs)
+ rparams.CommandDeps = normalizeStringArrayRelativeToTop(p.config, rparams.CommandDeps)
+ rparams.CommandOrderOnly = normalizeStringArrayRelativeToTop(p.config, rparams.CommandOrderOnly)
+
return TestingBuildParams{
BuildParams: bparams,
- RuleParams: provider.RuleParamsForTests()[bparams.Rule],
+ RuleParams: rparams,
+ }
+}
+
+func normalizeWritablePathRelativeToTop(path WritablePath) WritablePath {
+ if path == nil {
+ return nil
+ }
+ return path.RelativeToTop().(WritablePath)
+}
+
+func normalizePathRelativeToTop(path Path) Path {
+ if path == nil {
+ return nil
+ }
+ return path.RelativeToTop()
+}
+
+// baseTestingComponent provides functionality common to both TestingModule and TestingSingleton.
+type baseTestingComponent struct {
+ config Config
+ provider testBuildProvider
+}
+
+func newBaseTestingComponent(config Config, provider testBuildProvider) baseTestingComponent {
+ return baseTestingComponent{config, provider}
+}
+
+// A function that will normalize a string containing paths, e.g. ninja command, by replacing
+// any references to the test specific temporary build directory that changes with each run to a
+// fixed path relative to a notional top directory.
+//
+// This is similar to StringPathRelativeToTop except that assumes the string is a single path
+// containing at most one instance of the temporary build directory at the start of the path while
+// this assumes that there can be any number at any position.
+func normalizeStringRelativeToTop(config Config, s string) string {
+ // The buildDir usually looks something like: /tmp/testFoo2345/001
+ //
+ // Replace any usage of the buildDir with out/soong, e.g. replace "/tmp/testFoo2345/001" with
+ // "out/soong".
+ outSoongDir := filepath.Clean(config.buildDir)
+ re := regexp.MustCompile(`\Q` + outSoongDir + `\E\b`)
+ s = re.ReplaceAllString(s, "out/soong")
+
+ // Replace any usage of the buildDir/.. with out, e.g. replace "/tmp/testFoo2345" with
+ // "out". This must come after the previous replacement otherwise this would replace
+ // "/tmp/testFoo2345/001" with "out/001" instead of "out/soong".
+ outDir := filepath.Dir(outSoongDir)
+ re = regexp.MustCompile(`\Q` + outDir + `\E\b`)
+ s = re.ReplaceAllString(s, "out")
+
+ return s
+}
+
+// normalizeStringArrayRelativeToTop creates a new slice constructed by applying
+// normalizeStringRelativeToTop to each item in the slice.
+func normalizeStringArrayRelativeToTop(config Config, slice []string) []string {
+ newSlice := make([]string, len(slice))
+ for i, s := range slice {
+ newSlice[i] = normalizeStringRelativeToTop(config, s)
+ }
+ return newSlice
+}
+
+// normalizeStringMapRelativeToTop creates a new map constructed by applying
+// normalizeStringRelativeToTop to each value in the map.
+func normalizeStringMapRelativeToTop(config Config, m map[string]string) map[string]string {
+ newMap := map[string]string{}
+ for k, v := range m {
+ newMap[k] = normalizeStringRelativeToTop(config, v)
}
+ return newMap
}
-func maybeBuildParamsFromRule(provider testBuildProvider, rule string) TestingBuildParams {
- for _, p := range provider.BuildParamsForTests() {
- if strings.Contains(p.Rule.String(), rule) {
- return newTestingBuildParams(provider, p)
+func (b baseTestingComponent) newTestingBuildParams(bparams BuildParams) TestingBuildParams {
+ return TestingBuildParams{
+ config: b.config,
+ BuildParams: bparams,
+ RuleParams: b.provider.RuleParamsForTests()[bparams.Rule],
+ }.RelativeToTop()
+}
+
+func (b baseTestingComponent) maybeBuildParamsFromRule(rule string) (TestingBuildParams, []string) {
+ var searchedRules []string
+ buildParams := b.provider.BuildParamsForTests()
+ for _, p := range buildParams {
+ ruleAsString := p.Rule.String()
+ searchedRules = append(searchedRules, ruleAsString)
+ if strings.Contains(ruleAsString, rule) {
+ return b.newTestingBuildParams(p), searchedRules
}
}
- return TestingBuildParams{}
+ return TestingBuildParams{}, searchedRules
}
-func buildParamsFromRule(provider testBuildProvider, rule string) TestingBuildParams {
- p := maybeBuildParamsFromRule(provider, rule)
+func (b baseTestingComponent) buildParamsFromRule(rule string) TestingBuildParams {
+ p, searchRules := b.maybeBuildParamsFromRule(rule)
if p.Rule == nil {
- panic(fmt.Errorf("couldn't find rule %q", rule))
+ panic(fmt.Errorf("couldn't find rule %q.\nall rules:\n%s", rule, strings.Join(searchRules, "\n")))
}
return p
}
-func maybeBuildParamsFromDescription(provider testBuildProvider, desc string) TestingBuildParams {
- for _, p := range provider.BuildParamsForTests() {
+func (b baseTestingComponent) maybeBuildParamsFromDescription(desc string) TestingBuildParams {
+ for _, p := range b.provider.BuildParamsForTests() {
if strings.Contains(p.Description, desc) {
- return newTestingBuildParams(provider, p)
+ return b.newTestingBuildParams(p)
}
}
return TestingBuildParams{}
}
-func buildParamsFromDescription(provider testBuildProvider, desc string) TestingBuildParams {
- p := maybeBuildParamsFromDescription(provider, desc)
+func (b baseTestingComponent) buildParamsFromDescription(desc string) TestingBuildParams {
+ p := b.maybeBuildParamsFromDescription(desc)
if p.Rule == nil {
panic(fmt.Errorf("couldn't find description %q", desc))
}
return p
}
-func maybeBuildParamsFromOutput(provider testBuildProvider, file string) (TestingBuildParams, []string) {
+func (b baseTestingComponent) maybeBuildParamsFromOutput(file string) (TestingBuildParams, []string) {
var searchedOutputs []string
- for _, p := range provider.BuildParamsForTests() {
+ for _, p := range b.provider.BuildParamsForTests() {
outputs := append(WritablePaths(nil), p.Outputs...)
outputs = append(outputs, p.ImplicitOutputs...)
if p.Output != nil {
outputs = append(outputs, p.Output)
}
for _, f := range outputs {
- if f.String() == file || f.Rel() == file {
- return newTestingBuildParams(provider, p), nil
+ if f.String() == file || f.Rel() == file || PathRelativeToTop(f) == file {
+ return b.newTestingBuildParams(p), nil
}
searchedOutputs = append(searchedOutputs, f.Rel())
}
@@ -227,18 +767,18 @@ func maybeBuildParamsFromOutput(provider testBuildProvider, file string) (Testin
return TestingBuildParams{}, searchedOutputs
}
-func buildParamsFromOutput(provider testBuildProvider, file string) TestingBuildParams {
- p, searchedOutputs := maybeBuildParamsFromOutput(provider, file)
+func (b baseTestingComponent) buildParamsFromOutput(file string) TestingBuildParams {
+ p, searchedOutputs := b.maybeBuildParamsFromOutput(file)
if p.Rule == nil {
- panic(fmt.Errorf("couldn't find output %q.\nall outputs: %v",
- file, searchedOutputs))
+ panic(fmt.Errorf("couldn't find output %q.\nall outputs:\n %s\n",
+ file, strings.Join(searchedOutputs, "\n ")))
}
return p
}
-func allOutputs(provider testBuildProvider) []string {
+func (b baseTestingComponent) allOutputs() []string {
var outputFullPaths []string
- for _, p := range provider.BuildParamsForTests() {
+ for _, p := range b.provider.BuildParamsForTests() {
outputs := append(WritablePaths(nil), p.Outputs...)
outputs = append(outputs, p.ImplicitOutputs...)
if p.Output != nil {
@@ -249,109 +789,99 @@ func allOutputs(provider testBuildProvider) []string {
return outputFullPaths
}
-// TestingModule is wrapper around an android.Module that provides methods to find information about individual
-// ctx.Build parameters for verification in tests.
-type TestingModule struct {
- module Module
-}
-
-// Module returns the Module wrapped by the TestingModule.
-func (m TestingModule) Module() Module {
- return m.module
-}
-
// MaybeRule finds a call to ctx.Build with BuildParams.Rule set to a rule with the given name. Returns an empty
// BuildParams if no rule is found.
-func (m TestingModule) MaybeRule(rule string) TestingBuildParams {
- return maybeBuildParamsFromRule(m.module, rule)
+func (b baseTestingComponent) MaybeRule(rule string) TestingBuildParams {
+ r, _ := b.maybeBuildParamsFromRule(rule)
+ return r
}
// Rule finds a call to ctx.Build with BuildParams.Rule set to a rule with the given name. Panics if no rule is found.
-func (m TestingModule) Rule(rule string) TestingBuildParams {
- return buildParamsFromRule(m.module, rule)
+func (b baseTestingComponent) Rule(rule string) TestingBuildParams {
+ return b.buildParamsFromRule(rule)
}
// MaybeDescription finds a call to ctx.Build with BuildParams.Description set to a the given string. Returns an empty
// BuildParams if no rule is found.
-func (m TestingModule) MaybeDescription(desc string) TestingBuildParams {
- return maybeBuildParamsFromDescription(m.module, desc)
+func (b baseTestingComponent) MaybeDescription(desc string) TestingBuildParams {
+ return b.maybeBuildParamsFromDescription(desc)
}
// Description finds a call to ctx.Build with BuildParams.Description set to a the given string. Panics if no rule is
// found.
-func (m TestingModule) Description(desc string) TestingBuildParams {
- return buildParamsFromDescription(m.module, desc)
+func (b baseTestingComponent) Description(desc string) TestingBuildParams {
+ return b.buildParamsFromDescription(desc)
}
// MaybeOutput finds a call to ctx.Build with a BuildParams.Output or BuildParams.Outputs whose String() or Rel()
// value matches the provided string. Returns an empty BuildParams if no rule is found.
-func (m TestingModule) MaybeOutput(file string) TestingBuildParams {
- p, _ := maybeBuildParamsFromOutput(m.module, file)
+func (b baseTestingComponent) MaybeOutput(file string) TestingBuildParams {
+ p, _ := b.maybeBuildParamsFromOutput(file)
return p
}
// Output finds a call to ctx.Build with a BuildParams.Output or BuildParams.Outputs whose String() or Rel()
// value matches the provided string. Panics if no rule is found.
-func (m TestingModule) Output(file string) TestingBuildParams {
- return buildParamsFromOutput(m.module, file)
+func (b baseTestingComponent) Output(file string) TestingBuildParams {
+ return b.buildParamsFromOutput(file)
}
// AllOutputs returns all 'BuildParams.Output's and 'BuildParams.Outputs's in their full path string forms.
-func (m TestingModule) AllOutputs() []string {
- return allOutputs(m.module)
+func (b baseTestingComponent) AllOutputs() []string {
+ return b.allOutputs()
}
-// TestingSingleton is wrapper around an android.Singleton that provides methods to find information about individual
+// TestingModule is wrapper around an android.Module that provides methods to find information about individual
// ctx.Build parameters for verification in tests.
-type TestingSingleton struct {
- singleton Singleton
- provider testBuildProvider
-}
-
-// Singleton returns the Singleton wrapped by the TestingSingleton.
-func (s TestingSingleton) Singleton() Singleton {
- return s.singleton
+type TestingModule struct {
+ baseTestingComponent
+ module Module
}
-// MaybeRule finds a call to ctx.Build with BuildParams.Rule set to a rule with the given name. Returns an empty
-// BuildParams if no rule is found.
-func (s TestingSingleton) MaybeRule(rule string) TestingBuildParams {
- return maybeBuildParamsFromRule(s.provider, rule)
+func newTestingModule(config Config, module Module) TestingModule {
+ return TestingModule{
+ newBaseTestingComponent(config, module),
+ module,
+ }
}
-// Rule finds a call to ctx.Build with BuildParams.Rule set to a rule with the given name. Panics if no rule is found.
-func (s TestingSingleton) Rule(rule string) TestingBuildParams {
- return buildParamsFromRule(s.provider, rule)
+// Module returns the Module wrapped by the TestingModule.
+func (m TestingModule) Module() Module {
+ return m.module
}
-// MaybeDescription finds a call to ctx.Build with BuildParams.Description set to a the given string. Returns an empty
-// BuildParams if no rule is found.
-func (s TestingSingleton) MaybeDescription(desc string) TestingBuildParams {
- return maybeBuildParamsFromDescription(s.provider, desc)
+// VariablesForTestsRelativeToTop returns a copy of the Module.VariablesForTests() with every value
+// having any temporary build dir usages replaced with paths relative to a notional top.
+func (m TestingModule) VariablesForTestsRelativeToTop() map[string]string {
+ return normalizeStringMapRelativeToTop(m.config, m.module.VariablesForTests())
}
-// Description finds a call to ctx.Build with BuildParams.Description set to a the given string. Panics if no rule is
-// found.
-func (s TestingSingleton) Description(desc string) TestingBuildParams {
- return buildParamsFromDescription(s.provider, desc)
-}
+// OutputFiles calls OutputFileProducer.OutputFiles on the encapsulated module, exits the test
+// immediately if there is an error and otherwise returns the result of calling Paths.RelativeToTop
+// on the returned Paths.
+func (m TestingModule) OutputFiles(t *testing.T, tag string) Paths {
+ producer, ok := m.module.(OutputFileProducer)
+ if !ok {
+ t.Fatalf("%q must implement OutputFileProducer\n", m.module.Name())
+ }
+ paths, err := producer.OutputFiles(tag)
+ if err != nil {
+ t.Fatal(err)
+ }
-// MaybeOutput finds a call to ctx.Build with a BuildParams.Output or BuildParams.Outputs whose String() or Rel()
-// value matches the provided string. Returns an empty BuildParams if no rule is found.
-func (s TestingSingleton) MaybeOutput(file string) TestingBuildParams {
- p, _ := maybeBuildParamsFromOutput(s.provider, file)
- return p
+ return paths.RelativeToTop()
}
-// Output finds a call to ctx.Build with a BuildParams.Output or BuildParams.Outputs whose String() or Rel()
-// value matches the provided string. Panics if no rule is found.
-func (s TestingSingleton) Output(file string) TestingBuildParams {
- return buildParamsFromOutput(s.provider, file)
+// TestingSingleton is wrapper around an android.Singleton that provides methods to find information about individual
+// ctx.Build parameters for verification in tests.
+type TestingSingleton struct {
+ baseTestingComponent
+ singleton Singleton
}
-// AllOutputs returns all 'BuildParams.Output's and 'BuildParams.Outputs's in their full path string forms.
-func (s TestingSingleton) AllOutputs() []string {
- return allOutputs(s.provider)
+// Singleton returns the Singleton wrapped by the TestingSingleton.
+func (s TestingSingleton) Singleton() Singleton {
+ return s.singleton
}
func FailIfErrored(t *testing.T, errs []error) {
@@ -364,12 +894,15 @@ func FailIfErrored(t *testing.T, errs []error) {
}
}
-func FailIfNoMatchingErrors(t *testing.T, pattern string, errs []error) {
+// Fail if no errors that matched the regular expression were found.
+//
+// Returns true if a matching error was found, false otherwise.
+func FailIfNoMatchingErrors(t *testing.T, pattern string, errs []error) bool {
t.Helper()
matcher, err := regexp.Compile(pattern)
if err != nil {
- t.Errorf("failed to compile regular expression %q because %s", pattern, err)
+ t.Fatalf("failed to compile regular expression %q because %s", pattern, err)
}
found := false
@@ -382,9 +915,11 @@ func FailIfNoMatchingErrors(t *testing.T, pattern string, errs []error) {
if !found {
t.Errorf("missing the expected error %q (checked %d error(s))", pattern, len(errs))
for i, err := range errs {
- t.Errorf("errs[%d] = %s", i, err)
+ t.Errorf("errs[%d] = %q", i, err)
}
}
+
+ return found
}
func CheckErrorsAgainstExpectations(t *testing.T, errs []error, expectedErrorPatterns []string) {
@@ -405,16 +940,16 @@ func CheckErrorsAgainstExpectations(t *testing.T, errs []error, expectedErrorPat
for i, err := range errs {
t.Errorf("errs[%d] = %s", i, err)
}
+ t.FailNow()
}
}
-
}
-func SetInMakeForTests(config Config) {
- config.inMake = true
+func SetKatiEnabledForTests(config Config) {
+ config.katiEnabled = true
}
-func AndroidMkEntriesForTest(t *testing.T, config Config, bpPath string, mod blueprint.Module) []AndroidMkEntries {
+func AndroidMkEntriesForTest(t *testing.T, ctx *TestContext, mod blueprint.Module) []AndroidMkEntries {
var p AndroidMkEntriesProvider
var ok bool
if p, ok = mod.(AndroidMkEntriesProvider); !ok {
@@ -423,19 +958,19 @@ func AndroidMkEntriesForTest(t *testing.T, config Config, bpPath string, mod blu
entriesList := p.AndroidMkEntries()
for i, _ := range entriesList {
- entriesList[i].fillInEntries(config, bpPath, mod)
+ entriesList[i].fillInEntries(ctx, mod)
}
return entriesList
}
-func AndroidMkDataForTest(t *testing.T, config Config, bpPath string, mod blueprint.Module) AndroidMkData {
+func AndroidMkDataForTest(t *testing.T, ctx *TestContext, mod blueprint.Module) AndroidMkData {
var p AndroidMkDataProvider
var ok bool
if p, ok = mod.(AndroidMkDataProvider); !ok {
t.Errorf("module does not implement AndroidMkDataProvider: " + mod.Name())
}
data := p.AndroidMk()
- data.fillInData(config, bpPath, mod)
+ data.fillInData(ctx, mod)
return data
}
@@ -448,10 +983,16 @@ func AndroidMkDataForTest(t *testing.T, config Config, bpPath string, mod bluepr
// that is relative to the root of the source tree.
//
// The build and source paths should be distinguishable based on their contents.
+//
+// deprecated: use PathRelativeToTop instead as it handles make install paths and differentiates
+// between output and source properly.
func NormalizePathForTesting(path Path) string {
+ if path == nil {
+ return "<nil path>"
+ }
p := path.String()
if w, ok := path.(WritablePath); ok {
- rel, err := filepath.Rel(w.buildDir(), p)
+ rel, err := filepath.Rel(w.getBuildDir(), p)
if err != nil {
panic(err)
}
@@ -460,6 +1001,11 @@ func NormalizePathForTesting(path Path) string {
return p
}
+// NormalizePathsForTesting creates a slice of strings where each string is the result of applying
+// NormalizePathForTesting to the corresponding Path in the input slice.
+//
+// deprecated: use PathsRelativeToTop instead as it handles make install paths and differentiates
+// between output and source properly.
func NormalizePathsForTesting(paths Paths) []string {
var result []string
for _, path := range paths {
@@ -468,3 +1014,102 @@ func NormalizePathsForTesting(paths Paths) []string {
}
return result
}
+
+// PathRelativeToTop returns a string representation of the path relative to a notional top
+// directory.
+//
+// It return "<nil path>" if the supplied path is nil, otherwise it returns the result of calling
+// Path.RelativeToTop to obtain a relative Path and then calling Path.String on that to get the
+// string representation.
+func PathRelativeToTop(path Path) string {
+ if path == nil {
+ return "<nil path>"
+ }
+ return path.RelativeToTop().String()
+}
+
+// PathsRelativeToTop creates a slice of strings where each string is the result of applying
+// PathRelativeToTop to the corresponding Path in the input slice.
+func PathsRelativeToTop(paths Paths) []string {
+ var result []string
+ for _, path := range paths {
+ relative := PathRelativeToTop(path)
+ result = append(result, relative)
+ }
+ return result
+}
+
+// StringPathRelativeToTop returns a string representation of the path relative to a notional top
+// directory.
+//
+// See Path.RelativeToTop for more details as to what `relative to top` means.
+//
+// This is provided for processing paths that have already been converted into a string, e.g. paths
+// in AndroidMkEntries structures. As a result it needs to be supplied the soong output dir against
+// which it can try and relativize paths. PathRelativeToTop must be used for process Path objects.
+func StringPathRelativeToTop(soongOutDir string, path string) string {
+ ensureTestOnly()
+
+ // A relative path must be a source path so leave it as it is.
+ if !filepath.IsAbs(path) {
+ return path
+ }
+
+ // Check to see if the path is relative to the soong out dir.
+ rel, isRel, err := maybeRelErr(soongOutDir, path)
+ if err != nil {
+ panic(err)
+ }
+
+ if isRel {
+ // The path is in the soong out dir so indicate that in the relative path.
+ return filepath.Join("out/soong", rel)
+ }
+
+ // Check to see if the path is relative to the top level out dir.
+ outDir := filepath.Dir(soongOutDir)
+ rel, isRel, err = maybeRelErr(outDir, path)
+ if err != nil {
+ panic(err)
+ }
+
+ if isRel {
+ // The path is in the out dir so indicate that in the relative path.
+ return filepath.Join("out", rel)
+ }
+
+ // This should never happen.
+ panic(fmt.Errorf("internal error: absolute path %s is not relative to the out dir %s", path, outDir))
+}
+
+// StringPathsRelativeToTop creates a slice of strings where each string is the result of applying
+// StringPathRelativeToTop to the corresponding string path in the input slice.
+//
+// This is provided for processing paths that have already been converted into a string, e.g. paths
+// in AndroidMkEntries structures. As a result it needs to be supplied the soong output dir against
+// which it can try and relativize paths. PathsRelativeToTop must be used for process Paths objects.
+func StringPathsRelativeToTop(soongOutDir string, paths []string) []string {
+ var result []string
+ for _, path := range paths {
+ relative := StringPathRelativeToTop(soongOutDir, path)
+ result = append(result, relative)
+ }
+ return result
+}
+
+// StringRelativeToTop will normalize a string containing paths, e.g. ninja command, by replacing
+// any references to the test specific temporary build directory that changes with each run to a
+// fixed path relative to a notional top directory.
+//
+// This is similar to StringPathRelativeToTop except that assumes the string is a single path
+// containing at most one instance of the temporary build directory at the start of the path while
+// this assumes that there can be any number at any position.
+func StringRelativeToTop(config Config, command string) string {
+ return normalizeStringRelativeToTop(config, command)
+}
+
+// StringsRelativeToTop will return a new slice such that each item in the new slice is the result
+// of calling StringRelativeToTop on the corresponding item in the input slice.
+func StringsRelativeToTop(config Config, command []string) []string {
+ return normalizeStringArrayRelativeToTop(config, command)
+}