Support running bp2build tests in fixtures
Previously, the fixture test infrastructure was hard coded to
initialize itself in preparation for invoking the PrepareBuildActions()
method. That meant it could not be used for testing the bp2build and
apiBp2build modes which required different initialization and called a
different method.
This change extracts that behavior into a FixtureTestRunner and adds an
implementation that allows it to test the above two modes. It then uses
that to implement the runBp2BuildTestCaseWithSetup method.
The TestPrebuiltLibraryAdditionalAttrs was the only test which broke as
it supplied an invalid path to the MockFS (it does not support using a
trailing / to represent an empty build directory). So, it was modified
to add a couple of files inside those directories instead.
Test: m nothing
Change-Id: I6798a4f761160af8d1bfed81d46de9628bda3eb9
diff --git a/android/fixture.go b/android/fixture.go
index 3f01f5a..c2b16f6 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -213,6 +213,46 @@
})
}
+// FixtureTestRunner determines the type of test to run.
+//
+// If no custom FixtureTestRunner is provided (using the FixtureSetTestRunner) then the default test
+// runner will run a standard Soong test that corresponds to what happens when Soong is run on the
+// command line.
+type FixtureTestRunner interface {
+ // FinalPreparer is a function that is run immediately before parsing the blueprint files. It is
+ // intended to perform the initialization needed by PostParseProcessor.
+ //
+ // It returns a CustomTestResult that is passed into PostParseProcessor and returned from
+ // FixturePreparer.RunTestWithCustomResult. If it needs to return some custom data then it must
+ // provide its own implementation of CustomTestResult and return an instance of that. Otherwise,
+ // it can just return the supplied *TestResult.
+ FinalPreparer(result *TestResult) CustomTestResult
+
+ // PostParseProcessor is called after successfully parsing the blueprint files and can do further
+ // work on the result of parsing the files.
+ //
+ // Successfully parsing simply means that no errors were encountered when parsing the blueprint
+ // files.
+ //
+ // This must collate any information useful for testing, e.g. errs, ninja deps and custom data in
+ // the supplied result.
+ PostParseProcessor(result CustomTestResult)
+}
+
+// FixtureSetTestRunner sets the FixtureTestRunner in the fixture.
+//
+// It is an error if more than one of these is applied to a single fixture. If none of these are
+// applied then the fixture will use the defaultTestRunner which will run the test as if it was
+// being run in `m <target>`.
+func FixtureSetTestRunner(testRunner FixtureTestRunner) FixturePreparer {
+ return newSimpleFixturePreparer(func(fixture *fixture) {
+ if fixture.testRunner != nil {
+ panic("fixture test runner has already been set")
+ }
+ fixture.testRunner = testRunner
+ })
+}
+
// Modify the config
func FixtureModifyConfig(mutator func(config Config)) FixturePreparer {
return newSimpleFixturePreparer(func(f *fixture) {
@@ -391,6 +431,21 @@
// Shorthand for Fixture(t).RunTest()
RunTest(t *testing.T) *TestResult
+ // RunTestWithCustomResult runs the test just as RunTest(t) does but instead of returning a
+ // *TestResult it returns the CustomTestResult that was returned by the custom
+ // FixtureTestRunner.PostParseProcessor method that ran the test, or the *TestResult if that
+ // method returned nil.
+ //
+ // This method must be used when needing to access custom data collected by the
+ // FixtureTestRunner.PostParseProcessor method.
+ //
+ // e.g. something like this
+ //
+ // preparers := ...FixtureSetTestRunner(&myTestRunner)...
+ // customResult := preparers.RunTestWithCustomResult(t).(*myCustomTestResult)
+ // doSomething(customResult.data)
+ RunTestWithCustomResult(t *testing.T) CustomTestResult
+
// Run the test with the supplied Android.bp file.
//
// preparer.RunTestWithBp(t, bp) is shorthand for
@@ -619,7 +674,7 @@
MockFS() MockFS
// Run the test, checking any errors reported and returning a TestResult instance.
- RunTest() *TestResult
+ RunTest() CustomTestResult
}
// Struct to allow TestResult to embed a *TestContext and allow call forwarding to its methods.
@@ -642,6 +697,39 @@
NinjaDeps []string
}
+func (r *TestResult) testResult() *TestResult { return r }
+
+// CustomTestResult is the interface that FixtureTestRunner implementations who wish to return
+// custom data must implement. It must embed *TestResult and initialize that to the value passed
+// into the method. It is returned from the FixtureTestRunner.FinalPreparer, passed into the
+// FixtureTestRunner.PostParseProcessor and returned from FixturePreparer.RunTestWithCustomResult.
+//
+// e.g. something like this:
+//
+// type myCustomTestResult struct {
+// *android.TestResult
+// data []string
+// }
+//
+// func (r *myTestRunner) FinalPreparer(result *TestResult) CustomTestResult {
+// ... do some final test preparation ...
+// return &myCustomTestResult{TestResult: result)
+// }
+//
+// func (r *myTestRunner) PostParseProcessor(result CustomTestResult) {
+// ...
+// myData := []string {....}
+// ...
+// customResult := result.(*myCustomTestResult)
+// customResult.data = myData
+// }
+type CustomTestResult interface {
+ // testResult returns the embedded *TestResult.
+ testResult() *TestResult
+}
+
+var _ CustomTestResult = (*TestResult)(nil)
+
type TestPathContext struct {
*TestResult
}
@@ -696,6 +784,11 @@
func (b *baseFixturePreparer) RunTest(t *testing.T) *TestResult {
t.Helper()
+ return b.RunTestWithCustomResult(t).testResult()
+}
+
+func (b *baseFixturePreparer) RunTestWithCustomResult(t *testing.T) CustomTestResult {
+ t.Helper()
fixture := b.self.Fixture(t)
return fixture.RunTest()
}
@@ -724,13 +817,16 @@
ctx.SetModuleListFile(ctx.config.mockBpList)
}
- return fixture.RunTest()
+ return fixture.RunTest().testResult()
}
type fixture struct {
// The preparers used to create this fixture.
preparers []*simpleFixturePreparer
+ // The test runner used in this fixture, defaults to defaultTestRunner if not set.
+ testRunner FixtureTestRunner
+
// The gotest state of the go test within which this was created.
t *testing.T
@@ -762,7 +858,7 @@
return f.mockFS
}
-func (f *fixture) RunTest() *TestResult {
+func (f *fixture) RunTest() CustomTestResult {
f.t.Helper()
// If in debug mode output the state of the fixture before running the test.
@@ -800,30 +896,59 @@
// Set the NameResolver in the TestContext.
ctx.NameResolver = resolver
- ctx.Register()
- var ninjaDeps []string
- extraNinjaDeps, errs := ctx.ParseBlueprintsFiles("ignored")
- if len(errs) == 0 {
- ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
- extraNinjaDeps, errs = ctx.PrepareBuildActions(f.config)
- if len(errs) == 0 {
- ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
- }
+ // If test runner has not been set then use the default runner.
+ if f.testRunner == nil {
+ f.testRunner = defaultTestRunner
}
+ // Create the result to collate result information.
result := &TestResult{
testContext: testContext{ctx},
fixture: f,
Config: f.config,
- Errs: errs,
- NinjaDeps: ninjaDeps,
+ }
+
+ // Do any last minute preparation before parsing the blueprint files.
+ customResult := f.testRunner.FinalPreparer(result)
+
+ // Parse the blueprint files adding the information to the result.
+ extraNinjaDeps, errs := ctx.ParseBlueprintsFiles("ignored")
+ result.NinjaDeps = append(result.NinjaDeps, extraNinjaDeps...)
+ result.Errs = append(result.Errs, errs...)
+
+ if len(result.Errs) == 0 {
+ // If parsing the blueprint files was successful then perform any additional processing.
+ f.testRunner.PostParseProcessor(customResult)
}
f.errorHandler.CheckErrors(f.t, result)
+ return customResult
+}
+
+// standardTestRunner is the implementation of the default test runner
+type standardTestRunner struct{}
+
+func (s *standardTestRunner) FinalPreparer(result *TestResult) CustomTestResult {
+ // Register the hard coded mutators and singletons used by the standard Soong build as well as
+ // any additional instances that have been registered with this fixture.
+ result.TestContext.Register()
return result
}
+func (s *standardTestRunner) PostParseProcessor(customResult CustomTestResult) {
+ result := customResult.(*TestResult)
+ ctx := result.TestContext
+ cfg := result.Config
+ // Prepare the build actions, i.e. run all the mutators, singletons and then invoke the
+ // GenerateAndroidBuildActions methods on all the modules.
+ extraNinjaDeps, errs := ctx.PrepareBuildActions(cfg)
+ result.NinjaDeps = append(result.NinjaDeps, extraNinjaDeps...)
+ result.CollateErrs(errs)
+}
+
+var defaultTestRunner FixtureTestRunner = &standardTestRunner{}
+
func (f *fixture) outputDebugState() {
fmt.Printf("Begin Fixture State for %s\n", f.t.Name())
if len(f.config.env) == 0 {
@@ -909,3 +1034,10 @@
func (r *TestResult) Module(name string, variant string) Module {
return r.ModuleForTests(name, variant).Module()
}
+
+// CollateErrs adds additional errors to the result and returns true if there is more than one
+// error in the result.
+func (r *TestResult) CollateErrs(errs []error) bool {
+ r.Errs = append(r.Errs, errs...)
+ return len(r.Errs) > 0
+}
diff --git a/bp2build/cc_prebuilt_library_conversion_test.go b/bp2build/cc_prebuilt_library_conversion_test.go
index 47006ac..2fe158e 100644
--- a/bp2build/cc_prebuilt_library_conversion_test.go
+++ b/bp2build/cc_prebuilt_library_conversion_test.go
@@ -91,9 +91,9 @@
ModuleTypeUnderTest: "cc_prebuilt_library",
ModuleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
Filesystem: map[string]string{
- "libf.so": "",
- "testdir/1/": "",
- "testdir/2/": "",
+ "libf.so": "",
+ "testdir/1/include.h": "",
+ "testdir/2/other.h": "",
},
Blueprint: `
cc_prebuilt_library {
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 4e63d19..c059add 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -91,65 +91,66 @@
func RunBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
t.Helper()
- bp2buildSetup := func(ctx *android.TestContext) {
- registerModuleTypes(ctx)
- ctx.RegisterForBazelConversion()
- }
+ bp2buildSetup := android.GroupFixturePreparers(
+ android.FixtureRegisterWithContext(registerModuleTypes),
+ SetBp2BuildTestRunner,
+ )
runBp2BuildTestCaseWithSetup(t, bp2buildSetup, tc)
}
func RunApiBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
t.Helper()
- apiBp2BuildSetup := func(ctx *android.TestContext) {
- registerModuleTypes(ctx)
- ctx.RegisterForApiBazelConversion()
- }
+ apiBp2BuildSetup := android.GroupFixturePreparers(
+ android.FixtureRegisterWithContext(registerModuleTypes),
+ SetApiBp2BuildTestRunner,
+ )
runBp2BuildTestCaseWithSetup(t, apiBp2BuildSetup, tc)
}
-func runBp2BuildTestCaseWithSetup(t *testing.T, setup func(ctx *android.TestContext), tc Bp2buildTestCase) {
+func runBp2BuildTestCaseWithSetup(t *testing.T, extraPreparer android.FixturePreparer, tc Bp2buildTestCase) {
t.Helper()
dir := "."
filesystem := make(map[string][]byte)
- toParse := []string{
- "Android.bp",
- }
for f, content := range tc.Filesystem {
- if strings.HasSuffix(f, "Android.bp") {
- toParse = append(toParse, f)
- }
filesystem[f] = []byte(content)
}
- config := android.TestConfig(buildDir, nil, tc.Blueprint, filesystem)
- ctx := android.NewTestContext(config)
- setup(ctx)
- ctx.RegisterModuleType(tc.ModuleTypeUnderTest, tc.ModuleTypeUnderTestFactory)
-
- // A default configuration for tests to not have to specify bp2build_available on top level targets.
- bp2buildConfig := android.NewBp2BuildAllowlist().SetDefaultConfig(
- allowlists.Bp2BuildConfig{
- android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively,
- },
- )
- for _, f := range tc.KeepBuildFileForDirs {
- bp2buildConfig.SetKeepExistingBuildFile(map[string]bool{
- f: /*recursive=*/ false,
- })
- }
- ctx.RegisterBp2BuildConfig(bp2buildConfig)
-
- _, parseErrs := ctx.ParseFileList(dir, toParse)
- if errored(t, tc, parseErrs) {
- return
- }
- _, resolveDepsErrs := ctx.ResolveDependencies(config)
- if errored(t, tc, resolveDepsErrs) {
- return
+ preparers := []android.FixturePreparer{
+ extraPreparer,
+ android.FixtureMergeMockFs(filesystem),
+ android.FixtureWithRootAndroidBp(tc.Blueprint),
+ android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType(tc.ModuleTypeUnderTest, tc.ModuleTypeUnderTestFactory)
+ }),
+ android.FixtureModifyContext(func(ctx *android.TestContext) {
+ // A default configuration for tests to not have to specify bp2build_available on top level
+ // targets.
+ bp2buildConfig := android.NewBp2BuildAllowlist().SetDefaultConfig(
+ allowlists.Bp2BuildConfig{
+ android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively,
+ },
+ )
+ for _, f := range tc.KeepBuildFileForDirs {
+ bp2buildConfig.SetKeepExistingBuildFile(map[string]bool{
+ f: /*recursive=*/ false,
+ })
+ }
+ ctx.RegisterBp2BuildConfig(bp2buildConfig)
+ }),
+ android.FixtureModifyEnv(func(env map[string]string) {
+ if tc.UnconvertedDepsMode == errorModulesUnconvertedDeps {
+ env["BP2BUILD_ERROR_UNCONVERTED"] = "true"
+ }
+ }),
}
- parseAndResolveErrs := append(parseErrs, resolveDepsErrs...)
- if tc.ExpectedErr != nil && checkError(t, parseAndResolveErrs, tc.ExpectedErr) {
+ preparer := android.GroupFixturePreparers(preparers...)
+ if tc.ExpectedErr != nil {
+ pattern := "\\Q" + tc.ExpectedErr.Error() + "\\E"
+ preparer = preparer.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(pattern))
+ }
+ result := preparer.RunTestWithCustomResult(t).(*BazelTestResult)
+ if len(result.Errs) > 0 {
return
}
@@ -157,27 +158,115 @@
if tc.Dir != "" {
checkDir = tc.Dir
}
- codegenCtx := NewCodegenContext(config, ctx.Context, Bp2Build)
- codegenCtx.unconvertedDepMode = tc.UnconvertedDepsMode
- bazelTargets, errs := generateBazelTargetsForDir(codegenCtx, checkDir)
- if tc.ExpectedErr != nil {
- if checkError(t, errs, tc.ExpectedErr) {
- return
- } else {
- t.Errorf("Expected error: %q, got: %q and %q", tc.ExpectedErr, errs, parseAndResolveErrs)
- }
- } else {
- android.FailIfErrored(t, errs)
+ expectedTargets := map[string][]string{
+ checkDir: tc.ExpectedBazelTargets,
}
- if actualCount, expectedCount := len(bazelTargets), len(tc.ExpectedBazelTargets); actualCount != expectedCount {
+
+ result.CompareAllBazelTargets(t, tc.Description, expectedTargets, true)
+}
+
+// SetBp2BuildTestRunner customizes the test fixture mechanism to run tests in Bp2Build mode.
+var SetBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{Bp2Build})
+
+// SetApiBp2BuildTestRunner customizes the test fixture mechanism to run tests in ApiBp2build mode.
+var SetApiBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{ApiBp2build})
+
+// bazelTestRunner customizes the test fixture mechanism to run tests of the bp2build and
+// apiBp2build build modes.
+type bazelTestRunner struct {
+ mode CodegenMode
+}
+
+func (b *bazelTestRunner) FinalPreparer(result *android.TestResult) android.CustomTestResult {
+ ctx := result.TestContext
+ switch b.mode {
+ case Bp2Build:
+ ctx.RegisterForBazelConversion()
+ case ApiBp2build:
+ ctx.RegisterForApiBazelConversion()
+ default:
+ panic(fmt.Errorf("unknown build mode: %d", b.mode))
+ }
+
+ return &BazelTestResult{TestResult: result}
+}
+
+func (b *bazelTestRunner) PostParseProcessor(result android.CustomTestResult) {
+ bazelResult := result.(*BazelTestResult)
+ ctx := bazelResult.TestContext
+ config := bazelResult.Config
+ _, errs := ctx.ResolveDependencies(config)
+ if bazelResult.CollateErrs(errs) {
+ return
+ }
+
+ codegenCtx := NewCodegenContext(config, ctx.Context, Bp2Build)
+ res, errs := GenerateBazelTargets(codegenCtx, false)
+ if bazelResult.CollateErrs(errs) {
+ return
+ }
+
+ // Store additional data for access by tests.
+ bazelResult.conversionResults = res
+}
+
+// BazelTestResult is a wrapper around android.TestResult to provide type safe access to the bazel
+// specific data stored by the bazelTestRunner.
+type BazelTestResult struct {
+ *android.TestResult
+
+ // The result returned by the GenerateBazelTargets function.
+ conversionResults
+}
+
+// CompareAllBazelTargets compares the BazelTargets produced by the test for all the directories
+// with the supplied set of expected targets.
+//
+// If ignoreUnexpected=false then this enforces an exact match where every BazelTarget produced must
+// have a corresponding expected BazelTarget.
+//
+// If ignoreUnexpected=true then it will ignore directories for which there are no expected targets.
+func (b BazelTestResult) CompareAllBazelTargets(t *testing.T, description string, expectedTargets map[string][]string, ignoreUnexpected bool) {
+ actualTargets := b.buildFileToTargets
+
+ // Generate the sorted set of directories to check.
+ dirsToCheck := android.SortedStringKeys(expectedTargets)
+ if !ignoreUnexpected {
+ // This needs to perform an exact match so add the directories in which targets were
+ // produced to the list of directories to check.
+ dirsToCheck = append(dirsToCheck, android.SortedStringKeys(actualTargets)...)
+ dirsToCheck = android.SortedUniqueStrings(dirsToCheck)
+ }
+
+ for _, dir := range dirsToCheck {
+ expected := expectedTargets[dir]
+ actual := actualTargets[dir]
+
+ if expected == nil {
+ if actual != nil {
+ t.Errorf("did not expect any bazel modules in %q but found %d", dir, len(actual))
+ }
+ } else if actual == nil {
+ expectedCount := len(expected)
+ if expectedCount > 0 {
+ t.Errorf("expected %d bazel modules in %q but did not find any", expectedCount, dir)
+ }
+ } else {
+ b.CompareBazelTargets(t, description, expected, actual)
+ }
+ }
+}
+
+func (b BazelTestResult) CompareBazelTargets(t *testing.T, description string, expectedContents []string, actualTargets BazelTargets) {
+ if actualCount, expectedCount := len(actualTargets), len(expectedContents); actualCount != expectedCount {
t.Errorf("%s: Expected %d bazel target (%s), got %d (%s)",
- tc.Description, expectedCount, tc.ExpectedBazelTargets, actualCount, bazelTargets)
+ description, expectedCount, expectedContents, actualCount, actualTargets)
} else {
- for i, target := range bazelTargets {
- if w, g := tc.ExpectedBazelTargets[i], target.content; w != g {
+ for i, actualTarget := range actualTargets {
+ if w, g := expectedContents[i], actualTarget.content; w != g {
t.Errorf(
- "%s: Expected generated Bazel target to be `%s`, got `%s`",
- tc.Description, w, g)
+ "%s[%d]: Expected generated Bazel target to be `%s`, got `%s`",
+ description, i, w, g)
}
}
}