Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c29d0447 authored by Weijia He's avatar Weijia He Committed by Gerrit Code Review
Browse files

Merge changes Ifa21a69b,I540a19b0 into main

* changes:
  Allow test_suite to nest
  Implement test_suite.
parents aa83c512 32a9557e
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -383,6 +383,19 @@ func (m *testModuleConfigModule) generateManifestAndConfig(ctx android.ModuleCon

	// 4) Module.config / AndroidTest.xml
	m.testConfig = m.fixTestConfig(ctx, m.provider.TestConfig)

	// 5) We provide so we can be listed in test_suites.
	android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
		InstalledFiles:          m.supportFiles.Paths(),
		OutputFile:              baseApk,
		TestConfig:              m.testConfig,
		HostRequiredModuleNames: m.provider.HostRequiredModuleNames,
		RequiredModuleNames:     m.provider.RequiredModuleNames,
		TestSuites:              m.tradefedProperties.Test_suites,
		IsHost:                  m.provider.IsHost,
		LocalCertificate:        m.provider.LocalCertificate,
		IsUnitTest:              m.provider.IsUnitTest,
	})
}

var _ android.AndroidMkEntriesProvider = (*testModuleConfigHostModule)(nil)
+116 −4
Original line number Diff line number Diff line
@@ -15,17 +15,32 @@
package tradefed_modules

import (
	"fmt"
	"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("test_suite", TestSuiteFactory)
	ctx.RegisterModuleType(testSuiteModuleType, TestSuiteFactory)
}

var PrepareForTestWithTestSuiteBuildComponents = android.GroupFixturePreparers(
@@ -43,10 +58,64 @@ type testSuiteModule struct {
	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")
	android.WriteFileRule(ctx, manifestPath, fmt.Sprintf(`{"name": %q}`, suiteName))
	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)
}

@@ -54,8 +123,51 @@ func TestSuiteFactory() android.Module {
	module := &testSuiteModule{}
	module.AddProperties(&module.testSuiteProperties)

	android.InitAndroidModule(module)
	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
}
+103 −5
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@ package tradefed_modules
import (
	"android/soong/android"
	"android/soong/java"
	"encoding/json"
	"slices"
	"testing"
)

@@ -44,10 +46,106 @@ func TestTestSuites(t *testing.T) {
			]
		}
	`)
	manifestPath := ctx.ModuleForTests("my-suite", "").Output("out/soong/test_suites/my-suite/my-suite.json")
	got := android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)
	want := `{"name": "my-suite"}` + "\n"
	if got != want {
		t.Errorf("my-suite.json content was %q, want %q", got, want)
	manifestPath := ctx.ModuleForTests("my-suite", "android_common").Output("out/soong/test_suites/my-suite/my-suite.json")
	var actual testSuiteManifest
	if err := json.Unmarshal([]byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)), &actual); err != nil {
		t.Errorf("failed to unmarshal manifest: %v", err)
	}
	slices.Sort(actual.Files)

	expected := testSuiteManifest{
		Name: "my-suite",
		Files: []string{
			"target/testcases/TestModule1/TestModule1.config",
			"target/testcases/TestModule1/arm64/TestModule1.apk",
			"target/testcases/TestModule2/TestModule2.config",
			"target/testcases/TestModule2/arm64/TestModule2.apk",
		},
	}

	android.AssertDeepEquals(t, "manifests differ", expected, actual)
}

func TestTestSuitesWithNested(t *testing.T) {
	t.Parallel()
	ctx := android.GroupFixturePreparers(
		java.PrepareForTestWithJavaDefaultModules,
		android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
	).RunTestWithBp(t, `
		android_test {
			name: "TestModule1",
			sdk_version: "current",
		}

		android_test {
			name: "TestModule2",
			sdk_version: "current",
		}

		android_test {
			name: "TestModule3",
			sdk_version: "current",
		}

		test_suite {
			name: "my-child-suite",
			description: "a child test suite",
			tests: [
				"TestModule1",
				"TestModule2",
			]
		}

		test_suite {
			name: "my-all-tests-suite",
			description: "a parent test suite",
			tests: [
				"TestModule1",
				"TestModule3",
				"my-child-suite",
			]
		}
	`)
	manifestPath := ctx.ModuleForTests("my-all-tests-suite", "android_common").Output("out/soong/test_suites/my-all-tests-suite/my-all-tests-suite.json")
	var actual testSuiteManifest
	if err := json.Unmarshal([]byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)), &actual); err != nil {
		t.Errorf("failed to unmarshal manifest: %v", err)
	}
	slices.Sort(actual.Files)

	expected := testSuiteManifest{
		Name: "my-all-tests-suite",
		Files: []string{
			"target/testcases/TestModule1/TestModule1.config",
			"target/testcases/TestModule1/arm64/TestModule1.apk",
			"target/testcases/TestModule2/TestModule2.config",
			"target/testcases/TestModule2/arm64/TestModule2.apk",
			"target/testcases/TestModule3/TestModule3.config",
			"target/testcases/TestModule3/arm64/TestModule3.apk",
		},
	}

	android.AssertDeepEquals(t, "manifests differ", expected, actual)
}

func TestTestSuitesNotInstalledInTestcases(t *testing.T) {
	t.Parallel()
	android.GroupFixturePreparers(
		java.PrepareForTestWithJavaDefaultModules,
		android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern([]string{
		`"SomeHostTest" is not installed in testcases`,
	})).RunTestWithBp(t, `
			java_test_host {
				name: "SomeHostTest",
				srcs: ["a.java"],
			}
			test_suite {
				name: "my-suite",
				description: "a test suite",
				tests: [
					"SomeHostTest",
				]
			}
	`)
}