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