Add test fixture support

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

Bug: 181070625
Test: m nothing
Change-Id: I0a2b40fff93b6041f9aa8c4ef0aba91da1bc8bf3
diff --git a/android/fixture.go b/android/fixture.go
new file mode 100644
index 0000000..0efe329
--- /dev/null
+++ b/android/fixture.go
@@ -0,0 +1,604 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+)
+
+// Provides support for creating test fixtures on which tests can be run. Reduces duplication
+// of test setup by allow tests to easily reuse setup code.
+//
+// Fixture
+// =======
+// These determine the environment within which a test can be run. Fixtures are mutable and are
+// created by FixtureFactory instances and mutated by FixturePreparer instances. They are created by
+// first creating a base Fixture (which is essentially empty) and then applying FixturePreparer
+// instances to it to modify the environment.
+//
+// FixtureFactory
+// ==============
+// These are responsible for creating fixtures. Factories are immutable and are intended to be
+// initialized once and reused to create multiple fixtures. Each factory has a list of fixture
+// preparers that prepare a fixture for running a test. Factories can also be used to create other
+// factories by extending them with additional fixture preparers.
+//
+// FixturePreparer
+// ===============
+// These are responsible for modifying a Fixture in preparation for it to run a test. Preparers are
+// intended to be immutable and able to prepare multiple Fixture objects simultaneously without
+// them sharing any data.
+//
+// FixturePreparers are only ever invoked once per test fixture. Prior to invocation the list of
+// FixturePreparers are flattened and deduped while preserving the order they first appear in the
+// list. This makes it easy to reuse, group and combine FixturePreparers together.
+//
+// Each small self contained piece of test setup should be their own FixturePreparer. e.g.
+// * A group of related modules.
+// * A group of related mutators.
+// * A combination of both.
+// * Configuration.
+//
+// They should not overlap, e.g. the same module type should not be registered by different
+// FixturePreparers as using them both would cause a build error. In that case the preparer should
+// be split into separate parts and combined together using FixturePreparers(...).
+//
+// e.g. attempting to use AllPreparers in preparing a Fixture would break as it would attempt to
+// register module bar twice:
+//   var Preparer1 = FixtureRegisterWithContext(RegisterModuleFooAndBar)
+//   var Preparer2 = FixtureRegisterWithContext(RegisterModuleBarAndBaz)
+//   var AllPreparers = FixturePreparers(Preparer1, Preparer2)
+//
+// However, when restructured like this it would work fine:
+//   var PreparerFoo = FixtureRegisterWithContext(RegisterModuleFoo)
+//   var PreparerBar = FixtureRegisterWithContext(RegisterModuleBar)
+//   var PreparerBaz = FixtureRegisterWithContext(RegisterModuleBaz)
+//   var Preparer1 = FixturePreparers(RegisterModuleFoo, RegisterModuleBar)
+//   var Preparer2 = FixturePreparers(RegisterModuleBar, RegisterModuleBaz)
+//   var AllPreparers = FixturePreparers(Preparer1, Preparer2)
+//
+// As after deduping and flattening AllPreparers would result in the following preparers being
+// applied:
+// 1. PreparerFoo
+// 2. PreparerBar
+// 3. PreparerBaz
+//
+// Preparers can be used for both integration and unit tests.
+//
+// Integration tests typically use all the module types, mutators and singletons that are available
+// for that package to try and replicate the behavior of the runtime build as closely as possible.
+// However, that realism comes at a cost of increased fragility (as they can be broken by changes in
+// many different parts of the build) and also increased runtime, especially if they use lots of
+// singletons and mutators.
+//
+// Unit tests on the other hand try and minimize the amount of code being tested which makes them
+// less susceptible to changes elsewhere in the build and quick to run but at a cost of potentially
+// not testing realistic scenarios.
+//
+// Supporting unit tests effectively require that preparers are available at the lowest granularity
+// possible. Supporting integration tests effectively require that the preparers are organized into
+// groups that provide all the functionality available.
+//
+// At least in terms of tests that check the behavior of build components via processing
+// `Android.bp` there is no clear separation between a unit test and an integration test. Instead
+// they vary from one end that tests a single module (e.g. filegroup) to the other end that tests a
+// whole system of modules, mutators and singletons (e.g. apex + hiddenapi).
+//
+// TestResult
+// ==========
+// These are created by running tests in a Fixture and provide access to the Config and TestContext
+// in which the tests were run.
+//
+// Example
+// =======
+//
+// An exported preparer for use by other packages that need to use java modules.
+//
+// package java
+// var PrepareForIntegrationTestWithJava = FixturePreparers(
+//    android.PrepareForIntegrationTestWithAndroid,
+//    FixtureRegisterWithContext(RegisterAGroupOfRelatedModulesMutatorsAndSingletons),
+//    FixtureRegisterWithContext(RegisterAnotherGroupOfRelatedModulesMutatorsAndSingletons),
+//    ...
+// )
+//
+// Some files to use in tests in the java package.
+//
+// var javaMockFS = android.MockFS{
+//		"api/current.txt":        nil,
+//		"api/removed.txt":        nil,
+//    ...
+// }
+//
+// A package private factory for use for testing java within the java package.
+//
+// var javaFixtureFactory = NewFixtureFactory(
+//    PrepareForIntegrationTestWithJava,
+//    FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+//      ctx.RegisterModuleType("test_module", testModule)
+//    }),
+//    javaMockFS.AddToFixture(),
+//    ...
+// }
+//
+// func TestJavaStuff(t *testing.T) {
+//   result := javaFixtureFactory.RunTest(t,
+//       android.FixtureWithRootAndroidBp(`java_library {....}`),
+//       android.MockFS{...}.AddToFixture(),
+//   )
+//   ... test result ...
+// }
+//
+// package cc
+// var PrepareForTestWithCC = FixturePreparers(
+//    android.PrepareForArchMutator,
+//	  android.prepareForPrebuilts,
+//    FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
+//    ...
+// )
+//
+// package apex
+//
+// var PrepareForApex = FixturePreparers(
+//    ...
+// )
+//
+// Use modules and mutators from java, cc and apex. Any duplicate preparers (like
+// android.PrepareForArchMutator) will be automatically deduped.
+//
+// var apexFixtureFactory = android.NewFixtureFactory(
+//    PrepareForJava,
+//    PrepareForCC,
+//    PrepareForApex,
+// )
+
+// Factory for Fixture objects.
+//
+// This is configured with a set of FixturePreparer objects that are used to
+// initialize each Fixture instance this creates.
+type FixtureFactory interface {
+
+	// Creates a copy of this instance and adds some additional preparers.
+	//
+	// Before the preparers are used they are combined with the preparers provided when the factory
+	// was created, any groups of preparers are flattened, and the list is deduped so that each
+	// preparer is only used once. See the file documentation in android/fixture.go for more details.
+	Extend(preparers ...FixturePreparer) FixtureFactory
+
+	// Create a Fixture.
+	Fixture(t *testing.T, preparers ...FixturePreparer) Fixture
+
+	// Run the test, expecting no errors, returning a TestResult instance.
+	//
+	// Shorthand for Fixture(t, preparers...).RunTest()
+	RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult
+
+	// Run the test with the supplied Android.bp file.
+	//
+	// Shorthand for RunTest(t, android.FixtureWithRootAndroidBp(bp))
+	RunTestWithBp(t *testing.T, bp string) *TestResult
+}
+
+// Create a new FixtureFactory that will apply the supplied preparers.
+//
+// The buildDirSupplier is a pointer to the package level buildDir variable that is initialized by
+// the package level setUp method. It has to be a pointer to the variable as the variable will not
+// have been initialized at the time the factory is created.
+func NewFixtureFactory(buildDirSupplier *string, preparers ...FixturePreparer) FixtureFactory {
+	return &fixtureFactory{
+		buildDirSupplier: buildDirSupplier,
+		preparers:        dedupAndFlattenPreparers(nil, preparers),
+	}
+}
+
+// A set of mock files to add to the mock file system.
+type MockFS map[string][]byte
+
+func (fs MockFS) Merge(extra map[string][]byte) {
+	for p, c := range extra {
+		fs[p] = c
+	}
+}
+
+func (fs MockFS) AddToFixture() FixturePreparer {
+	return FixtureMergeMockFs(fs)
+}
+
+// Modify the config
+func FixtureModifyConfig(mutator func(config Config)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.config)
+	})
+}
+
+// Modify the config and context
+func FixtureModifyConfigAndContext(mutator func(config Config, ctx *TestContext)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.config, f.ctx)
+	})
+}
+
+// Modify the context
+func FixtureModifyContext(mutator func(ctx *TestContext)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.ctx)
+	})
+}
+
+func FixtureRegisterWithContext(registeringFunc func(ctx RegistrationContext)) FixturePreparer {
+	return FixtureModifyContext(func(ctx *TestContext) { registeringFunc(ctx) })
+}
+
+// Modify the mock filesystem
+func FixtureModifyMockFS(mutator func(fs MockFS)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.mockFS)
+	})
+}
+
+// Merge the supplied file system into the mock filesystem.
+//
+// Paths that already exist in the mock file system are overridden.
+func FixtureMergeMockFs(mockFS MockFS) FixturePreparer {
+	return FixtureModifyMockFS(func(fs MockFS) {
+		fs.Merge(mockFS)
+	})
+}
+
+// Add a file to the mock filesystem
+func FixtureAddFile(path string, contents []byte) FixturePreparer {
+	return FixtureModifyMockFS(func(fs MockFS) {
+		fs[path] = contents
+	})
+}
+
+// Add a text file to the mock filesystem
+func FixtureAddTextFile(path string, contents string) FixturePreparer {
+	return FixtureAddFile(path, []byte(contents))
+}
+
+// Add the root Android.bp file with the supplied contents.
+func FixtureWithRootAndroidBp(contents string) FixturePreparer {
+	return FixtureAddTextFile("Android.bp", contents)
+}
+
+// Create a composite FixturePreparer that is equivalent to applying each of the supplied
+// FixturePreparer instances in order.
+func FixturePreparers(preparers ...FixturePreparer) FixturePreparer {
+	return &compositeFixturePreparer{dedupAndFlattenPreparers(nil, preparers)}
+}
+
+type simpleFixturePreparerVisitor func(preparer *simpleFixturePreparer)
+
+// FixturePreparer is an opaque interface that can change a fixture.
+type FixturePreparer interface {
+	// visit calls the supplied visitor with each *simpleFixturePreparer instances in this preparer,
+	visit(simpleFixturePreparerVisitor)
+}
+
+type fixturePreparers []FixturePreparer
+
+func (f fixturePreparers) visit(visitor simpleFixturePreparerVisitor) {
+	for _, p := range f {
+		p.visit(visitor)
+	}
+}
+
+// dedupAndFlattenPreparers removes any duplicates and flattens any composite FixturePreparer
+// instances.
+//
+// base      - a list of already flattened and deduped preparers that will be applied first before
+//             the list of additional preparers. Any duplicates of these in the additional preparers
+//             will be ignored.
+//
+// preparers - a list of additional unflattened, undeduped preparers that will be applied after the
+//             base preparers.
+//
+// Returns a deduped and flattened list of the preparers minus any that exist in the base preparers.
+func dedupAndFlattenPreparers(base []*simpleFixturePreparer, preparers fixturePreparers) []*simpleFixturePreparer {
+	var list []*simpleFixturePreparer
+	visited := make(map[*simpleFixturePreparer]struct{})
+
+	// Mark the already flattened and deduped preparers, if any, as having been seen so that
+	// duplicates of these in the additional preparers will be discarded.
+	for _, s := range base {
+		visited[s] = struct{}{}
+	}
+
+	preparers.visit(func(preparer *simpleFixturePreparer) {
+		if _, seen := visited[preparer]; !seen {
+			visited[preparer] = struct{}{}
+			list = append(list, preparer)
+		}
+	})
+	return list
+}
+
+// compositeFixturePreparer is a FixturePreparer created from a list of fixture preparers.
+type compositeFixturePreparer struct {
+	preparers []*simpleFixturePreparer
+}
+
+func (c *compositeFixturePreparer) visit(visitor simpleFixturePreparerVisitor) {
+	for _, p := range c.preparers {
+		p.visit(visitor)
+	}
+}
+
+// simpleFixturePreparer is a FixturePreparer that applies a function to a fixture.
+type simpleFixturePreparer struct {
+	function func(fixture *fixture)
+}
+
+func (s *simpleFixturePreparer) visit(visitor simpleFixturePreparerVisitor) {
+	visitor(s)
+}
+
+func newSimpleFixturePreparer(preparer func(fixture *fixture)) FixturePreparer {
+	return &simpleFixturePreparer{function: preparer}
+}
+
+// Fixture defines the test environment.
+type Fixture interface {
+	// Run the test, expecting no errors, returning a TestResult instance.
+	RunTest() *TestResult
+}
+
+// Provides general test support.
+type TestHelper struct {
+	*testing.T
+}
+
+// AssertBoolEquals checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertBoolEquals(message string, expected bool, actual bool) {
+	h.Helper()
+	if actual != expected {
+		h.Errorf("%s: expected %t, actual %t", message, expected, actual)
+	}
+}
+
+// AssertStringEquals checks if the expected and actual values are equal and if they are not then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertStringEquals(message string, expected string, actual string) {
+	h.Helper()
+	if actual != expected {
+		h.Errorf("%s: expected %s, actual %s", message, expected, actual)
+	}
+}
+
+// AssertTrimmedStringEquals checks if the expected and actual values are the same after trimming
+// leading and trailing spaces from them both. If they are not then it reports an error prefixed
+// with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertTrimmedStringEquals(message string, expected string, actual string) {
+	h.Helper()
+	h.AssertStringEquals(message, strings.TrimSpace(expected), strings.TrimSpace(actual))
+}
+
+// AssertStringDoesContain checks if the string contains the expected substring. If it does not
+// then it reports an error prefixed with the supplied message and including a reason for why it
+// failed.
+func (h *TestHelper) AssertStringDoesContain(message string, s string, expectedSubstring string) {
+	h.Helper()
+	if !strings.Contains(s, expectedSubstring) {
+		h.Errorf("%s: could not find %q within %q", message, expectedSubstring, s)
+	}
+}
+
+// AssertStringDoesNotContain checks if the string contains the expected substring. If it does then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertStringDoesNotContain(message string, s string, unexpectedSubstring string) {
+	h.Helper()
+	if strings.Contains(s, unexpectedSubstring) {
+		h.Errorf("%s: unexpectedly found %q within %q", message, unexpectedSubstring, s)
+	}
+}
+
+// AssertArrayString checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertArrayString(message string, expected, actual []string) {
+	h.Helper()
+	if len(actual) != len(expected) {
+		h.Errorf("%s: expected %d (%q), actual (%d) %q", message, len(expected), expected, len(actual), actual)
+		return
+	}
+	for i := range actual {
+		if actual[i] != expected[i] {
+			h.Errorf("%s: expected %d-th, %q (%q), actual %q (%q)",
+				message, i, expected[i], expected, actual[i], actual)
+			return
+		}
+	}
+}
+
+// AssertArrayString checks if the expected and actual values are equal using reflect.DeepEqual and
+// if they are not then it reports an error prefixed with the supplied message and including a
+// reason for why it failed.
+func (h *TestHelper) AssertDeepEquals(message string, expected interface{}, actual interface{}) {
+	h.Helper()
+	if !reflect.DeepEqual(actual, expected) {
+		h.Errorf("%s: expected:\n  %#v\n got:\n  %#v", message, expected, actual)
+	}
+}
+
+// Struct to allow TestResult to embed a *TestContext and allow call forwarding to its methods.
+type testContext struct {
+	*TestContext
+}
+
+// The result of running a test.
+type TestResult struct {
+	TestHelper
+	testContext
+
+	fixture *fixture
+	Config  Config
+}
+
+var _ FixtureFactory = (*fixtureFactory)(nil)
+
+type fixtureFactory struct {
+	buildDirSupplier *string
+	preparers        []*simpleFixturePreparer
+}
+
+func (f *fixtureFactory) Extend(preparers ...FixturePreparer) FixtureFactory {
+	all := append(f.preparers, dedupAndFlattenPreparers(f.preparers, preparers)...)
+	return &fixtureFactory{
+		buildDirSupplier: f.buildDirSupplier,
+		preparers:        all,
+	}
+}
+
+func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fixture {
+	config := TestConfig(*f.buildDirSupplier, nil, "", nil)
+	ctx := NewTestContext(config)
+	fixture := &fixture{
+		factory: f,
+		t:       t,
+		config:  config,
+		ctx:     ctx,
+		mockFS:  make(MockFS),
+	}
+
+	for _, preparer := range f.preparers {
+		preparer.function(fixture)
+	}
+
+	for _, preparer := range dedupAndFlattenPreparers(f.preparers, preparers) {
+		preparer.function(fixture)
+	}
+
+	return fixture
+}
+
+func (f *fixtureFactory) RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult {
+	t.Helper()
+	fixture := f.Fixture(t, preparers...)
+	return fixture.RunTest()
+}
+
+func (f *fixtureFactory) RunTestWithBp(t *testing.T, bp string) *TestResult {
+	t.Helper()
+	return f.RunTest(t, FixtureWithRootAndroidBp(bp))
+}
+
+type fixture struct {
+	factory *fixtureFactory
+	t       *testing.T
+	config  Config
+	ctx     *TestContext
+	mockFS  MockFS
+}
+
+func (f *fixture) RunTest() *TestResult {
+	f.t.Helper()
+
+	ctx := f.ctx
+
+	// The TestConfig() method assumes that the mock filesystem is available when creating so creates
+	// the mock file system immediately. Similarly, the NewTestContext(Config) method assumes that the
+	// supplied Config's FileSystem has been properly initialized before it is called and so it takes
+	// its own reference to the filesystem. However, fixtures create the Config and TestContext early
+	// so they can be modified by preparers at which time the mockFS has not been populated (because
+	// it too is modified by preparers). So, this reinitializes the Config and TestContext's
+	// FileSystem using the now populated mockFS.
+	f.config.mockFileSystem("", f.mockFS)
+	ctx.SetFs(ctx.config.fs)
+	if ctx.config.mockBpList != "" {
+		ctx.SetModuleListFile(ctx.config.mockBpList)
+	}
+
+	ctx.Register()
+	_, errs := ctx.ParseBlueprintsFiles("ignored")
+	FailIfErrored(f.t, errs)
+	_, errs = ctx.PrepareBuildActions(f.config)
+	FailIfErrored(f.t, errs)
+
+	result := &TestResult{
+		TestHelper:  TestHelper{T: f.t},
+		testContext: testContext{ctx},
+		fixture:     f,
+		Config:      f.config,
+	}
+	return result
+}
+
+// NormalizePathForTesting removes the test invocation specific build directory from the supplied
+// path.
+//
+// If the path is within the build directory (e.g. an OutputPath) then this returns the relative
+// path to avoid tests having to deal with the dynamically generated build directory.
+//
+// Otherwise, this returns the supplied path as it is almost certainly a source path that is
+// relative to the root of the source tree.
+//
+// Even though some information is removed from some paths and not others it should be possible to
+// differentiate between them by the paths themselves, e.g. output paths will likely include
+// ".intermediates" but source paths won't.
+func (r *TestResult) NormalizePathForTesting(path Path) string {
+	pathContext := PathContextForTesting(r.Config)
+	pathAsString := path.String()
+	if rel, isRel := MaybeRel(pathContext, r.Config.BuildDir(), pathAsString); isRel {
+		return rel
+	}
+	return pathAsString
+}
+
+// NormalizePathsForTesting normalizes each path in the supplied list and returns their normalized
+// forms.
+func (r *TestResult) NormalizePathsForTesting(paths Paths) []string {
+	var result []string
+	for _, path := range paths {
+		result = append(result, r.NormalizePathForTesting(path))
+	}
+	return result
+}
+
+// NewFixture creates a new test fixture that is based on the one that created this result. It is
+// intended to test the output of module types that generate content to be processed by the build,
+// e.g. sdk snapshots.
+func (r *TestResult) NewFixture(preparers ...FixturePreparer) Fixture {
+	return r.fixture.factory.Fixture(r.T, preparers...)
+}
+
+// RunTest is shorthand for NewFixture(preparers...).RunTest().
+func (r *TestResult) RunTest(preparers ...FixturePreparer) *TestResult {
+	r.Helper()
+	return r.fixture.factory.Fixture(r.T, preparers...).RunTest()
+}
+
+// Module returns the module with the specific name and of the specified variant.
+func (r *TestResult) Module(name string, variant string) Module {
+	return r.ModuleForTests(name, variant).Module()
+}
+
+// Create a *TestResult object suitable for use within a subtest.
+//
+// This ensures that any errors reported by the TestResult, e.g. from within one of its
+// Assert... methods, will be associated with the sub test and not the main test.
+//
+// result := ....RunTest()
+// t.Run("subtest", func(t *testing.T) {
+//    subResult := result.ResultForSubTest(t)
+//    subResult.AssertStringEquals("something", ....)
+// })
+func (r *TestResult) ResultForSubTest(t *testing.T) *TestResult {
+	subTestResult := *r
+	r.T = t
+	return &subTestResult
+}