Add test fixture support

Adds the test fixture support and converts a few tests to exercise the
code and show how it works.

Bug: 181070625
Test: m nothing
Change-Id: I0a2b40fff93b6041f9aa8c4ef0aba91da1bc8bf3
diff --git a/android/Android.bp b/android/Android.bp
index a8fa53a..8f6c9ad 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -34,6 +34,7 @@
         "deptag.go",
         "expand.go",
         "filegroup.go",
+        "fixture.go",
         "hooks.go",
         "image.go",
         "license.go",
@@ -87,6 +88,7 @@
         "depset_test.go",
         "deptag_test.go",
         "expand_test.go",
+        "fixture_test.go",
         "license_kind_test.go",
         "license_test.go",
         "licenses_test.go",
diff --git a/android/android_test.go b/android/android_test.go
index 46b7054..68cb705 100644
--- a/android/android_test.go
+++ b/android/android_test.go
@@ -44,3 +44,5 @@
 
 	os.Exit(run())
 }
+
+var emptyTestFixtureFactory = NewFixtureFactory(&buildDir)
diff --git a/android/config.go b/android/config.go
index f0bba81..ef5eadf 100644
--- a/android/config.go
+++ b/android/config.go
@@ -305,10 +305,7 @@
 	return testConfig
 }
 
-// TestArchConfig returns a Config object suitable for using for tests that
-// need to run the arch mutator.
-func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
-	testConfig := TestConfig(buildDir, env, bp, fs)
+func modifyTestConfigToSupportArchMutator(testConfig Config) {
 	config := testConfig.config
 
 	config.Targets = map[OsType][]Target{
@@ -334,7 +331,13 @@
 	config.TestProductVariables.DeviceArchVariant = proptools.StringPtr("armv8-a")
 	config.TestProductVariables.DeviceSecondaryArch = proptools.StringPtr("arm")
 	config.TestProductVariables.DeviceSecondaryArchVariant = proptools.StringPtr("armv7-a-neon")
+}
 
+// TestArchConfig returns a Config object suitable for using for tests that
+// need to run the arch mutator.
+func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
+	testConfig := TestConfig(buildDir, env, bp, fs)
+	modifyTestConfigToSupportArchMutator(testConfig)
 	return testConfig
 }
 
diff --git a/android/filegroup.go b/android/filegroup.go
index 593e470..2eb4741 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -24,6 +24,10 @@
 	RegisterBp2BuildMutator("filegroup", FilegroupBp2Build)
 }
 
