Merge "Add service-connectivity-pre-jarjar to legacy core platform list"
diff --git a/android/Android.bp b/android/Android.bp
index f17a8a0..588ad5e 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -67,6 +67,7 @@
         "singleton.go",
         "singleton_module.go",
         "soong_config_modules.go",
+        "test_asserts.go",
         "test_suites.go",
         "testing.go",
         "util.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index 32d7712..9317567 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -44,6 +44,14 @@
 	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
 }
 
+// Enable androidmk support.
+// * Register the singleton
+// * Configure that we are inside make
+var PrepareForTestWithAndroidMk = GroupFixturePreparers(
+	FixtureRegisterWithContext(RegisterAndroidMkBuildComponents),
+	FixtureModifyConfig(SetKatiEnabledForTests),
+)
+
 // Deprecated: Use AndroidMkEntriesProvider instead, especially if you're not going to use the
 // Custom function. It's easier to use and test.
 type AndroidMkDataProvider interface {
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 9cd9fad..6675840 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -37,6 +37,7 @@
 const (
 	getAllFiles CqueryRequestType = iota
 	getCcObjectFiles
+	getAllFilesAndCcObjectFiles
 )
 
 // Map key to describe bazel cquery requests.
@@ -58,7 +59,9 @@
 	// Retrieves these files from Bazel's CcInfo provider.
 	GetCcObjectFiles(label string, archType ArchType) ([]string, bool)
 
-	// TODO(cparsons): Other cquery-related methods should be added here.
+	// Returns the results of GetAllFiles and GetCcObjectFiles in a single query (in that order).
+	GetAllFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool)
+
 	// ** End cquery methods
 
 	// Issues commands to Bazel to receive results for all cquery requests
@@ -116,6 +119,11 @@
 	return result, ok
 }
 
+func (m MockBazelContext) GetAllFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
+	result, ok := m.AllFiles[label]
+	return result, result, ok
+}
+
 func (m MockBazelContext) InvokeBazel() error {
 	panic("unimplemented")
 }
@@ -154,6 +162,22 @@
 	}
 }
 
+func (bazelCtx *bazelContext) GetAllFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
+	var allFiles []string
+	var ccObjects []string
+
+	result, ok := bazelCtx.cquery(label, getAllFilesAndCcObjectFiles, archType)
+	if ok {
+		bazelOutput := strings.TrimSpace(result)
+		splitString := strings.Split(bazelOutput, "|")
+		allFilesString := splitString[0]
+		ccObjectsString := splitString[1]
+		allFiles = strings.Split(allFilesString, ", ")
+		ccObjects = strings.Split(ccObjectsString, ", ")
+	}
+	return allFiles, ccObjects, ok
+}
+
 func (n noopBazelContext) GetAllFiles(label string, archType ArchType) ([]string, bool) {
 	panic("unimplemented")
 }
@@ -162,6 +186,10 @@
 	panic("unimplemented")
 }
 
+func (n noopBazelContext) GetAllFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
+	panic("unimplemented")
+}
+
 func (n noopBazelContext) InvokeBazel() error {
 	panic("unimplemented")
 }
@@ -253,8 +281,12 @@
 	return ""
 }
 
+// Issues the given bazel command with given build label and additional flags.
+// Returns (stdout, stderr, error). The first and second return values are strings
+// containing the stdout and stderr of the run command, and an error is returned if
+// the invocation returned an error code.
 func (context *bazelContext) issueBazelCommand(runName bazel.RunName, command string, labels []string,
-	extraFlags ...string) (string, error) {
+	extraFlags ...string) (string, string, error) {
 
 	cmdFlags := []string{"--output_base=" + context.outputBase, command}
 	cmdFlags = append(cmdFlags, labels...)
@@ -281,9 +313,10 @@
 	bazelCmd.Stderr = stderr
 
 	if output, err := bazelCmd.Output(); err != nil {
-		return "", fmt.Errorf("bazel command failed. command: [%s], env: [%s], error [%s]", bazelCmd, bazelCmd.Env, stderr)
+		return "", string(stderr.Bytes()),
+			fmt.Errorf("bazel command failed. command: [%s], env: [%s], error [%s]", bazelCmd, bazelCmd.Env, stderr)
 	} else {
-		return string(output), nil
+		return string(output), string(stderr.Bytes()), nil
 	}
 }
 
@@ -452,6 +485,11 @@
 		strings.Join(deps_arm, ",\n            ")))
 }
 
+// Returns the file contents of the buildroot.cquery file that should be used for the cquery
+// expression in order to obtain information about buildroot and its dependencies.
+// The contents of this file depend on the bazelContext's requests; requests are enumerated
+// and grouped by their request type. The data retrieved for each label depends on its
+// request type.
 func (context *bazelContext) cqueryStarlarkFileContents() []byte {
 	formatString := `
 # This file is generated by soong_build. Do not edit.
@@ -463,6 +501,13 @@
   %s
 }
 
+getAllFilesAndCcObjectFilesLabels = {
+  %s
+}
+
+def get_all_files(target):
+  return [f.path for f in target.files.to_list()]
+
 def get_cc_object_files(target):
   result = []
   linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
@@ -492,9 +537,11 @@
 def format(target):
   id_string = str(target.label) + "|" + get_arch(target)
   if id_string in getAllFilesLabels:
-    return id_string + ">>" + ', '.join([f.path for f in target.files.to_list()])
+    return id_string + ">>" + ', '.join(get_all_files(target))
   elif id_string in getCcObjectFilesLabels:
     return id_string + ">>" + ', '.join(get_cc_object_files(target))
+  elif id_string in getAllFilesAndCcObjectFilesLabels:
+    return id_string + ">>" + ', '.join(get_all_files(target)) + "|" + ', '.join(get_cc_object_files(target))
   else:
     # This target was not requested via cquery, and thus must be a dependency
     # of a requested target.
@@ -502,6 +549,7 @@
 `
 	var getAllFilesDeps []string = nil
 	var getCcObjectFilesDeps []string = nil
+	var getAllFilesAndCcObjectFilesDeps []string = nil
 
 	for val, _ := range context.requests {
 		labelWithArch := getCqueryId(val)
@@ -511,12 +559,16 @@
 			getAllFilesDeps = append(getAllFilesDeps, mapEntryString)
 		case getCcObjectFiles:
 			getCcObjectFilesDeps = append(getCcObjectFilesDeps, mapEntryString)
+		case getAllFilesAndCcObjectFiles:
+			getAllFilesAndCcObjectFilesDeps = append(getAllFilesAndCcObjectFilesDeps, mapEntryString)
 		}
 	}
 	getAllFilesDepsString := strings.Join(getAllFilesDeps, ",\n  ")
 	getCcObjectFilesDepsString := strings.Join(getCcObjectFilesDeps, ",\n  ")
+	getAllFilesAndCcObjectFilesDepsString := strings.Join(getAllFilesAndCcObjectFilesDeps, ",\n  ")
 
-	return []byte(fmt.Sprintf(formatString, getAllFilesDepsString, getCcObjectFilesDepsString))
+	return []byte(fmt.Sprintf(formatString, getAllFilesDepsString, getCcObjectFilesDepsString,
+		getAllFilesAndCcObjectFilesDepsString))
 }
 
 // Returns a workspace-relative path containing build-related metadata required
@@ -531,6 +583,7 @@
 	context.results = make(map[cqueryKey]string)
 
 	var cqueryOutput string
+	var cqueryErr string
 	var err error
 
 	intermediatesDirPath := absolutePath(context.intermediatesDir())
@@ -568,7 +621,7 @@
 		return err
 	}
 	buildrootLabel := "//:buildroot"
-	cqueryOutput, err = context.issueBazelCommand(bazel.CqueryBuildRootRunName, "cquery",
+	cqueryOutput, cqueryErr, err = context.issueBazelCommand(bazel.CqueryBuildRootRunName, "cquery",
 		[]string{fmt.Sprintf("kind(rule, deps(%s))", buildrootLabel)},
 		"--output=starlark",
 		"--starlark:file="+cqueryFileRelpath)
@@ -595,7 +648,8 @@
 		if cqueryResult, ok := cqueryResults[getCqueryId(val)]; ok {
 			context.results[val] = string(cqueryResult)
 		} else {
-			return fmt.Errorf("missing result for bazel target %s. query output: [%s]", getCqueryId(val), cqueryOutput)
+			return fmt.Errorf("missing result for bazel target %s. query output: [%s], cquery err: [%s]",
+				getCqueryId(val), cqueryOutput, cqueryErr)
 		}
 	}
 
@@ -603,7 +657,7 @@
 	//
 	// TODO(cparsons): Use --target_pattern_file to avoid command line limits.
 	var aqueryOutput string
