// Copyright 2024 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tradefed_modules import ( "encoding/json" "path" "path/filepath" "android/soong/android" "android/soong/tradefed" "github.com/google/blueprint" ) const testSuiteModuleType = "test_suite" type testSuiteTag struct{ blueprint.BaseDependencyTag } type testSuiteManifest struct { Name string `json:"name"` Files []string `json:"files"` } func init() { RegisterTestSuiteBuildComponents(android.InitRegistrationContext) } func RegisterTestSuiteBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType(testSuiteModuleType, TestSuiteFactory) } var PrepareForTestWithTestSuiteBuildComponents = android.GroupFixturePreparers( android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents), ) type testSuiteProperties struct { Description string Tests []string `android:"path,arch_variant"` } type testSuiteModule struct { android.ModuleBase android.DefaultableModuleBase testSuiteProperties } func (t *testSuiteModule) DepsMutator(ctx android.BottomUpMutatorContext) { for _, test := range t.Tests { if ctx.OtherModuleDependencyVariantExists(ctx.Config().BuildOSCommonTarget.Variations(), test) { // Host tests. ctx.AddVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), testSuiteTag{}, test) } else { // Target tests. ctx.AddDependency(ctx.Module(), testSuiteTag{}, test) } } } func (t *testSuiteModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { suiteName := ctx.ModuleName() modulesByName := make(map[string]android.Module) ctx.WalkDeps(func(child, parent android.Module) bool { // Recurse into test_suite dependencies. if ctx.OtherModuleType(child) == testSuiteModuleType { ctx.Phony(suiteName, android.PathForPhony(ctx, child.Name())) return true } // Only write out top level test suite dependencies here. if _, ok := ctx.OtherModuleDependencyTag(child).(testSuiteTag); !ok { return false } if !child.InstallInTestcases() { ctx.ModuleErrorf("test_suite only supports modules installed in testcases. %q is not installed in testcases.", child.Name()) return false } modulesByName[child.Name()] = child return false }) var files []string for name, module := range modulesByName { // Get the test provider data from the child. tp, ok := android.OtherModuleProvider(ctx, module, tradefed.BaseTestProviderKey) if !ok { // TODO: Consider printing out a list of all module types. ctx.ModuleErrorf("%q is not a test module.", name) continue } files = append(files, packageModuleFiles(ctx, suiteName, module, tp)...) ctx.Phony(suiteName, android.PathForPhony(ctx, name)) } manifestPath := android.PathForSuiteInstall(ctx, suiteName, suiteName+".json") b, err := json.Marshal(testSuiteManifest{Name: suiteName, Files: files}) if err != nil { ctx.ModuleErrorf("Failed to marshal manifest: %v", err) return } android.WriteFileRule(ctx, manifestPath, string(b)) ctx.Phony(suiteName, manifestPath) } func TestSuiteFactory() android.Module { module := &testSuiteModule{} module.AddProperties(&module.testSuiteProperties) android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) android.InitDefaultableModule(module) return module } func packageModuleFiles(ctx android.ModuleContext, suiteName string, module android.Module, tp tradefed.BaseTestProviderData) []string { hostOrTarget := "target" if tp.IsHost { hostOrTarget = "host" } // suiteRoot at out/soong/packaging/. suiteRoot := android.PathForSuiteInstall(ctx, suiteName) var installed android.InstallPaths // Install links to installed files from the module. if installFilesInfo, ok := android.OtherModuleProvider(ctx, module, android.InstallFilesProvider); ok { for _, f := range installFilesInfo.InstallFiles { // rel is anything under .../, normally under .../testcases. rel := android.Rel(ctx, f.PartitionDir(), f.String()) // Install the file under //. installDir := suiteRoot.Join(ctx, hostOrTarget, f.Partition(), path.Dir(rel)) linkTo, err := filepath.Rel(installDir.String(), f.String()) if err != nil { ctx.ModuleErrorf("Failed to get relative path from %s to %s: %v", installDir.String(), f.String(), err) continue } installed = append(installed, ctx.InstallAbsoluteSymlink(installDir, path.Base(rel), linkTo)) } } // Install config file. if tp.TestConfig != nil { moduleRoot := suiteRoot.Join(ctx, hostOrTarget, "testcases", module.Name()) installed = append(installed, ctx.InstallFile(moduleRoot, module.Name() + ".config", tp.TestConfig)) } // Add to phony and manifest, manifestpaths are relative to suiteRoot. var manifestEntries []string for _, f := range installed { manifestEntries = append(manifestEntries, android.Rel(ctx, suiteRoot.String(), f.String())) ctx.Phony(suiteName, f) } return manifestEntries }