1// Copyright 2024 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package tradefed_modules 16 17import ( 18 "encoding/json" 19 "path" 20 "path/filepath" 21 22 "android/soong/android" 23 "android/soong/tradefed" 24 "github.com/google/blueprint" 25) 26 27const testSuiteModuleType = "test_suite" 28 29type testSuiteTag struct{ 30 blueprint.BaseDependencyTag 31} 32 33type testSuiteManifest struct { 34 Name string `json:"name"` 35 Files []string `json:"files"` 36} 37 38func init() { 39 RegisterTestSuiteBuildComponents(android.InitRegistrationContext) 40} 41 42func RegisterTestSuiteBuildComponents(ctx android.RegistrationContext) { 43 ctx.RegisterModuleType(testSuiteModuleType, TestSuiteFactory) 44} 45 46var PrepareForTestWithTestSuiteBuildComponents = android.GroupFixturePreparers( 47 android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents), 48) 49 50type testSuiteProperties struct { 51 Description string 52 Tests []string `android:"path,arch_variant"` 53} 54 55type testSuiteModule struct { 56 android.ModuleBase 57 android.DefaultableModuleBase 58 testSuiteProperties 59} 60 61func (t *testSuiteModule) DepsMutator(ctx android.BottomUpMutatorContext) { 62 for _, test := range t.Tests { 63 if ctx.OtherModuleDependencyVariantExists(ctx.Config().BuildOSCommonTarget.Variations(), test) { 64 // Host tests. 65 ctx.AddVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), testSuiteTag{}, test) 66 } else { 67 // Target tests. 68 ctx.AddDependency(ctx.Module(), testSuiteTag{}, test) 69 } 70 } 71} 72 73func (t *testSuiteModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { 74 suiteName := ctx.ModuleName() 75 modulesByName := make(map[string]android.Module) 76 ctx.WalkDeps(func(child, parent android.Module) bool { 77 // Recurse into test_suite dependencies. 78 if ctx.OtherModuleType(child) == testSuiteModuleType { 79 ctx.Phony(suiteName, android.PathForPhony(ctx, child.Name())) 80 return true 81 } 82 83 // Only write out top level test suite dependencies here. 84 if _, ok := ctx.OtherModuleDependencyTag(child).(testSuiteTag); !ok { 85 return false 86 } 87 88 if !child.InstallInTestcases() { 89 ctx.ModuleErrorf("test_suite only supports modules installed in testcases. %q is not installed in testcases.", child.Name()) 90 return false 91 } 92 93 modulesByName[child.Name()] = child 94 return false 95 }) 96 97 var files []string 98 for name, module := range modulesByName { 99 // Get the test provider data from the child. 100 tp, ok := android.OtherModuleProvider(ctx, module, tradefed.BaseTestProviderKey) 101 if !ok { 102 // TODO: Consider printing out a list of all module types. 103 ctx.ModuleErrorf("%q is not a test module.", name) 104 continue 105 } 106 107 files = append(files, packageModuleFiles(ctx, suiteName, module, tp)...) 108 ctx.Phony(suiteName, android.PathForPhony(ctx, name)) 109 } 110 111 manifestPath := android.PathForSuiteInstall(ctx, suiteName, suiteName+".json") 112 b, err := json.Marshal(testSuiteManifest{Name: suiteName, Files: files}) 113 if err != nil { 114 ctx.ModuleErrorf("Failed to marshal manifest: %v", err) 115 return 116 } 117 android.WriteFileRule(ctx, manifestPath, string(b)) 118 119 ctx.Phony(suiteName, manifestPath) 120} 121 122func TestSuiteFactory() android.Module { 123 module := &testSuiteModule{} 124 module.AddProperties(&module.testSuiteProperties) 125 126 android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) 127 android.InitDefaultableModule(module) 128 129 return module 130} 131 132func packageModuleFiles(ctx android.ModuleContext, suiteName string, module android.Module, tp tradefed.BaseTestProviderData) []string { 133 134 hostOrTarget := "target" 135 if tp.IsHost { 136 hostOrTarget = "host" 137 } 138 139 // suiteRoot at out/soong/packaging/<suiteName>. 140 suiteRoot := android.PathForSuiteInstall(ctx, suiteName) 141 142 var installed android.InstallPaths 143 // Install links to installed files from the module. 144 if installFilesInfo, ok := android.OtherModuleProvider(ctx, module, android.InstallFilesProvider); ok { 145 for _, f := range installFilesInfo.InstallFiles { 146 // rel is anything under .../<partition>, normally under .../testcases. 147 rel := android.Rel(ctx, f.PartitionDir(), f.String()) 148 149 // Install the file under <suiteRoot>/<host|target>/<partition>. 150 installDir := suiteRoot.Join(ctx, hostOrTarget, f.Partition(), path.Dir(rel)) 151 linkTo, err := filepath.Rel(installDir.String(), f.String()) 152 if err != nil { 153 ctx.ModuleErrorf("Failed to get relative path from %s to %s: %v", installDir.String(), f.String(), err) 154 continue 155 } 156 installed = append(installed, ctx.InstallAbsoluteSymlink(installDir, path.Base(rel), linkTo)) 157 } 158 } 159 160 // Install config file. 161 if tp.TestConfig != nil { 162 moduleRoot := suiteRoot.Join(ctx, hostOrTarget, "testcases", module.Name()) 163 installed = append(installed, ctx.InstallFile(moduleRoot, module.Name() + ".config", tp.TestConfig)) 164 } 165 166 // Add to phony and manifest, manifestpaths are relative to suiteRoot. 167 var manifestEntries []string 168 for _, f := range installed { 169 manifestEntries = append(manifestEntries, android.Rel(ctx, suiteRoot.String(), f.String())) 170 ctx.Phony(suiteName, f) 171 } 172 return manifestEntries 173} 174