-	aqueryOutput, err = context.issueBazelCommand(bazel.AqueryBuildRootRunName, "aquery",
+	aqueryOutput, _, err = context.issueBazelCommand(bazel.AqueryBuildRootRunName, "aquery",
 		[]string{fmt.Sprintf("deps(%s)", buildrootLabel),
 			// Use jsonproto instead of proto; actual proto parsing would require a dependency on Bazel's
 			// proto sources, which would add a number of unnecessary dependencies.
@@ -621,7 +675,7 @@
 	// Issue a build command of the phony root to generate symlink forests for dependencies of the
 	// Bazel build. This is necessary because aquery invocations do not generate this symlink forest,
 	// but some of symlinks may be required to resolve source dependencies of the build.
-	_, err = context.issueBazelCommand(bazel.BazelBuildPhonyRootRunName, "build",
+	_, _, err = context.issueBazelCommand(bazel.BazelBuildPhonyRootRunName, "build",
 		[]string{"//:phonyroot"})
 
 	if err != nil {
diff --git a/android/config.go b/android/config.go
index c10ad09..bc1aa3a 100644
--- a/android/config.go
+++ b/android/config.go
@@ -287,24 +287,21 @@
 	return testConfig
 }
 
-// TestArchConfigFuchsia returns a Config object suitable for using for
-// tests that need to run the arch mutator for the Fuchsia arch.
-func TestArchConfigFuchsia(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
-	testConfig := TestConfig(buildDir, env, bp, fs)
-	config := testConfig.config
-
-	config.Targets = map[OsType][]Target{
-		Fuchsia: []Target{
+func fuchsiaTargets() map[OsType][]Target {
+	return map[OsType][]Target{
+		Fuchsia: {
 			{Fuchsia, Arch{ArchType: Arm64, ArchVariant: "", Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", "", false},
 		},
-		BuildOs: []Target{
+		BuildOs: {
 			{BuildOs, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", false},
 		},
 	}
-
-	return testConfig
 }
 
+var PrepareForTestSetDeviceToFuchsia = FixtureModifyConfig(func(config Config) {
+	config.Targets = fuchsiaTargets()
+})
+
 func modifyTestConfigToSupportArchMutator(testConfig Config) {
 	config := testConfig.config
 
@@ -527,26 +524,6 @@
 	return PathForOutput(ctx, "host", c.PrebuiltOS(), "framework", path)
 }
 
-// NonHermeticHostSystemTool looks for non-hermetic tools from the system we're
-// running on. These tools are not checked-in to AOSP, and therefore could lead
-// to reproducibility problems. Should not be used for other than finding the
-// XCode SDK (xcrun, sw_vers), etc. See ui/build/paths/config.go for the
-// allowlist of host system tools.
-func (c *config) NonHermeticHostSystemTool(name string) string {
-	for _, dir := range filepath.SplitList(c.Getenv("PATH")) {
-		path := filepath.Join(dir, name)
-		if s, err := os.Stat(path); err != nil {
-			continue
-		} else if m := s.Mode(); !s.IsDir() && m&0111 != 0 {
-			return path
-		}
-	}
-	panic(fmt.Errorf(
-		"Unable to use '%s' as a host system tool for build system "+
-			"hermeticity reasons. See build/soong/ui/build/paths/config.go "+
-			"for the full list of allowed host tools on your system.", name))
-}
-
 // PrebuiltOS returns the name of the host OS used in prebuilts directories.
 func (c *config) PrebuiltOS() string {
 	switch runtime.GOOS {
diff --git a/android/fixture.go b/android/fixture.go
index 67c4c92..d8893f7 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -16,8 +16,6 @@
 
 import (
 	"fmt"
-	"reflect"
-	"strings"
 	"testing"
 )
 
@@ -199,6 +197,24 @@
 	//
 	// Shorthand for RunTest(t, android.FixtureWithRootAndroidBp(bp))
 	RunTestWithBp(t *testing.T, bp string) *TestResult
+
+	// RunTestWithConfig is a temporary method added to help ease the migration of existing tests to
+	// the test fixture.
+	//
+	// In order to allow the Config object to be customized separately to the TestContext a lot of
+	// existing test code has `test...WithConfig` funcs that allow the Config object to be supplied
+	// from the test and then have the TestContext created and configured automatically. e.g.
+	// testCcWithConfig, testCcErrorWithConfig, testJavaWithConfig, etc.
+	//
+	// This method allows those methods to be migrated to use the test fixture pattern without
+	// requiring that every test that uses those methods be migrated at the same time. That allows
+	// those tests to benefit from correctness in the order of registration quickly.
+	//
+	// This method discards the config (along with its mock file system, product variables,
+	// environment, etc.) that may have been set up by FixturePreparers.
+	//
+	// deprecated
+	RunTestWithConfig(t *testing.T, config Config) *TestResult
 }
 
 // Create a new FixtureFactory that will apply the supplied preparers.
@@ -219,8 +235,14 @@
 // A set of mock files to add to the mock file system.
 type MockFS map[string][]byte
 
+// Merge adds the extra entries from the supplied map to this one.
+//
+// Fails if the supplied map files with the same paths are present in both of them.
 func (fs MockFS) Merge(extra map[string][]byte) {
 	for p, c := range extra {
+		if _, ok := fs[p]; ok {
+			panic(fmt.Errorf("attempted to add file %s to the mock filesystem but it already exists", p))
+		}
 		fs[p] = c
 	}
 }
@@ -271,17 +293,40 @@
 }
 
 // Add a file to the mock filesystem
+//
+// Fail if the filesystem already contains a file with that path, use FixtureOverrideFile instead.
 func FixtureAddFile(path string, contents []byte) FixturePreparer {
 	return FixtureModifyMockFS(func(fs MockFS) {
+		if _, ok := fs[path]; ok {
+			panic(fmt.Errorf("attempted to add file %s to the mock filesystem but it already exists, use FixtureOverride*File instead", path))
+		}
 		fs[path] = contents
 	})
 }
 
 // Add a text file to the mock filesystem
+//
+// Fail if the filesystem already contains a file with that path.
 func FixtureAddTextFile(path string, contents string) FixturePreparer {
 	return FixtureAddFile(path, []byte(contents))
 }
 
+// Override a file in the mock filesystem
+//
+// If the file does not exist this behaves as FixtureAddFile.
+func FixtureOverrideFile(path string, contents []byte) FixturePreparer {
+	return FixtureModifyMockFS(func(fs MockFS) {
+		fs[path] = contents
+	})
+}
+
+// Override a text file in the mock filesystem
+//
+// If the file does not exist this behaves as FixtureAddTextFile.
+func FixtureOverrideTextFile(path string, contents string) FixturePreparer {
+	return FixtureOverrideFile(path, []byte(contents))
+}
+
 // Add the root Android.bp file with the supplied contents.
 func FixtureWithRootAndroidBp(contents string) FixturePreparer {
 	return FixtureAddTextFile("Android.bp", contents)
@@ -422,16 +467,16 @@
 	// The supplied result can be used to access the state of the code under test just as the main
 	// body of the test would but if any errors other than ones expected are reported the state may
 	// be indeterminate.
-	CheckErrors(result *TestResult)
+	CheckErrors(t *testing.T, result *TestResult)
 }
 
 type simpleErrorHandler struct {
-	function func(result *TestResult)
+	function func(t *testing.T, result *TestResult)
 }
 
-func (h simpleErrorHandler) CheckErrors(result *TestResult) {
-	result.Helper()
-	h.function(result)
+func (h simpleErrorHandler) CheckErrors(t *testing.T, result *TestResult) {
+	t.Helper()
+	h.function(t, result)
 }
 
 // The default fixture error handler.
@@ -441,9 +486,9 @@
 // If the test fails this handler will call `result.FailNow()` which will exit the goroutine within
 // which the test is being run which means that the RunTest() method will not return.
 var FixtureExpectsNoErrors = FixtureCustomErrorHandler(
-	func(result *TestResult) {
-		result.Helper()
-		FailIfErrored(result.T, result.Errs)
+	func(t *testing.T, result *TestResult) {
+		t.Helper()
+		FailIfErrored(t, result.Errs)
 	},
 )
 
@@ -460,10 +505,10 @@
 // If the test fails this handler will call `result.FailNow()` which will exit the goroutine within
 // which the test is being run which means that the RunTest() method will not return.
 func FixtureExpectsAtLeastOneErrorMatchingPattern(pattern string) FixtureErrorHandler {
-	return FixtureCustomErrorHandler(func(result *TestResult) {
-		result.Helper()
-		if !FailIfNoMatchingErrors(result.T, pattern, result.Errs) {
-			result.FailNow()
+	return FixtureCustomErrorHandler(func(t *testing.T, result *TestResult) {
+		t.Helper()
+		if !FailIfNoMatchingErrors(t, pattern, result.Errs) {
+			t.FailNow()
 		}
 	})
 }
@@ -482,14 +527,14 @@
 // If the test fails this handler will call `result.FailNow()` which will exit the goroutine within
 // which the test is being run which means that the RunTest() method will not return.
 func FixtureExpectsAllErrorsToMatchAPattern(patterns []string) FixtureErrorHandler {
-	return FixtureCustomErrorHandler(func(result *TestResult) {
-		result.Helper()
-		CheckErrorsAgainstExpectations(result.T, result.Errs, patterns)
+	return FixtureCustomErrorHandler(func(t *testing.T, result *TestResult) {
+		t.Helper()
+		CheckErrorsAgainstExpectations(t, result.Errs, patterns)
 	})
 }
 
 // FixtureCustomErrorHandler creates a custom error handler
-func FixtureCustomErrorHandler(function func(result *TestResult)) FixtureErrorHandler {
+func FixtureCustomErrorHandler(function func(t *testing.T, result *TestResult)) FixtureErrorHandler {
 	return simpleErrorHandler{
 		function: function,
 	}
@@ -501,83 +546,6 @@
 	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
@@ -585,7 +553,6 @@
 
 // The result of running a test.
 type TestResult struct {
-	TestHelper
 	testContext
 
 	fixture *fixture
@@ -658,6 +625,28 @@
 	return f.RunTest(t, FixtureWithRootAndroidBp(bp))
 }
 
+func (f *fixtureFactory) RunTestWithConfig(t *testing.T, config Config) *TestResult {
+	t.Helper()
+	// Create the fixture as normal.
+	fixture := f.Fixture(t).(*fixture)
+
+	// Discard the mock filesystem as otherwise that will override the one in the config.
+	fixture.mockFS = nil
+
+	// Replace the config with the supplied one in the fixture.
+	fixture.config = config
+
+	// Ditto with config derived information in the TestContext.
+	ctx := fixture.ctx
+	ctx.config = config
+	ctx.SetFs(ctx.config.fs)
+	if ctx.config.mockBpList != "" {
+		ctx.SetModuleListFile(ctx.config.mockBpList)
+	}
+
+	return fixture.RunTest()
+}
+
 type fixture struct {
 	// The factory used to create this fixture.
 	factory *fixtureFactory
@@ -683,17 +672,22 @@
 
 	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)
+	// Do not use the fixture's mockFS to initialize the config's mock file system if it has been
+	// cleared by RunTestWithConfig.
+	if f.mockFS != nil {
+		// 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()
@@ -703,14 +697,13 @@
 	}
 
 	result := &TestResult{
-		TestHelper:  TestHelper{T: f.t},
 		testContext: testContext{ctx},
 		fixture:     f,
 		Config:      f.config,
 		Errs:        errs,
 	}
 
-	f.errorHandler.CheckErrors(result)
+	f.errorHandler.CheckErrors(f.t, result)
 
 	return result
 }
@@ -746,36 +739,7 @@
 	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
index 5a7bf3b..a31ef16 100644
--- a/android/fixture_test.go
+++ b/android/fixture_test.go
@@ -43,7 +43,6 @@
 
 	extension.Fixture(t, preparer1, preparer2, preparer2Then1, preparer3)
 
-	h := TestHelper{t}
-	h.AssertDeepEquals("preparers called in wrong order",
+	AssertDeepEquals(t, "preparers called in wrong order",
 		[]string{"preparer1", "preparer2", "preparer4", "preparer3"}, list)
 }
diff --git a/android/license_kind_test.go b/android/license_kind_test.go
index 767b64e..83e83ce 100644
--- a/android/license_kind_test.go
+++ b/android/license_kind_test.go
@@ -8,7 +8,7 @@
 
 var licenseKindTests = []struct {
 	name           string
-	fs             map[string][]byte
+	fs             MockFS
 	expectedErrors []string
 }{
 	{
@@ -97,48 +97,18 @@
 func TestLicenseKind(t *testing.T) {
 	for _, test := range licenseKindTests {
 		t.Run(test.name, func(t *testing.T) {
-			_, errs := testLicenseKind(test.fs)
-
-			expectedErrors := test.expectedErrors
-			if expectedErrors == nil {
-				FailIfErrored(t, errs)
-			} else {
-				for _, expectedError := range expectedErrors {
-					FailIfNoMatchingErrors(t, expectedError, errs)
-				}
-				if len(errs) > len(expectedErrors) {
-					t.Errorf("additional errors found, expected %d, found %d", len(expectedErrors), len(errs))
-					for i, expectedError := range expectedErrors {
-						t.Errorf("expectedErrors[%d] = %s", i, expectedError)
-					}
-					for i, err := range errs {
-						t.Errorf("errs[%d] = %s", i, err)
-					}
-				}
-			}
+			licenseTestFixtureFactory.
+				Extend(
+					FixtureRegisterWithContext(func(ctx RegistrationContext) {
+						ctx.RegisterModuleType("mock_license", newMockLicenseModule)
+					}),
+					test.fs.AddToFixture(),
+				).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
+				RunTest(t)
 		})
 	}
 }
 
-func testLicenseKind(fs map[string][]byte) (*TestContext, []error) {
-
-	// Create a new config per test as license_kind information is stored in the config.
-	config := TestArchConfig(buildDir, nil, "", fs)
-
-	ctx := NewTestArchContext(config)
-	RegisterLicenseKindBuildComponents(ctx)
-	ctx.RegisterModuleType("mock_license", newMockLicenseModule)
-	ctx.Register()
-
-	_, errs := ctx.ParseBlueprintsFiles(".")
-	if len(errs) > 0 {
-		return ctx, errs
-	}
-
-	_, errs = ctx.PrepareBuildActions(config)
-	return ctx, errs
-}
-
 type mockLicenseProperties struct {
 	License_kinds []string
 }
diff --git a/android/license_test.go b/android/license_test.go
index 9f68713..a564827 100644
--- a/android/license_test.go
+++ b/android/license_test.go
@@ -4,9 +4,24 @@
 	"testing"
 )
 
+// Common test set up for license tests.
+var licenseTestFixtureFactory = emptyTestFixtureFactory.Extend(
+	// General preparers in alphabetical order.
+	PrepareForTestWithDefaults,
+	prepareForTestWithLicenses,
+	PrepareForTestWithOverrides,
+	PrepareForTestWithPackageModule,
+	PrepareForTestWithPrebuilts,
+	PrepareForTestWithVisibility,
+
+	// Additional test specific stuff
+	prepareForTestWithFakePrebuiltModules,
+	FixtureMergeEnv(map[string]string{"ANDROID_REQUIRE_LICENSES": "1"}),
+)
+
 var licenseTests = []struct {
 	name           string
-	fs             map[string][]byte
+	fs             MockFS
 	expectedErrors []string
 }{
 	{
@@ -163,58 +178,20 @@
 func TestLicense(t *testing.T) {
 	for _, test := range licenseTests {
 		t.Run(test.name, func(t *testing.T) {
-			_, errs := testLicense(test.fs)
-
-			expectedErrors := test.expectedErrors
-			if expectedErrors == nil {
-				FailIfErrored(t, errs)
-			} else {
-				for _, expectedError := range expectedErrors {
-					FailIfNoMatchingErrors(t, expectedError, errs)
-				}
-				if len(errs) > len(expectedErrors) {
-					t.Errorf("additional errors found, expected %d, found %d", len(expectedErrors), len(errs))
-					for i, expectedError := range expectedErrors {
-						t.Errorf("expectedErrors[%d] = %s", i, expectedError)
-					}
-					for i, err := range errs {
-						t.Errorf("errs[%d] = %s", i, err)
-					}
-				}
-			}
+			// Customize the common license text fixture factory.
+			licenseTestFixtureFactory.Extend(
+				FixtureRegisterWithContext(func(ctx RegistrationContext) {
+					ctx.RegisterModuleType("rule", newMockRuleModule)
+				}),
+				test.fs.AddToFixture(),
+			).
+				ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
+				RunTest(t)
 		})
 	}
 }
 
-func testLicense(fs map[string][]byte) (*TestContext, []error) {
-
-	// Create a new config per test as visibility information is stored in the config.
-	env := make(map[string]string)
-	env["ANDROID_REQUIRE_LICENSES"] = "1"
-	config := TestArchConfig(buildDir, env, "", fs)
-
-	ctx := NewTestArchContext(config)
-	RegisterPackageBuildComponents(ctx)
-	registerTestPrebuiltBuildComponents(ctx)
-	RegisterLicenseKindBuildComponents(ctx)
-	RegisterLicenseBuildComponents(ctx)
-	ctx.RegisterModuleType("rule", newMockRuleModule)
-	ctx.PreArchMutators(RegisterVisibilityRuleChecker)
-	ctx.PreArchMutators(RegisterLicensesPackageMapper)
-	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
-	ctx.PreArchMutators(RegisterLicensesPropertyGatherer)
-	ctx.PreArchMutators(RegisterVisibilityRuleGatherer)
-	ctx.PostDepsMutators(RegisterVisibilityRuleEnforcer)
-	ctx.PostDepsMutators(RegisterLicensesDependencyChecker)
-	ctx.Register()
-
-	_, errs := ctx.ParseBlueprintsFiles(".")
-	if len(errs) > 0 {
-		return ctx, errs
-	}
-
-	_, errs = ctx.PrepareBuildActions(config)
-	return ctx, errs
+func testLicense(t *testing.T, fs MockFS, expectedErrors []string) {
 }
 
 type mockRuleModule struct {
diff --git a/android/licenses_test.go b/android/licenses_test.go
index c043791..a581932 100644
--- a/android/licenses_test.go
+++ b/android/licenses_test.go
@@ -6,9 +6,21 @@
 	"github.com/google/blueprint"
 )
 
+var prepareForTestWithLicenses = GroupFixturePreparers(
+	FixtureRegisterWithContext(RegisterLicenseKindBuildComponents),
+	FixtureRegisterWithContext(RegisterLicenseBuildComponents),
+	FixtureRegisterWithContext(registerLicenseMutators),
+)
+
+func registerLicenseMutators(ctx RegistrationContext) {
+	ctx.PreArchMutators(RegisterLicensesPackageMapper)
+	ctx.PreArchMutators(RegisterLicensesPropertyGatherer)
+	ctx.PostDepsMutators(RegisterLicensesDependencyChecker)
+}
+
 var licensesTests = []struct {
 	name                       string
-	fs                         map[string][]byte
+	fs                         MockFS
 	expectedErrors             []string
 	effectiveLicenses          map[string][]string
 	effectiveInheritedLicenses map[string][]string
@@ -457,40 +469,48 @@
 func TestLicenses(t *testing.T) {
 	for _, test := range licensesTests {
 		t.Run(test.name, func(t *testing.T) {
-			ctx, errs := testLicenses(buildDir, test.fs)
-
-			CheckErrorsAgainstExpectations(t, errs, test.expectedErrors)
+			// Customize the common license text fixture factory.
+			result := licenseTestFixtureFactory.Extend(
+				FixtureRegisterWithContext(func(ctx RegistrationContext) {
+					ctx.RegisterModuleType("mock_bad_module", newMockLicensesBadModule)
+					ctx.RegisterModuleType("mock_library", newMockLicensesLibraryModule)
+					ctx.RegisterModuleType("mock_defaults", defaultsLicensesFactory)
+				}),
+				test.fs.AddToFixture(),
+			).
+				ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
+				RunTest(t)
 
 			if test.effectiveLicenses != nil {
-				checkEffectiveLicenses(t, ctx, test.effectiveLicenses)
+				checkEffectiveLicenses(t, result, test.effectiveLicenses)
 			}
 
 			if test.effectivePackage != nil {
-				checkEffectivePackage(t, ctx, test.effectivePackage)
+				checkEffectivePackage(t, result, test.effectivePackage)
 			}
 
 			if test.effectiveNotices != nil {
-				checkEffectiveNotices(t, ctx, test.effectiveNotices)
+				checkEffectiveNotices(t, result, test.effectiveNotices)
 			}
 
 			if test.effectiveKinds != nil {
-				checkEffectiveKinds(t, ctx, test.effectiveKinds)
+				checkEffectiveKinds(t, result, test.effectiveKinds)
 			}
 
 			if test.effectiveConditions != nil {
-				checkEffectiveConditions(t, ctx, test.effectiveConditions)
+				checkEffectiveConditions(t, result, test.effectiveConditions)
 			}
 
 			if test.effectiveInheritedLicenses != nil {
-				checkEffectiveInheritedLicenses(t, ctx, test.effectiveInheritedLicenses)
+				checkEffectiveInheritedLicenses(t, result, test.effectiveInheritedLicenses)
 			}
 		})
 	}
 }
 
-func checkEffectiveLicenses(t *testing.T, ctx *TestContext, effectiveLicenses map[string][]string) {
+func checkEffectiveLicenses(t *testing.T, result *TestResult, effectiveLicenses map[string][]string) {
 	actualLicenses := make(map[string][]string)
-	ctx.Context.Context.VisitAllModules(func(m blueprint.Module) {
+	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
 		if _, ok := m.(*licenseModule); ok {
 			return
 		}
@@ -523,9 +543,9 @@
 	}
 }
 
-func checkEffectiveInheritedLicenses(t *testing.T, ctx *TestContext, effectiveInheritedLicenses map[string][]string) {
+func checkEffectiveInheritedLicenses(t *testing.T, result *TestResult, effectiveInheritedLicenses map[string][]string) {
 	actualLicenses := make(map[string][]string)
-	ctx.Context.Context.VisitAllModules(func(m blueprint.Module) {
+	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
 		if _, ok := m.(*licenseModule); ok {
 			return
 		}
@@ -548,7 +568,7 @@
 		for _, l := range base.commonProperties.Effective_licenses {
 			inherited[l] = true
 		}
-		ctx.Context.Context.VisitDepsDepthFirst(m, func(c blueprint.Module) {
+		result.Context.Context.VisitDepsDepthFirst(m, func(c blueprint.Module) {
 			if _, ok := c.(*licenseModule); ok {
 				return
 			}
@@ -588,9 +608,9 @@
 	}
 }
 
-func checkEffectivePackage(t *testing.T, ctx *TestContext, effectivePackage map[string]string) {
+func checkEffectivePackage(t *testing.T, result *TestResult, effectivePackage map[string]string) {
 	actualPackage := make(map[string]string)
-	ctx.Context.Context.VisitAllModules(func(m blueprint.Module) {
+	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
 		if _, ok := m.(*licenseModule); ok {
 			return
 		}
@@ -628,9 +648,9 @@
 	}
 }
 
-func checkEffectiveNotices(t *testing.T, ctx *TestContext, effectiveNotices map[string][]string) {
+func checkEffectiveNotices(t *testing.T, result *TestResult, effectiveNotices map[string][]string) {
 	actualNotices := make(map[string][]string)
-	ctx.Context.Context.VisitAllModules(func(m blueprint.Module) {
+	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
 		if _, ok := m.(*licenseModule); ok {
 			return
 		}
@@ -663,9 +683,9 @@
 	}
 }
 
-func checkEffectiveKinds(t *testing.T, ctx *TestContext, effectiveKinds map[string][]string) {
+func checkEffectiveKinds(t *testing.T, result *TestResult, effectiveKinds map[string][]string) {
 	actualKinds := make(map[string][]string)
-	ctx.Context.Context.VisitAllModules(func(m blueprint.Module) {
+	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
 		if _, ok := m.(*licenseModule); ok {
 			return
 		}
@@ -698,9 +718,9 @@
 	}
 }
 
-func checkEffectiveConditions(t *testing.T, ctx *TestContext, effectiveConditions map[string][]string) {
+func checkEffectiveConditions(t *testing.T, result *TestResult, effectiveConditions map[string][]string) {
 	actualConditions := make(map[string][]string)
-	ctx.Context.Context.VisitAllModules(func(m blueprint.Module) {
+	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
 		if _, ok := m.(*licenseModule); ok {
 			return
 		}
@@ -754,41 +774,6 @@
 	return true
 }
 
-func testLicenses(buildDir string, fs map[string][]byte) (*TestContext, []error) {
-
-	// Create a new config per test as licenses information is stored in the config.
-	env := make(map[string]string)
-	env["ANDROID_REQUIRE_LICENSES"] = "1"
-	config := TestArchConfig(buildDir, env, "", fs)
-
-	ctx := NewTestArchContext(config)
-	ctx.RegisterModuleType("mock_bad_module", newMockLicensesBadModule)
-	ctx.RegisterModuleType("mock_library", newMockLicensesLibraryModule)
-	ctx.RegisterModuleType("mock_defaults", defaultsLicensesFactory)
-
-	// Order of the following method calls is significant.
-	RegisterPackageBuildComponents(ctx)
-	registerTestPrebuiltBuildComponents(ctx)
-	RegisterLicenseKindBuildComponents(ctx)
-	RegisterLicenseBuildComponents(ctx)
-	ctx.PreArchMutators(RegisterVisibilityRuleChecker)
-	ctx.PreArchMutators(RegisterLicensesPackageMapper)
-	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
-	ctx.PreArchMutators(RegisterLicensesPropertyGatherer)
-	ctx.PreArchMutators(RegisterVisibilityRuleGatherer)
-	ctx.PostDepsMutators(RegisterVisibilityRuleEnforcer)
-	ctx.PostDepsMutators(RegisterLicensesDependencyChecker)
-	ctx.Register()
-
-	_, errs := ctx.ParseBlueprintsFiles(".")
-	if len(errs) > 0 {
-		return ctx, errs
-	}
-
-	_, errs = ctx.PrepareBuildActions(config)
-	return ctx, errs
-}
-
 type mockLicensesBadProperties struct {
 	Visibility []string
 }
diff --git a/android/path_properties.go b/android/path_properties.go
index 853e5a9..2c8d27c 100644
--- a/android/path_properties.go
+++ b/android/path_properties.go
@@ -118,6 +118,13 @@
 				*values = append(*values, v.Index(i).Field(index[0]))
 			}
 		} else {
+			// Dereference it if it's a pointer.
+			if v.Kind() == reflect.Ptr {
+				if v.IsNil() {
+					return
+				}
+				v = v.Elem()
+			}
 			*values = append(*values, v.Field(index[0]))
 		}
 		return
diff --git a/android/path_properties_test.go b/android/path_properties_test.go
index 2aab748..85c96ee 100644
--- a/android/path_properties_test.go
+++ b/android/path_properties_test.go
@@ -26,6 +26,9 @@
 		Bar []string `android:"path,arch_variant"`
 		Baz *string  `android:"path"`
 		Qux string
+		V   *struct {
+			W string `android:"path"`
+		}
 	}
 
 	// A second property struct with a duplicate property name
@@ -94,8 +97,11 @@
 						],
 					},
 				],
+				v: {
+					w: ":w",
+				},
 			}`,
-			deps: []string{"a", "b", "c", "x", "y", "z"},
+			deps: []string{"a", "b", "c", "w", "x", "y", "z"},
 		},
 		{
 			name: "arch variant",
@@ -136,6 +142,10 @@
 				}
 
 				filegroup {
+					name: "w",
+				}
+
+				filegroup {
 					name: "x",
 				}
 
diff --git a/android/test_asserts.go b/android/test_asserts.go
new file mode 100644
index 0000000..d0de523
--- /dev/null
+++ b/android/test_asserts.go
@@ -0,0 +1,134 @@
+// 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"
+)
+
+// This file contains general purpose test assert functions.
+
+// 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 AssertBoolEquals(t *testing.T, message string, expected bool, actual bool) {
+	t.Helper()
+	if actual != expected {
+		t.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 AssertStringEquals(t *testing.T, message string, expected string, actual string) {
+	t.Helper()
+	if actual != expected {
+		t.Errorf("%s: expected %s, actual %s", message, expected, actual)
+	}
+}
+
+// AssertErrorMessageEquals checks if the error is not nil and has the expected message. If it does
+// not then this reports an error prefixed with the supplied message and including a reason for why
+// it failed.
+func AssertErrorMessageEquals(t *testing.T, message string, expected string, actual error) {
+	t.Helper()
+	if actual == nil {
+		t.Errorf("Expected error but was nil")
+	} else if actual.Error() != expected {
+		t.Errorf("%s: expected %s, actual %s", message, expected, actual.Error())
+	}
+}
+
+// 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 AssertTrimmedStringEquals(t *testing.T, message string, expected string, actual string) {
+	t.Helper()
+	AssertStringEquals(t, 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 AssertStringDoesContain(t *testing.T, message string, s string, expectedSubstring string) {
+	t.Helper()
+	if !strings.Contains(s, expectedSubstring) {
+		t.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 AssertStringDoesNotContain(t *testing.T, message string, s string, unexpectedSubstring string) {
+	t.Helper()
+	if strings.Contains(s, unexpectedSubstring) {
+		t.Errorf("%s: unexpectedly found %q within %q", message, unexpectedSubstring, s)
+	}
+}
+
+// AssertStringListContains checks if the list of strings contains the expected string. If it does
+// not then it reports an error prefixed with the supplied message and including a reason for why it
+// failed.
+func AssertStringListContains(t *testing.T, message string, list []string, expected string) {
+	t.Helper()
+	if !InList(expected, list) {
+		t.Errorf("%s: could not find %q within %q", message, expected, list)
+	}
+}
+
+// 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 AssertArrayString(t *testing.T, message string, expected, actual []string) {
+	t.Helper()
+	if len(actual) != len(expected) {
+		t.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] {
+			t.Errorf("%s: expected %d-th, %q (%q), actual %q (%q)",
+				message, i, expected[i], expected, actual[i], actual)
+			return
+		}
+	}
+}
+
+// AssertDeepEquals 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 AssertDeepEquals(t *testing.T, message string, expected interface{}, actual interface{}) {
+	t.Helper()
+	if !reflect.DeepEqual(actual, expected) {
+		t.Errorf("%s: expected:\n  %#v\n got:\n  %#v", message, expected, actual)
+	}
+}
+
+// AssertPanic checks that the supplied function panics as expected.
+func AssertPanic(t *testing.T, message string, funcThatShouldPanic func()) {
+	t.Helper()
+	panicked := false
+	func() {
+		defer func() {
+			if x := recover(); x != nil {
+				panicked = true
+			}
+		}()
+		funcThatShouldPanic()
+	}()
+	if !panicked {
+		t.Error(message)
+	}
+}
diff --git a/android/testing.go b/android/testing.go
index 03d7bd5..dd3d607 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -24,6 +24,7 @@
 	"testing"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 func NewTestContext(config Config) *TestContext {
@@ -105,6 +106,17 @@
 	PrepareForTestWithAndroidBuildComponents,
 )
 
+// Prepares a test that may be missing dependencies by setting allow_missing_dependencies to
+// true.
+var PrepareForTestWithAllowMissingDependencies = GroupFixturePreparers(
+	FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+		variables.Allow_missing_dependencies = proptools.BoolPtr(true)
+	}),
+	FixtureModifyContext(func(ctx *TestContext) {
+		ctx.SetAllowMissingDependencies(true)
+	}),
+)
+
 func NewTestArchContext(config Config) *TestContext {
 	ctx := NewTestContext(config)
 	ctx.preDeps = append(ctx.preDeps, registerArchMutator)
diff --git a/android/variable.go b/android/variable.go
index dd000ad..be12a0a 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -24,11 +24,17 @@
 )
 
 func init() {
-	PreDepsMutators(func(ctx RegisterMutatorsContext) {
+	registerVariableBuildComponents(InitRegistrationContext)
+}
+
+func registerVariableBuildComponents(ctx RegistrationContext) {
+	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
 		ctx.BottomUp("variable", VariableMutator).Parallel()
 	})
 }
 
+var PrepareForTestWithVariables = FixtureRegisterWithContext(registerVariableBuildComponents)
+
 type variableProperties struct {
 	Product_variables struct {
 		Platform_sdk_version struct {
diff --git a/android/visibility_test.go b/android/visibility_test.go
index eb4071e..fdf18ce 100644
--- a/android/visibility_test.go
+++ b/android/visibility_test.go
@@ -1166,17 +1166,17 @@
 				RunTest(t)
 
 			if test.effectiveVisibility != nil {
-				checkEffectiveVisibility(result, test.effectiveVisibility)
+				checkEffectiveVisibility(t, result, test.effectiveVisibility)
 			}
 		})
 	}
 }
 
-func checkEffectiveVisibility(result *TestResult, effectiveVisibility map[qualifiedModuleName][]string) {
+func checkEffectiveVisibility(t *testing.T, result *TestResult, effectiveVisibility map[qualifiedModuleName][]string) {
 	for moduleName, expectedRules := range effectiveVisibility {
 		rule := effectiveVisibilityRules(result.Config, moduleName)
 		stringRules := rule.Strings()
-		result.AssertDeepEquals("effective rules mismatch", expectedRules, stringRules)
+		AssertDeepEquals(t, "effective rules mismatch", expectedRules, stringRules)
 	}
 }
 
diff --git a/android/writedocs.go b/android/writedocs.go
index 6cb2f10..67b9aa3 100644
--- a/android/writedocs.go
+++ b/android/writedocs.go
@@ -35,10 +35,11 @@
 
 func primaryBuilderPath(ctx SingletonContext) Path {
 	buildDir := absolutePath(ctx.Config().BuildDir())
-	primaryBuilder, err := filepath.Rel(buildDir, os.Args[0])
+	binary := absolutePath(os.Args[0])
+	primaryBuilder, err := filepath.Rel(buildDir, binary)
 	if err != nil {
-		ctx.Errorf("path to primary builder %q is not in build dir %q",
-			os.Args[0], ctx.Config().BuildDir())
+		ctx.Errorf("path to primary builder %q is not in build dir %q (%q)",
+			os.Args[0], ctx.Config().BuildDir(), err)
 	}
 
 	return PathForOutput(ctx, primaryBuilder)
diff --git a/apex/Android.bp b/apex/Android.bp
index 8a2edeb..1890b89 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -25,6 +25,7 @@
         "deapexer.go",
         "key.go",
         "prebuilt.go",
+        "testing.go",
         "vndk.go",
     ],
     testSrcs: [
diff --git a/apex/allowed_deps.txt b/apex/allowed_deps.txt
index faa6569..154b9aa 100644
--- a/apex/allowed_deps.txt
+++ b/apex/allowed_deps.txt
@@ -168,7 +168,9 @@
 derive_sdk(minSdkVersion:current)
 derive_sdk_prefer32(minSdkVersion:30)
 derive_sdk_prefer32(minSdkVersion:current)
+dnsresolver_aidl_interface-lateststable-ndk_platform(minSdkVersion:29)
 dnsresolver_aidl_interface-unstable-ndk_platform(minSdkVersion:29)
+dnsresolver_aidl_interface-V7-ndk_platform(minSdkVersion:29)
 dnsresolver_aidl_interface-V8-ndk_platform(minSdkVersion:29)
 DocumentsUI-res-lib(minSdkVersion:29)
 exoplayer2-extractor(minSdkVersion:16)
@@ -406,6 +408,7 @@
 libring(minSdkVersion:29)
 libring-core(minSdkVersion:29)
 librustc_demangle.rust_sysroot(minSdkVersion:29)
+libruy_static(minSdkVersion:30)
 libsdk_proto(minSdkVersion:30)
 libsfplugin_ccodec_utils(minSdkVersion:29)
 libsonivoxwithoutjet(minSdkVersion:29)
@@ -504,8 +507,10 @@
 netd_aidl_interface-V5-java(minSdkVersion:29)
 netd_aidl_interface-V6-java(minSdkVersion:29)
 netd_event_listener_interface-java(minSdkVersion:29)
+netd_event_listener_interface-lateststable-ndk_platform(minSdkVersion:29)
 netd_event_listener_interface-ndk_platform(minSdkVersion:29)
 netd_event_listener_interface-unstable-ndk_platform(minSdkVersion:29)
+netd_event_listener_interface-V1-ndk_platform(minSdkVersion:29)
 netd_event_listener_interface-V2-ndk_platform(minSdkVersion:29)
 netlink-client(minSdkVersion:29)
 networkstack-aidl-interfaces-unstable-java(minSdkVersion:29)
diff --git a/apex/apex.go b/apex/apex.go
index efd1736..e5b5c92 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -39,16 +39,20 @@
 )
 
 func init() {
-	android.RegisterModuleType("apex", BundleFactory)
-	android.RegisterModuleType("apex_test", testApexBundleFactory)
-	android.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
-	android.RegisterModuleType("apex_defaults", defaultsFactory)
-	android.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
-	android.RegisterModuleType("override_apex", overrideApexFactory)
-	android.RegisterModuleType("apex_set", apexSetFactory)
+	registerApexBuildComponents(android.InitRegistrationContext)
+}
 
-	android.PreDepsMutators(RegisterPreDepsMutators)
-	android.PostDepsMutators(RegisterPostDepsMutators)
+func registerApexBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("apex", BundleFactory)
+	ctx.RegisterModuleType("apex_test", testApexBundleFactory)
+	ctx.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
+	ctx.RegisterModuleType("apex_defaults", defaultsFactory)
+	ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
+	ctx.RegisterModuleType("override_apex", overrideApexFactory)
+	ctx.RegisterModuleType("apex_set", apexSetFactory)
+
+	ctx.PreDepsMutators(RegisterPreDepsMutators)
+	ctx.PostDepsMutators(RegisterPostDepsMutators)
 }
 
 func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
@@ -970,6 +974,10 @@
 	// If any of the dep is not available to platform, this module is also considered as being
 	// not available to platform even if it has "//apex_available:platform"
 	mctx.VisitDirectDeps(func(child android.Module) {
+		depTag := mctx.OtherModuleDependencyTag(child)
+		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
+			return
+		}
 		if !am.DepIsInSameApex(mctx, child) {
 			// if the dependency crosses apex boundary, don't consider it
 			return
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 3e5ba7f..3fde144 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -127,6 +127,94 @@
 	config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
 }
 
+var apexFixtureFactory = android.NewFixtureFactory(
+	&buildDir,
+	// General preparers in alphabetical order as test infrastructure will enforce correct
+	// registration order.
+	android.PrepareForTestWithAndroidBuildComponents,
+	bpf.PrepareForTestWithBpf,
+	cc.PrepareForTestWithCcBuildComponents,
+	java.PrepareForTestWithJavaDefaultModules,
+	prebuilt_etc.PrepareForTestWithPrebuiltEtc,
+	rust.PrepareForTestWithRustDefaultModules,
+	sh.PrepareForTestWithShBuildComponents,
+
+	PrepareForTestWithApexBuildComponents,
+
+	// Additional apex test specific preparers.
+	android.FixtureAddTextFile("system/sepolicy/Android.bp", `
+		filegroup {
+			name: "myapex-file_contexts",
+			srcs: [
+				"apex/myapex-file_contexts",
+			],
+		}
+	`),
+	android.FixtureMergeMockFs(android.MockFS{
+		"a.java":                                                      nil,
+		"PrebuiltAppFoo.apk":                                          nil,
+		"PrebuiltAppFooPriv.apk":                                      nil,
+		"build/make/target/product/security":                          nil,
+		"apex_manifest.json":                                          nil,
+		"AndroidManifest.xml":                                         nil,
+		"system/sepolicy/apex/myapex-file_contexts":                   nil,
+		"system/sepolicy/apex/myapex.updatable-file_contexts":         nil,
+		"system/sepolicy/apex/myapex2-file_contexts":                  nil,
+		"system/sepolicy/apex/otherapex-file_contexts":                nil,
+		"system/sepolicy/apex/com.android.vndk-file_contexts":         nil,
+		"system/sepolicy/apex/com.android.vndk.current-file_contexts": nil,
+		"mylib.cpp":                                  nil,
+		"mytest.cpp":                                 nil,
+		"mytest1.cpp":                                nil,
+		"mytest2.cpp":                                nil,
+		"mytest3.cpp":                                nil,
+		"myprebuilt":                                 nil,
+		"my_include":                                 nil,
+		"foo/bar/MyClass.java":                       nil,
+		"prebuilt.jar":                               nil,
+		"prebuilt.so":                                nil,
+		"vendor/foo/devkeys/test.x509.pem":           nil,
+		"vendor/foo/devkeys/test.pk8":                nil,
+		"testkey.x509.pem":                           nil,
+		"testkey.pk8":                                nil,
+		"testkey.override.x509.pem":                  nil,
+		"testkey.override.pk8":                       nil,
+		"vendor/foo/devkeys/testkey.avbpubkey":       nil,
+		"vendor/foo/devkeys/testkey.pem":             nil,
+		"NOTICE":                                     nil,
+		"custom_notice":                              nil,
+		"custom_notice_for_static_lib":               nil,
+		"testkey2.avbpubkey":                         nil,
+		"testkey2.pem":                               nil,
+		"myapex-arm64.apex":                          nil,
+		"myapex-arm.apex":                            nil,
+		"myapex.apks":                                nil,
+		"frameworks/base/api/current.txt":            nil,
+		"framework/aidl/a.aidl":                      nil,
+		"build/make/core/proguard.flags":             nil,
+		"build/make/core/proguard_basic_keeps.flags": nil,
+		"dummy.txt":                                  nil,
+		"baz":                                        nil,
+		"bar/baz":                                    nil,
+		"testdata/baz":                               nil,
+		"AppSet.apks":                                nil,
+		"foo.rs":                                     nil,
+		"libfoo.jar":                                 nil,
+		"libbar.jar":                                 nil,
+	},
+	),
+
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.DeviceVndkVersion = proptools.StringPtr("current")
+		variables.DefaultAppCertificate = proptools.StringPtr("vendor/foo/devkeys/test")
+		variables.CertificateOverrides = []string{"myapex_keytest:myapex.certificate.override"}
+		variables.Platform_sdk_codename = proptools.StringPtr("Q")
+		variables.Platform_sdk_final = proptools.BoolPtr(false)
+		variables.Platform_version_active_codenames = []string{"Q"}
+		variables.Platform_vndk_version = proptools.StringPtr("VER")
+	}),
+)
+
 func testApexContext(_ *testing.T, bp string, handlers ...testCustomizer) (*android.TestContext, android.Config) {
 	bp = bp + `
 		filegroup {
@@ -229,14 +317,8 @@
 	android.RegisterPackageBuildComponents(ctx)
 	ctx.PreArchMutators(android.RegisterVisibilityRuleChecker)
 
-	ctx.RegisterModuleType("apex", BundleFactory)
-	ctx.RegisterModuleType("apex_test", testApexBundleFactory)
-	ctx.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
-	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
-	ctx.RegisterModuleType("apex_defaults", defaultsFactory)
-	ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
-	ctx.RegisterModuleType("override_apex", overrideApexFactory)
-	ctx.RegisterModuleType("apex_set", apexSetFactory)
+	registerApexBuildComponents(ctx)
+	registerApexKeyBuildComponents(ctx)
 
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	ctx.PreArchMutators(android.RegisterComponentsMutator)
@@ -263,12 +345,8 @@
 	ctx.RegisterModuleType("platform_compat_config", java.PlatformCompatConfigFactory)
 	ctx.RegisterModuleType("sh_binary", sh.ShBinaryFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.RegisterSingletonType("apex_keys_text", apexKeysTextFactory)
 	ctx.RegisterModuleType("bpf", bpf.BpfFactory)
 
-	ctx.PreDepsMutators(RegisterPreDepsMutators)
-	ctx.PostDepsMutators(RegisterPostDepsMutators)
-
 	ctx.Register()
 
 	return ctx, config
diff --git a/apex/key.go b/apex/key.go
index 752888d..8b33b59 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -27,8 +27,12 @@
 var String = proptools.String
 
 func init() {
-	android.RegisterModuleType("apex_key", ApexKeyFactory)
-	android.RegisterSingletonType("apex_keys_text", apexKeysTextFactory)
+	registerApexKeyBuildComponents(android.InitRegistrationContext)
+}
+
+func registerApexKeyBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
+	ctx.RegisterSingletonType("apex_keys_text", apexKeysTextFactory)
 }
 
 type apexKey struct {
diff --git a/apex/testing.go b/apex/testing.go
new file mode 100644
index 0000000..e662cad
--- /dev/null
+++ b/apex/testing.go
@@ -0,0 +1,22 @@
+// 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 apex
+
+import "android/soong/android"
+
+var PrepareForTestWithApexBuildComponents = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(registerApexBuildComponents),
+	android.FixtureRegisterWithContext(registerApexKeyBuildComponents),
+)
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 8deb5a2..99d706c 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -19,6 +19,7 @@
         "soong-bazel",
         "soong-cc",
         "soong-genrule",
+        "soong-python",
         "soong-sh",
     ],
     testSrcs: [
@@ -27,6 +28,7 @@
         "cc_library_headers_conversion_test.go",
         "cc_object_conversion_test.go",
         "conversion_test.go",
+        "python_binary_conversion_test.go",
         "sh_conversion_test.go",
         "testing.go",
     ],
diff --git a/bp2build/python_binary_conversion_test.go b/bp2build/python_binary_conversion_test.go
new file mode 100644
index 0000000..7600e36
--- /dev/null
+++ b/bp2build/python_binary_conversion_test.go
@@ -0,0 +1,170 @@
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/python"
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func TestPythonBinaryHost(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		blueprint                          string
+		expectedBazelTargets               []string
+		filesystem                         map[string]string
+	}{
+		{
+			description:                        "simple python_binary_host converts to a native py_binary",
+			moduleTypeUnderTest:                "python_binary_host",
+			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
+			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+			filesystem: map[string]string{
+				"a.py":           "",
+				"b/c.py":         "",
+				"b/d.py":         "",
+				"b/e.py":         "",
+				"files/data.txt": "",
+			},
+			blueprint: `python_binary_host {
+    name: "foo",
+    main: "a.py",
+    srcs: [
+        "**/*.py"
+    ],
+    exclude_srcs: [
+        "b/e.py"
+    ],
+    data: [
+        "files/data.txt",
+    ],
+
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{`py_binary(
+    name = "foo",
+    data = [
+        "files/data.txt",
+    ],
+    main = "a.py",
+    srcs = [
+        "a.py",
+        "b/c.py",
+        "b/d.py",
+    ],
+)`,
+			},
+		},
+		{
+			description:                        "py2 python_binary_host",
+			moduleTypeUnderTest:                "python_binary_host",
+			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
+			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+			blueprint: `python_binary_host {
+    name: "foo",
+    srcs: ["a.py"],
+    version: {
+        py2: {
+            enabled: true,
+        },
+        py3: {
+            enabled: false,
+        },
+    },
+
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{`py_binary(
+    name = "foo",
+    python_version = "PY2",
+    srcs = [
+        "a.py",
+    ],
+)`,
+			},
+		},
+		{
+			description:                        "py3 python_binary_host",
+			moduleTypeUnderTest:                "python_binary_host",
+			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
+			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+			blueprint: `python_binary_host {
+    name: "foo",
+    srcs: ["a.py"],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+        },
+    },
+
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				// python_version is PY3 by default.
+				`py_binary(
+    name = "foo",
+    srcs = [
+        "a.py",
+    ],
+)`,
+			},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		filesystem := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		for f, content := range testCase.filesystem {
+			if strings.HasSuffix(f, "Android.bp") {
+				toParse = append(toParse, f)
+			}
+			filesystem[f] = []byte(content)
+		}
+		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
+		ctx := android.NewTestContext(config)
+
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+		_, errs = ctx.ResolveDependencies(config)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+			fmt.Println(bazelTargets)
+			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
+		} else {
+			for i, target := range bazelTargets {
+				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+					t.Errorf(
+						"%s: Expected generated Bazel target to be '%s', got '%s'",
+						testCase.description,
+						w,
+						g,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/bpf/bpf.go b/bpf/bpf.go
index 8142f10..fa1a84d 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -26,7 +26,7 @@
 )
 
 func init() {
-	android.RegisterModuleType("bpf", BpfFactory)
+	registerBpfBuildComponents(android.InitRegistrationContext)
 	pctx.Import("android/soong/cc/config")
 }
 
@@ -43,6 +43,12 @@
 		"ccCmd", "cFlags")
 )
 
+func registerBpfBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("bpf", BpfFactory)
+}
+
+var PrepareForTestWithBpf = android.FixtureRegisterWithContext(registerBpfBuildComponents)
+
 // BpfModule interface is used by the apex package to gather information from a bpf module.
 type BpfModule interface {
 	android.Module
diff --git a/bpf/bpf_test.go b/bpf/bpf_test.go
index be9e36e..eb0d8c8 100644
--- a/bpf/bpf_test.go
+++ b/bpf/bpf_test.go
@@ -46,24 +46,20 @@
 	}
 
 	os.Exit(run())
+
 }
 
-func testConfig(buildDir string, env map[string]string, bp string) android.Config {
-	mockFS := map[string][]byte{
-		"bpf.c":       nil,
-		"BpfTest.cpp": nil,
-	}
-
-	return cc.TestConfig(buildDir, android.Android, env, bp, mockFS)
-}
-
-func testContext(config android.Config) *android.TestContext {
-	ctx := cc.CreateTestContext(config)
-	ctx.RegisterModuleType("bpf", BpfFactory)
-	ctx.Register()
-
-	return ctx
-}
+var bpfFactory = android.NewFixtureFactory(
+	&buildDir,
+	cc.PrepareForTestWithCcDefaultModules,
+	android.FixtureMergeMockFs(
+		map[string][]byte{
+			"bpf.c":       nil,
+			"BpfTest.cpp": nil,
+		},
+	),
+	PrepareForTestWithBpf,
+)
 
 func TestBpfDataDependency(t *testing.T) {
 	bp := `
@@ -80,16 +76,7 @@
 		}
 	`
 
-	config := testConfig(buildDir, nil, bp)
-	ctx := testContext(config)
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	if errs == nil {
-		_, errs = ctx.PrepareBuildActions(config)
-	}
-	if errs != nil {
-		t.Fatal(errs)
-	}
+	bpfFactory.RunTestWithBp(t, bp)
 
 	// We only verify the above BP configuration is processed successfully since the data property
 	// value is not available for testing from this package.
diff --git a/cc/cc_test.go b/cc/cc_test.go
index e640f12..16ae7ee 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -52,29 +52,49 @@
 	os.Exit(run())
 }
 
+var ccFixtureFactory = android.NewFixtureFactory(
+	&buildDir,
+	PrepareForTestWithCcIncludeVndk,
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.DeviceVndkVersion = StringPtr("current")
+		variables.ProductVndkVersion = StringPtr("current")
+		variables.Platform_vndk_version = StringPtr("VER")
+	}),
+)
+
+// testCcWithConfig runs tests using the ccFixtureFactory
+//
+// See testCc for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testCcWithConfig(t *testing.T, config android.Config) *android.TestContext {
 	t.Helper()
-	ctx := CreateTestContext(config)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-
-	return ctx
+	result := ccFixtureFactory.RunTestWithConfig(t, config)
+	return result.TestContext
 }
 
+// testCc runs tests using the ccFixtureFactory
+//
+// Do not add any new usages of this, instead use the ccFixtureFactory directly as it makes it much
+// easier to customize the test behavior.
+//
+// If it is necessary to customize the behavior of an existing test that uses this then please first
+// convert the test to using ccFixtureFactory first and then in a following change add the
+// appropriate fixture preparers. Keeping the conversion change separate makes it easy to verify
+// that it did not change the test behavior unexpectedly.
+//
+// deprecated
 func testCc(t *testing.T, bp string) *android.TestContext {
 	t.Helper()
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
-	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.ProductVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
-
-	return testCcWithConfig(t, config)
+	result := ccFixtureFactory.RunTestWithBp(t, bp)
+	return result.TestContext
 }
 
+// testCcNoVndk runs tests using the ccFixtureFactory
+//
+// See testCc for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testCcNoVndk(t *testing.T, bp string) *android.TestContext {
 	t.Helper()
 	config := TestConfig(buildDir, android.Android, nil, bp, nil)
@@ -83,6 +103,11 @@
 	return testCcWithConfig(t, config)
 }
 
+// testCcNoProductVndk runs tests using the ccFixtureFactory
+//
+// See testCc for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testCcNoProductVndk(t *testing.T, bp string) *android.TestContext {
 	t.Helper()
 	config := TestConfig(buildDir, android.Android, nil, bp, nil)
@@ -92,27 +117,24 @@
 	return testCcWithConfig(t, config)
 }
 
+// testCcErrorWithConfig runs tests using the ccFixtureFactory
+//
+// See testCc for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testCcErrorWithConfig(t *testing.T, pattern string, config android.Config) {
 	t.Helper()
 
-	ctx := CreateTestContext(config)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return
-	}
-
-	_, errs = ctx.PrepareBuildActions(config)
-	if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return
-	}
-
-	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
+	ccFixtureFactory.Extend().
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
+		RunTestWithConfig(t, config)
 }
 
+// testCcError runs tests using the ccFixtureFactory
+//
+// See testCc for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testCcError(t *testing.T, pattern string, bp string) {
 	t.Helper()
 	config := TestConfig(buildDir, android.Android, nil, bp, nil)
@@ -122,6 +144,11 @@
 	return
 }
 
+// testCcErrorProductVndk runs tests using the ccFixtureFactory
+//
+// See testCc for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testCcErrorProductVndk(t *testing.T, pattern string, bp string) {
 	t.Helper()
 	config := TestConfig(buildDir, android.Android, nil, bp, nil)
@@ -153,13 +180,12 @@
 			},
 		}`
 
-	config := TestConfig(buildDir, android.Fuchsia, nil, bp, nil)
-	ctx := testCcWithConfig(t, config)
+	result := ccFixtureFactory.Extend(PrepareForTestOnFuchsia).RunTestWithBp(t, bp)
 
 	rt := false
 	fb := false
 
-	ld := ctx.ModuleForTests("libTest", "fuchsia_arm64_shared").Rule("ld")
+	ld := result.ModuleForTests("libTest", "fuchsia_arm64_shared").Rule("ld")
 	implicits := ld.Implicits
 	for _, lib := range implicits {
 		if strings.Contains(lib.Rel(), "libcompiler_rt") {
@@ -190,16 +216,13 @@
 			},
 		}`
 
-	config := TestConfig(buildDir, android.Fuchsia, nil, bp, nil)
-	ctx := testCcWithConfig(t, config)
-	ld := ctx.ModuleForTests("libTest", "fuchsia_arm64_shared").Rule("ld")
+	result := ccFixtureFactory.Extend(PrepareForTestOnFuchsia).RunTestWithBp(t, bp)
+	ld := result.ModuleForTests("libTest", "fuchsia_arm64_shared").Rule("ld")
 	var objs []string
 	for _, o := range ld.Inputs {
 		objs = append(objs, o.Base())
 	}
-	if len(objs) != 2 || objs[0] != "foo.o" || objs[1] != "bar.o" {
-		t.Errorf("inputs of libTest must be []string{\"foo.o\", \"bar.o\"}, but was %#v.", objs)
-	}
+	android.AssertArrayString(t, "libTest inputs", []string{"foo.o", "bar.o"}, objs)
 }
 
 func TestVendorSrc(t *testing.T) {
@@ -3397,24 +3420,16 @@
 		}
 	`
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
-	config.TestProductVariables.Debuggable = BoolPtr(true)
+	result := ccFixtureFactory.Extend(
+		android.PrepareForTestWithVariables,
 
-	ctx := CreateTestContext(config)
-	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("variable", android.VariableMutator).Parallel()
-	})
-	ctx.Register()
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Debuggable = BoolPtr(true)
+		}),
+	).RunTestWithBp(t, bp)
 
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static").Module().(*Module)
-	if !android.InList("-DBAR", libfoo.flags.Local.CppFlags) {
-		t.Errorf("expected -DBAR in cppflags, got %q", libfoo.flags.Local.CppFlags)
-	}
+	libfoo := result.Module("libfoo", "android_arm64_armv8-a_static").(*Module)
+	android.AssertStringListContains(t, "cppflags", libfoo.flags.Local.CppFlags, "-DBAR")
 }
 
 func TestEmptyWholeStaticLibsAllowMissingDependencies(t *testing.T) {
@@ -3432,32 +3447,17 @@
 		}
 	`
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
-	config.TestProductVariables.Allow_missing_dependencies = BoolPtr(true)
+	result := ccFixtureFactory.Extend(
+		android.PrepareForTestWithAllowMissingDependencies,
+	).RunTestWithBp(t, bp)
 
-	ctx := CreateTestContext(config)
-	ctx.SetAllowMissingDependencies(true)
-	ctx.Register()
+	libbar := result.ModuleForTests("libbar", "android_arm64_armv8-a_static").Output("libbar.a")
+	android.AssertDeepEquals(t, "libbar rule", android.ErrorRule, libbar.Rule)
 
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
+	android.AssertStringDoesContain(t, "libbar error", libbar.Args["error"], "missing dependencies: libmissing")
 
-	libbar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_static").Output("libbar.a")
-	if g, w := libbar.Rule, android.ErrorRule; g != w {
-		t.Fatalf("Expected libbar rule to be %q, got %q", w, g)
-	}
-
-	if g, w := libbar.Args["error"], "missing dependencies: libmissing"; !strings.Contains(g, w) {
-		t.Errorf("Expected libbar error to contain %q, was %q", w, g)
-	}
-
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static").Output("libfoo.a")
-	if g, w := libfoo.Inputs.Strings(), libbar.Output.String(); !android.InList(w, g) {
-		t.Errorf("Expected libfoo.a to depend on %q, got %q", w, g)
-	}
-
+	libfoo := result.ModuleForTests("libfoo", "android_arm64_armv8-a_static").Output("libfoo.a")
+	android.AssertStringListContains(t, "libfoo.a dependencies", libfoo.Inputs.Strings(), libbar.Output.String())
 }
 
 func TestInstallSharedLibs(t *testing.T) {
@@ -3647,8 +3647,9 @@
 	}
 }
 
-func makeMemtagTestConfig(t *testing.T) android.Config {
-	templateBp := `
+var prepareForTestWithMemtagHeap = android.GroupFixturePreparers(
+	android.FixtureModifyMockFS(func(fs android.MockFS) {
+		templateBp := `
 		cc_test {
 			name: "%[1]s_test",
 			gtest: false,
@@ -3702,35 +3703,30 @@
 			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
 		}
 		`
-	subdirDefaultBp := fmt.Sprintf(templateBp, "default")
-	subdirExcludeBp := fmt.Sprintf(templateBp, "exclude")
-	subdirSyncBp := fmt.Sprintf(templateBp, "sync")
-	subdirAsyncBp := fmt.Sprintf(templateBp, "async")
+		subdirDefaultBp := fmt.Sprintf(templateBp, "default")
+		subdirExcludeBp := fmt.Sprintf(templateBp, "exclude")
+		subdirSyncBp := fmt.Sprintf(templateBp, "sync")
+		subdirAsyncBp := fmt.Sprintf(templateBp, "async")
 
-	mockFS := map[string][]byte{
-		"subdir_default/Android.bp": []byte(subdirDefaultBp),
-		"subdir_exclude/Android.bp": []byte(subdirExcludeBp),
-		"subdir_sync/Android.bp":    []byte(subdirSyncBp),
-		"subdir_async/Android.bp":   []byte(subdirAsyncBp),
-	}
-
-	return TestConfig(buildDir, android.Android, nil, "", mockFS)
-}
+		fs.Merge(android.MockFS{
+			"subdir_default/Android.bp": []byte(subdirDefaultBp),
+			"subdir_exclude/Android.bp": []byte(subdirExcludeBp),
+			"subdir_sync/Android.bp":    []byte(subdirSyncBp),
+			"subdir_async/Android.bp":   []byte(subdirAsyncBp),
+		})
+	}),
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.MemtagHeapExcludePaths = []string{"subdir_exclude"}
+		variables.MemtagHeapSyncIncludePaths = []string{"subdir_sync"}
+		variables.MemtagHeapAsyncIncludePaths = []string{"subdir_async"}
+	}),
+)
 
 func TestSanitizeMemtagHeap(t *testing.T) {
 	variant := "android_arm64_armv8-a"
 
-	config := makeMemtagTestConfig(t)
-	config.TestProductVariables.MemtagHeapExcludePaths = []string{"subdir_exclude"}
-	config.TestProductVariables.MemtagHeapSyncIncludePaths = []string{"subdir_sync"}
-	config.TestProductVariables.MemtagHeapAsyncIncludePaths = []string{"subdir_async"}
-	ctx := CreateTestContext(config)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp", "subdir_default/Android.bp", "subdir_exclude/Android.bp", "subdir_sync/Android.bp", "subdir_async/Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
+	result := ccFixtureFactory.Extend(prepareForTestWithMemtagHeap).RunTest(t)
+	ctx := result.TestContext
 
 	checkHasMemtagNote(t, ctx.ModuleForTests("default_test", variant), Sync)
 	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_false", variant), None)
@@ -3784,18 +3780,13 @@
 func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
 	variant := "android_arm64_armv8-a"
 
-	config := makeMemtagTestConfig(t)
-	config.TestProductVariables.MemtagHeapExcludePaths = []string{"subdir_exclude"}
-	config.TestProductVariables.MemtagHeapSyncIncludePaths = []string{"subdir_sync"}
-	config.TestProductVariables.MemtagHeapAsyncIncludePaths = []string{"subdir_async"}
-	config.TestProductVariables.SanitizeDevice = []string{"memtag_heap"}
-	ctx := CreateTestContext(config)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp", "subdir_default/Android.bp", "subdir_exclude/Android.bp", "subdir_sync/Android.bp", "subdir_async/Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
+	result := ccFixtureFactory.Extend(
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
 
 	checkHasMemtagNote(t, ctx.ModuleForTests("default_test", variant), Sync)
 	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_false", variant), None)
@@ -3849,19 +3840,14 @@
 func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
 	variant := "android_arm64_armv8-a"
 
-	config := makeMemtagTestConfig(t)
-	config.TestProductVariables.MemtagHeapExcludePaths = []string{"subdir_exclude"}
-	config.TestProductVariables.MemtagHeapSyncIncludePaths = []string{"subdir_sync"}
-	config.TestProductVariables.MemtagHeapAsyncIncludePaths = []string{"subdir_async"}
-	config.TestProductVariables.SanitizeDevice = []string{"memtag_heap"}
-	config.TestProductVariables.SanitizeDeviceDiag = []string{"memtag_heap"}
-	ctx := CreateTestContext(config)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp", "subdir_default/Android.bp", "subdir_exclude/Android.bp", "subdir_sync/Android.bp", "subdir_async/Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
+	result := ccFixtureFactory.Extend(
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+			variables.SanitizeDeviceDiag = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
 
 	checkHasMemtagNote(t, ctx.ModuleForTests("default_test", variant), Sync)
 	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_false", variant), None)
diff --git a/cc/cmakelists.go b/cc/cmakelists.go
index d441c57..04536fc 100644
--- a/cc/cmakelists.go
+++ b/cc/cmakelists.go
@@ -319,6 +319,9 @@
 	if strings.HasPrefix(parameter, "-fsanitize-blacklist") {
 		return relativeFilePathFlag
 	}
+	if strings.HasPrefix(parameter, "-fprofile-sample-use") {
+		return relativeFilePathFlag
+	}
 	return flag
 }
 
diff --git a/cc/config/x86_darwin_host.go b/cc/config/x86_darwin_host.go
index 1035df3..b0344af 100644
--- a/cc/config/x86_darwin_host.go
+++ b/cc/config/x86_darwin_host.go
@@ -136,7 +136,7 @@
 
 func getMacTools(ctx android.PackageVarContext) *macPlatformTools {
 	macTools.once.Do(func() {
-		xcrunTool := ctx.Config().NonHermeticHostSystemTool("xcrun")
+		xcrunTool := "/usr/bin/xcrun"
 
 		xcrun := func(args ...string) string {
 			if macTools.err != nil {
diff --git a/cc/library.go b/cc/library.go
index 0e6e107..6a3b876 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -230,6 +230,7 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyStatic()
 	module.sdkMemberTypes = []android.SdkMemberType{staticLibrarySdkMemberType}
+	module.bazelHandler = &staticLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -406,6 +407,49 @@
 	collectedSnapshotHeaders android.Paths
 }
 
+type staticLibraryBazelHandler struct {
+	bazelHandler
+
+	module *Module
+}
+
+func (handler *staticLibraryBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+	bazelCtx := ctx.Config().BazelContext
+	outputPaths, objPaths, ok := bazelCtx.GetAllFilesAndCcObjectFiles(label, ctx.Arch().ArchType)
+	if ok {
+		if len(outputPaths) != 1 {
+			// TODO(cparsons): This is actually expected behavior for static libraries with no srcs.
+			// We should support this.
+			ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, objPaths)
+			return false
+		}
+		outputFilePath := android.PathForBazelOut(ctx, outputPaths[0])
+		handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+
+		objFiles := make(android.Paths, len(objPaths))
+		for i, objPath := range objPaths {
+			objFiles[i] = android.PathForBazelOut(ctx, objPath)
+		}
+		objects := Objects{
+			objFiles: objFiles,
+		}
+
+		ctx.SetProvider(StaticLibraryInfoProvider, StaticLibraryInfo{
+			StaticLibrary: outputFilePath,
+			ReuseObjects:  objects,
+			Objects:       objects,
+
+			// TODO(cparsons): Include transitive static libraries in this provider to support
+			// static libraries with deps.
+			TransitiveStaticLibrariesForOrdering: android.NewDepSetBuilder(android.TOPOLOGICAL).
+				Direct(outputFilePath).
+				Build(),
+		})
+		handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
+	}
+	return ok
+}
+
 // collectHeadersForSnapshot collects all exported headers from library.
 // It globs header files in the source tree for exported include directories,
 // and tracks generated header files separately.
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index ee4de6e..20274b2 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -23,27 +23,16 @@
 	"github.com/google/blueprint"
 )
 
-func testPrebuilt(t *testing.T, bp string, fs map[string][]byte, handlers ...configCustomizer) *android.TestContext {
-	config := TestConfig(buildDir, android.Android, nil, bp, fs)
-	ctx := CreateTestContext(config)
+var prebuiltFixtureFactory = ccFixtureFactory.Extend(
+	android.PrepareForTestWithAndroidMk,
+)
 
-	// Enable androidmk support.
-	// * Register the singleton
-	// * Configure that we are inside make
-	// * Add CommonOS to ensure that androidmk processing works.
-	android.RegisterAndroidMkBuildComponents(ctx)
-	android.SetKatiEnabledForTests(config)
+func testPrebuilt(t *testing.T, bp string, fs android.MockFS, handlers ...android.FixturePreparer) *android.TestContext {
+	result := prebuiltFixtureFactory.Extend(
+		fs.AddToFixture(),
+	).Extend(handlers...).RunTestWithBp(t, bp)
 
-	for _, handler := range handlers {
-		handler(config)
-	}
-
-	ctx.Register()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-	return ctx
+	return result.TestContext
 }
 
 type configCustomizer func(config android.Config)
@@ -370,9 +359,11 @@
 	assertString(t, static2.OutputFile().Path().Base(), "libf.a")
 
 	// With SANITIZE_TARGET=hwaddress
-	ctx = testPrebuilt(t, bp, fs, func(config android.Config) {
-		config.TestProductVariables.SanitizeDevice = []string{"hwaddress"}
-	})
+	ctx = testPrebuilt(t, bp, fs,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"hwaddress"}
+		}),
+	)
 
 	shared_rule = ctx.ModuleForTests("libtest", "android_arm64_armv8-a_shared_hwasan").Rule("android/soong/cc.strip")
 	assertString(t, shared_rule.Input.String(), "hwasan/libf.so")
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 8218d97..cd09e6e 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -404,12 +404,6 @@
 		s.Diag.Cfi = boolPtr(false)
 	}
 
-	// Also disable CFI for arm32 until b/35157333 is fixed.
-	if ctx.Arch().ArchType == android.Arm {
-		s.Cfi = boolPtr(false)
-		s.Diag.Cfi = boolPtr(false)
-	}
-
 	// HWASan requires AArch64 hardware feature (top-byte-ignore).
 	if ctx.Arch().ArchType != android.Arm64 {
 		s.Hwaddress = nil
diff --git a/cc/testing.go b/cc/testing.go
index f62c5f1..d8adc61 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -37,7 +37,31 @@
 }
 
 func GatherRequiredDepsForTest(oses ...android.OsType) string {
-	ret := `
+	ret := commonDefaultModules()
+
+	supportLinuxBionic := false
+	for _, os := range oses {
+		if os == android.Fuchsia {
+			ret += withFuchsiaModules()
+		}
+		if os == android.Windows {
+			ret += withWindowsModules()
+		}
+		if os == android.LinuxBionic {
+			supportLinuxBionic = true
+			ret += withLinuxBionic()
+		}
+	}
+
+	if !supportLinuxBionic {
+		ret += withoutLinuxBionic()
+	}
+
+	return ret
+}
+
+func commonDefaultModules() string {
+	return `
 		toolchain_library {
 			name: "libatomic",
 			defaults: ["linux_bionic_supported"],
@@ -475,23 +499,10 @@
 			name: "note_memtag_heap_sync",
 		}
 	`
+}
 
-	supportLinuxBionic := false
-	for _, os := range oses {
-		if os == android.Fuchsia {
-			ret += `
-		cc_library {
-			name: "libbioniccompat",
-			stl: "none",
-		}
-		cc_library {
-			name: "libcompiler_rt",
-			stl: "none",
-		}
-		`
-		}
-		if os == android.Windows {
-			ret += `
+func withWindowsModules() string {
+	return `
 		toolchain_library {
 			name: "libwinpthread",
 			host_supported: true,
@@ -504,10 +515,23 @@
 			src: "",
 		}
 		`
+}
+
+func withFuchsiaModules() string {
+	return `
+		cc_library {
+			name: "libbioniccompat",
+			stl: "none",
 		}
-		if os == android.LinuxBionic {
-			supportLinuxBionic = true
-			ret += `
+		cc_library {
+			name: "libcompiler_rt",
+			stl: "none",
+		}
+		`
+}
+
+func withLinuxBionic() string {
+	return `
 				cc_binary {
 					name: "linker",
 					defaults: ["linux_bionic_supported"],
@@ -547,23 +571,115 @@
 					},
 				}
 			`
-		}
-	}
+}
 
-	if !supportLinuxBionic {
-		ret += `
+func withoutLinuxBionic() string {
+	return `
 			cc_defaults {
 				name: "linux_bionic_supported",
 			}
 		`
-	}
-
-	return ret
 }
 
 func GatherRequiredFilesForTest(fs map[string][]byte) {
 }
 
+// The directory in which cc linux bionic default modules will be defined.
+//
+// Placing them here ensures that their location does not conflict with default test modules
+// defined by other packages.
+const linuxBionicDefaultsPath = "defaults/cc/linux-bionic/Android.bp"
+
+// The directory in which the default cc common test modules will be defined.
+//
+// Placing them here ensures that their location does not conflict with default test modules
+// defined by other packages.
+const DefaultCcCommonTestModulesDir = "defaults/cc/common/"
+
+// Test fixture preparer that will register most cc build components.
+//
+// Singletons and mutators should only be added here if they are needed for a majority of cc
+// module types, otherwise they should be added under a separate preparer to allow them to be
+// selected only when needed to reduce test execution time.
+//
+// Module types do not have much of an overhead unless they are used so this should include as many
+// module types as possible. The exceptions are those module types that require mutators and/or
+// singletons in order to function in which case they should be kept together in a separate
+// preparer.
+var PrepareForTestWithCcBuildComponents = android.GroupFixturePreparers(
+	android.PrepareForTestWithAndroidBuildComponents,
+	android.FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("cc_fuzz", FuzzFactory)
+		ctx.RegisterModuleType("cc_test", TestFactory)
+		ctx.RegisterModuleType("cc_test_library", TestLibraryFactory)
+		ctx.RegisterModuleType("llndk_headers", llndkHeadersFactory)
+		ctx.RegisterModuleType("vendor_public_library", vendorPublicLibraryFactory)
+		ctx.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
+
+		RegisterVndkLibraryTxtTypes(ctx)
+	}),
+)
+
+// Preparer that will define default cc modules, e.g. standard prebuilt modules.
+var PrepareForTestWithCcDefaultModules = android.GroupFixturePreparers(
+	PrepareForTestWithCcBuildComponents,
+	// Place the default cc test modules that are common to all platforms in a location that will not
+	// conflict with default test modules defined by other packages.
+	android.FixtureAddTextFile(DefaultCcCommonTestModulesDir+"Android.bp", commonDefaultModules()),
+	// Disable linux bionic by default.
+	android.FixtureAddTextFile(linuxBionicDefaultsPath, withoutLinuxBionic()),
+)
+
+// Prepare a fixture to use all cc module types, mutators and singletons fully.
+//
+// This should only be used by tests that want to run with as much of the build enabled as possible.
+var PrepareForIntegrationTestWithCc = android.GroupFixturePreparers(
+	android.PrepareForIntegrationTestWithAndroid,
+	genrule.PrepareForIntegrationTestWithGenrule,
+	PrepareForTestWithCcDefaultModules,
+)
+
+// The preparer to include if running a cc related test for windows.
+var PrepareForTestOnWindows = android.GroupFixturePreparers(
+	// Place the default cc test modules for windows platforms in a location that will not conflict
+	// with default test modules defined by other packages.
+	android.FixtureAddTextFile("defaults/cc/windows/Android.bp", withWindowsModules()),
+)
+
+// The preparer to include if running a cc related test for linux bionic.
+var PrepareForTestOnLinuxBionic = android.GroupFixturePreparers(
+	// Enable linux bionic
+	//
+	// Can be used after PrepareForTestWithCcDefaultModules to override its default behavior of
+	// disabling linux bionic, hence why this uses FixtureOverrideTextFile.
+	android.FixtureOverrideTextFile(linuxBionicDefaultsPath, withLinuxBionic()),
+)
+
+// The preparer to include if running a cc related test for fuchsia.
+var PrepareForTestOnFuchsia = android.GroupFixturePreparers(
+	// Place the default cc test modules for fuschia in a location that will not conflict with default
+	// test modules defined by other packages.
+	android.FixtureAddTextFile("defaults/cc/fuschia/Android.bp", withFuchsiaModules()),
+	android.PrepareForTestSetDeviceToFuchsia,
+)
+
+// This adds some additional modules and singletons which might negatively impact the performance
+// of tests so they are not included in the PrepareForIntegrationTestWithCc.
+var PrepareForTestWithCcIncludeVndk = android.GroupFixturePreparers(
+	PrepareForIntegrationTestWithCc,
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		vendorSnapshotImageSingleton.init(ctx)
+		recoverySnapshotImageSingleton.init(ctx)
+		ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
+	}),
+)
+
+// TestConfig is the legacy way of creating a test Config for testing cc modules.
+//
+// See testCc for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func TestConfig(buildDir string, os android.OsType, env map[string]string,
 	bp string, fs map[string][]byte) android.Config {
 
@@ -580,7 +696,7 @@
 
 	var config android.Config
 	if os == android.Fuchsia {
-		config = android.TestArchConfigFuchsia(buildDir, env, bp, mockFS)
+		panic("Fuchsia not supported use test fixture instead")
 	} else {
 		config = android.TestArchConfig(buildDir, env, bp, mockFS)
 	}
@@ -588,6 +704,11 @@
 	return config
 }
 
+// CreateTestContext is the legacy way of creating a TestContext for testing cc modules.
+//
+// See testCc for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func CreateTestContext(config android.Config) *android.TestContext {
 	ctx := android.NewTestArchContext(config)
 	genrule.RegisterGenruleBuildComponents(ctx)
@@ -598,13 +719,15 @@
 	ctx.RegisterModuleType("vendor_public_library", vendorPublicLibraryFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
+
 	vendorSnapshotImageSingleton.init(ctx)
 	recoverySnapshotImageSingleton.init(ctx)
+	ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 	RegisterVndkLibraryTxtTypes(ctx)
+
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	android.RegisterPrebuiltMutators(ctx)
 	RegisterRequiredBuildComponentsForTest(ctx)
-	ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 
 	return ctx
 }
diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go
index bccbfc1..8e90295 100644
--- a/dexpreopt/testing.go
+++ b/dexpreopt/testing.go
@@ -45,3 +45,36 @@
 		}
 	`
 }
+
+// Prepares a test fixture by enabling dexpreopt.
+var PrepareForTestWithDexpreopt = FixtureModifyGlobalConfig(func(*GlobalConfig) {})
+
+// FixtureModifyGlobalConfig enables dexpreopt (unless modified by the mutator) and modifies the
+// configuration.
+func FixtureModifyGlobalConfig(configModifier func(dexpreoptConfig *GlobalConfig)) android.FixturePreparer {
+	return android.FixtureModifyConfig(func(config android.Config) {
+		// Initialize the dexpreopt GlobalConfig to an empty structure. This has no effect if it has
+		// already been set.
+		pathCtx := android.PathContextForTesting(config)
+		dexpreoptConfig := GlobalConfigForTests(pathCtx)
+		SetTestGlobalConfig(config, dexpreoptConfig)
+
+		// Retrieve the existing configuration and modify it.
+		dexpreoptConfig = GetGlobalConfig(pathCtx)
+		configModifier(dexpreoptConfig)
+	})
+}
+
+// FixtureSetArtBootJars enables dexpreopt and sets the ArtApexJars property.
+func FixtureSetArtBootJars(bootJars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.ArtApexJars = android.CreateTestConfiguredJarList(bootJars)
+	})
+}
+
+// FixtureSetBootJars enables dexpreopt and sets the BootJars property.
+func FixtureSetBootJars(bootJars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList(bootJars)
+	})
+}
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index b07ad91..6291325 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -54,6 +54,8 @@
 	ctx.RegisterModuleType("prebuilt_dsp", PrebuiltDSPFactory)
 }
 
+var PrepareForTestWithPrebuiltEtc = android.FixtureRegisterWithContext(RegisterPrebuiltEtcBuildComponents)
+
 type prebuiltEtcProperties struct {
 	// Source file of this prebuilt. Can reference a genrule type module with the ":module" syntax.
 	Src *string `android:"path,arch_variant"`
diff --git a/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index 585760d..09f2e8c 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -18,7 +18,6 @@
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"reflect"
 	"testing"
 
 	"android/soong/android"
@@ -49,60 +48,19 @@
 	os.Exit(run())
 }
 
-func testPrebuiltEtcContext(t *testing.T, bp string) (*android.TestContext, android.Config) {
-	fs := map[string][]byte{
+var prebuiltEtcFixtureFactory = android.NewFixtureFactory(
+	&buildDir,
+	android.PrepareForTestWithArchMutator,
+	PrepareForTestWithPrebuiltEtc,
+	android.FixtureMergeMockFs(android.MockFS{
 		"foo.conf": nil,
 		"bar.conf": nil,
 		"baz.conf": nil,
-	}
+	}),
+)
 
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("prebuilt_etc", PrebuiltEtcFactory)
-	ctx.RegisterModuleType("prebuilt_etc_host", PrebuiltEtcHostFactory)
-	ctx.RegisterModuleType("prebuilt_usr_share", PrebuiltUserShareFactory)
-	ctx.RegisterModuleType("prebuilt_usr_share_host", PrebuiltUserShareHostFactory)
-	ctx.RegisterModuleType("prebuilt_font", PrebuiltFontFactory)
-	ctx.RegisterModuleType("prebuilt_firmware", PrebuiltFirmwareFactory)
-	ctx.RegisterModuleType("prebuilt_dsp", PrebuiltDSPFactory)
-	ctx.Register()
-
-	return ctx, config
-}
-
-func testPrebuiltEtc(t *testing.T, bp string) (*android.TestContext, android.Config) {
-	t.Helper()
-
-	ctx, config := testPrebuiltEtcContext(t, bp)
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-
-	return ctx, config
-}
-
-func testPrebuiltEtcError(t *testing.T, pattern, bp string) {
-	t.Helper()
-
-	ctx, config := testPrebuiltEtcContext(t, bp)
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return
-	}
-
-	_, errs = ctx.PrepareBuildActions(config)
-	if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return
-	}
-
-	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
-}
 func TestPrebuiltEtcVariants(t *testing.T) {
-	ctx, _ := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_etc {
 			name: "foo.conf",
 			src: "foo.conf",
@@ -119,24 +77,24 @@
 		}
 	`)
 
-	foo_variants := ctx.ModuleVariantsForTests("foo.conf")
+	foo_variants := result.ModuleVariantsForTests("foo.conf")
 	if len(foo_variants) != 1 {
 		t.Errorf("expected 1, got %#v", foo_variants)
 	}
 
-	bar_variants := ctx.ModuleVariantsForTests("bar.conf")
+	bar_variants := result.ModuleVariantsForTests("bar.conf")
 	if len(bar_variants) != 2 {
 		t.Errorf("expected 2, got %#v", bar_variants)
 	}
 
-	baz_variants := ctx.ModuleVariantsForTests("baz.conf")
+	baz_variants := result.ModuleVariantsForTests("baz.conf")
 	if len(baz_variants) != 1 {
 		t.Errorf("expected 1, got %#v", bar_variants)
 	}
 }
 
 func TestPrebuiltEtcOutputPath(t *testing.T) {
-	ctx, _ := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_etc {
 			name: "foo.conf",
 			src: "foo.conf",
@@ -144,14 +102,12 @@
 		}
 	`)
 
-	p := ctx.ModuleForTests("foo.conf", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
-	if p.outputFilePath.Base() != "foo.installed.conf" {
-		t.Errorf("expected foo.installed.conf, got %q", p.outputFilePath.Base())
-	}
+	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "output file path", "foo.installed.conf", p.outputFilePath.Base())
 }
 
 func TestPrebuiltEtcGlob(t *testing.T) {
-	ctx, _ := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_etc {
 			name: "my_foo",
 			src: "foo.*",
@@ -163,19 +119,15 @@
 		}
 	`)
 
-	p := ctx.ModuleForTests("my_foo", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
-	if p.outputFilePath.Base() != "my_foo" {
-		t.Errorf("expected my_foo, got %q", p.outputFilePath.Base())
-	}
+	p := result.Module("my_foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "my_foo output file path", "my_foo", p.outputFilePath.Base())
 
-	p = ctx.ModuleForTests("my_bar", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
-	if p.outputFilePath.Base() != "bar.conf" {
-		t.Errorf("expected bar.conf, got %q", p.outputFilePath.Base())
-	}
+	p = result.Module("my_bar", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "my_bar output file path", "bar.conf", p.outputFilePath.Base())
 }
 
 func TestPrebuiltEtcAndroidMk(t *testing.T) {
-	ctx, _ := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_etc {
 			name: "foo",
 			src: "foo.conf",
@@ -197,13 +149,11 @@
 		"LOCAL_TARGET_REQUIRED_MODULES": {"targetModA"},
 	}
 
-	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
-	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	mod := result.Module("foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 	for k, expectedValue := range expected {
 		if value, ok := entries.EntryMap[k]; ok {
-			if !reflect.DeepEqual(value, expectedValue) {
-				t.Errorf("Incorrect %s '%s', expected '%s'", k, value, expectedValue)
-			}
+			android.AssertDeepEquals(t, k, expectedValue, value)
 		} else {
 			t.Errorf("No %s defined, saw %q", k, entries.EntryMap)
 		}
@@ -211,7 +161,7 @@
 }
 
 func TestPrebuiltEtcRelativeInstallPathInstallDirPath(t *testing.T) {
-	ctx, _ := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_etc {
 			name: "foo.conf",
 			src: "foo.conf",
@@ -219,26 +169,26 @@
 		}
 	`)
 
-	p := ctx.ModuleForTests("foo.conf", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
+	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := buildDir + "/target/product/test_device/system/etc/bar"
-	if p.installDirPath.String() != expected {
-		t.Errorf("expected %q, got %q", expected, p.installDirPath.String())
-	}
+	android.AssertStringEquals(t, "install dir", expected, p.installDirPath.String())
 }
 
 func TestPrebuiltEtcCannotSetRelativeInstallPathAndSubDir(t *testing.T) {
-	testPrebuiltEtcError(t, "relative_install_path is set. Cannot set sub_dir", `
-		prebuilt_etc {
-			name: "foo.conf",
-			src: "foo.conf",
-			sub_dir: "bar",
-			relative_install_path: "bar",
-		}
-	`)
+	prebuiltEtcFixtureFactory.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("relative_install_path is set. Cannot set sub_dir")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo.conf",
+				src: "foo.conf",
+				sub_dir: "bar",
+				relative_install_path: "bar",
+			}
+		`)
 }
 
 func TestPrebuiltEtcHost(t *testing.T) {
-	ctx, _ := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_etc_host {
 			name: "foo.conf",
 			src: "foo.conf",
@@ -246,14 +196,14 @@
 	`)
 
 	buildOS := android.BuildOs.String()
-	p := ctx.ModuleForTests("foo.conf", buildOS+"_common").Module().(*PrebuiltEtc)
+	p := result.Module("foo.conf", buildOS+"_common").(*PrebuiltEtc)
 	if !p.Host() {
 		t.Errorf("host bit is not set for a prebuilt_etc_host module.")
 	}
 }
 
 func TestPrebuiltUserShareInstallDirPath(t *testing.T) {
-	ctx, _ := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_usr_share {
 			name: "foo.conf",
 			src: "foo.conf",
@@ -261,15 +211,13 @@
 		}
 	`)
 
-	p := ctx.ModuleForTests("foo.conf", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
+	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := buildDir + "/target/product/test_device/system/usr/share/bar"
-	if p.installDirPath.String() != expected {
-		t.Errorf("expected %q, got %q", expected, p.installDirPath.String())
-	}
+	android.AssertStringEquals(t, "install dir", expected, p.installDirPath.String())
 }
 
 func TestPrebuiltUserShareHostInstallDirPath(t *testing.T) {
-	ctx, config := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_usr_share_host {
 			name: "foo.conf",
 			src: "foo.conf",
@@ -278,26 +226,22 @@
 	`)
 
 	buildOS := android.BuildOs.String()
-	p := ctx.ModuleForTests("foo.conf", buildOS+"_common").Module().(*PrebuiltEtc)
-	expected := filepath.Join(buildDir, "host", config.PrebuiltOS(), "usr", "share", "bar")
-	if p.installDirPath.String() != expected {
-		t.Errorf("expected %q, got %q", expected, p.installDirPath.String())
-	}
+	p := result.Module("foo.conf", buildOS+"_common").(*PrebuiltEtc)
+	expected := filepath.Join(buildDir, "host", result.Config.PrebuiltOS(), "usr", "share", "bar")
+	android.AssertStringEquals(t, "install dir", expected, p.installDirPath.String())
 }
 
 func TestPrebuiltFontInstallDirPath(t *testing.T) {
-	ctx, _ := testPrebuiltEtc(t, `
+	result := prebuiltEtcFixtureFactory.RunTestWithBp(t, `
 		prebuilt_font {
 			name: "foo.conf",
 			src: "foo.conf",
 		}
 	`)
 
-	p := ctx.ModuleForTests("foo.conf", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
+	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := buildDir + "/target/product/test_device/system/fonts"
-	if p.installDirPath.String() != expected {
-		t.Errorf("expected %q, got %q", expected, p.installDirPath.String())
-	}
+	android.AssertStringEquals(t, "install dir", expected, p.installDirPath.String())
 }
 
 func TestPrebuiltFirmwareDirPath(t *testing.T) {
@@ -327,11 +271,9 @@
 	}}
 	for _, tt := range tests {
 		t.Run(tt.description, func(t *testing.T) {
-			ctx, _ := testPrebuiltEtc(t, tt.config)
-			p := ctx.ModuleForTests("foo.conf", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
-			if p.installDirPath.String() != tt.expectedPath {
-				t.Errorf("expected %q, got %q", tt.expectedPath, p.installDirPath)
-			}
+			result := prebuiltEtcFixtureFactory.RunTestWithBp(t, tt.config)
+			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
+			android.AssertStringEquals(t, "install dir", tt.expectedPath, p.installDirPath.String())
 		})
 	}
 }
@@ -363,11 +305,9 @@
 	}}
 	for _, tt := range tests {
 		t.Run(tt.description, func(t *testing.T) {
-			ctx, _ := testPrebuiltEtc(t, tt.config)
-			p := ctx.ModuleForTests("foo.conf", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
-			if p.installDirPath.String() != tt.expectedPath {
-				t.Errorf("expected %q, got %q", tt.expectedPath, p.installDirPath)
-			}
+			result := prebuiltEtcFixtureFactory.RunTestWithBp(t, tt.config)
+			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
+			android.AssertStringEquals(t, "install dir", tt.expectedPath, p.installDirPath.String())
 		})
 	}
 }
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 764f045..876e189 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -153,7 +153,7 @@
 		if vendor {
 			flag = "--vendor_cmdline "
 		}
-		cmd.FlagWithArg(flag, "\""+proptools.ShellEscape(cmdline)+"\"")
+		cmd.FlagWithArg(flag, proptools.ShellEscapeIncludingSpaces(cmdline))
 	}
 
 	headerVersion := proptools.String(b.properties.Header_version)
@@ -237,3 +237,13 @@
 func (b *bootimg) OutputPath() android.Path {
 	return b.output
 }
+
+var _ android.OutputFileProducer = (*bootimg)(nil)
+
+// Implements android.OutputFileProducer
+func (b *bootimg) OutputFiles(tag string) (android.Paths, error) {
+	if tag == "" {
+		return []android.Path{b.output}, nil
+	}
+	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+}
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index e547203..16b6037 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -208,3 +208,13 @@
 func (l *logicalPartition) OutputPath() android.Path {
 	return l.output
 }
+
+var _ android.OutputFileProducer = (*logicalPartition)(nil)
+
+// Implements android.OutputFileProducer
+func (l *logicalPartition) OutputFiles(tag string) (android.Paths, error) {
+	if tag == "" {
+		return []android.Path{l.output}, nil
+	}
+	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 50c77cf..5349906 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -37,6 +37,27 @@
 	RegisterGenruleBuildComponents(android.InitRegistrationContext)
 }
 
+// Test fixture preparer that will register most genrule build components.
+//
+// Singletons and mutators should only be added here if they are needed for a majority of genrule
+// module types, otherwise they should be added under a separate preparer to allow them to be
+// selected only when needed to reduce test execution time.
+//
+// Module types do not have much of an overhead unless they are used so this should include as many
+// module types as possible. The exceptions are those module types that require mutators and/or
+// singletons in order to function in which case they should be kept together in a separate
+// preparer.
+var PrepareForTestWithGenRuleBuildComponents = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(RegisterGenruleBuildComponents),
+)
+
+// Prepare a fixture to use all genrule module types, mutators and singletons fully.
+//
+// This should only be used by tests that want to run with as much of the build enabled as possible.
+var PrepareForIntegrationTestWithGenrule = android.GroupFixturePreparers(
+	PrepareForTestWithGenRuleBuildComponents,
+)
+
 func RegisterGenruleBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
 
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index 2f5605e..690277c 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -17,8 +17,7 @@
 import (
 	"io/ioutil"
 	"os"
-	"reflect"
-	"strings"
+	"regexp"
 	"testing"
 
 	"android/soong/android"
@@ -51,22 +50,30 @@
 	os.Exit(run())
 }
 
-func testContext(config android.Config) *android.TestContext {
+var genruleFixtureFactory = android.NewFixtureFactory(
+	&buildDir,
+	android.PrepareForTestWithArchMutator,
+	android.PrepareForTestWithDefaults,
 
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.RegisterModuleType("tool", toolFactory)
+	android.PrepareForTestWithFilegroup,
+	PrepareForTestWithGenRuleBuildComponents,
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("tool", toolFactory)
+	}),
+	android.FixtureMergeMockFs(android.MockFS{
+		"tool":       nil,
+		"tool_file1": nil,
+		"tool_file2": nil,
+		"in1":        nil,
+		"in2":        nil,
+		"in1.txt":    nil,
+		"in2.txt":    nil,
+		"in3.txt":    nil,
+	}),
+)
 
-	RegisterGenruleBuildComponents(ctx)
-
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	ctx.Register()
-
-	return ctx
-}
-
-func testConfig(bp string, fs map[string][]byte) android.Config {
-	bp += `
+func testGenruleBp() string {
+	return `
 		tool {
 			name: "tool",
 		}
@@ -105,23 +112,6 @@
 			name: "empty",
 		}
 	`
-
-	mockFS := map[string][]byte{
-		"tool":       nil,
-		"tool_file1": nil,
-		"tool_file2": nil,
-		"in1":        nil,
-		"in2":        nil,
-		"in1.txt":    nil,
-		"in2.txt":    nil,
-		"in3.txt":    nil,
-	}
-
-	for k, v := range fs {
-		mockFS[k] = v
-	}
-
-	return android.TestArchConfig(buildDir, nil, bp, mockFS)
 }
 
 func TestGenruleCmd(t *testing.T) {
@@ -466,38 +456,28 @@
 			bp += test.prop
 			bp += "}\n"
 
-			config := testConfig(bp, nil)
-			config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(test.allowMissingDependencies)
-
-			ctx := testContext(config)
-			ctx.SetAllowMissingDependencies(test.allowMissingDependencies)
-
-			_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-			if errs == nil {
-				_, errs = ctx.PrepareBuildActions(config)
+			var expectedErrors []string
+			if test.err != "" {
+				expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err))
 			}
-			if errs == nil && test.err != "" {
-				t.Fatalf("want error %q, got no error", test.err)
-			} else if errs != nil && test.err == "" {
-				android.FailIfErrored(t, errs)
-			} else if test.err != "" {
-				if len(errs) != 1 {
-					t.Errorf("want 1 error, got %d errors:", len(errs))
-					for _, err := range errs {
-						t.Errorf("   %s", err.Error())
-					}
-					t.FailNow()
-				}
-				if !strings.Contains(errs[0].Error(), test.err) {
-					t.Fatalf("want %q, got %q", test.err, errs[0].Error())
-				}
+
+			result := genruleFixtureFactory.Extend(
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.Allow_missing_dependencies = proptools.BoolPtr(test.allowMissingDependencies)
+				}),
+				android.FixtureModifyContext(func(ctx *android.TestContext) {
+					ctx.SetAllowMissingDependencies(test.allowMissingDependencies)
+				}),
+			).
+				ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)).
+				RunTestWithBp(t, testGenruleBp()+bp)
+
+			if expectedErrors != nil {
 				return
 			}
 
-			gen := ctx.ModuleForTests("gen", "").Module().(*Module)
-			if g, w := gen.rawCommands[0], test.expect; w != g {
-				t.Errorf("want %q, got %q", w, g)
-			}
+			gen := result.Module("gen", "").(*Module)
+			android.AssertStringEquals(t, "raw commands", test.expect, gen.rawCommands[0])
 		})
 	}
 }
@@ -557,25 +537,15 @@
 		},
 	}
 
-	config := testConfig(bp, nil)
-	ctx := testContext(config)
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	if errs == nil {
-		_, errs = ctx.PrepareBuildActions(config)
-	}
-	if errs != nil {
-		t.Fatal(errs)
-	}
+	result := genruleFixtureFactory.RunTestWithBp(t, testGenruleBp()+bp)
 
 	for _, test := range testcases {
 		t.Run(test.name, func(t *testing.T) {
-			gen := ctx.ModuleForTests(test.name, "")
+			gen := result.ModuleForTests(test.name, "")
 			manifest := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto"))
 			hash := manifest.Commands[0].GetInputHash()
 
-			if g, w := hash, test.expectedHash; g != w {
-				t.Errorf("Expected has %q, got %q", w, g)
-			}
+			android.AssertStringEquals(t, "hash", test.expectedHash, hash)
 		})
 	}
 }
@@ -630,46 +600,27 @@
 			bp += test.prop
 			bp += "}\n"
 
-			config := testConfig(bp, nil)
-			ctx := testContext(config)
-
-			_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-			if errs == nil {
-				_, errs = ctx.PrepareBuildActions(config)
+			var expectedErrors []string
+			if test.err != "" {
+				expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err))
 			}
-			if errs == nil && test.err != "" {
-				t.Fatalf("want error %q, got no error", test.err)
-			} else if errs != nil && test.err == "" {
-				android.FailIfErrored(t, errs)
-			} else if test.err != "" {
-				if len(errs) != 1 {
-					t.Errorf("want 1 error, got %d errors:", len(errs))
-					for _, err := range errs {
-						t.Errorf("   %s", err.Error())
-					}
-					t.FailNow()
-				}
-				if !strings.Contains(errs[0].Error(), test.err) {
-					t.Fatalf("want %q, got %q", test.err, errs[0].Error())
-				}
+
+			result := genruleFixtureFactory.
+				ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)).
+				RunTestWithBp(t, testGenruleBp()+bp)
+
+			if expectedErrors != nil {
 				return
 			}
 
-			gen := ctx.ModuleForTests("gen", "").Module().(*Module)
-			if g, w := gen.rawCommands, test.cmds; !reflect.DeepEqual(w, g) {
-				t.Errorf("want %q, got %q", w, g)
-			}
+			gen := result.Module("gen", "").(*Module)
+			android.AssertDeepEquals(t, "cmd", test.cmds, gen.rawCommands)
 
-			if g, w := gen.outputDeps.Strings(), test.deps; !reflect.DeepEqual(w, g) {
-				t.Errorf("want deps %q, got %q", w, g)
-			}
+			android.AssertDeepEquals(t, "deps", test.deps, gen.outputDeps.Strings())
 
-			if g, w := gen.outputFiles.Strings(), test.files; !reflect.DeepEqual(w, g) {
-				t.Errorf("want files %q, got %q", w, g)
-			}
+			android.AssertDeepEquals(t, "files", test.files, gen.outputFiles.Strings())
 		})
 	}
-
 }
 
 func TestGenruleDefaults(t *testing.T) {
@@ -690,26 +641,16 @@
 					defaults: ["gen_defaults1", "gen_defaults2"],
 				}
 			`
-	config := testConfig(bp, nil)
-	ctx := testContext(config)
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	if errs == nil {
-		_, errs = ctx.PrepareBuildActions(config)
-	}
-	if errs != nil {
-		t.Fatal(errs)
-	}
-	gen := ctx.ModuleForTests("gen", "").Module().(*Module)
+
+	result := genruleFixtureFactory.RunTestWithBp(t, testGenruleBp()+bp)
+
+	gen := result.Module("gen", "").(*Module)
 
 	expectedCmd := "cp in1 __SBOX_SANDBOX_DIR__/out/out"
-	if gen.rawCommands[0] != expectedCmd {
-		t.Errorf("Expected cmd: %q, actual: %q", expectedCmd, gen.rawCommands[0])
-	}
+	android.AssertStringEquals(t, "cmd", expectedCmd, gen.rawCommands[0])
 
 	expectedSrcs := []string{"in1"}
-	if !reflect.DeepEqual(expectedSrcs, gen.properties.Srcs) {
-		t.Errorf("Expected srcs: %q, actual: %q", expectedSrcs, gen.properties.Srcs)
-	}
+	android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.Srcs)
 }
 
 func TestGenruleWithBazel(t *testing.T) {
@@ -721,29 +662,18 @@
 		}
 	`
 
-	config := testConfig(bp, nil)
-	config.BazelContext = android.MockBazelContext{
-		AllFiles: map[string][]string{
-			"//foo/bar:bar": []string{"bazelone.txt", "bazeltwo.txt"}}}
+	result := genruleFixtureFactory.Extend(android.FixtureModifyConfig(func(config android.Config) {
+		config.BazelContext = android.MockBazelContext{
+			AllFiles: map[string][]string{
+				"//foo/bar:bar": []string{"bazelone.txt", "bazeltwo.txt"}}}
+	})).RunTestWithBp(t, testGenruleBp()+bp)
 
-	ctx := testContext(config)
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	if errs == nil {
-		_, errs = ctx.PrepareBuildActions(config)
-	}
-	if errs != nil {
-		t.Fatal(errs)
-	}
-	gen := ctx.ModuleForTests("foo", "").Module().(*Module)
+	gen := result.Module("foo", "").(*Module)
 
 	expectedOutputFiles := []string{"outputbase/execroot/__main__/bazelone.txt",
 		"outputbase/execroot/__main__/bazeltwo.txt"}
-	if !reflect.DeepEqual(gen.outputFiles.Strings(), expectedOutputFiles) {
-		t.Errorf("Expected output files: %q, actual: %q", expectedOutputFiles, gen.outputFiles)
-	}
-	if !reflect.DeepEqual(gen.outputDeps.Strings(), expectedOutputFiles) {
-		t.Errorf("Expected output deps: %q, actual: %q", expectedOutputFiles, gen.outputDeps)
-	}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, gen.outputFiles.Strings())
+	android.AssertDeepEquals(t, "output deps", expectedOutputFiles, gen.outputDeps.Strings())
 }
 
 type testTool struct {
diff --git a/java/Android.bp b/java/Android.bp
index 461b16d..9e2db83 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -78,6 +78,7 @@
         "plugin_test.go",
         "rro_test.go",
         "sdk_test.go",
+        "system_modules_test.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/java/app.go b/java/app.go
index 0660aa6..e98fe31 100755
--- a/java/app.go
+++ b/java/app.go
@@ -122,8 +122,8 @@
 	// or an android_app_certificate module name in the form ":module".
 	Certificate *string
 
-	// Name of the signing certificate lineage file.
-	Lineage *string
+	// Name of the signing certificate lineage file or filegroup module.
+	Lineage *string `android:"path"`
 
 	// the package name of this app. The package name in the manifest file is used if one was not given.
 	Package_name *string
diff --git a/java/app_import.go b/java/app_import.go
index d38f63e..d4da64d 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -74,8 +74,8 @@
 	// be set for presigned modules.
 	Presigned *bool
 
-	// Name of the signing certificate lineage file.
-	Lineage *string
+	// Name of the signing certificate lineage file or filegroup module.
+	Lineage *string `android:"path"`
 
 	// Sign with the default system dev certificate. Must be used judiciously. Most imported apps
 	// need to either specify a specific certificate or be presigned.
diff --git a/java/app_import_test.go b/java/app_import_test.go
index 00406aa..cae41d0 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -138,6 +138,32 @@
 	}
 }
 
+func TestAndroidAppImport_SigningLineageFilegroup(t *testing.T) {
+	ctx, _ := testJava(t, `
+	  android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "platform",
+			lineage: ":lineage_bin",
+		}
+
+		filegroup {
+			name: "lineage_bin",
+			srcs: ["lineage.bin"],
+		}
+	`)
+
+	variant := ctx.ModuleForTests("foo", "android_common")
+
+	signedApk := variant.Output("signed/foo.apk")
+	// Check cert signing lineage flag.
+	signingFlag := signedApk.Args["flags"]
+	expected := "--lineage lineage.bin"
+	if expected != signingFlag {
+		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
+	}
+}
+
 func TestAndroidAppImport_DefaultDevCert(t *testing.T) {
 	ctx, _ := testJava(t, `
 		android_app_import {
diff --git a/java/app_test.go b/java/app_test.go
index f41047a..78e1a57 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -1576,6 +1576,31 @@
 			expectedLineage:     "--lineage lineage.bin",
 			expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8",
 		},
+		{
+			name: "lineage from filegroup",
+			bp: `
+				android_app {
+					name: "foo",
+					srcs: ["a.java"],
+					certificate: ":new_certificate",
+					lineage: ":lineage_bin",
+					sdk_version: "current",
+				}
+
+				android_app_certificate {
+					name: "new_certificate",
+					certificate: "cert/new_cert",
+				}
+
+				filegroup {
+					name: "lineage_bin",
+					srcs: ["lineage.bin"],
+				}
+			`,
+			certificateOverride: "",
+			expectedLineage:     "--lineage lineage.bin",
+			expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8",
+		},
 	}
 
 	for _, test := range testCases {
diff --git a/java/boot_image.go b/java/boot_image.go
index 8a1e3c9..12e2874 100644
--- a/java/boot_image.go
+++ b/java/boot_image.go
@@ -25,10 +25,18 @@
 
 func init() {
 	RegisterBootImageBuildComponents(android.InitRegistrationContext)
+
+	android.RegisterSdkMemberType(&bootImageMemberType{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
+			PropertyName: "boot_images",
+			SupportsSdk:  true,
+		},
+	})
 }
 
 func RegisterBootImageBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("boot_image", bootImageFactory)
+	ctx.RegisterModuleType("prebuilt_boot_image", prebuiltBootImageFactory)
 }
 
 type bootImageProperties struct {
@@ -41,7 +49,7 @@
 type BootImageModule struct {
 	android.ModuleBase
 	android.ApexModuleBase
-
+	android.SdkBase
 	properties bootImageProperties
 }
 
@@ -50,6 +58,7 @@
 	m.AddProperties(&m.properties)
 	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
 	android.InitApexModule(m)
+	android.InitSdkAwareModule(m)
 	return m
 }
 
@@ -138,3 +147,74 @@
 	// Make it available for other modules.
 	ctx.SetProvider(BootImageInfoProvider, info)
 }
+
+type bootImageMemberType struct {
+	android.SdkMemberTypeBase
+}
+
+func (b *bootImageMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
+	mctx.AddVariationDependencies(nil, dependencyTag, names...)
+}
+
+func (b *bootImageMemberType) IsInstance(module android.Module) bool {
+	_, ok := module.(*BootImageModule)
+	return ok
+}
+
+func (b *bootImageMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
+	return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_boot_image")
+}
+
+func (b *bootImageMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
+	return &bootImageSdkMemberProperties{}
+}
+
+type bootImageSdkMemberProperties struct {
+	android.SdkMemberPropertiesBase
+
+	Image_name string
+}
+
+func (b *bootImageSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
+	module := variant.(*BootImageModule)
+
+	b.Image_name = module.properties.Image_name
+}
+
+func (b *bootImageSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
+	if b.Image_name != "" {
+		propertySet.AddProperty("image_name", b.Image_name)
+	}
+}
+
+var _ android.SdkMemberType = (*bootImageMemberType)(nil)
+
+// A prebuilt version of the boot image module.
+//
+// At the moment this is basically just a boot image module that can be used as a prebuilt.
+// Eventually as more functionality is migrated into the boot image module from the singleton then
+// this will diverge.
+type prebuiltBootImageModule struct {
+	BootImageModule
+	prebuilt android.Prebuilt
+}
+
+func (module *prebuiltBootImageModule) Prebuilt() *android.Prebuilt {
+	return &module.prebuilt
+}
+
+func (module *prebuiltBootImageModule) Name() string {
+	return module.prebuilt.Name(module.ModuleBase.Name())
+}
+
+func prebuiltBootImageFactory() android.Module {
+	m := &prebuiltBootImageModule{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+	// This doesn't actually have any prebuilt files of its own so pass a placeholder for the srcs
+	// array.
+	android.InitPrebuiltModule(m, &[]string{"placeholder"})
+	android.InitApexModule(m)
+	android.InitSdkAwareModule(m)
+	return m
+}
diff --git a/java/boot_image_test.go b/java/boot_image_test.go
index a295782..65e590d 100644
--- a/java/boot_image_test.go
+++ b/java/boot_image_test.go
@@ -29,3 +29,12 @@
 		}
 `)
 }
+
+func TestUnknownPrebuiltBootImage(t *testing.T) {
+	testJavaError(t, "image_name: Unknown image name \\\"unknown\\\", expected one of art, boot", `
+		prebuilt_boot_image {
+			name: "unknown-boot-image",
+			image_name: "unknown",
+		}
+`)
+}
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index 48bc244..1b910fa 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"path/filepath"
-	"reflect"
 	"sort"
 	"testing"
 
@@ -44,17 +43,11 @@
 		}
 	`
 
-	config := testConfig(nil, bp, nil)
+	result := javaFixtureFactory.
+		Extend(dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar", "platform:baz")).
+		RunTestWithBp(t, bp)
 
-	pathCtx := android.PathContextForTesting(config)
-	dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx)
-	dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList([]string{"platform:foo", "platform:bar", "platform:baz"})
-	dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig)
-
-	ctx := testContext(config)
-	run(t, ctx, config)
-
-	dexpreoptBootJars := ctx.SingletonForTests("dex_bootjars")
+	dexpreoptBootJars := result.SingletonForTests("dex_bootjars")
 	rule := dexpreoptBootJars.Output(ruleFile)
 
 	for i := range expectedInputs {
@@ -73,13 +66,9 @@
 	sort.Strings(outputs)
 	sort.Strings(expectedOutputs)
 
-	if !reflect.DeepEqual(inputs, expectedInputs) {
-		t.Errorf("want inputs %q\n got inputs %q", expectedInputs, inputs)
-	}
+	android.AssertDeepEquals(t, "inputs", expectedInputs, inputs)
 
-	if !reflect.DeepEqual(outputs, expectedOutputs) {
-		t.Errorf("want outputs %q\n got outputs %q", expectedOutputs, outputs)
-	}
+	android.AssertDeepEquals(t, "outputs", expectedOutputs, outputs)
 }
 
 func TestDexpreoptBootJars(t *testing.T) {
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 8f1644c..f0decec 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -1203,8 +1203,14 @@
 }
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
-	srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths, implicitsRsp android.WritablePath, sandbox bool) *android.RuleBuilderCommand {
+	srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths,
+	implicitsRsp, homeDir android.WritablePath, sandbox bool) *android.RuleBuilderCommand {
+	rule.Command().Text("rm -rf").Flag(homeDir.String())
+	rule.Command().Text("mkdir -p").Flag(homeDir.String())
+
 	cmd := rule.Command()
+	cmd.FlagWithArg("ANDROID_SDK_HOME=", homeDir.String())
+
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA") {
 		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
 		pool := ctx.Config().GetenvWithDefault("RBE_METALAVA_POOL", "metalava")
@@ -1214,17 +1220,21 @@
 			execStrategy = remoteexec.LocalExecStrategy
 			labels["shallow"] = "true"
 		}
-		inputs := []string{android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "metalava.jar").String()}
+		inputs := []string{
+			ctx.Config().HostJavaToolPath(ctx, "metalava").String(),
+			homeDir.String(),
+		}
 		if v := ctx.Config().Getenv("RBE_METALAVA_INPUTS"); v != "" {
 			inputs = append(inputs, strings.Split(v, ",")...)
 		}
 		cmd.Text((&remoteexec.REParams{
-			Labels:          labels,
-			ExecStrategy:    execStrategy,
-			Inputs:          inputs,
-			RSPFile:         implicitsRsp.String(),
-			ToolchainInputs: []string{config.JavaCmd(ctx).String()},
-			Platform:        map[string]string{remoteexec.PoolKey: pool},
+			Labels:               labels,
+			ExecStrategy:         execStrategy,
+			Inputs:               inputs,
+			RSPFile:              implicitsRsp.String(),
+			ToolchainInputs:      []string{config.JavaCmd(ctx).String()},
+			Platform:             map[string]string{remoteexec.PoolKey: pool},
+			EnvironmentVariables: []string{"ANDROID_SDK_HOME"},
 		}).NoVarTemplate(ctx.Config()))
 	}
 
@@ -1302,9 +1312,9 @@
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
 
 	implicitsRsp := android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"implicits.rsp")
-
+	homeDir := android.PathForModuleOut(ctx, "metalava-home")
 	cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList,
-		deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths, implicitsRsp,
+		deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths, implicitsRsp, homeDir,
 		Bool(d.Javadoc.properties.Sandbox))
 	cmd.Implicits(d.Javadoc.implicits)
 
diff --git a/java/java.go b/java/java.go
index 9e35835..567ebbb 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2499,7 +2499,7 @@
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	if j.testProperties.Test_options.Unit_test == nil && ctx.Host() {
 		// TODO(b/): Clean temporary heuristic to avoid unexpected onboarding.
-		defaultUnitTest := !inList("tradefed", j.properties.Static_libs) && !inList("tradefed", j.properties.Libs) && !inList("cts", j.testProperties.Test_suites) && !inList("robolectric-host-android_all", j.properties.Static_libs) && !inList("robolectric-host-android_all", j.properties.Libs)
+		defaultUnitTest := !inList("tradefed", j.properties.Static_libs) && !inList("tradefed", j.properties.Libs) && !inList("cts", j.testProperties.Test_suites)
 		j.testProperties.Test_options.Unit_test = proptools.BoolPtr(defaultUnitTest)
 	}
 	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template,
@@ -3319,10 +3319,6 @@
 //         ],
 //         javacflags: ["-Xlint:all"],
 //     }
-func defaultsFactory() android.Module {
-	return DefaultsFactory()
-}
-
 func DefaultsFactory() android.Module {
 	module := &Defaults{}
 
diff --git a/java/java_test.go b/java/java_test.go
index 9112655..670eefc 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -48,6 +48,26 @@
 	os.RemoveAll(buildDir)
 }
 
+// Factory to use to create fixtures for tests in this package.
+var javaFixtureFactory = android.NewFixtureFactory(
+	&buildDir,
+	genrule.PrepareForTestWithGenRuleBuildComponents,
+	// Get the CC build components but not default modules.
+	cc.PrepareForTestWithCcBuildComponents,
+	// Include all the default java modules.
+	PrepareForTestWithJavaDefaultModules,
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("java_plugin", PluginFactory)
+		ctx.RegisterModuleType("python_binary_host", python.PythonBinaryHostFactory)
+
+		ctx.PreDepsMutators(python.RegisterPythonPreDepsMutators)
+		ctx.RegisterPreSingletonType("overlay", OverlaySingletonFactory)
+		ctx.RegisterPreSingletonType("sdk_versions", sdkPreSingletonFactory)
+	}),
+	javaMockFS().AddToFixture(),
+	dexpreopt.PrepareForTestWithDexpreopt,
+)
+
 func TestMain(m *testing.M) {
 	run := func() int {
 		setUp()
@@ -59,10 +79,20 @@
 	os.Exit(run())
 }
 
+// testConfig is a legacy way of creating a test Config for testing java modules.
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testConfig(env map[string]string, bp string, fs map[string][]byte) android.Config {
 	return TestConfig(buildDir, env, bp, fs)
 }
 
+// testContext is a legacy way of creating a TestContext for testing java modules.
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testContext(config android.Config) *android.TestContext {
 
 	ctx := android.NewTestArchContext(config)
@@ -92,6 +122,11 @@
 	return ctx
 }
 
+// run is a legacy way of running tests of java modules.
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func run(t *testing.T, ctx *android.TestContext, config android.Config) {
 	t.Helper()
 
@@ -105,23 +140,38 @@
 	android.FailIfErrored(t, errs)
 }
 
+// testJavaError is a legacy way of running tests of java modules that expect errors.
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testJavaError(t *testing.T, pattern string, bp string) (*android.TestContext, android.Config) {
 	t.Helper()
 	return testJavaErrorWithConfig(t, pattern, testConfig(nil, bp, nil))
 }
 
+// testJavaErrorWithConfig is a legacy way of running tests of java modules that expect errors.
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testJavaErrorWithConfig(t *testing.T, pattern string, config android.Config) (*android.TestContext, android.Config) {
 	t.Helper()
-	ctx := testContext(config)
-
+	// This must be done on the supplied config and not as part of the fixture because any changes to
+	// the fixture's config will be ignored when RunTestWithConfig replaces it.
 	pathCtx := android.PathContextForTesting(config)
 	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
-
-	runWithErrors(t, ctx, config, pattern)
-
-	return ctx, config
+	result := javaFixtureFactory.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
+		RunTestWithConfig(t, config)
+	return result.TestContext, result.Config
 }
 
+// runWithErrors is a legacy way of running tests of java modules that expect errors.
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func runWithErrors(t *testing.T, ctx *android.TestContext, config android.Config, pattern string) {
 	ctx.Register()
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
@@ -139,22 +189,43 @@
 	return
 }
 
-func testJavaWithFS(t *testing.T, bp string, fs map[string][]byte) (*android.TestContext, android.Config) {
+// testJavaWithFS runs tests using the javaFixtureFactory
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
+func testJavaWithFS(t *testing.T, bp string, fs android.MockFS) (*android.TestContext, android.Config) {
 	t.Helper()
-	return testJavaWithConfig(t, testConfig(nil, bp, fs))
+	result := javaFixtureFactory.Extend(fs.AddToFixture()).RunTestWithBp(t, bp)
+	return result.TestContext, result.Config
 }
 
+// testJava runs tests using the javaFixtureFactory
+//
+// Do not add any new usages of this, instead use the javaFixtureFactory directly as it makes it
+// much easier to customize the test behavior.
+//
+// If it is necessary to customize the behavior of an existing test that uses this then please first
+// convert the test to using javaFixtureFactory first and then in a following change add the
+// appropriate fixture preparers. Keeping the conversion change separate makes it easy to verify
+// that it did not change the test behavior unexpectedly.
+//
+// deprecated
 func testJava(t *testing.T, bp string) (*android.TestContext, android.Config) {
 	t.Helper()
-	return testJavaWithFS(t, bp, nil)
+	result := javaFixtureFactory.RunTestWithBp(t, bp)
+	return result.TestContext, result.Config
 }
 
+// testJavaWithConfig runs tests using the javaFixtureFactory
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
 func testJavaWithConfig(t *testing.T, config android.Config) (*android.TestContext, android.Config) {
 	t.Helper()
-	ctx := testContext(config)
-	run(t, ctx, config)
-
-	return ctx, config
+	result := javaFixtureFactory.RunTestWithConfig(t, config)
+	return result.TestContext, result.Config
 }
 
 func moduleToPath(name string) string {
@@ -168,6 +239,12 @@
 	}
 }
 
+// defaultModuleToPath constructs a path to the turbine generate jar for a default test module that
+// is defined in PrepareForIntegrationTestWithJava
+func defaultModuleToPath(name string) string {
+	return filepath.Join(buildDir, ".intermediates", defaultJavaDir, name, "android_common", "turbine-combined", name+".jar")
+}
+
 func TestJavaLinkType(t *testing.T) {
 	testJava(t, `
 		java_library {
@@ -2352,75 +2429,11 @@
 		expected := "java.base=.:" + buildDir
 		checkPatchModuleFlag(t, ctx, "bar", expected)
 		expected = "java.base=" + strings.Join([]string{
-			".", buildDir, "dir", "dir2", "nested", moduleToPath("ext"), moduleToPath("framework")}, ":")
+			".", buildDir, "dir", "dir2", "nested", defaultModuleToPath("ext"), defaultModuleToPath("framework")}, ":")
 		checkPatchModuleFlag(t, ctx, "baz", expected)
 	})
 }
 
-func TestJavaSystemModules(t *testing.T) {
-	ctx, _ := testJava(t, `
-		java_system_modules {
-			name: "system-modules",
-			libs: ["system-module1", "system-module2"],
-		}
-		java_library {
-			name: "system-module1",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "none",
-		}
-		java_library {
-			name: "system-module2",
-			srcs: ["b.java"],
-			sdk_version: "none",
-			system_modules: "none",
-		}
-		`)
-
-	// check the existence of the module
-	systemModules := ctx.ModuleForTests("system-modules", "android_common")
-
-	cmd := systemModules.Rule("jarsTosystemModules")
-
-	// make sure the command compiles against the supplied modules.
-	for _, module := range []string{"system-module1.jar", "system-module2.jar"} {
-		if !strings.Contains(cmd.Args["classpath"], module) {
-			t.Errorf("system modules classpath %v does not contain %q", cmd.Args["classpath"],
-				module)
-		}
-	}
-}
-
-func TestJavaSystemModulesImport(t *testing.T) {
-	ctx, _ := testJava(t, `
-		java_system_modules_import {
-			name: "system-modules",
-			libs: ["system-module1", "system-module2"],
-		}
-		java_import {
-			name: "system-module1",
-			jars: ["a.jar"],
-		}
-		java_import {
-			name: "system-module2",
-			jars: ["b.jar"],
-		}
-		`)
-
-	// check the existence of the module
-	systemModules := ctx.ModuleForTests("system-modules", "android_common")
-
-	cmd := systemModules.Rule("jarsTosystemModules")
-
-	// make sure the command compiles against the supplied modules.
-	for _, module := range []string{"system-module1.jar", "system-module2.jar"} {
-		if !strings.Contains(cmd.Args["classpath"], module) {
-			t.Errorf("system modules classpath %v does not contain %q", cmd.Args["classpath"],
-				module)
-		}
-	}
-}
-
 func TestJavaLibraryWithSystemModules(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_library {
diff --git a/java/lint.go b/java/lint.go
index 50b84dc..9f677db 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -22,6 +22,8 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/java/config"
+	"android/soong/remoteexec"
 )
 
 type LintProperties struct {
@@ -172,8 +174,44 @@
 		extraLintCheckTag, extraCheckModules...)
 }
 
-func (l *linter) writeLintProjectXML(ctx android.ModuleContext,
-	rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir, homeDir android.WritablePath, deps android.Paths) {
+// lintPaths contains the paths to lint's inputs and outputs to make it easier to pass them
+// around.
+type lintPaths struct {
+	projectXML android.WritablePath
+	configXML  android.WritablePath
+	cacheDir   android.WritablePath
+	homeDir    android.WritablePath
+	srcjarDir  android.WritablePath
+
+	deps android.Paths
+
+	remoteInputs    android.Paths
+	remoteRSPInputs android.Paths
+}
+
+func lintRBEExecStrategy(ctx android.ModuleContext) string {
+	return ctx.Config().GetenvWithDefault("RBE_LINT_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
+}
+
+func (l *linter) writeLintProjectXML(ctx android.ModuleContext, rule *android.RuleBuilder) lintPaths {
+	var deps android.Paths
+	var remoteInputs android.Paths
+	var remoteRSPInputs android.Paths
+
+	// Paths passed to trackInputDependency will be added as dependencies of the rule that runs
+	// lint and passed as inputs to the remote execution proxy.
+	trackInputDependency := func(paths ...android.Path) {
+		deps = append(deps, paths...)
+		remoteInputs = append(remoteInputs, paths...)
+	}
+
+	// Paths passed to trackRSPDependency will be added as dependencies of the rule that runs
+	// lint, but the RSP file will be used by the remote execution proxy to find the files so that
+	// it doesn't overflow command line limits.
+	trackRSPDependency := func(paths android.Paths, rsp android.Path) {
+		deps = append(deps, paths...)
+		remoteRSPInputs = append(remoteRSPInputs, rsp)
+	}
 
 	var resourcesList android.WritablePath
 	if len(l.resources) > 0 {
@@ -184,17 +222,27 @@
 		resListRule := android.NewRuleBuilder(pctx, ctx)
 		resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList)
 		resListRule.Build("lint_resources_list", "lint resources list")
-		deps = append(deps, l.resources...)
+		trackRSPDependency(l.resources, resourcesList)
 	}
 
-	projectXMLPath = android.PathForModuleOut(ctx, "lint", "project.xml")
+	projectXMLPath := android.PathForModuleOut(ctx, "lint", "project.xml")
 	// Lint looks for a lint.xml file next to the project.xml file, give it one.
-	configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml")
-	cacheDir = android.PathForModuleOut(ctx, "lint", "cache")
-	homeDir = android.PathForModuleOut(ctx, "lint", "home")
+	configXMLPath := android.PathForModuleOut(ctx, "lint", "lint.xml")
+	cacheDir := android.PathForModuleOut(ctx, "lint", "cache")
+	homeDir := android.PathForModuleOut(ctx, "lint", "home")
 
 	srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars")
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
+	// TODO(ccross): this is a little fishy.  The files extracted from the srcjars are referenced
+	// by the project.xml and used by the later lint rule, but the lint rule depends on the srcjars,
+	// not the extracted files.
+	trackRSPDependency(l.srcJars, srcJarList)
+
+	// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
+	// lint separately.
+	srcsList := android.PathForModuleOut(ctx, "lint", "srcs.list")
+	rule.Command().Text("cp").FlagWithRspFileInputList("", l.srcs).Output(srcsList)
+	trackRSPDependency(l.srcs, srcsList)
 
 	cmd := rule.Command().
 		BuiltTool("lint-project-xml").
@@ -209,38 +257,40 @@
 		cmd.Flag("--test")
 	}
 	if l.manifest != nil {
-		deps = append(deps, l.manifest)
 		cmd.FlagWithArg("--manifest ", l.manifest.String())
+		trackInputDependency(l.manifest)
 	}
 	if l.mergedManifest != nil {
-		deps = append(deps, l.mergedManifest)
 		cmd.FlagWithArg("--merged_manifest ", l.mergedManifest.String())
+		trackInputDependency(l.mergedManifest)
 	}
 
-	// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
-	// lint separately.
-	cmd.FlagWithRspFileInputList("--srcs ", l.srcs)
-	deps = append(deps, l.srcs...)
+	cmd.FlagWithInput("--srcs ", srcsList)
 
 	cmd.FlagWithInput("--generated_srcs ", srcJarList)
-	deps = append(deps, l.srcJars...)
 
 	if resourcesList != nil {
 		cmd.FlagWithInput("--resources ", resourcesList)
 	}
 
 	if l.classes != nil {
-		deps = append(deps, l.classes)
 		cmd.FlagWithArg("--classes ", l.classes.String())
+		trackInputDependency(l.classes)
 	}
 
 	cmd.FlagForEachArg("--classpath ", l.classpath.Strings())
-	deps = append(deps, l.classpath...)
+	trackInputDependency(l.classpath...)
 
 	cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings())
-	deps = append(deps, l.extraLintCheckJars...)
+	trackInputDependency(l.extraLintCheckJars...)
 
-	cmd.FlagWithArg("--root_dir ", "$PWD")
+	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_LINT") &&
+		lintRBEExecStrategy(ctx) != remoteexec.LocalExecStrategy {
+		// TODO(b/181912787): remove these and use "." instead.
+		cmd.FlagWithArg("--root_dir ", "/b/f/w")
+	} else {
+		cmd.FlagWithArg("--root_dir ", "$PWD")
+	}
 
 	// The cache tag in project.xml is relative to the root dir, or the project.xml file if
 	// the root dir is not set.
@@ -254,7 +304,18 @@
 	cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
 	cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
 
-	return projectXMLPath, configXMLPath, cacheDir, homeDir, deps
+	return lintPaths{
+		projectXML: projectXMLPath,
+		configXML:  configXMLPath,
+		cacheDir:   cacheDir,
+		homeDir:    homeDir,
+
+		deps: deps,
+
+		remoteInputs:    remoteInputs,
+		remoteRSPInputs: remoteRSPInputs,
+	}
+
 }
 
 // generateManifest adds a command to the rule to write a simple manifest that contains the
@@ -297,7 +358,7 @@
 		l.manifest = manifest
 	}
 
-	projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule)
+	lintPaths := l.writeLintProjectXML(ctx, rule)
 
 	html := android.PathForModuleOut(ctx, "lint-report.html")
 	text := android.PathForModuleOut(ctx, "lint-report.txt")
@@ -311,8 +372,8 @@
 		}
 	})
 
-	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
-	rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String())
+	rule.Command().Text("rm -rf").Flag(lintPaths.cacheDir.String()).Flag(lintPaths.homeDir.String())
+	rule.Command().Text("mkdir -p").Flag(lintPaths.cacheDir.String()).Flag(lintPaths.homeDir.String())
 	rule.Command().Text("rm -f").Output(html).Output(text).Output(xml)
 
 	var annotationsZipPath, apiVersionsXMLPath android.Path
@@ -324,16 +385,53 @@
 		apiVersionsXMLPath = copiedAPIVersionsXmlPath(ctx)
 	}
 
-	cmd := rule.Command().
-		Text("(").
-		Flag("JAVA_OPTS=-Xmx3072m").
-		FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
+	cmd := rule.Command()
+
+	cmd.Flag("JAVA_OPTS=-Xmx3072m").
+		FlagWithArg("ANDROID_SDK_HOME=", lintPaths.homeDir.String()).
 		FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath).
-		FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath).
-		BuiltTool("lint").
+		FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath)
+
+	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_LINT") {
+		pool := ctx.Config().GetenvWithDefault("RBE_LINT_POOL", "java16")
+		// TODO(b/181912787): this should be local fallback once the hack that passes /b/f/w in project.xml
+		// is removed.
+		execStrategy := lintRBEExecStrategy(ctx)
+		labels := map[string]string{"type": "tool", "name": "lint"}
+		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
+		remoteInputs := lintPaths.remoteInputs
+		remoteInputs = append(remoteInputs,
+			lintPaths.projectXML,
+			lintPaths.configXML,
+			lintPaths.homeDir,
+			lintPaths.cacheDir,
+			ctx.Config().HostJavaToolPath(ctx, "lint.jar"),
+			annotationsZipPath,
+			apiVersionsXMLPath,
+		)
+
+		cmd.Text((&remoteexec.REParams{
+			Labels:          labels,
+			ExecStrategy:    execStrategy,
+			ToolchainInputs: []string{config.JavaCmd(ctx).String()},
+			Inputs:          remoteInputs.Strings(),
+			OutputFiles:     android.Paths{html, text, xml}.Strings(),
+			RSPFile:         strings.Join(lintPaths.remoteRSPInputs.Strings(), ","),
+			EnvironmentVariables: []string{
+				"JAVA_OPTS",
+				"ANDROID_SDK_HOME",
+				"SDK_ANNOTATIONS",
+				"LINT_OPTS",
+				"LANG",
+			},
+			Platform: map[string]string{remoteexec.PoolKey: pool},
+		}).NoVarTemplate(ctx.Config()))
+	}
+
+	cmd.BuiltTool("lint").
 		Flag("--quiet").
-		FlagWithInput("--project ", projectXML).
-		FlagWithInput("--config ", lintXML).
+		FlagWithInput("--project ", lintPaths.projectXML).
+		FlagWithInput("--config ", lintPaths.configXML).
 		FlagWithOutput("--html ", html).
 		FlagWithOutput("--text ", text).
 		FlagWithOutput("--xml ", xml).
@@ -343,7 +441,9 @@
 		FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
 		Flag("--exitcode").
 		Flags(l.properties.Lint.Flags).
-		Implicits(deps)
+		Implicit(annotationsZipPath).
+		Implicit(apiVersionsXMLPath).
+		Implicits(lintPaths.deps)
 
 	if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" {
 		cmd.FlagWithArg("--check ", checkOnly)
@@ -362,9 +462,9 @@
 		}
 	}
 
-	cmd.Text("|| (").Text("if [ -e").Input(text).Text("]; then cat").Input(text).Text("; fi; exit 7)").Text(")")
+	cmd.Text("|| (").Text("if [ -e").Input(text).Text("]; then cat").Input(text).Text("; fi; exit 7)")
 
-	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
+	rule.Command().Text("rm -rf").Flag(lintPaths.cacheDir.String()).Flag(lintPaths.homeDir.String())
 
 	rule.Build("lint", "lint")
 
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index c91b321..8a442b5 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -178,7 +178,7 @@
 	props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, "system_modules", "public", apiver))
 	props.Libs = append(props.Libs, prebuiltApiModuleName(mctx, "core-for-system-modules", "public", apiver))
 
-	mctx.CreateModule(SystemModulesFactory, &props)
+	mctx.CreateModule(systemModulesImportFactory, &props)
 }
 
 func prebuiltSdkSystemModules(mctx android.LoadHookContext, p *prebuiltApis) {
@@ -248,9 +248,9 @@
 	}
 
 	// Create incompatibilities tracking files for all modules, if we have a "next" api.
+	incompatibilities := make(map[string]bool)
 	if nextApiDir := String(p.properties.Next_api_dir); nextApiDir != "" {
 		files := getPrebuiltFilesInSubdir(mctx, nextApiDir, "api/*incompatibilities.txt")
-		incompatibilities := make(map[string]bool)
 		for _, f := range files {
 			localPath := strings.TrimPrefix(f, mydir)
 			module, _, scope := parseApiFilePath(mctx, localPath)
@@ -266,11 +266,11 @@
 
 			incompatibilities[referencedModule+"."+scope] = true
 		}
-		// Create empty incompatibilities files for remaining modules
-		for _, k := range android.SortedStringKeys(m) {
-			if _, ok := incompatibilities[k]; !ok {
-				createEmptyFile(mctx, apiModuleName(m[k].module+"-incompatibilities", m[k].scope, "latest"))
-			}
+	}
+	// Create empty incompatibilities files for remaining modules
+	for _, k := range android.SortedStringKeys(m) {
+		if _, ok := incompatibilities[k]; !ok {
+			createEmptyFile(mctx, apiModuleName(m[k].module+"-incompatibilities", m[k].scope, "latest"))
 		}
 	}
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 30d120d..b03f90c 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -452,6 +452,7 @@
 	// that references the latest released API and remove API specification files.
 	// * API specification filegroup -> <dist-stem>.api.<scope>.latest
 	// * Removed API specification filegroup -> <dist-stem>-removed.api.<scope>.latest
+	// * API incompatibilities baseline filegroup -> <dist-stem>-incompatibilities.api.<scope>.latest
 	Dist_stem *string
 
 	// A compatibility mode that allows historical API-tracking files to not exist.
@@ -1059,6 +1060,9 @@
 		if m := android.SrcIsModule(module.latestRemovedApiFilegroupName(apiScope)); !ctx.OtherModuleExists(m) {
 			missingApiModules = append(missingApiModules, m)
 		}
+		if m := android.SrcIsModule(module.latestIncompatibilitiesFilegroupName(apiScope)); !ctx.OtherModuleExists(m) {
+			missingApiModules = append(missingApiModules, m)
+		}
 	}
 	if len(missingApiModules) != 0 && !module.sdkLibraryProperties.Unsafe_ignore_missing_latest_api {
 		m := module.Name() + " is missing tracking files for previously released library versions.\n"
@@ -1165,6 +1169,10 @@
 	return ":" + module.distStem() + "-removed.api." + apiScope.name + ".latest"
 }
 
+func (module *SdkLibrary) latestIncompatibilitiesFilegroupName(apiScope *apiScope) string {
+	return ":" + module.distStem() + "-incompatibilities.api." + apiScope.name + ".latest"
+}
+
 func childModuleVisibility(childVisibility []string) []string {
 	if childVisibility == nil {
 		// No child visibility set. The child will use the visibility of the sdk_library.
@@ -1389,6 +1397,8 @@
 		props.Check_api.Last_released.Api_file = latestApiFilegroupName
 		props.Check_api.Last_released.Removed_api_file = proptools.StringPtr(
 			module.latestRemovedApiFilegroupName(apiScope))
+		props.Check_api.Last_released.Baseline_file = proptools.StringPtr(
+			module.latestIncompatibilitiesFilegroupName(apiScope))
 
 		if proptools.Bool(module.sdkLibraryProperties.Api_lint.Enabled) {
 			// Enable api lint.
diff --git a/java/system_modules.go b/java/system_modules.go
index 95f71b8..8c69051 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -169,7 +169,13 @@
 	system.outputDir, system.outputDeps = TransformJarsToSystemModules(ctx, jars)
 }
 
-func (system *SystemModules) DepsMutator(ctx android.BottomUpMutatorContext) {
+// ComponentDepsMutator is called before prebuilt modules without a corresponding source module are
+// renamed so unless the supplied libs specifically includes the prebuilt_ prefix this is guaranteed
+// to only add dependencies on source modules.
+//
+// The systemModuleLibsTag will prevent the prebuilt mutators from replacing this dependency so it
+// will never be changed to depend on a prebuilt either.
+func (system *SystemModules) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
 	ctx.AddVariationDependencies(nil, systemModulesLibsTag, system.properties.Libs...)
 }
 
@@ -225,6 +231,15 @@
 	return &system.prebuilt
 }
 
+// ComponentDepsMutator is called before prebuilt modules without a corresponding source module are
+// renamed so as this adds a prebuilt_ prefix this is guaranteed to only add dependencies on source
+// modules.
+func (system *systemModulesImport) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
+	for _, lib := range system.properties.Libs {
+		ctx.AddVariationDependencies(nil, systemModulesLibsTag, "prebuilt_"+lib)
+	}
+}
+
 type systemModulesSdkMemberType struct {
 	android.SdkMemberTypeBase
 }
diff --git a/java/system_modules_test.go b/java/system_modules_test.go
new file mode 100644
index 0000000..3d9f398
--- /dev/null
+++ b/java/system_modules_test.go
@@ -0,0 +1,112 @@
+// 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 java
+
+import (
+	"testing"
+
+	"android/soong/android"
+)
+
+func getModuleHeaderJarsAsNormalizedPaths(result *android.TestResult, moduleNames ...string) []string {
+	paths := []string{}
+	for _, moduleName := range moduleNames {
+		module := result.Module(moduleName, "android_common")
+		info := result.ModuleProvider(module, JavaInfoProvider).(JavaInfo)
+		paths = append(paths, result.NormalizePathsForTesting(info.HeaderJars)...)
+	}
+	return paths
+}
+
+var addSourceSystemModules = android.FixtureAddTextFile("source/Android.bp", `
+		java_system_modules {
+			name: "system-modules",
+			libs: ["system-module1", "system-module2"],
+		}
+		java_library {
+			name: "system-module1",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+		}
+		java_library {
+			name: "system-module2",
+			srcs: ["b.java"],
+			sdk_version: "none",
+			system_modules: "none",
+		}
+`)
+
+func TestJavaSystemModules(t *testing.T) {
+	result := javaFixtureFactory.RunTest(t, addSourceSystemModules)
+
+	// check the existence of the source module
+	sourceSystemModules := result.ModuleForTests("system-modules", "android_common")
+	sourceInputs := sourceSystemModules.Rule("jarsTosystemModules").Inputs
+
+	// The expected paths are the header jars from the source input modules.
+	expectedSourcePaths := getModuleHeaderJarsAsNormalizedPaths(result, "system-module1", "system-module2")
+	android.AssertArrayString(t, "source system modules inputs", expectedSourcePaths, result.NormalizePathsForTesting(sourceInputs))
+}
+
+var addPrebuiltSystemModules = android.FixtureAddTextFile("prebuilts/Android.bp", `
+		java_system_modules_import {
+			name: "system-modules",
+			libs: ["system-module1", "system-module2"],
+		}
+		java_import {
+			name: "system-module1",
+			jars: ["a.jar"],
+		}
+		java_import {
+			name: "system-module2",
+			jars: ["b.jar"],
+		}
+`)
+
+func TestJavaSystemModulesImport(t *testing.T) {
+	result := javaFixtureFactory.RunTest(t, addPrebuiltSystemModules)
+
+	// check the existence of the renamed prebuilt module
+	prebuiltSystemModules := result.ModuleForTests("system-modules", "android_common")
+	prebuiltInputs := prebuiltSystemModules.Rule("jarsTosystemModules").Inputs
+
+	// The expected paths are the header jars from the renamed prebuilt input modules.
+	expectedPrebuiltPaths := getModuleHeaderJarsAsNormalizedPaths(result, "system-module1", "system-module2")
+	android.AssertArrayString(t, "renamed prebuilt system modules inputs", expectedPrebuiltPaths, result.NormalizePathsForTesting(prebuiltInputs))
+}
+
+func TestJavaSystemModulesMixSourceAndPrebuilt(t *testing.T) {
+	result := javaFixtureFactory.RunTest(t,
+		addSourceSystemModules,
+		addPrebuiltSystemModules,
+	)
+
+	// check the existence of the source module
+	sourceSystemModules := result.ModuleForTests("system-modules", "android_common")
+	sourceInputs := sourceSystemModules.Rule("jarsTosystemModules").Inputs
+
+	// The expected paths are the header jars from the source input modules.
+	expectedSourcePaths := getModuleHeaderJarsAsNormalizedPaths(result, "system-module1", "system-module2")
+	android.AssertArrayString(t, "source system modules inputs", expectedSourcePaths, result.NormalizePathsForTesting(sourceInputs))
+
+	// check the existence of the renamed prebuilt module
+	prebuiltSystemModules := result.ModuleForTests("prebuilt_system-modules", "android_common")
+	prebuiltInputs := prebuiltSystemModules.Rule("jarsTosystemModules").Inputs
+
+	// The expected paths are the header jars from the renamed prebuilt input modules.
+	expectedPrebuiltPaths := getModuleHeaderJarsAsNormalizedPaths(result, "prebuilt_system-module1", "prebuilt_system-module2")
+	android.AssertArrayString(t, "prebuilt system modules inputs", expectedPrebuiltPaths, result.NormalizePathsForTesting(prebuiltInputs))
+}
diff --git a/java/testing.go b/java/testing.go
index bfa1e6b..4e1997e 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -29,10 +29,40 @@
 	"github.com/google/blueprint"
 )
 
-func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) android.Config {
-	bp += GatherRequiredDepsForTest()
+const defaultJavaDir = "default/java"
 
-	mockFS := map[string][]byte{
+// Test fixture preparer that will register most java build components.
+//
+// Singletons and mutators should only be added here if they are needed for a majority of java
+// module types, otherwise they should be added under a separate preparer to allow them to be
+// selected only when needed to reduce test execution time.
+//
+// Module types do not have much of an overhead unless they are used so this should include as many
+// module types as possible. The exceptions are those module types that require mutators and/or
+// singletons in order to function in which case they should be kept together in a separate
+// preparer.
+var PrepareForTestWithJavaBuildComponents = android.FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest)
+
+// Test fixture preparer that will define default java modules, e.g. standard prebuilt modules.
+var PrepareForTestWithJavaDefaultModules = android.GroupFixturePreparers(
+	// Make sure that mutators and module types, e.g. prebuilt mutators available.
+	android.PrepareForTestWithAndroidBuildComponents,
+	// Make sure that all the module types used in the defaults are registered.
+	PrepareForTestWithJavaBuildComponents,
+	// The java default module definitions.
+	android.FixtureAddTextFile(defaultJavaDir+"/Android.bp", GatherRequiredDepsForTest()),
+)
+
+// Prepare a fixture to use all java module types, mutators and singletons fully.
+//
+// This should only be used by tests that want to run with as much of the build enabled as possible.
+var PrepareForIntegrationTestWithJava = android.GroupFixturePreparers(
+	cc.PrepareForIntegrationTestWithCc,
+	PrepareForTestWithJavaDefaultModules,
+)
+
+func javaMockFS() android.MockFS {
+	mockFS := android.MockFS{
 		"api/current.txt":        nil,
 		"api/removed.txt":        nil,
 		"api/system-current.txt": nil,
@@ -64,6 +94,14 @@
 		mockFS[k] = v
 	}
 
+	return mockFS
+}
+
+func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) android.Config {
+	bp += GatherRequiredDepsForTest()
+
+	mockFS := javaMockFS()
+
 	cc.GatherRequiredFilesForTest(mockFS)
 
 	for k, v := range fs {
diff --git a/python/binary.go b/python/binary.go
index 416a7ee..372b8a8 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -20,10 +20,92 @@
 	"fmt"
 
 	"android/soong/android"
+	"android/soong/bazel"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
 	android.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
+	android.RegisterBp2BuildMutator("python_binary_host", PythonBinaryBp2Build)
+}
+
+type bazelPythonBinaryAttributes struct {
+	Main           string
+	Srcs           bazel.LabelList
+	Data           bazel.LabelList
+	Python_version string
+}
+
+type bazelPythonBinary struct {
+	android.BazelTargetModuleBase
+	bazelPythonBinaryAttributes
+}
+
+func BazelPythonBinaryFactory() android.Module {
+	module := &bazelPythonBinary{}
+	module.AddProperties(&module.bazelPythonBinaryAttributes)
+	android.InitBazelTargetModule(module)
+	return module
+}
+
+func (m *bazelPythonBinary) Name() string {
+	return m.BaseModuleName()
+}
+
+func (m *bazelPythonBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+
+func PythonBinaryBp2Build(ctx android.TopDownMutatorContext) {
+	m, ok := ctx.Module().(*Module)
+	if !ok || !m.ConvertWithBp2build() {
+		return
+	}
+
+	// a Module can be something other than a python_binary_host
+	if ctx.ModuleType() != "python_binary_host" {
+		return
+	}
+
+	var main string
+	for _, propIntf := range m.GetProperties() {
+		if props, ok := propIntf.(*BinaryProperties); ok {
+			// main is optional.
+			if props.Main != nil {
+				main = *props.Main
+				break
+			}
+		}
+	}
+	// TODO(b/182306917): this doesn't fully handle all nested props versioned
+	// by the python version, which would have been handled by the version split
+	// mutator. This is sufficient for very simple python_binary_host modules
+	// under Bionic.
+	py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, false)
+	py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
+	var python_version string
+	if py3Enabled && py2Enabled {
+		panic(fmt.Errorf(
+			"error for '%s' module: bp2build's python_binary_host converter does not support "+
+				"converting a module that is enabled for both Python 2 and 3 at the same time.", m.Name()))
+	} else if py2Enabled {
+		python_version = "PY2"
+	} else {
+		// do nothing, since python_version defaults to PY3.
+	}
+
+	attrs := &bazelPythonBinaryAttributes{
+		Main:           main,
+		Srcs:           android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs),
+		Data:           android.BazelLabelForModuleSrc(ctx, m.properties.Data),
+		Python_version: python_version,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		// Use the native py_binary rule.
+		Rule_class: "py_binary",
+	}
+
+	ctx.CreateBazelTargetModule(BazelPythonBinaryFactory, m.Name(), props, attrs)
 }
 
 type BinaryProperties struct {
@@ -81,6 +163,8 @@
 func PythonBinaryHostFactory() android.Module {
 	module, _ := NewBinary(android.HostSupported)
 
+	android.InitBazelModule(module)
+
 	return module.init()
 }
 
diff --git a/python/python.go b/python/python.go
index b3e3d13..a078c0b 100644
--- a/python/python.go
+++ b/python/python.go
@@ -125,6 +125,7 @@
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
+	android.BazelModuleBase
 
 	properties      BaseProperties
 	protoProperties android.ProtoProperties
diff --git a/remoteexec/remoteexec.go b/remoteexec/remoteexec.go
index d6e2c0a..5f0426a 100644
--- a/remoteexec/remoteexec.go
+++ b/remoteexec/remoteexec.go
@@ -81,6 +81,9 @@
 	// ToolchainInputs is a list of paths or ninja variables pointing to the location of
 	// toolchain binaries used by the rule.
 	ToolchainInputs []string
+	// EnvironmentVariables is a list of environment variables whose values should be passed through
+	// to the remote execution.
+	EnvironmentVariables []string
 }
 
 func init() {
@@ -162,6 +165,10 @@
 		args += " --toolchain_inputs=" + strings.Join(r.ToolchainInputs, ",")
 	}
 
+	if len(r.EnvironmentVariables) > 0 {
+		args += " --env_var_allowlist=" + strings.Join(r.EnvironmentVariables, ",")
+	}
+
 	return args + " -- "
 }
 
diff --git a/rust/testing.go b/rust/testing.go
index 9534ab5..5be71c9 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -20,6 +20,30 @@
 	"android/soong/genrule"
 )
 
+// Preparer that will define all cc module types and a limited set of mutators and singletons that
+// make those module types usable.
+var PrepareForTestWithRustBuildComponents = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
+)
+
+// The directory in which rust test default modules will be defined.
+//
+// Placing them here ensures that their location does not conflict with default test modules
+// defined by other packages.
+const rustDefaultsDir = "defaults/rust/"
+
+// Preparer that will define default rust modules, e.g. standard prebuilt modules.
+var PrepareForTestWithRustDefaultModules = android.GroupFixturePreparers(
+	cc.PrepareForTestWithCcDefaultModules,
+	PrepareForTestWithRustBuildComponents,
+	android.FixtureAddTextFile(rustDefaultsDir+"Android.bp", GatherRequiredDepsForTest()),
+)
+
+// Preparer that will allow use of all rust modules fully.
+var PrepareForIntegrationTestWithRust = android.GroupFixturePreparers(
+	PrepareForTestWithRustDefaultModules,
+)
+
 func GatherRequiredDepsForTest() string {
 	bp := `
 		rust_prebuilt_library {
diff --git a/sdk/Android.bp b/sdk/Android.bp
index 8a3119c..6e49c6d 100644
--- a/sdk/Android.bp
+++ b/sdk/Android.bp
@@ -20,6 +20,7 @@
         "update.go",
     ],
     testSrcs: [
+        "boot_image_sdk_test.go",
         "bp_test.go",
         "cc_sdk_test.go",
         "exports_test.go",
diff --git a/sdk/boot_image_sdk_test.go b/sdk/boot_image_sdk_test.go
new file mode 100644
index 0000000..9805a6a
--- /dev/null
+++ b/sdk/boot_image_sdk_test.go
@@ -0,0 +1,62 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 sdk
+
+import "testing"
+
+func TestSnapshotWithBootImage(t *testing.T) {
+	result := testSdkWithJava(t, `
+		sdk {
+			name: "mysdk",
+			boot_images: ["mybootimage"],
+		}
+
+		boot_image {
+			name: "mybootimage",
+			image_name: "art",
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_boot_image {
+    name: "mybootimage",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    image_name: "art",
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_boot_image {
+    name: "mysdk_mybootimage@current",
+    sdk_member_name: "mybootimage",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    image_name: "art",
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    boot_images: ["mysdk_mybootimage@current"],
+}
+`),
+		checkAllCopyRules(""))
+}
diff --git a/sdk/bp_test.go b/sdk/bp_test.go
index e1edc51..2bd8a43 100644
--- a/sdk/bp_test.go
+++ b/sdk/bp_test.go
@@ -54,26 +54,25 @@
 	return str
 }
 
-func checkPropertySetFixture(h *TestHelper, val interface{}, hasTags bool) {
+func checkPropertySetFixture(t *testing.T, val interface{}, hasTags bool) {
 	set := val.(*bpPropertySet)
-	h.AssertDeepEquals("wrong x value", "taxi", set.getValue("x"))
-	h.AssertDeepEquals("wrong y value", 1729, set.getValue("y"))
+	android.AssertDeepEquals(t, "wrong x value", "taxi", set.getValue("x"))
+	android.AssertDeepEquals(t, "wrong y value", 1729, set.getValue("y"))
 
 	subset := set.getValue("sub").(*bpPropertySet)
-	h.AssertDeepEquals("wrong sub.x value", "taxi", subset.getValue("x"))
-	h.AssertDeepEquals("wrong sub.y value", 1729, subset.getValue("y"))
+	android.AssertDeepEquals(t, "wrong sub.x value", "taxi", subset.getValue("x"))
+	android.AssertDeepEquals(t, "wrong sub.y value", 1729, subset.getValue("y"))
 
 	if hasTags {
-		h.AssertDeepEquals("wrong y tag", "tag_y", set.getTag("y"))
-		h.AssertDeepEquals("wrong sub.x tag", "tag_x", subset.getTag("x"))
+		android.AssertDeepEquals(t, "wrong y tag", "tag_y", set.getTag("y"))
+		android.AssertDeepEquals(t, "wrong sub.x tag", "tag_x", subset.getTag("x"))
 	} else {
-		h.AssertDeepEquals("wrong y tag", nil, set.getTag("y"))
-		h.AssertDeepEquals("wrong sub.x tag", nil, subset.getTag("x"))
+		android.AssertDeepEquals(t, "wrong y tag", nil, set.getTag("y"))
+		android.AssertDeepEquals(t, "wrong sub.x tag", nil, subset.getTag("x"))
 	}
 }
 
 func TestAddPropertySimple(t *testing.T) {
-	h := &TestHelper{t}
 	set := newPropertySet()
 	for name, val := range map[string]interface{}{
 		"x":   "taxi",
@@ -83,16 +82,15 @@
 		"arr": []string{"a", "b", "c"},
 	} {
 		set.AddProperty(name, val)
-		h.AssertDeepEquals("wrong value", val, set.getValue(name))
+		android.AssertDeepEquals(t, "wrong value", val, set.getValue(name))
 	}
-	h.AssertPanic("adding x again should panic",
+	android.AssertPanic(t, "adding x again should panic",
 		func() { set.AddProperty("x", "taxi") })
-	h.AssertPanic("adding arr again should panic",
+	android.AssertPanic(t, "adding arr again should panic",
 		func() { set.AddProperty("arr", []string{"d"}) })
 }
 
 func TestAddPropertySubset(t *testing.T) {
-	h := &TestHelper{t}
 	getFixtureMap := map[string]func() interface{}{
 		"property set":    propertySetFixture,
 		"property struct": propertyStructFixture,
@@ -103,8 +101,8 @@
 			t.Run(name, func(t *testing.T) {
 				set := propertySetFixture().(*bpPropertySet)
 				set.AddProperty("new", getFixture())
-				checkPropertySetFixture(h, set, true)
-				checkPropertySetFixture(h, set.getValue("new"), name == "property set")
+				checkPropertySetFixture(t, set, true)
+				checkPropertySetFixture(t, set.getValue("new"), name == "property set")
 			})
 		}
 	})
@@ -118,40 +116,38 @@
 				subset.AddPropertySet("sub")
 				set.AddProperty("sub", getFixture())
 				merged := set.getValue("sub").(*bpPropertySet)
-				h.AssertDeepEquals("wrong flag value", false, merged.getValue("flag"))
-				checkPropertySetFixture(h, merged, name == "property set")
+				android.AssertDeepEquals(t, "wrong flag value", false, merged.getValue("flag"))
+				checkPropertySetFixture(t, merged, name == "property set")
 			})
 		}
 	})
 
 	t.Run("add conflicting subset", func(t *testing.T) {
 		set := propertySetFixture().(*bpPropertySet)
-		h.AssertPanic("adding x again should panic",
+		android.AssertPanic(t, "adding x again should panic",
 			func() { set.AddProperty("x", propertySetFixture()) })
 	})
 
 	t.Run("add non-pointer struct", func(t *testing.T) {
 		set := propertySetFixture().(*bpPropertySet)
 		str := propertyStructFixture().(*propertyStruct)
-		h.AssertPanic("adding a non-pointer struct should panic",
+		android.AssertPanic(t, "adding a non-pointer struct should panic",
 			func() { set.AddProperty("new", *str) })
 	})
 }
 
 func TestAddPropertySetNew(t *testing.T) {
-	h := &TestHelper{t}
 	set := newPropertySet()
 	subset := set.AddPropertySet("sub")
 	subset.AddProperty("new", "d^^b")
-	h.AssertDeepEquals("wrong sub.new value", "d^^b", set.getValue("sub").(*bpPropertySet).getValue("new"))
+	android.AssertDeepEquals(t, "wrong sub.new value", "d^^b", set.getValue("sub").(*bpPropertySet).getValue("new"))
 }
 
 func TestAddPropertySetExisting(t *testing.T) {
-	h := &TestHelper{t}
 	set := propertySetFixture().(*bpPropertySet)
 	subset := set.AddPropertySet("sub")
 	subset.AddProperty("new", "d^^b")
-	h.AssertDeepEquals("wrong sub.new value", "d^^b", set.getValue("sub").(*bpPropertySet).getValue("new"))
+	android.AssertDeepEquals(t, "wrong sub.new value", "d^^b", set.getValue("sub").(*bpPropertySet).getValue("new"))
 }
 
 type removeFredTransformation struct {
@@ -180,9 +176,6 @@
 }
 
 func TestTransformRemoveProperty(t *testing.T) {
-
-	helper := &TestHelper{t}
-
 	set := newPropertySet()
 	set.AddProperty("name", "name")
 	set.AddProperty("fred", "12")
@@ -191,13 +184,10 @@
 
 	contents := &generatedContents{}
 	outputPropertySet(contents, set)
-	helper.AssertTrimmedStringEquals("removing property failed", "name: \"name\",\n", contents.content.String())
+	android.AssertTrimmedStringEquals(t, "removing property failed", "name: \"name\",\n", contents.content.String())
 }
 
 func TestTransformRemovePropertySet(t *testing.T) {
-
-	helper := &TestHelper{t}
-
 	set := newPropertySet()
 	set.AddProperty("name", "name")
 	set.AddPropertySet("fred")
@@ -206,5 +196,5 @@
 
 	contents := &generatedContents{}
 	outputPropertySet(contents, set)
-	helper.AssertTrimmedStringEquals("removing property set failed", "name: \"name\",\n", contents.content.String())
+	android.AssertTrimmedStringEquals(t, "removing property set failed", "name: \"name\",\n", contents.content.String())
 }
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 3591777..a2539c9 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -21,7 +21,7 @@
 	"android/soong/cc"
 )
 
-var ccTestFs = map[string][]byte{
+var ccTestFs = android.MockFS{
 	"Test.cpp":                        nil,
 	"myinclude/Test.h":                nil,
 	"myinclude-android/AndroidTest.h": nil,
@@ -32,7 +32,7 @@
 	"some/where/stubslib.map.txt":     nil,
 }
 
-func testSdkWithCc(t *testing.T, bp string) *testSdkResult {
+func testSdkWithCc(t *testing.T, bp string) *android.TestResult {
 	t.Helper()
 	return testSdkWithFs(t, bp, ccTestFs)
 }
@@ -101,7 +101,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -353,7 +353,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -440,7 +440,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAllCopyRules(`
 myinclude/Test.h -> include/myinclude/Test.h
 .intermediates/mynativelib1/android_arm64_armv8-a_shared/mynativelib1.so -> arm64/lib/mynativelib1.so
@@ -486,7 +486,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -556,7 +556,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -615,7 +615,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mymodule_exports", "",
+	CheckSnapshot(t, result, "mymodule_exports", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -700,7 +700,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -808,7 +808,15 @@
 }
 
 func TestSnapshotWithSingleHostOsType(t *testing.T) {
-	ctx, config := testSdkContext(`
+	result := sdkFixtureFactory.Extend(
+		ccTestFs.AddToFixture(),
+		cc.PrepareForTestOnLinuxBionic,
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.Targets[android.LinuxBionic] = []android.Target{
+				{android.LinuxBionic, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", "", false},
+			}
+		}),
+	).RunTestWithBp(t, `
 		cc_defaults {
 			name: "mydefaults",
 			device_supported: false,
@@ -849,11 +857,9 @@
 			],
 			stl: "none",
 		}
-	`, ccTestFs, []android.OsType{android.LinuxBionic})
+	`)
 
-	result := runTests(t, ctx, config)
-
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -991,7 +997,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mymodule_exports", "",
+	CheckSnapshot(t, result, "mymodule_exports", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1099,7 +1105,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1200,7 +1206,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1297,7 +1303,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1424,7 +1430,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1552,7 +1558,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1615,7 +1621,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1729,7 +1735,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1843,7 +1849,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1940,7 +1946,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1978,7 +1984,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -2080,7 +2086,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -2112,7 +2118,6 @@
     },
 }
 `),
-		// Verifi
 		checkVersionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -2193,7 +2198,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -2266,7 +2271,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -2377,7 +2382,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -2430,7 +2435,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -2543,7 +2548,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -2658,7 +2663,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
diff --git a/sdk/exports.go b/sdk/exports.go
index d313057..9a0ba4e 100644
--- a/sdk/exports.go
+++ b/sdk/exports.go
@@ -17,8 +17,12 @@
 import "android/soong/android"
 
 func init() {
-	android.RegisterModuleType("module_exports", ModuleExportsFactory)
-	android.RegisterModuleType("module_exports_snapshot", ModuleExportsSnapshotsFactory)
+	registerModuleExportsBuildComponents(android.InitRegistrationContext)
+}
+
+func registerModuleExportsBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("module_exports", ModuleExportsFactory)
+	ctx.RegisterModuleType("module_exports_snapshot", ModuleExportsSnapshotsFactory)
 }
 
 // module_exports defines the exports of a mainline module. The exports are Soong modules
diff --git a/sdk/exports_test.go b/sdk/exports_test.go
index 1c59244..fd7741c 100644
--- a/sdk/exports_test.go
+++ b/sdk/exports_test.go
@@ -42,7 +42,7 @@
 			"package/Android.bp": []byte(packageBp),
 		})
 
-	result.CheckSnapshot("myexports", "package",
+	CheckSnapshot(t, result, "myexports", "package",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 111b22c..f4e9380 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -17,10 +17,11 @@
 import (
 	"testing"
 
+	"android/soong/android"
 	"android/soong/java"
 )
 
-func testSdkWithJava(t *testing.T, bp string) *testSdkResult {
+func testSdkWithJava(t *testing.T, bp string) *android.TestResult {
 	t.Helper()
 
 	fs := map[string][]byte{
@@ -125,9 +126,9 @@
 	`)
 
 	// Make sure that the mysdk module depends on "sdkmember" and not "prebuilt_sdkmember".
-	java.CheckModuleDependencies(t, result.ctx, "mysdk", "android_common", []string{"sdkmember"})
+	java.CheckModuleDependencies(t, result.TestContext, "mysdk", "android_common", []string{"sdkmember"})
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`// This is auto-generated. DO NOT EDIT.
 
 java_import {
@@ -224,11 +225,11 @@
 		}
 	`)
 
-	sdkMemberV1 := result.ctx.ModuleForTests("sdkmember_mysdk_1", "android_common").Rule("combineJar").Output
-	sdkMemberV2 := result.ctx.ModuleForTests("sdkmember_mysdk_2", "android_common").Rule("combineJar").Output
+	sdkMemberV1 := result.ModuleForTests("sdkmember_mysdk_1", "android_common").Rule("combineJar").Output
+	sdkMemberV2 := result.ModuleForTests("sdkmember_mysdk_2", "android_common").Rule("combineJar").Output
 
-	javalibForMyApex := result.ctx.ModuleForTests("myjavalib", "android_common_apex10000_mysdk_1")
-	javalibForMyApex2 := result.ctx.ModuleForTests("myjavalib", "android_common_apex10000_mysdk_2")
+	javalibForMyApex := result.ModuleForTests("myjavalib", "android_common_apex10000_mysdk_1")
+	javalibForMyApex2 := result.ModuleForTests("myjavalib", "android_common_apex10000_mysdk_2")
 
 	// Depending on the uses_sdks value, different libs are linked
 	ensureListContains(t, pathsToStrings(javalibForMyApex.Rule("javac").Implicits), sdkMemberV1.String())
@@ -255,7 +256,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -312,7 +313,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -369,7 +370,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -440,7 +441,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -496,7 +497,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -551,7 +552,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -607,7 +608,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -662,7 +663,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -731,7 +732,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -827,7 +828,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -918,7 +919,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("myexports", "",
+	CheckSnapshot(t, result, "myexports", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1032,7 +1033,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1133,7 +1134,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1202,7 +1203,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1274,7 +1275,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1367,7 +1368,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1475,7 +1476,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1563,7 +1564,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -1639,7 +1640,7 @@
 		}
 	`)
 
-	result.CheckSnapshot("mysdk", "",
+	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
diff --git a/sdk/sdk.go b/sdk/sdk.go
index f3d0750..2c84a2e 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -33,10 +33,14 @@
 	pctx.Import("android/soong/android")
 	pctx.Import("android/soong/java/config")
 
-	android.RegisterModuleType("sdk", SdkModuleFactory)
-	android.RegisterModuleType("sdk_snapshot", SnapshotModuleFactory)
-	android.PreDepsMutators(RegisterPreDepsMutators)
-	android.PostDepsMutators(RegisterPostDepsMutators)
+	registerSdkBuildComponents(android.InitRegistrationContext)
+}
+
+func registerSdkBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("sdk", SdkModuleFactory)
+	ctx.RegisterModuleType("sdk_snapshot", SnapshotModuleFactory)
+	ctx.PreDepsMutators(RegisterPreDepsMutators)
+	ctx.PostDepsMutators(RegisterPostDepsMutators)
 }
 
 type sdk struct {
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index c4dc41b..05d8bdb 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -169,7 +169,7 @@
 			"package/Android.bp": []byte(packageBp),
 		})
 
-	result.CheckSnapshot("mysdk", "package",
+	CheckSnapshot(t, result, "mysdk", "package",
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -309,20 +309,16 @@
 `)
 }
 
-func TestSDkInstall(t *testing.T) {
+func TestSdkInstall(t *testing.T) {
 	sdk := `
 		sdk {
 			name: "mysdk",
 		}
 	`
-	result := testSdkWithFs(t, ``,
-		map[string][]byte{
-			"Android.bp": []byte(sdk),
-		})
+	result := testSdkWithFs(t, sdk, nil)
 
-	result.CheckSnapshot("mysdk", "",
-		checkAllOtherCopyRules(`.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip`),
-	)
+	CheckSnapshot(t, result, "mysdk", "",
+		checkAllOtherCopyRules(`.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip`))
 }
 
 type EmbeddedPropertiesStruct struct {
@@ -390,12 +386,10 @@
 
 	extractor := newCommonValueExtractor(common)
 
-	h := TestHelper{t}
-
 	err := extractor.extractCommonProperties(common, structs)
-	h.AssertDeepEquals("unexpected error", nil, err)
+	android.AssertDeepEquals(t, "unexpected error", nil, err)
 
-	h.AssertDeepEquals("common properties not correct",
+	android.AssertDeepEquals(t, "common properties not correct",
 		&testPropertiesStruct{
 			name:        "common",
 			private:     "",
@@ -413,7 +407,7 @@
 		},
 		common)
 
-	h.AssertDeepEquals("updated properties[0] not correct",
+	android.AssertDeepEquals(t, "updated properties[0] not correct",
 		&testPropertiesStruct{
 			name:        "struct-0",
 			private:     "common",
@@ -431,7 +425,7 @@
 		},
 		structs[0])
 
-	h.AssertDeepEquals("updated properties[1] not correct",
+	android.AssertDeepEquals(t, "updated properties[1] not correct",
 		&testPropertiesStruct{
 			name:        "struct-1",
 			private:     "common",
@@ -465,10 +459,8 @@
 
 	extractor := newCommonValueExtractor(common)
 
-	h := TestHelper{t}
-
 	err := extractor.extractCommonProperties(common, structs)
-	h.AssertErrorMessageEquals("unexpected error", `field "S_Common" is not tagged as "arch_variant" but has arch specific properties:
+	android.AssertErrorMessageEquals(t, "unexpected error", `field "S_Common" is not tagged as "arch_variant" but has arch specific properties:
     "struct-0" has value "should-be-but-is-not-common0"
     "struct-1" has value "should-be-but-is-not-common1"`, err)
 }
diff --git a/sdk/testing.go b/sdk/testing.go
index 7a2540a..a5519f8 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -19,7 +19,6 @@
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"reflect"
 	"strings"
 	"testing"
 
@@ -30,10 +29,15 @@
 	"android/soong/java"
 )
 
-func testSdkContext(bp string, fs map[string][]byte, extraOsTypes []android.OsType) (*android.TestContext, android.Config) {
-	extraOsTypes = append(extraOsTypes, android.Android, android.Windows)
+var sdkFixtureFactory = android.NewFixtureFactory(
+	&buildDir,
+	apex.PrepareForTestWithApexBuildComponents,
+	cc.PrepareForTestWithCcDefaultModules,
+	genrule.PrepareForTestWithGenRuleBuildComponents,
+	java.PrepareForTestWithJavaBuildComponents,
+	PrepareForTestWithSdkBuildComponents,
 
-	bp = bp + `
+	android.FixtureAddTextFile("sdk/tests/Android.bp", `
 		apex_key {
 			name: "myapex.key",
 			public_key: "myapex.avbpubkey",
@@ -44,9 +48,9 @@
 			name: "myapex.cert",
 			certificate: "myapex",
 		}
-	` + cc.GatherRequiredDepsForTest(extraOsTypes...)
+	`),
 
-	mockFS := map[string][]byte{
+	android.FixtureMergeMockFs(map[string][]byte{
 		"build/make/target/product/security":           nil,
 		"apex_manifest.json":                           nil,
 		"system/sepolicy/apex/myapex-file_contexts":    nil,
@@ -56,118 +60,33 @@
 		"myapex.pem":                                   nil,
 		"myapex.x509.pem":                              nil,
 		"myapex.pk8":                                   nil,
-	}
+	}),
 
-	cc.GatherRequiredFilesForTest(mockFS)
-
-	for k, v := range fs {
-		mockFS[k] = v
-	}
-
-	config := android.TestArchConfig(buildDir, nil, bp, mockFS)
-
-	// Add windows as a default disable OS to test behavior when some OS variants
-	// are disabled.
-	config.Targets[android.Windows] = []android.Target{
-		{android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", "", true},
-	}
-
-	for _, extraOsType := range extraOsTypes {
-		switch extraOsType {
-		case android.LinuxBionic:
-			config.Targets[android.LinuxBionic] = []android.Target{
-				{android.LinuxBionic, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", "", false},
-			}
+	cc.PrepareForTestOnWindows,
+	android.FixtureModifyConfig(func(config android.Config) {
+		// Add windows as a default disable OS to test behavior when some OS variants
+		// are disabled.
+		config.Targets[android.Windows] = []android.Target{
+			{android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", "", true},
 		}
-	}
+	}),
+)
 
-	ctx := android.NewTestArchContext(config)
+var PrepareForTestWithSdkBuildComponents = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(registerModuleExportsBuildComponents),
+	android.FixtureRegisterWithContext(registerSdkBuildComponents),
+)
 
-	// Enable androidmk support.
-	// * Register the singleton
-	// * Configure that we are inside make
-	// * Add CommonOS to ensure that androidmk processing works.
-	android.RegisterAndroidMkBuildComponents(ctx)
-	android.SetKatiEnabledForTests(config)
-	config.Targets[android.CommonOS] = []android.Target{
-		{android.CommonOS, android.Arch{ArchType: android.Common}, android.NativeBridgeDisabled, "", "", true},
-	}
-
-	// from android package
-	android.RegisterPackageBuildComponents(ctx)
-	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.PreArchMutators(android.RegisterVisibilityRuleChecker)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	ctx.PreArchMutators(android.RegisterComponentsMutator)
-
-	android.RegisterPrebuiltMutators(ctx)
-
-	// Register these after the prebuilt mutators have been registered to match what
-	// happens at runtime.
-	ctx.PreArchMutators(android.RegisterVisibilityRuleGatherer)
-	ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer)
-
-	// from java package
-	java.RegisterRequiredBuildComponentsForTest(ctx)
-
-	// from genrule package
-	genrule.RegisterGenruleBuildComponents(ctx)
-
-	// from cc package
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-
-	// from apex package
-	ctx.RegisterModuleType("apex", apex.BundleFactory)
-	ctx.RegisterModuleType("apex_key", apex.ApexKeyFactory)
-	ctx.PostDepsMutators(apex.RegisterPostDepsMutators)
-
-	// from this package
-	ctx.RegisterModuleType("sdk", SdkModuleFactory)
-	ctx.RegisterModuleType("sdk_snapshot", SnapshotModuleFactory)
-	ctx.RegisterModuleType("module_exports", ModuleExportsFactory)
-	ctx.RegisterModuleType("module_exports_snapshot", ModuleExportsSnapshotsFactory)
-	ctx.PreDepsMutators(RegisterPreDepsMutators)
-	ctx.PostDepsMutators(RegisterPostDepsMutators)
-
-	ctx.Register()
-
-	return ctx, config
-}
-
-func runTests(t *testing.T, ctx *android.TestContext, config android.Config) *testSdkResult {
+func testSdkWithFs(t *testing.T, bp string, fs android.MockFS) *android.TestResult {
 	t.Helper()
-	_, errs := ctx.ParseBlueprintsFiles(".")
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-	return &testSdkResult{
-		TestHelper: TestHelper{t: t},
-		ctx:        ctx,
-		config:     config,
-	}
-}
-
-func testSdkWithFs(t *testing.T, bp string, fs map[string][]byte) *testSdkResult {
-	t.Helper()
-	ctx, config := testSdkContext(bp, fs, nil)
-	return runTests(t, ctx, config)
+	return sdkFixtureFactory.RunTest(t, fs.AddToFixture(), android.FixtureWithRootAndroidBp(bp))
 }
 
 func testSdkError(t *testing.T, pattern, bp string) {
 	t.Helper()
-	ctx, config := testSdkContext(bp, nil, nil)
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return
-	}
-	_, errs = ctx.PrepareBuildActions(config)
-	if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return
-	}
-
-	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
+	sdkFixtureFactory.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
+		RunTestWithBp(t, bp)
 }
 
 func ensureListContains(t *testing.T, result []string, expected string) {
@@ -185,74 +104,14 @@
 	return ret
 }
 
-// Provides general test support.
-type TestHelper struct {
-	t *testing.T
-}
-
-func (h *TestHelper) AssertStringEquals(message string, expected string, actual string) {
-	h.t.Helper()
-	if actual != expected {
-		h.t.Errorf("%s: expected %s, actual %s", message, expected, actual)
-	}
-}
-
-func (h *TestHelper) AssertErrorMessageEquals(message string, expected string, actual error) {
-	h.t.Helper()
-	if actual == nil {
-		h.t.Errorf("Expected error but was nil")
-	} else if actual.Error() != expected {
-		h.t.Errorf("%s: expected %s, actual %s", message, expected, actual.Error())
-	}
-}
-
-func (h *TestHelper) AssertTrimmedStringEquals(message string, expected string, actual string) {
-	h.t.Helper()
-	expected = strings.TrimSpace(expected)
-	actual = strings.TrimSpace(actual)
-	if actual != expected {
-		h.t.Errorf("%s: expected:\n%s\nactual:\n%s\n", message, expected, actual)
-	}
-}
-
-func (h *TestHelper) AssertDeepEquals(message string, expected interface{}, actual interface{}) {
-	h.t.Helper()
-	if !reflect.DeepEqual(actual, expected) {
-		h.t.Errorf("%s: expected %#v, actual %#v", message, expected, actual)
-	}
-}
-
-func (h *TestHelper) AssertPanic(message string, funcThatShouldPanic func()) {
-	h.t.Helper()
-	panicked := false
-	func() {
-		defer func() {
-			if x := recover(); x != nil {
-				panicked = true
-			}
-		}()
-		funcThatShouldPanic()
-	}()
-	if !panicked {
-		h.t.Error(message)
-	}
-}
-
-// Encapsulates result of processing an SDK definition. Provides support for
-// checking the state of the build structures.
-type testSdkResult struct {
-	TestHelper
-	ctx    *android.TestContext
-	config android.Config
-}
-
 // Analyse the sdk build rules to extract information about what it is doing.
-
+//
 // e.g. find the src/dest pairs from each cp command, the various zip files
 // generated, etc.
-func (r *testSdkResult) getSdkSnapshotBuildInfo(sdk *sdk) *snapshotBuildInfo {
+func getSdkSnapshotBuildInfo(t *testing.T, result *android.TestResult, sdk *sdk) *snapshotBuildInfo {
 	info := &snapshotBuildInfo{
-		r:                            r,
+		t:                            t,
+		r:                            result,
 		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
@@ -295,7 +154,7 @@
 			info.intermediateZip = info.outputZip
 			mergeInput := android.NormalizePathForTesting(bp.Input)
 			if info.intermediateZip != mergeInput {
-				r.t.Errorf("Expected intermediate zip %s to be an input to merge zips but found %s instead",
+				t.Errorf("Expected intermediate zip %s to be an input to merge zips but found %s instead",
 					info.intermediateZip, mergeInput)
 			}
 
@@ -314,28 +173,20 @@
 	return info
 }
 
-func (r *testSdkResult) Module(name string, variant string) android.Module {
-	return r.ctx.ModuleForTests(name, variant).Module()
-}
-
-func (r *testSdkResult) ModuleForTests(name string, variant string) android.TestingModule {
-	return r.ctx.ModuleForTests(name, variant)
-}
-
 // Check the snapshot build rules.
 //
 // Takes a list of functions which check different facets of the snapshot build rules.
 // Allows each test to customize what is checked without duplicating lots of code
 // or proliferating check methods of different flavors.
-func (r *testSdkResult) CheckSnapshot(name string, dir string, checkers ...snapshotBuildInfoChecker) {
-	r.t.Helper()
+func CheckSnapshot(t *testing.T, result *android.TestResult, name string, dir string, checkers ...snapshotBuildInfoChecker) {
+	t.Helper()
 
 	// The sdk CommonOS variant is always responsible for generating the snapshot.
 	variant := android.CommonOS.Name
 
-	sdk := r.Module(name, variant).(*sdk)
+	sdk := result.Module(name, variant).(*sdk)
 
-	snapshotBuildInfo := r.getSdkSnapshotBuildInfo(sdk)
+	snapshotBuildInfo := getSdkSnapshotBuildInfo(t, result, sdk)
 
 	// Check state of the snapshot build.
 	for _, checker := range checkers {
@@ -347,7 +198,7 @@
 	if dir != "" {
 		dir = filepath.Clean(dir) + "/"
 	}
-	r.AssertStringEquals("Snapshot zip file in wrong place",
+	android.AssertStringEquals(t, "Snapshot zip file in wrong place",
 		fmt.Sprintf(".intermediates/%s%s/%s/%s-current.zip", dir, name, variant, name), actual)
 
 	// Populate a mock filesystem with the files that would have been copied by
@@ -358,7 +209,7 @@
 	}
 
 	// Process the generated bp file to make sure it is valid.
-	testSdkWithFs(r.t, snapshotBuildInfo.androidBpContents, fs)
+	testSdkWithFs(t, snapshotBuildInfo.androidBpContents, fs)
 }
 
 type snapshotBuildInfoChecker func(info *snapshotBuildInfo)
@@ -368,8 +219,8 @@
 // Both the expected and actual string are both trimmed before comparing.
 func checkAndroidBpContents(expected string) snapshotBuildInfoChecker {
 	return func(info *snapshotBuildInfo) {
-		info.r.t.Helper()
-		info.r.AssertTrimmedStringEquals("Android.bp contents do not match", expected, info.androidBpContents)
+		info.t.Helper()
+		android.AssertTrimmedStringEquals(info.t, "Android.bp contents do not match", expected, info.androidBpContents)
 	}
 }
 
@@ -380,8 +231,8 @@
 // Both the expected and actual string are both trimmed before comparing.
 func checkUnversionedAndroidBpContents(expected string) snapshotBuildInfoChecker {
 	return func(info *snapshotBuildInfo) {
-		info.r.t.Helper()
-		info.r.AssertTrimmedStringEquals("unversioned Android.bp contents do not match", expected, info.androidUnversionedBpContents)
+		info.t.Helper()
+		android.AssertTrimmedStringEquals(info.t, "unversioned Android.bp contents do not match", expected, info.androidUnversionedBpContents)
 	}
 }
 
@@ -395,8 +246,8 @@
 // Both the expected and actual string are both trimmed before comparing.
 func checkVersionedAndroidBpContents(expected string) snapshotBuildInfoChecker {
 	return func(info *snapshotBuildInfo) {
-		info.r.t.Helper()
-		info.r.AssertTrimmedStringEquals("versioned Android.bp contents do not match", expected, info.androidVersionedBpContents)
+		info.t.Helper()
+		android.AssertTrimmedStringEquals(info.t, "versioned Android.bp contents do not match", expected, info.androidVersionedBpContents)
 	}
 }
 
@@ -407,27 +258,27 @@
 // before comparing.
 func checkAllCopyRules(expected string) snapshotBuildInfoChecker {
 	return func(info *snapshotBuildInfo) {
-		info.r.t.Helper()
-		info.r.AssertTrimmedStringEquals("Incorrect copy rules", expected, info.copyRules)
+		info.t.Helper()
+		android.AssertTrimmedStringEquals(info.t, "Incorrect copy rules", expected, info.copyRules)
 	}
 }
 
 func checkAllOtherCopyRules(expected string) snapshotBuildInfoChecker {
 	return func(info *snapshotBuildInfo) {
-		info.r.t.Helper()
-		info.r.AssertTrimmedStringEquals("Incorrect copy rules", expected, info.otherCopyRules)
+		info.t.Helper()
+		android.AssertTrimmedStringEquals(info.t, "Incorrect copy rules", expected, info.otherCopyRules)
 	}
 }
 
 // Check that the specified paths match the list of zips to merge with the intermediate zip.
 func checkMergeZips(expected ...string) snapshotBuildInfoChecker {
 	return func(info *snapshotBuildInfo) {
-		info.r.t.Helper()
+		info.t.Helper()
 		if info.intermediateZip == "" {
-			info.r.t.Errorf("No intermediate zip file was created")
+			info.t.Errorf("No intermediate zip file was created")
 		}
 
-		info.r.AssertDeepEquals("mismatching merge zip files", expected, info.mergeZips)
+		android.AssertDeepEquals(info.t, "mismatching merge zip files", expected, info.mergeZips)
 	}
 }
 
@@ -437,7 +288,10 @@
 // All source/input paths are relative either the build directory. All dest/output paths are
 // relative to the snapshot root directory.
 type snapshotBuildInfo struct {
-	r *testSdkResult
+	t *testing.T
+
+	// The result from RunTest()
+	r *android.TestResult
 
 	// The contents of the generated Android.bp file
 	androidBpContents string
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 031cd47..49f4961 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -40,14 +40,32 @@
 func init() {
 	pctx.Import("android/soong/android")
 
-	android.RegisterModuleType("sh_binary", ShBinaryFactory)
-	android.RegisterModuleType("sh_binary_host", ShBinaryHostFactory)
-	android.RegisterModuleType("sh_test", ShTestFactory)
-	android.RegisterModuleType("sh_test_host", ShTestHostFactory)
+	registerShBuildComponents(android.InitRegistrationContext)
 
 	android.RegisterBp2BuildMutator("sh_binary", ShBinaryBp2Build)
 }
 
+func registerShBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("sh_binary", ShBinaryFactory)
+	ctx.RegisterModuleType("sh_binary_host", ShBinaryHostFactory)
+	ctx.RegisterModuleType("sh_test", ShTestFactory)
+	ctx.RegisterModuleType("sh_test_host", ShTestHostFactory)
+}
+
+// Test fixture preparer that will register most sh build components.
+//
+// Singletons and mutators should only be added here if they are needed for a majority of sh
+// module types, otherwise they should be added under a separate preparer to allow them to be
+// selected only when needed to reduce test execution time.
+//
+// Module types do not have much of an overhead unless they are used so this should include as many
+// module types as possible. The exceptions are those module types that require mutators and/or
+// singletons in order to function in which case they should be kept together in a separate
+// preparer.
+var PrepareForTestWithShBuildComponents = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(registerShBuildComponents),
+)
+
 type shBinaryProperties struct {
 	// Source file of this prebuilt.
 	Src *string `android:"path,arch_variant"`
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index f48f7fb..7a24168 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -37,28 +37,32 @@
 	os.Exit(run())
 }
 
-func testShBinary(t *testing.T, bp string) (*android.TestContext, android.Config) {
-	fs := map[string][]byte{
+var shFixtureFactory = android.NewFixtureFactory(
+	&buildDir,
+	cc.PrepareForTestWithCcBuildComponents,
+	PrepareForTestWithShBuildComponents,
+	android.FixtureMergeMockFs(android.MockFS{
 		"test.sh":            nil,
 		"testdata/data1":     nil,
 		"testdata/sub/data2": nil,
-	}
+	}),
+)
 
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
+// testShBinary runs tests using the shFixtureFactory
+//
+// Do not add any new usages of this, instead use the shFixtureFactory directly as it makes it much
+// easier to customize the test behavior.
+//
+// If it is necessary to customize the behavior of an existing test that uses this then please first
+// convert the test to using shFixtureFactory first and then in a following change add the
+// appropriate fixture preparers. Keeping the conversion change separate makes it easy to verify
+// that it did not change the test behavior unexpectedly.
+//
+// deprecated
+func testShBinary(t *testing.T, bp string) (*android.TestContext, android.Config) {
+	result := shFixtureFactory.RunTestWithBp(t, bp)
 
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("sh_test", ShTestFactory)
-	ctx.RegisterModuleType("sh_test_host", ShTestHostFactory)
-
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-
-	ctx.Register()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-
-	return ctx, config
+	return result.TestContext, result.Config
 }
 
 func TestShTestSubDir(t *testing.T) {
diff --git a/ui/build/config.go b/ui/build/config.go
index 15da1bc..1152cd7 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -56,7 +56,6 @@
 	katiSuffix      string
 	targetDevice    string
 	targetDeviceDir string
-	fullBuild       bool
 
 	// Autodetected
 	totalRAM uint64
@@ -792,14 +791,6 @@
 	c.targetDevice = device
 }
 
-func (c *configImpl) FullBuild() bool {
-	return c.fullBuild
-}
-
-func (c *configImpl) SetFullBuild(fullBuild bool) {
-	c.fullBuild = fullBuild
-}
-
 func (c *configImpl) TargetBuildVariant() string {
 	if v, ok := c.environ.Get("TARGET_BUILD_VARIANT"); ok {
 		return v
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index be6f00a..fe0aca9 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -214,9 +214,6 @@
 		// So that later Kati runs can find BoardConfig.mk faster
 		"TARGET_DEVICE_DIR",
 
-		// To decide whether to skip the old installed cleanup step.
-		"FULL_BUILD",
-
 		// Whether --werror_overriding_commands will work
 		"BUILD_BROKEN_DUP_RULES",
 
@@ -281,7 +278,6 @@
 	config.SetNinjaArgs(strings.Fields(makeVars["NINJA_GOALS"]))
 	config.SetTargetDevice(makeVars["TARGET_DEVICE"])
 	config.SetTargetDeviceDir(makeVars["TARGET_DEVICE_DIR"])
-	config.SetFullBuild(makeVars["FULL_BUILD"] == "true")
 
 	config.SetBuildBrokenDupRules(makeVars["BUILD_BROKEN_DUP_RULES"] == "true")
 	config.SetBuildBrokenUsesNetwork(makeVars["BUILD_BROKEN_USES_NETWORK"] == "true")
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 668b20e..06ec646 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -229,11 +229,7 @@
 
 	// Cleanup steps.
 	cleanCopyHeaders(ctx, config)
-	// Skip the old installed file cleanup step for few non-full build goals as we don't create
-	// an installed file list for them.
-	if config.FullBuild() {
-		cleanOldInstalledFiles(ctx, config)
-	}
+	cleanOldInstalledFiles(ctx, config)
 }
 
 // Clean out obsolete header files on the disk that were *not copied* during the
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 5f4a203..884e957 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -116,7 +116,7 @@
 
 		envFile := filepath.Join(config.SoongOutDir(), "soong.environment.used")
 		getenv := func(k string) string {
-			v, _ := config.Environment().Get(k)
+			v, _ := soongBuildEnv.Get(k)
 			return v
 		}
 		if stale, _ := shared.StaleEnvFile(envFile, getenv); stale {
@@ -177,17 +177,9 @@
 		ninjaEnv.Set("TOP", os.Getenv("TOP"))
 		ninjaEnv.Set("SOONG_OUTDIR", config.SoongOutDir())
 
-		// Needed for NonHermeticHostSystemTool() and that, only in tests. We should
-		// probably find a better way of running tests other than making $PATH
-		// available also to production builds. Note that this is not get same as
-		// os.Getenv("PATH"): config.Environment() contains the $PATH that redirects
-		// every binary through the path interposer.
-		configPath, _ := config.Environment().Get("PATH")
-		ninjaEnv.Set("PATH", configPath)
-
 		// For debugging
 		if os.Getenv("SOONG_DELVE") != "" {
-			// SOONG_DELVE is already in cmd.Environment
+			ninjaEnv.Set("SOONG_DELVE", os.Getenv("SOONG_DELVE"))
 			ninjaEnv.Set("SOONG_DELVE_PATH", shared.ResolveDelveBinary())
 		}