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
This commit is contained in:
parent
30e3e9d21d
commit
358161232c
8 changed files with 730 additions and 24 deletions
|
@ -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",
|
||||
|
|
|
@ -44,3 +44,5 @@ func TestMain(m *testing.M) {
|
|||
|
||||
os.Exit(run())
|
||||
}
|
||||
|
||||
var emptyTestFixtureFactory = NewFixtureFactory(&buildDir)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
604
android/fixture.go
Normal file
604
android/fixture.go
Normal file
|
@ -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
|
||||
}
|
49
android/fixture_test.go
Normal file
49
android/fixture_test.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue