// Copyright 2017 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 android import ( "bytes" "fmt" "path/filepath" "regexp" "sort" "strings" "sync" "testing" mkparser "android/soong/androidmk/parser" "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) func newTestContextForFixture(config Config) *TestContext { ctx := &TestContext{ Context: &Context{blueprint.NewContext(), config}, } ctx.postDeps = append(ctx.postDeps, registerPathDepsMutator) ctx.SetFs(ctx.config.fs) if ctx.config.mockBpList != "" { ctx.SetModuleListFile(ctx.config.mockBpList) } return ctx } func NewTestContext(config Config) *TestContext { ctx := newTestContextForFixture(config) nameResolver := NewNameResolver(config) ctx.NameResolver = nameResolver ctx.SetNameInterface(nameResolver) return ctx } 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), ) var PrepareForTestWithGenNotice = FixtureRegisterWithContext(RegisterGenNoticeBuildComponents) 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), ) var PrepareForTestWithNamespace = FixtureRegisterWithContext(func(ctx RegistrationContext) { registerNamespaceBuildComponents(ctx) ctx.PreArchMutators(RegisterNamespaceMutator) }) var PrepareForTestWithMakevars = FixtureRegisterWithContext(func(ctx RegistrationContext) { ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc) }) // 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 // The list of singletons registered for the test. singletons sortableComponents // The order in which the mutators and singletons will be run in this test // context; for debugging. mutatorOrder, singletonOrder []string } func (ctx *TestContext) PreArchMutators(f RegisterMutatorFunc) { ctx.preArch = append(ctx.preArch, f) } func (ctx *TestContext) HardCodedPreArchMutators(f RegisterMutatorFunc) { // Register mutator function as normal for testing. ctx.PreArchMutators(f) } func (ctx *TestContext) moduleProvider(m blueprint.Module, p blueprint.AnyProviderKey) (any, bool) { return ctx.Context.ModuleProvider(m, p) } func (ctx *TestContext) PreDepsMutators(f RegisterMutatorFunc) { ctx.preDeps = append(ctx.preDeps, f) } func (ctx *TestContext) PostDepsMutators(f RegisterMutatorFunc) { ctx.postDeps = append(ctx.postDeps, f) } func (ctx *TestContext) FinalDepsMutators(f RegisterMutatorFunc) { ctx.finalDeps = append(ctx.finalDeps, f) } func (ctx *TestContext) OtherModuleProviderAdaptor() OtherModuleProviderContext { return NewOtherModuleProviderAdaptor(func(module blueprint.Module, provider blueprint.AnyProviderKey) (any, bool) { return ctx.moduleProvider(module, provider) }) } // 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 } } // 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 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() { // 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() 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) // 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.mutatorOrder = componentsToNames(mutators) ctx.singletonOrder = componentsToNames(singletons) } func (ctx *TestContext) ParseFileList(rootDir string, filePaths []string) (deps []string, errs []error) { // This function adapts the old style ParseFileList calls that are spread throughout the tests // to the new style that takes a config. return ctx.Context.ParseFileList(rootDir, filePaths, ctx.config) } func (ctx *TestContext) ParseBlueprintsFiles(rootDir string) (deps []string, errs []error) { // This function adapts the old style ParseBlueprintsFiles calls that are spread throughout the // tests to the new style that takes a config. return ctx.Context.ParseBlueprintsFiles(rootDir, ctx.config) } 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) RegisterParallelSingletonModuleType(name string, factory SingletonModuleFactory) { s, m := SingletonModuleFactoryAdaptor(name, factory) ctx.RegisterParallelSingletonType(name, s) ctx.RegisterModuleType(name, m) } func (ctx *TestContext) RegisterSingletonType(name string, factory SingletonFactory) { ctx.singletons = append(ctx.singletons, newSingleton(name, factory, false)) } func (ctx *TestContext) RegisterParallelSingletonType(name string, factory SingletonFactory) { ctx.singletons = append(ctx.singletons, newSingleton(name, factory, true)) } // ModuleVariantForTests selects a specific variant of the module with the given // name by matching the variations map against the variations of each module // variant. A module variant matches the map if every variation that exists in // both have the same value. Both the module and the map are allowed to have // extra variations that the other doesn't have. Panics if not exactly one // module variant matches. func (ctx *TestContext) ModuleVariantForTests(name string, matchVariations map[string]string) TestingModule { modules := []Module{} ctx.VisitAllModules(func(m blueprint.Module) { if ctx.ModuleName(m) == name { am := m.(Module) amMut := am.base().commonProperties.DebugMutators amVar := am.base().commonProperties.DebugVariations matched := true for i, mut := range amMut { if wantedVar, found := matchVariations[mut]; found && amVar[i] != wantedVar { matched = false break } } if matched { modules = append(modules, am) } } }) if len(modules) == 0 { // Show all the modules or module variants that do exist. var allModuleNames []string var allVariants []string ctx.VisitAllModules(func(m blueprint.Module) { allModuleNames = append(allModuleNames, ctx.ModuleName(m)) if ctx.ModuleName(m) == name { allVariants = append(allVariants, m.(Module).String()) } }) if len(allVariants) == 0 { panic(fmt.Errorf("failed to find module %q. All modules:\n %s", name, strings.Join(SortedUniqueStrings(allModuleNames), "\n "))) } else { sort.Strings(allVariants) panic(fmt.Errorf("failed to find module %q matching %v. All variants:\n %s", name, matchVariations, strings.Join(allVariants, "\n "))) } } if len(modules) > 1 { moduleStrings := []string{} for _, m := range modules { moduleStrings = append(moduleStrings, m.String()) } sort.Strings(moduleStrings) panic(fmt.Errorf("module %q has more than one variant that match %v:\n %s", name, matchVariations, strings.Join(moduleStrings, "\n "))) } return newTestingModule(ctx.config, modules[0]) } func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule { var module Module ctx.VisitAllModules(func(m blueprint.Module) { if ctx.ModuleName(m) == name && ctx.ModuleSubDir(m) == variant { module = m.(Module) } }) if module == nil { // find all the modules that do exist var allModuleNames []string var allVariants []string ctx.VisitAllModules(func(m blueprint.Module) { allModuleNames = append(allModuleNames, ctx.ModuleName(m)) if ctx.ModuleName(m) == name { allVariants = append(allVariants, ctx.ModuleSubDir(m)) } }) sort.Strings(allVariants) if len(allVariants) == 0 { panic(fmt.Errorf("failed to find module %q. All modules:\n %s", name, strings.Join(SortedUniqueStrings(allModuleNames), "\n "))) } else { panic(fmt.Errorf("failed to find module %q variant %q. All variants:\n %s", name, variant, strings.Join(allVariants, "\n "))) } } return newTestingModule(ctx.config, module) } func (ctx *TestContext) ModuleVariantsForTests(name string) []string { var variants []string ctx.VisitAllModules(func(m blueprint.Module) { if ctx.ModuleName(m) == name { variants = append(variants, ctx.ModuleSubDir(m)) } }) return variants } // SingletonForTests returns a TestingSingleton for the singleton registered with the given name. func (ctx *TestContext) SingletonForTests(name string) TestingSingleton { allSingletonNames := []string{} for _, s := range ctx.Singletons() { n := ctx.SingletonName(s) if n == name { return TestingSingleton{ baseTestingComponent: newBaseTestingComponent(ctx.config, s.(testBuildProvider)), singleton: s.(*singletonAdaptor).Singleton, } } allSingletonNames = append(allSingletonNames, n) } panic(fmt.Errorf("failed to find singleton %q."+ "\nall singletons: %v", name, allSingletonNames)) } type InstallMakeRule struct { Target string Deps []string OrderOnlyDeps []string } func parseMkRules(t *testing.T, config Config, nodes []mkparser.Node) []InstallMakeRule { var rules []InstallMakeRule for _, node := range nodes { if mkParserRule, ok := node.(*mkparser.Rule); ok { var rule InstallMakeRule if targets := mkParserRule.Target.Words(); len(targets) == 0 { t.Fatalf("no targets for rule %s", mkParserRule.Dump()) } else if len(targets) > 1 { t.Fatalf("unsupported multiple targets for rule %s", mkParserRule.Dump()) } else if !targets[0].Const() { t.Fatalf("unsupported non-const target for rule %s", mkParserRule.Dump()) } else { rule.Target = normalizeStringRelativeToTop(config, targets[0].Value(nil)) } prereqList := &rule.Deps for _, prereq := range mkParserRule.Prerequisites.Words() { if !prereq.Const() { t.Fatalf("unsupported non-const prerequisite for rule %s", mkParserRule.Dump()) } if prereq.Value(nil) == "|" { prereqList = &rule.OrderOnlyDeps continue } *prereqList = append(*prereqList, normalizeStringRelativeToTop(config, prereq.Value(nil))) } rules = append(rules, rule) } } return rules } func (ctx *TestContext) InstallMakeRulesForTesting(t *testing.T) []InstallMakeRule { installs := ctx.SingletonForTests("makevars").Singleton().(*makeVarsSingleton).installsForTesting buf := bytes.NewBuffer(append([]byte(nil), installs...)) parser := mkparser.NewParser("makevars", buf) nodes, errs := parser.Parse() if len(errs) > 0 { t.Fatalf("error parsing install rules: %s", errs[0]) } return parseMkRules(t, ctx.config, nodes) } // MakeVarVariable provides access to make vars that will be written by the makeVarsSingleton type MakeVarVariable interface { // Name is the name of the variable. Name() string // Value is the value of the variable. Value() string } func (v makeVarsVariable) Name() string { return v.name } func (v makeVarsVariable) Value() string { return v.value } // PrepareForTestAccessingMakeVars sets up the test so that MakeVarsForTesting will work. var PrepareForTestAccessingMakeVars = GroupFixturePreparers( PrepareForTestWithAndroidMk, PrepareForTestWithMakevars, ) // MakeVarsForTesting returns a filtered list of MakeVarVariable objects that represent the // variables that will be written out. // // It is necessary to use PrepareForTestAccessingMakeVars in tests that want to call this function. // Along with any other preparers needed to add the make vars. func (ctx *TestContext) MakeVarsForTesting(filter func(variable MakeVarVariable) bool) []MakeVarVariable { vars := ctx.SingletonForTests("makevars").Singleton().(*makeVarsSingleton).varsForTesting result := make([]MakeVarVariable, 0, len(vars)) for _, v := range vars { if filter(v) { result = append(result, v) } } return result } func (ctx *TestContext) Config() Config { return ctx.config } type testBuildProvider interface { BuildParamsForTests() []BuildParams RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams } type TestingBuildParams struct { BuildParams RuleParams blueprint.RuleParams config Config } // 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 // - 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.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.CommandDeps = normalizeStringArrayRelativeToTop(p.config, rparams.CommandDeps) rparams.CommandOrderOnly = normalizeStringArrayRelativeToTop(p.config, rparams.CommandOrderOnly) return TestingBuildParams{ BuildParams: bparams, 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() } func allOutputs(p BuildParams) []string { outputs := append(WritablePaths(nil), p.Outputs...) outputs = append(outputs, p.ImplicitOutputs...) if p.Output != nil { outputs = append(outputs, p.Output) } return outputs.Strings() } // AllOutputs returns all 'BuildParams.Output's and 'BuildParams.Outputs's in their full path string forms. func (p TestingBuildParams) AllOutputs() []string { return allOutputs(p.BuildParams) } // 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 soongOutDir usually looks something like: /tmp/testFoo2345/001 // // Replace any usage of the soongOutDir with out/soong, e.g. replace "/tmp/testFoo2345/001" with // "out/soong". outSoongDir := filepath.Clean(config.soongOutDir) re := regexp.MustCompile(`\Q` + outSoongDir + `\E\b`) s = re.ReplaceAllString(s, "out/soong") // Replace any usage of the soongOutDir/.. 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 (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{}, searchedRules } func (b baseTestingComponent) buildParamsFromRule(rule string) TestingBuildParams { p, searchRules := b.maybeBuildParamsFromRule(rule) if p.Rule == nil { panic(fmt.Errorf("couldn't find rule %q.\nall rules:\n%s", rule, strings.Join(searchRules, "\n"))) } return p } func (b baseTestingComponent) maybeBuildParamsFromDescription(desc string) (TestingBuildParams, []string) { var searchedDescriptions []string for _, p := range b.provider.BuildParamsForTests() { searchedDescriptions = append(searchedDescriptions, p.Description) if strings.Contains(p.Description, desc) { return b.newTestingBuildParams(p), searchedDescriptions } } return TestingBuildParams{}, searchedDescriptions } func (b baseTestingComponent) buildParamsFromDescription(desc string) TestingBuildParams { p, searchedDescriptions := b.maybeBuildParamsFromDescription(desc) if p.Rule == nil { panic(fmt.Errorf("couldn't find description %q\nall descriptions:\n%s", desc, strings.Join(searchedDescriptions, "\n"))) } return p } func (b baseTestingComponent) maybeBuildParamsFromOutput(file string) (TestingBuildParams, []string) { searchedOutputs := WritablePaths(nil) 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 || PathRelativeToTop(f) == file { return b.newTestingBuildParams(p), nil } searchedOutputs = append(searchedOutputs, f) } } formattedOutputs := []string{} for _, f := range searchedOutputs { formattedOutputs = append(formattedOutputs, fmt.Sprintf("%s (rel=%s)", PathRelativeToTop(f), f.Rel())) } return TestingBuildParams{}, formattedOutputs } 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:\n %s\n", file, strings.Join(searchedOutputs, "\n "))) } return p } func (b baseTestingComponent) allOutputs() []string { var outputFullPaths []string for _, p := range b.provider.BuildParamsForTests() { outputFullPaths = append(outputFullPaths, allOutputs(p)...) } return outputFullPaths } // 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 (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 (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 (b baseTestingComponent) MaybeDescription(desc string) TestingBuildParams { p, _ := b.maybeBuildParamsFromDescription(desc) return p } // Description finds a call to ctx.Build with BuildParams.Description set to a the given string. Panics if no rule is // found. 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 (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 (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 (b baseTestingComponent) AllOutputs() []string { return b.allOutputs() } // 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 { baseTestingComponent module Module } func newTestingModule(config Config, module Module) TestingModule { return TestingModule{ newBaseTestingComponent(config, module), module, } } // Module returns the Module wrapped by the TestingModule. func (m TestingModule) Module() Module { return m.module } // 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()) } // OutputFiles first checks if module base outputFiles property has any output // files can be used to return. // If not, it 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 { // TODO: add non-empty-string tag case and remove OutputFileProducer part if tag == "" && m.module.base().outputFiles.DefaultOutputFiles != nil { return m.module.base().outputFiles.DefaultOutputFiles.RelativeToTop() } 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) } return paths.RelativeToTop() } // 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 } // Singleton returns the Singleton wrapped by the TestingSingleton. func (s TestingSingleton) Singleton() Singleton { return s.singleton } func FailIfErrored(t *testing.T, errs []error) { t.Helper() if len(errs) > 0 { for _, err := range errs { t.Error(err) } t.FailNow() } } // 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.Fatalf("failed to compile regular expression %q because %s", pattern, err) } found := false for _, err := range errs { if matcher.FindStringIndex(err.Error()) != nil { found = true break } } if !found { t.Errorf("could not match the expected error regex %q (checked %d error(s))", pattern, len(errs)) for i, err := range errs { t.Errorf("errs[%d] = %q", i, err) } } return found } func CheckErrorsAgainstExpectations(t *testing.T, errs []error, expectedErrorPatterns []string) { t.Helper() if expectedErrorPatterns == nil { FailIfErrored(t, errs) } else { for _, expectedError := range expectedErrorPatterns { FailIfNoMatchingErrors(t, expectedError, errs) } if len(errs) > len(expectedErrorPatterns) { t.Errorf("additional errors found, expected %d, found %d", len(expectedErrorPatterns), len(errs)) for i, expectedError := range expectedErrorPatterns { t.Errorf("expectedErrors[%d] = %s", i, expectedError) } for i, err := range errs { t.Errorf("errs[%d] = %s", i, err) } t.FailNow() } } } func SetKatiEnabledForTests(config Config) { config.katiEnabled = true } func SetTrimmedApexEnabledForTests(config Config) { config.productVariables.TrimmedApex = new(bool) *config.productVariables.TrimmedApex = true } func AndroidMkEntriesForTest(t *testing.T, ctx *TestContext, mod blueprint.Module) []AndroidMkEntries { t.Helper() var p AndroidMkEntriesProvider var ok bool if p, ok = mod.(AndroidMkEntriesProvider); !ok { t.Errorf("module does not implement AndroidMkEntriesProvider: " + mod.Name()) } entriesList := p.AndroidMkEntries() aconfigUpdateAndroidMkEntries(ctx, mod.(Module), &entriesList) for i := range entriesList { entriesList[i].fillInEntries(ctx, mod) } return entriesList } func AndroidMkDataForTest(t *testing.T, ctx *TestContext, mod blueprint.Module) AndroidMkData { t.Helper() var p AndroidMkDataProvider var ok bool if p, ok = mod.(AndroidMkDataProvider); !ok { t.Fatalf("module does not implement AndroidMkDataProvider: " + mod.Name()) } data := p.AndroidMk() data.fillInData(ctx, mod) aconfigUpdateAndroidMkData(ctx, mod.(Module), &data) return data } // Normalize the path for testing. // // If the path is relative to the build directory then return the relative path // to avoid tests having to deal with the dynamically generated build directory. // // Otherwise, return the supplied path as it is almost certainly a source path // 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 "" } p := path.String() if w, ok := path.(WritablePath); ok { rel, err := filepath.Rel(w.getSoongOutDir(), p) if err != nil { panic(err) } return rel } 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 { relative := NormalizePathForTesting(path) result = append(result, relative) } return result } // PathRelativeToTop returns a string representation of the path relative to a notional top // directory. // // It return "" 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 "" } 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) } func EnsureListContainsSuffix(t *testing.T, result []string, expected string) { t.Helper() if !SuffixInList(result, expected) { t.Errorf("%q is not found in %v", expected, result) } } type panickingConfigAndErrorContext struct { ctx *TestContext } func (ctx *panickingConfigAndErrorContext) OtherModulePropertyErrorf(module Module, property, fmt string, args ...interface{}) { panic(ctx.ctx.PropertyErrorf(module, property, fmt, args...).Error()) } func (ctx *panickingConfigAndErrorContext) Config() Config { return ctx.ctx.Config() } func PanickingConfigAndErrorContext(ctx *TestContext) ConfigAndErrorContext { return &panickingConfigAndErrorContext{ ctx: ctx, } }