blob: 00585f5f6c3bed9d3efcb3b68a0a4f07e8b18f70 [file] [log] [blame] [edit]
// 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/<suiteName>.
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 .../<partition>, normally under .../testcases.
rel := android.Rel(ctx, f.PartitionDir(), f.String())
// Install the file under <suiteRoot>/<host|target>/<partition>.
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
}