| // 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 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) |
| } |
| |
| 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 "<nil path>" |
| } |
| 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 "<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) |
| } |
| |
| 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) |
| } |
| } |