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",
|
"deptag.go",
|
||||||
"expand.go",
|
"expand.go",
|
||||||
"filegroup.go",
|
"filegroup.go",
|
||||||
|
"fixture.go",
|
||||||
"hooks.go",
|
"hooks.go",
|
||||||
"image.go",
|
"image.go",
|
||||||
"license.go",
|
"license.go",
|
||||||
|
@ -87,6 +88,7 @@ bootstrap_go_package {
|
||||||
"depset_test.go",
|
"depset_test.go",
|
||||||
"deptag_test.go",
|
"deptag_test.go",
|
||||||
"expand_test.go",
|
"expand_test.go",
|
||||||
|
"fixture_test.go",
|
||||||
"license_kind_test.go",
|
"license_kind_test.go",
|
||||||
"license_test.go",
|
"license_test.go",
|
||||||
"licenses_test.go",
|
"licenses_test.go",
|
||||||
|
|
|
@ -44,3 +44,5 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
os.Exit(run())
|
os.Exit(run())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var emptyTestFixtureFactory = NewFixtureFactory(&buildDir)
|
||||||
|
|
|
@ -305,10 +305,7 @@ func TestArchConfigFuchsia(buildDir string, env map[string]string, bp string, fs
|
||||||
return testConfig
|
return testConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestArchConfig returns a Config object suitable for using for tests that
|
func modifyTestConfigToSupportArchMutator(testConfig Config) {
|
||||||
// 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)
|
|
||||||
config := testConfig.config
|
config := testConfig.config
|
||||||
|
|
||||||
config.Targets = map[OsType][]Target{
|
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.DeviceArchVariant = proptools.StringPtr("armv8-a")
|
||||||
config.TestProductVariables.DeviceSecondaryArch = proptools.StringPtr("arm")
|
config.TestProductVariables.DeviceSecondaryArch = proptools.StringPtr("arm")
|
||||||
config.TestProductVariables.DeviceSecondaryArchVariant = proptools.StringPtr("armv7-a-neon")
|
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
|
return testConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,10 @@ func init() {
|
||||||
RegisterBp2BuildMutator("filegroup", FilegroupBp2Build)
|
RegisterBp2BuildMutator("filegroup", FilegroupBp2Build)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var PrepareForTestWithFilegroup = FixtureRegisterWithContext(func(ctx RegistrationContext) {
|
||||||
|
ctx.RegisterModuleType("filegroup", FileGroupFactory)
|
||||||
|
})
|
||||||
|
|
||||||
// https://docs.bazel.build/versions/master/be/general.html#filegroup
|
// https://docs.bazel.build/versions/master/be/general.html#filegroup
|
||||||
type bazelFilegroupAttributes struct {
|
type bazelFilegroupAttributes struct {
|
||||||
Srcs bazel.LabelList
|
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) {
|
func TestPrebuilts(t *testing.T) {
|
||||||
fs := map[string][]byte{
|
fs := MockFS{
|
||||||
"prebuilt_file": nil,
|
"prebuilt_file": nil,
|
||||||
"source_file": nil,
|
"source_file": nil,
|
||||||
}
|
}
|
||||||
|
@ -277,32 +277,33 @@ func TestPrebuilts(t *testing.T) {
|
||||||
deps: [":bar"],
|
deps: [":bar"],
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
config := TestArchConfig(buildDir, nil, bp, fs)
|
|
||||||
|
|
||||||
// Add windows to the target list to test the logic when a variant is
|
// Add windows to the target list to test the logic when a variant is
|
||||||
// disabled by default.
|
// disabled by default.
|
||||||
if !Windows.DefaultDisabled {
|
if !Windows.DefaultDisabled {
|
||||||
t.Errorf("windows is assumed to be disabled by default")
|
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)
|
result := emptyTestFixtureFactory.Extend(
|
||||||
registerTestPrebuiltBuildComponents(ctx)
|
PrepareForTestWithArchMutator,
|
||||||
ctx.RegisterModuleType("filegroup", FileGroupFactory)
|
PrepareForTestWithPrebuilts,
|
||||||
ctx.Register()
|
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")
|
for _, variant := range result.ModuleVariantsForTests("foo") {
|
||||||
FailIfErrored(t, errs)
|
foo := result.ModuleForTests("foo", variant)
|
||||||
_, errs = ctx.PrepareBuildActions(config)
|
|
||||||
FailIfErrored(t, errs)
|
|
||||||
|
|
||||||
for _, variant := range ctx.ModuleVariantsForTests("foo") {
|
|
||||||
foo := ctx.ModuleForTests("foo", variant)
|
|
||||||
t.Run(foo.Module().Target().Os.String(), func(t *testing.T) {
|
t.Run(foo.Module().Target().Os.String(), func(t *testing.T) {
|
||||||
var dependsOnSourceModule, dependsOnPrebuiltModule bool
|
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 {
|
if _, ok := m.(*sourceModule); ok {
|
||||||
dependsOnSourceModule = true
|
dependsOnSourceModule = true
|
||||||
}
|
}
|
||||||
|
@ -381,14 +382,18 @@ func TestPrebuilts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerTestPrebuiltBuildComponents(ctx RegistrationContext) {
|
func registerTestPrebuiltBuildComponents(ctx RegistrationContext) {
|
||||||
ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
|
registerTestPrebuiltModules(ctx)
|
||||||
ctx.RegisterModuleType("source", newSourceModule)
|
|
||||||
ctx.RegisterModuleType("override_source", newOverrideSourceModule)
|
|
||||||
|
|
||||||
RegisterPrebuiltMutators(ctx)
|
RegisterPrebuiltMutators(ctx)
|
||||||
ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
|
ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerTestPrebuiltModules(ctx RegistrationContext) {
|
||||||
|
ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
|
||||||
|
ctx.RegisterModuleType("source", newSourceModule)
|
||||||
|
ctx.RegisterModuleType("override_source", newOverrideSourceModule)
|
||||||
|
}
|
||||||
|
|
||||||
type prebuiltModule struct {
|
type prebuiltModule struct {
|
||||||
ModuleBase
|
ModuleBase
|
||||||
prebuilt Prebuilt
|
prebuilt Prebuilt
|
||||||
|
|
|
@ -48,6 +48,43 @@ func NewTestContext(config Config) *TestContext {
|
||||||
return ctx
|
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 {
|
func NewTestArchContext(config Config) *TestContext {
|
||||||
ctx := NewTestContext(config)
|
ctx := NewTestContext(config)
|
||||||
ctx.preDeps = append(ctx.preDeps, registerArchMutator)
|
ctx.preDeps = append(ctx.preDeps, registerArchMutator)
|
||||||
|
|
Loading…
Reference in a new issue