xref: /aosp_15_r20/build/soong/tradefed_modules/test_suite.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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