diff options
Diffstat (limited to 'android/testing.go')
-rw-r--r-- | android/testing.go | 887 |
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) +} |