+var PrepareForTestWithFilegroup = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.RegisterModuleType("filegroup", FileGroupFactory)
+})
+
 // https://docs.bazel.build/versions/master/be/general.html#filegroup
 type bazelFilegroupAttributes struct {
 	Srcs bazel.LabelList
diff --git a/android/fixture.go b/android/fixture.go
new file mode 100644
index 0000000..0efe329
--- /dev/null
+++ b/android/fixture.go
@@ -0,0 +1,604 @@
+// Copyright 2021 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 android
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+)
+
+// Provides support for creating test fixtures on which tests can be run. Reduces duplication
+// of test setup by allow tests to easily reuse setup code.
+//
+// Fixture
+// =======
+// These determine the environment within which a test can be run. Fixtures are mutable and are
+// created by FixtureFactory instances and mutated by FixturePreparer instances. They are created by
+// first creating a base Fixture (which is essentially empty) and then applying FixturePreparer
+// instances to it to modify the environment.
+//
+// FixtureFactory
+// ==============
+// These are responsible for creating fixtures. Factories are immutable and are intended to be
+// initialized once and reused to create multiple fixtures. Each factory has a list of fixture
+// preparers that prepare a fixture for running a test. Factories can also be used to create other
+// factories by extending them with additional fixture preparers.
+//
+// FixturePreparer
+// ===============
+// These are responsible for modifying a Fixture in preparation for it to run a test. Preparers are
+// intended to be immutable and able to prepare multiple Fixture objects simultaneously without
+// them sharing any data.
+//
+// FixturePreparers are only ever invoked once per test fixture. Prior to invocation the list of
+// FixturePreparers are flattened and deduped while preserving the order they first appear in the
+// list. This makes it easy to reuse, group and combine FixturePreparers together.
+//
+// Each small self contained piece of test setup should be their own FixturePreparer. e.g.
+// * A group of related modules.
+// * A group of related mutators.
+// * A combination of both.
+// * Configuration.
+//
+// They should not overlap, e.g. the same module type should not be registered by different
+// FixturePreparers as using them both would cause a build error. In that case the preparer should
+// be split into separate parts and combined together using FixturePreparers(...).
+//
+// e.g. attempting to use AllPreparers in preparing a Fixture would break as it would attempt to
+// register module bar twice:
+//   var Preparer1 = FixtureRegisterWithContext(RegisterModuleFooAndBar)
+//   var Preparer2 = FixtureRegisterWithContext(RegisterModuleBarAndBaz)
+//   var AllPreparers = FixturePreparers(Preparer1, Preparer2)
+//
+// However, when restructured like this it would work fine:
+//   var PreparerFoo = FixtureRegisterWithContext(RegisterModuleFoo)
+//   var PreparerBar = FixtureRegisterWithContext(RegisterModuleBar)
+//   var PreparerBaz = FixtureRegisterWithContext(RegisterModuleBaz)
+//   var Preparer1 = FixturePreparers(RegisterModuleFoo, RegisterModuleBar)
+//   var Preparer2 = FixturePreparers(RegisterModuleBar, RegisterModuleBaz)
+//   var AllPreparers = FixturePreparers(Preparer1, Preparer2)
+//
+// As after deduping and flattening AllPreparers would result in the following preparers being
+// applied:
+// 1. PreparerFoo
+// 2. PreparerBar
+// 3. PreparerBaz
+//
+// Preparers can be used for both integration and unit tests.
+//
+// Integration tests typically use all the module types, mutators and singletons that are available
+// for that package to try and replicate the behavior of the runtime build as closely as possible.
+// However, that realism comes at a cost of increased fragility (as they can be broken by changes in
+// many different parts of the build) and also increased runtime, especially if they use lots of
+// singletons and mutators.
+//
+// Unit tests on the other hand try and minimize the amount of code being tested which makes them
+// less susceptible to changes elsewhere in the build and quick to run but at a cost of potentially
+// not testing realistic scenarios.
+//
+// Supporting unit tests effectively require that preparers are available at the lowest granularity
+// possible. Supporting integration tests effectively require that the preparers are organized into
+// groups that provide all the functionality available.
+//
+// At least in terms of tests that check the behavior of build components via processing
+// `Android.bp` there is no clear separation between a unit test and an integration test. Instead
+// they vary from one end that tests a single module (e.g. filegroup) to the other end that tests a
+// whole system of modules, mutators and singletons (e.g. apex + hiddenapi).
+//
+// TestResult
+// ==========
+// These are created by running tests in a Fixture and provide access to the Config and TestContext
+// in which the tests were run.
+//
+// Example
+// =======
+//
+// An exported preparer for use by other packages that need to use java modules.
+//
+// package java
+// var PrepareForIntegrationTestWithJava = FixturePreparers(
+//    android.PrepareForIntegrationTestWithAndroid,
+//    FixtureRegisterWithContext(RegisterAGroupOfRelatedModulesMutatorsAndSingletons),
+//    FixtureRegisterWithContext(RegisterAnotherGroupOfRelatedModulesMutatorsAndSingletons),
+//    ...
+// )
+//
+// Some files to use in tests in the java package.
+//
+// var javaMockFS = android.MockFS{
+//		"api/current.txt":        nil,
+//		"api/removed.txt":        nil,
+//    ...
+// }
+//
+// A package private factory for use for testing java within the java package.
+//
+// var javaFixtureFactory = NewFixtureFactory(
+//    PrepareForIntegrationTestWithJava,
+//    FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+//      ctx.RegisterModuleType("test_module", testModule)
+//    }),
+//    javaMockFS.AddToFixture(),
+//    ...
+// }
+//
+// func TestJavaStuff(t *testing.T) {
+//   result := javaFixtureFactory.RunTest(t,
+//       android.FixtureWithRootAndroidBp(`java_library {....}`),
+//       android.MockFS{...}.AddToFixture(),
+//   )
+//   ... test result ...
+// }
+//
+// package cc
+// var PrepareForTestWithCC = FixturePreparers(
+//    android.PrepareForArchMutator,
+//	  android.prepareForPrebuilts,
+//    FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
+//    ...
+// )
+//
+// package apex
+//
+// var PrepareForApex = FixturePreparers(
+//    ...
+// )
+//
+// Use modules and mutators from java, cc and apex. Any duplicate preparers (like
+// android.PrepareForArchMutator) will be automatically deduped.
+//
+// var apexFixtureFactory = android.NewFixtureFactory(
+//    PrepareForJava,
+//    PrepareForCC,
+//    PrepareForApex,
+// )
+
+// Factory for Fixture objects.
+//
+// This is configured with a set of FixturePreparer objects that are used to
+// initialize each Fixture instance this creates.
+type FixtureFactory interface {
+
+	// Creates a copy of this instance and adds some additional preparers.
+	//
+	// Before the preparers are used they are combined with the preparers provided when the factory
+	// was created, any groups of preparers are flattened, and the list is deduped so that each
+	// preparer is only used once. See the file documentation in android/fixture.go for more details.
+	Extend(preparers ...FixturePreparer) FixtureFactory
+
+	// Create a Fixture.
+	Fixture(t *testing.T, preparers ...FixturePreparer) Fixture
+
+	// Run the test, expecting no errors, returning a TestResult instance.
+	//
+	// Shorthand for Fixture(t, preparers...).RunTest()
+	RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult
+
+	// Run the test with the supplied Android.bp file.
+	//
+	// Shorthand for RunTest(t, android.FixtureWithRootAndroidBp(bp))
+	RunTestWithBp(t *testing.T, bp string) *TestResult
+}
+
+// Create a new FixtureFactory that will apply the supplied preparers.
+//
+// The buildDirSupplier is a pointer to the package level buildDir variable that is initialized by
+// the package level setUp method. It has to be a pointer to the variable as the variable will not
+// have been initialized at the time the factory is created.
+func NewFixtureFactory(buildDirSupplier *string, preparers ...FixturePreparer) FixtureFactory {
+	return &fixtureFactory{
+		buildDirSupplier: buildDirSupplier,
+		preparers:        dedupAndFlattenPreparers(nil, preparers),
+	}
+}
+
+// A set of mock files to add to the mock file system.
+type MockFS map[string][]byte
+
+func (fs MockFS) Merge(extra map[string][]byte) {
+	for p, c := range extra {
+		fs[p] = c
+	}
+}
+
+func (fs MockFS) AddToFixture() FixturePreparer {
+	return FixtureMergeMockFs(fs)
+}
+
+// Modify the config
+func FixtureModifyConfig(mutator func(config Config)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.config)
+	})
+}
+
+// Modify the config and context
+func FixtureModifyConfigAndContext(mutator func(config Config, ctx *TestContext)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.config, f.ctx)
+	})
+}
+
+// Modify the context
+func FixtureModifyContext(mutator func(ctx *TestContext)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.ctx)
+	})
+}
+
+func FixtureRegisterWithContext(registeringFunc func(ctx RegistrationContext)) FixturePreparer {
+	return FixtureModifyContext(func(ctx *TestContext) { registeringFunc(ctx) })
+}
+
+// Modify the mock filesystem
+func FixtureModifyMockFS(mutator func(fs MockFS)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.mockFS)
+	})
+}
+
+// Merge the supplied file system into the mock filesystem.
+//
+// Paths that already exist in the mock file system are overridden.
+func FixtureMergeMockFs(mockFS MockFS) FixturePreparer {
+	return FixtureModifyMockFS(func(fs MockFS) {
+		fs.Merge(mockFS)
+	})
+}
+
+// Add a file to the mock filesystem
+func FixtureAddFile(path string, contents []byte) FixturePreparer {
+	return FixtureModifyMockFS(func(fs MockFS) {
+		fs[path] = contents
+	})
+}
+
+// Add a text file to the mock filesystem
+func FixtureAddTextFile(path string, contents string) FixturePreparer {
+	return FixtureAddFile(path, []byte(contents))
+}
+
+// Add the root Android.bp file with the supplied contents.
+func FixtureWithRootAndroidBp(contents string) FixturePreparer {
+	return FixtureAddTextFile("Android.bp", contents)
+}
+
+// Create a composite FixturePreparer that is equivalent to applying each of the supplied
+// FixturePreparer instances in order.
+func FixturePreparers(preparers ...FixturePreparer) FixturePreparer {
+	return &compositeFixturePreparer{dedupAndFlattenPreparers(nil, preparers)}
+}
+
+type simpleFixturePreparerVisitor func(preparer *simpleFixturePreparer)
+
+// FixturePreparer is an opaque interface that can change a fixture.
+type FixturePreparer interface {
+	// visit calls the supplied visitor with each *simpleFixturePreparer instances in this preparer,
+	visit(simpleFixturePreparerVisitor)
+}
+
+type fixturePreparers []FixturePreparer
+
+func (f fixturePreparers) visit(visitor simpleFixturePreparerVisitor) {
+	for _, p := range f {
+		p.visit(visitor)
+	}
+}
+
+// dedupAndFlattenPreparers removes any duplicates and flattens any composite FixturePreparer
+// instances.
+//
+// base      - a list of already flattened and deduped preparers that will be applied first before
+//             the list of additional preparers. Any duplicates of these in the additional preparers
+//             will be ignored.
+//
+// preparers - a list of additional unflattened, undeduped preparers that will be applied after the
+//             base preparers.
+//
+// Returns a deduped and flattened list of the preparers minus any that exist in the base preparers.
+func dedupAndFlattenPreparers(base []*simpleFixturePreparer, preparers fixturePreparers) []*simpleFixturePreparer {
+	var list []*simpleFixturePreparer
+	visited := make(map[*simpleFixturePreparer]struct{})
+
+	// Mark the already flattened and deduped preparers, if any, as having been seen so that
+	// duplicates of these in the additional preparers will be discarded.
+	for _, s := range base {
+		visited[s] = struct{}{}
+	}
+
+	preparers.visit(func(preparer *simpleFixturePreparer) {
+		if _, seen := visited[preparer]; !seen {
+			visited[preparer] = struct{}{}
+			list = append(list, preparer)
+		}
+	})
+	return list
+}
+
+// compositeFixturePreparer is a FixturePreparer created from a list of fixture preparers.
+type compositeFixturePreparer struct {
+	preparers []*simpleFixturePreparer
+}
+
+func (c *compositeFixturePreparer) visit(visitor simpleFixturePreparerVisitor) {
+	for _, p := range c.preparers {
+		p.visit(visitor)
+	}
+}
+
+// simpleFixturePreparer is a FixturePreparer that applies a function to a fixture.
+type simpleFixturePreparer struct {
+	function func(fixture *fixture)
+}
+
+func (s *simpleFixturePreparer) visit(visitor simpleFixturePreparerVisitor) {
+	visitor(s)
+}
+
+func newSimpleFixturePreparer(preparer func(fixture *fixture)) FixturePreparer {
+	return &simpleFixturePreparer{function: preparer}
+}
+
+// Fixture defines the test environment.
+type Fixture interface {
+	// Run the test, expecting no errors, returning a TestResult instance.
+	RunTest() *TestResult
+}
+
+// Provides general test support.
+type TestHelper struct {
+	*testing.T
+}
+
+// AssertBoolEquals checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertBoolEquals(message string, expected bool, actual bool) {
+	h.Helper()
+	if actual != expected {
+		h.Errorf("%s: expected %t, actual %t", message, expected, actual)
+	}
+}
+
+// AssertStringEquals checks if the expected and actual values are equal and if they are not then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertStringEquals(message string, expected string, actual string) {
+	h.Helper()
+	if actual != expected {
+		h.Errorf("%s: expected %s, actual %s", message, expected, actual)
+	}
+}
+
+// AssertTrimmedStringEquals checks if the expected and actual values are the same after trimming
+// leading and trailing spaces from them both. If they are not then it reports an error prefixed
+// with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertTrimmedStringEquals(message string, expected string, actual string) {
+	h.Helper()
+	h.AssertStringEquals(message, strings.TrimSpace(expected), strings.TrimSpace(actual))
+}
+
+// AssertStringDoesContain checks if the string contains the expected substring. If it does not
+// then it reports an error prefixed with the supplied message and including a reason for why it
+// failed.
+func (h *TestHelper) AssertStringDoesContain(message string, s string, expectedSubstring string) {
+	h.Helper()
+	if !strings.Contains(s, expectedSubstring) {
+		h.Errorf("%s: could not find %q within %q", message, expectedSubstring, s)
+	}
+}
+
+// AssertStringDoesNotContain checks if the string contains the expected substring. If it does then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertStringDoesNotContain(message string, s string, unexpectedSubstring string) {
+	h.Helper()
+	if strings.Contains(s, unexpectedSubstring) {
+		h.Errorf("%s: unexpectedly found %q within %q", message, unexpectedSubstring, s)
+	}
+}
+
+// AssertArrayString checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertArrayString(message string, expected, actual []string) {
+	h.Helper()
+	if len(actual) != len(expected) {
+		h.Errorf("%s: expected %d (%q), actual (%d) %q", message, len(expected), expected, len(actual), actual)
+		return
+	}
+	for i := range actual {
+		if actual[i] != expected[i] {
+			h.Errorf("%s: expected %d-th, %q (%q), actual %q (%q)",
+				message, i, expected[i], expected, actual[i], actual)
+			return
+		}
+	}
+}
+
+// AssertArrayString checks if the expected and actual values are equal using reflect.DeepEqual and
+// if they are not then it reports an error prefixed with the supplied message and including a
+// reason for why it failed.
+func (h *TestHelper) AssertDeepEquals(message string, expected interface{}, actual interface{}) {
+	h.Helper()
+	if !reflect.DeepEqual(actual, expected) {
+		h.Errorf("%s: expected:\n  %#v\n got:\n  %#v", message, expected, actual)
+	}
+}
+
+// Struct to allow TestResult to embed a *TestContext and allow call forwarding to its methods.
+type testContext struct {
+	*TestContext
+}
+
+// The result of running a test.
+type TestResult struct {
+	TestHelper
+	testContext
+
+	fixture *fixture
+	Config  Config
+}
+
+var _ FixtureFactory = (*fixtureFactory)(nil)
+
+type fixtureFactory struct {
+	buildDirSupplier *string
+	preparers        []*simpleFixturePreparer
+}
+
+func (f *fixtureFactory) Extend(preparers ...FixturePreparer) FixtureFactory {
+	all := append(f.preparers, dedupAndFlattenPreparers(f.preparers, preparers)...)
+	return &fixtureFactory{
+		buildDirSupplier: f.buildDirSupplier,
+		preparers:        all,
+	}
+}
+
+func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fixture {
+	config := TestConfig(*f.buildDirSupplier, nil, "", nil)
+	ctx := NewTestContext(config)
+	fixture := &fixture{
+		factory: f,
+		t:       t,
+		config:  config,
+		ctx:     ctx,
+		mockFS:  make(MockFS),
+	}
+
+	for _, preparer := range f.preparers {
+		preparer.function(fixture)
+	}
+
+	for _, preparer := range dedupAndFlattenPreparers(f.preparers, preparers) {
+		preparer.function(fixture)
+	}
+
+	return fixture
+}
+
+func (f *fixtureFactory) RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult {
+	t.Helper()
+	fixture := f.Fixture(t, preparers...)
+	return fixture.RunTest()
+}
+
+func (f *fixtureFactory) RunTestWithBp(t *testing.T, bp string) *TestResult {
+	t.Helper()
+	return f.RunTest(t, FixtureWithRootAndroidBp(bp))
+}
+
+type fixture struct {
+	factory *fixtureFactory
+	t       *testing.T
+	config  Config
+	ctx     *TestContext
+	mockFS  MockFS
+}
+
+func (f *fixture) RunTest() *TestResult {
+	f.t.Helper()
+
+	ctx := f.ctx
+
+	// The TestConfig() method assumes that the mock filesystem is available when creating so creates
+	// the mock file system immediately. Similarly, the NewTestContext(Config) method assumes that the
+	// supplied Config's FileSystem has been properly initialized before it is called and so it takes
+	// its own reference to the filesystem. However, fixtures create the Config and TestContext early
+	// so they can be modified by preparers at which time the mockFS has not been populated (because
+	// it too is modified by preparers). So, this reinitializes the Config and TestContext's
+	// FileSystem using the now populated mockFS.
+	f.config.mockFileSystem("", f.mockFS)
+	ctx.SetFs(ctx.config.fs)
+	if ctx.config.mockBpList != "" {
+		ctx.SetModuleListFile(ctx.config.mockBpList)
+	}
+
+	ctx.Register()
+	_, errs := ctx.ParseBlueprintsFiles("ignored")
+	FailIfErrored(f.t, errs)
+	_, errs = ctx.PrepareBuildActions(f.config)
+	FailIfErrored(f.t, errs)
+
+	result := &TestResult{
+		TestHelper:  TestHelper{T: f.t},
+		testContext: testContext{ctx},
+		fixture:     f,
+		Config:      f.config,
+	}
+	return result
+}
+
+// NormalizePathForTesting removes the test invocation specific build directory from the supplied
+// path.
+//
+// If the path is within the build directory (e.g. an OutputPath) then this returns the relative
+// path to avoid tests having to deal with the dynamically generated build directory.
+//
+// Otherwise, this returns the supplied path as it is almost certainly a source path that is
+// relative to the root of the source tree.
+//
+// Even though some information is removed from some paths and not others it should be possible to
+// differentiate between them by the paths themselves, e.g. output paths will likely include
+// ".intermediates" but source paths won't.
+func (r *TestResult) NormalizePathForTesting(path Path) string {
+	pathContext := PathContextForTesting(r.Config)
+	pathAsString := path.String()
+	if rel, isRel := MaybeRel(pathContext, r.Config.BuildDir(), pathAsString); isRel {
+		return rel
+	}
+	return pathAsString
+}
+
+// NormalizePathsForTesting normalizes each path in the supplied list and returns their normalized
+// forms.
+func (r *TestResult) NormalizePathsForTesting(paths Paths) []string {
+	var result []string
+	for _, path := range paths {
+		result = append(result, r.NormalizePathForTesting(path))
+	}
+	return result
+}
+
+// NewFixture creates a new test fixture that is based on the one that created this result. It is
+// intended to test the output of module types that generate content to be processed by the build,
+// e.g. sdk snapshots.
+func (r *TestResult) NewFixture(preparers ...FixturePreparer) Fixture {
+	return r.fixture.factory.Fixture(r.T, preparers...)
+}
+
+// RunTest is shorthand for NewFixture(preparers...).RunTest().
+func (r *TestResult) RunTest(preparers ...FixturePreparer) *TestResult {
+	r.Helper()
+	return r.fixture.factory.Fixture(r.T, preparers...).RunTest()
+}
+
+// Module returns the module with the specific name and of the specified variant.
+func (r *TestResult) Module(name string, variant string) Module {
+	return r.ModuleForTests(name, variant).Module()
+}
+
+// Create a *TestResult object suitable for use within a subtest.
+//
+// This ensures that any errors reported by the TestResult, e.g. from within one of its
+// Assert... methods, will be associated with the sub test and not the main test.
+//
+// result := ....RunTest()
+// t.Run("subtest", func(t *testing.T) {
+//    subResult := result.ResultForSubTest(t)
+//    subResult.AssertStringEquals("something", ....)
+// })
+func (r *TestResult) ResultForSubTest(t *testing.T) *TestResult {
+	subTestResult := *r
+	r.T = t
+	return &subTestResult
+}
diff --git a/android/fixture_test.go b/android/fixture_test.go
new file mode 100644
index 0000000..7bc033b
--- /dev/null
+++ b/android/fixture_test.go
@@ -0,0 +1,49 @@
+// Copyright 2021 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 android
+
+import "testing"
+
+// Make sure that FixturePreparer instances are only called once per fixture and in the order in
+// which they were added.
+func TestFixtureDedup(t *testing.T) {
+	list := []string{}
+
+	appendToList := func(s string) FixturePreparer {
+		return FixtureModifyConfig(func(_ Config) {
+			list = append(list, s)
+		})
+	}
+
+	preparer1 := appendToList("preparer1")
+	preparer2 := appendToList("preparer2")
+	preparer3 := appendToList("preparer3")
+	preparer4 := appendToList("preparer4")
+
+	preparer1Then2 := FixturePreparers(preparer1, preparer2)
+
+	preparer2Then1 := FixturePreparers(preparer2, preparer1)
+
+	buildDir := "build"
+	factory := NewFixtureFactory(&buildDir, preparer1, preparer2, preparer1, preparer1Then2)
+
+	extension := factory.Extend(preparer4, preparer2)
+
+	extension.Fixture(t, preparer1, preparer2, preparer2Then1, preparer3)
+
+	h := TestHelper{t}
+	h.AssertDeepEquals("preparers called in wrong order",
+		[]string{"preparer1", "preparer2", "preparer4", "preparer3"}, list)
+}
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 9ac3875..164b1bc 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -262,7 +262,7 @@
 }
 
 func TestPrebuilts(t *testing.T) {
-	fs := map[string][]byte{
+	fs := MockFS{
 		"prebuilt_file": nil,
 		"source_file":   nil,
 	}
@@ -277,32 +277,33 @@
 						deps: [":bar"],
 					}`
 			}
-			config := TestArchConfig(buildDir, nil, bp, fs)
 
 			// Add windows to the target list to test the logic when a variant is
 			// disabled by default.
 			if !Windows.DefaultDisabled {
 				t.Errorf("windows is assumed to be disabled by default")
 			}
-			config.config.Targets[Windows] = []Target{
-				{Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
-			}
 
-			ctx := NewTestArchContext(config)
-			registerTestPrebuiltBuildComponents(ctx)
-			ctx.RegisterModuleType("filegroup", FileGroupFactory)
-			ctx.Register()
+			result := emptyTestFixtureFactory.Extend(
+				PrepareForTestWithArchMutator,
+				PrepareForTestWithPrebuilts,
+				PrepareForTestWithOverrides,
+				PrepareForTestWithFilegroup,
+				// Add a Windows target to the configuration.
+				FixtureModifyConfig(func(config Config) {
+					config.Targets[Windows] = []Target{
+						{Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
+					}
+				}),
+				fs.AddToFixture(),
+				FixtureRegisterWithContext(registerTestPrebuiltModules),
+			).RunTestWithBp(t, bp)
 
-			_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-			FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(config)
-			FailIfErrored(t, errs)
-
-			for _, variant := range ctx.ModuleVariantsForTests("foo") {
-				foo := ctx.ModuleForTests("foo", variant)
+			for _, variant := range result.ModuleVariantsForTests("foo") {
+				foo := result.ModuleForTests("foo", variant)
 				t.Run(foo.Module().Target().Os.String(), func(t *testing.T) {
 					var dependsOnSourceModule, dependsOnPrebuiltModule bool
-					ctx.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
+					result.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
 						if _, ok := m.(*sourceModule); ok {
 							dependsOnSourceModule = true
 						}
@@ -381,14 +382,18 @@
 }
 
 func registerTestPrebuiltBuildComponents(ctx RegistrationContext) {
-	ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
-	ctx.RegisterModuleType("source", newSourceModule)
-	ctx.RegisterModuleType("override_source", newOverrideSourceModule)
+	registerTestPrebuiltModules(ctx)
 
 	RegisterPrebuiltMutators(ctx)
 	ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
 }
 
+func registerTestPrebuiltModules(ctx RegistrationContext) {
+	ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
+	ctx.RegisterModuleType("source", newSourceModule)
+	ctx.RegisterModuleType("override_source", newOverrideSourceModule)
+}
+
 type prebuiltModule struct {
 	ModuleBase
 	prebuilt   Prebuilt
diff --git a/android/testing.go b/android/testing.go
index 1b1feb7..5832796 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -48,6 +48,43 @@
 	return ctx
 }
 
+var PrepareForTestWithArchMutator = FixturePreparers(
+	// Configure architecture targets in the fixture config.
+	FixtureModifyConfig(modifyTestConfigToSupportArchMutator),
+
+	// Add the arch mutator to the context.
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.PreDepsMutators(registerArchMutator)
+	}),
+)
+
+var PrepareForTestWithDefaults = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
+})
+
+var PrepareForTestWithComponentsMutator = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.PreArchMutators(RegisterComponentsMutator)
+})
+
+var PrepareForTestWithPrebuilts = FixtureRegisterWithContext(RegisterPrebuiltMutators)
+
+var PrepareForTestWithOverrides = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
+})
+
+// Prepares an integration test with build components from the android package.
+var PrepareForIntegrationTestWithAndroid = FixturePreparers(
+	// Mutators. Must match order in mutator.go.
+	PrepareForTestWithArchMutator,
+	PrepareForTestWithDefaults,
+	PrepareForTestWithComponentsMutator,
+	PrepareForTestWithPrebuilts,
+	PrepareForTestWithOverrides,
+
+	// Modules
+	PrepareForTestWithFilegroup,
+)
+
 func NewTestArchContext(config Config) *TestContext {
 	ctx := NewTestContext(config)
 	ctx.preDeps = append(ctx.preDeps, registerArchMutator)