Add integration testing infrastructure

Fix mutator registration for tests to allow different tests
in the same package to register different mutators.

Allow tests to track the resulting ModuleBuildParams objects
to use in assertions, and provide helpers for getting them.
For example:
    config := android.TestConfig(buildDir)
    ctx := android.NewTestContext()
    ctx.RegisterModuleType(...)
    ctx.MockFileSystem(...)
    ctx.ParseBlueprintsFile("Android.bp")
    ctx.PrepareBuildActions(config)
    ctx.Register()
    // Get the Inputs value passed to the javac rule for the foo module
    inputs := ctx.ModuleForTests("foo".Rule("javac").Inputs

Test: java_test.go
Change-Id: I10c82967f5f3586d2c176f169906b571ed82fc73
This commit is contained in:
Colin Cross
2017-07-13 14:43:27 -07:00
parent eb54da6ebe
commit cec8171420
12 changed files with 193 additions and 122 deletions

View File

@@ -51,6 +51,7 @@ bootstrap_go_package {
"android/paths.go", "android/paths.go",
"android/prebuilt.go", "android/prebuilt.go",
"android/register.go", "android/register.go",
"android/testing.go",
"android/util.go", "android/util.go",
"android/variable.go", "android/variable.go",

View File

@@ -84,6 +84,8 @@ type config struct {
inMake bool inMake bool
captureBuild bool // true for tests, saves build parameters for each module
OncePer OncePer
} }
@@ -171,7 +173,8 @@ func TestConfig(buildDir string) Config {
DeviceName: stringPtr("test_device"), DeviceName: stringPtr("test_device"),
}, },
buildDir: buildDir, buildDir: buildDir,
captureBuild: true,
} }
config.deviceConfig = &deviceConfig{ config.deviceConfig = &deviceConfig{
config: config, config: config,

View File

@@ -113,6 +113,11 @@ func (defaultable *DefaultableModule) applyDefaults(ctx TopDownMutatorContext,
} }
} }
func registerDefaultsPreArchMutators(ctx RegisterMutatorsContext) {
ctx.BottomUp("defaults_deps", defaultsDepsMutator).Parallel()
ctx.TopDown("defaults", defaultsMutator).Parallel()
}
func defaultsDepsMutator(ctx BottomUpMutatorContext) { func defaultsDepsMutator(ctx BottomUpMutatorContext) {
if defaultable, ok := ctx.Module().(Defaultable); ok { if defaultable, ok := ctx.Module().(Defaultable); ok {
ctx.AddDependency(ctx.Module(), DefaultsDepTag, defaultable.defaults().Defaults...) ctx.AddDependency(ctx.Module(), DefaultsDepTag, defaultable.defaults().Defaults...)

View File

@@ -111,6 +111,8 @@ type Module interface {
AddProperties(props ...interface{}) AddProperties(props ...interface{})
GetProperties() []interface{} GetProperties() []interface{}
BuildParamsForTests() []ModuleBuildParams
} }
type nameProperties struct { type nameProperties struct {
@@ -291,6 +293,9 @@ type ModuleBase struct {
hooks hooks hooks hooks
registerProps []interface{} registerProps []interface{}
// For tests
buildParams []ModuleBuildParams
} }
func (a *ModuleBase) AddProperties(props ...interface{}) { func (a *ModuleBase) AddProperties(props ...interface{}) {
@@ -301,6 +306,10 @@ func (a *ModuleBase) GetProperties() []interface{} {
return a.registerProps return a.registerProps
} }
func (a *ModuleBase) BuildParamsForTests() []ModuleBuildParams {
return a.buildParams
}
// Name returns the name of the module. It may be overridden by individual module types, for // Name returns the name of the module. It may be overridden by individual module types, for
// example prebuilts will prepend prebuilt_ to the name. // example prebuilts will prepend prebuilt_ to the name.
func (a *ModuleBase) Name() string { func (a *ModuleBase) Name() string {
@@ -520,6 +529,8 @@ func (a *ModuleBase) GenerateBuildActions(ctx blueprint.ModuleContext) {
return return
} }
} }
a.buildParams = androidCtx.buildParams
} }
type androidBaseContextImpl struct { type androidBaseContextImpl struct {
@@ -538,6 +549,9 @@ type androidModuleContext struct {
checkbuildFiles Paths checkbuildFiles Paths
missingDeps []string missingDeps []string
module Module module Module
// For tests
buildParams []ModuleBuildParams
} }
func (a *androidModuleContext) ninjaError(desc string, outputs []string, err error) { func (a *androidModuleContext) ninjaError(desc string, outputs []string, err error) {
@@ -566,6 +580,10 @@ func (a *androidModuleContext) Build(pctx blueprint.PackageContext, params bluep
} }
func (a *androidModuleContext) ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams) { func (a *androidModuleContext) ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams) {
if a.config.captureBuild {
a.buildParams = append(a.buildParams, params)
}
bparams := blueprint.BuildParams{ bparams := blueprint.BuildParams{
Rule: params.Rule, Rule: params.Rule,
Deps: params.Deps, Deps: params.Deps,

View File

@@ -15,8 +15,6 @@
package android package android
import ( import (
"sync"
"github.com/google/blueprint" "github.com/google/blueprint"
) )
@@ -27,9 +25,6 @@ import (
// Deps // Deps
// PostDeps // PostDeps
var registerMutatorsOnce sync.Once
var registeredMutators []*mutator
func registerMutatorsToContext(ctx *blueprint.Context, mutators []*mutator) { func registerMutatorsToContext(ctx *blueprint.Context, mutators []*mutator) {
for _, t := range mutators { for _, t := range mutators {
var handle blueprint.MutatorHandle var handle blueprint.MutatorHandle
@@ -44,56 +39,24 @@ func registerMutatorsToContext(ctx *blueprint.Context, mutators []*mutator) {
} }
} }
func registerMutators(ctx *blueprint.Context) { func registerMutators(ctx *blueprint.Context, preArch, preDeps, postDeps []RegisterMutatorFunc) {
mctx := &registerMutatorsContext{}
registerMutatorsOnce.Do(func() {
ctx := &registerMutatorsContext{}
register := func(funcs []RegisterMutatorFunc) {
for _, f := range funcs {
f(ctx)
}
}
ctx.TopDown("load_hooks", loadHookMutator).Parallel()
ctx.BottomUp("prebuilts", prebuiltMutator).Parallel()
ctx.BottomUp("defaults_deps", defaultsDepsMutator).Parallel()
ctx.TopDown("defaults", defaultsMutator).Parallel()
register(preArch)
ctx.BottomUp("arch", archMutator).Parallel()
ctx.TopDown("arch_hooks", archHookMutator).Parallel()
register(preDeps)
ctx.BottomUp("deps", depsMutator).Parallel()
ctx.TopDown("prebuilt_select", PrebuiltSelectModuleMutator).Parallel()
ctx.BottomUp("prebuilt_replace", PrebuiltReplaceMutator).Parallel()
register(postDeps)
registeredMutators = ctx.mutators
})
registerMutatorsToContext(ctx, registeredMutators)
}
func RegisterTestMutators(ctx *blueprint.Context) {
mutators := &registerMutatorsContext{}
register := func(funcs []RegisterMutatorFunc) { register := func(funcs []RegisterMutatorFunc) {
for _, f := range funcs { for _, f := range funcs {
f(mutators) f(mctx)
} }
} }
register(testPreDeps) register(preArch)
mutators.BottomUp("deps", depsMutator).Parallel()
register(testPostDeps)
registerMutatorsToContext(ctx, mutators.mutators) register(preDeps)
mctx.BottomUp("deps", depsMutator).Parallel()
register(postDeps)
registerMutatorsToContext(ctx, mctx.mutators)
} }
type registerMutatorsContext struct { type registerMutatorsContext struct {
@@ -107,7 +70,24 @@ type RegisterMutatorsContext interface {
type RegisterMutatorFunc func(RegisterMutatorsContext) type RegisterMutatorFunc func(RegisterMutatorsContext)
var preArch, preDeps, postDeps, testPreDeps, testPostDeps []RegisterMutatorFunc var preArch = []RegisterMutatorFunc{
func(ctx RegisterMutatorsContext) {
ctx.TopDown("load_hooks", loadHookMutator).Parallel()
},
registerPrebuiltsPreArchMutators,
registerDefaultsPreArchMutators,
}
var preDeps = []RegisterMutatorFunc{
func(ctx RegisterMutatorsContext) {
ctx.BottomUp("arch", archMutator).Parallel()
ctx.TopDown("arch_hooks", archHookMutator).Parallel()
},
}
var postDeps = []RegisterMutatorFunc{
registerPrebuiltsPostDepsMutators,
}
func PreArchMutators(f RegisterMutatorFunc) { func PreArchMutators(f RegisterMutatorFunc) {
preArch = append(preArch, f) preArch = append(preArch, f)
@@ -121,14 +101,6 @@ func PostDepsMutators(f RegisterMutatorFunc) {
postDeps = append(postDeps, f) postDeps = append(postDeps, f)
} }
func TestPreDepsMutators(f RegisterMutatorFunc) {
testPreDeps = append(testPreDeps, f)
}
func TeststPostDepsMutators(f RegisterMutatorFunc) {
testPostDeps = append(testPostDeps, f)
}
type AndroidTopDownMutator func(TopDownMutatorContext) type AndroidTopDownMutator func(TopDownMutatorContext)
type TopDownMutatorContext interface { type TopDownMutatorContext interface {

View File

@@ -61,6 +61,15 @@ type PrebuiltInterface interface {
Prebuilt() *Prebuilt Prebuilt() *Prebuilt
} }
func registerPrebuiltsPreArchMutators(ctx RegisterMutatorsContext) {
ctx.BottomUp("prebuilts", prebuiltMutator).Parallel()
}
func registerPrebuiltsPostDepsMutators(ctx RegisterMutatorsContext) {
ctx.TopDown("prebuilt_select", PrebuiltSelectModuleMutator).Parallel()
ctx.BottomUp("prebuilt_replace", PrebuiltReplaceMutator).Parallel()
}
// prebuiltMutator ensures that there is always a module with an undecorated name, and marks // prebuiltMutator ensures that there is always a module with an undecorated name, and marks
// prebuilt modules that have both a prebuilt and a source module. // prebuilt modules that have both a prebuilt and a source module.
func prebuiltMutator(ctx BottomUpMutatorContext) { func prebuiltMutator(ctx BottomUpMutatorContext) {

View File

@@ -122,9 +122,12 @@ func TestPrebuilts(t *testing.T) {
for _, test := range prebuiltsTests { for _, test := range prebuiltsTests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ctx := NewContext() ctx := NewTestContext()
ctx.PreArchMutators(registerPrebuiltsPreArchMutators)
ctx.PostDepsMutators(registerPrebuiltsPostDepsMutators)
ctx.RegisterModuleType("prebuilt", ModuleFactoryAdaptor(newPrebuiltModule)) ctx.RegisterModuleType("prebuilt", ModuleFactoryAdaptor(newPrebuiltModule))
ctx.RegisterModuleType("source", ModuleFactoryAdaptor(newSourceModule)) ctx.RegisterModuleType("source", ModuleFactoryAdaptor(newSourceModule))
ctx.Register()
ctx.MockFileSystem(map[string][]byte{ ctx.MockFileSystem(map[string][]byte{
"Blueprints": []byte(` "Blueprints": []byte(`
source { source {
@@ -139,13 +142,10 @@ func TestPrebuilts(t *testing.T) {
_, errs = ctx.PrepareBuildActions(config) _, errs = ctx.PrepareBuildActions(config)
fail(t, errs) fail(t, errs)
foo := findModule(ctx, "foo") foo := ctx.ModuleForTests("foo", "")
if foo == nil {
t.Fatalf("failed to find module foo")
}
var dependsOnSourceModule, dependsOnPrebuiltModule bool var dependsOnSourceModule, dependsOnPrebuiltModule bool
ctx.VisitDirectDeps(foo, func(m blueprint.Module) { ctx.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
if _, ok := m.(*sourceModule); ok { if _, ok := m.(*sourceModule); ok {
dependsOnSourceModule = true dependsOnSourceModule = true
} }
@@ -228,16 +228,6 @@ func (s *sourceModule) DepsMutator(ctx BottomUpMutatorContext) {
func (s *sourceModule) GenerateAndroidBuildActions(ctx ModuleContext) { func (s *sourceModule) GenerateAndroidBuildActions(ctx ModuleContext) {
} }
func findModule(ctx *blueprint.Context, name string) blueprint.Module {
var ret blueprint.Module
ctx.VisitAllModules(func(m blueprint.Module) {
if ctx.ModuleName(m) == name {
ret = m
}
})
return ret
}
func fail(t *testing.T, errs []error) { func fail(t *testing.T, errs []error) {
if len(errs) > 0 { if len(errs) > 0 {
for _, err := range errs { for _, err := range errs {

View File

@@ -60,9 +60,15 @@ func RegisterSingletonType(name string, factory blueprint.SingletonFactory) {
singletons = append(singletons, singleton{name, factory}) singletons = append(singletons, singleton{name, factory})
} }
func NewContext() *blueprint.Context { type Context struct {
ctx := blueprint.NewContext() *blueprint.Context
}
func NewContext() *Context {
return &Context{blueprint.NewContext()}
}
func (ctx *Context) Register() {
for _, t := range moduleTypes { for _, t := range moduleTypes {
ctx.RegisterModuleType(t.name, t.factory) ctx.RegisterModuleType(t.name, t.factory)
} }
@@ -71,9 +77,7 @@ func NewContext() *blueprint.Context {
ctx.RegisterSingletonType(t.name, t.factory) ctx.RegisterSingletonType(t.name, t.factory)
} }
registerMutators(ctx) registerMutators(ctx.Context, preArch, preDeps, postDeps)
ctx.RegisterSingletonType("env", EnvSingleton) ctx.RegisterSingletonType("env", EnvSingleton)
return ctx
} }

98
android/testing.go Normal file
View File

@@ -0,0 +1,98 @@
// Copyright 2017 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 (
"fmt"
"strings"
"github.com/google/blueprint"
)
func NewTestContext() *TestContext {
return &TestContext{
Context: blueprint.NewContext(),
}
}
type TestContext struct {
*blueprint.Context
preArch, preDeps, postDeps []RegisterMutatorFunc
}
func (ctx *TestContext) PreArchMutators(f RegisterMutatorFunc) {
ctx.preArch = append(ctx.preArch, f)
}
func (ctx *TestContext) PreDepsMutators(f RegisterMutatorFunc) {
ctx.preDeps = append(ctx.preDeps, f)
}
func (ctx *TestContext) PostDepsMutators(f RegisterMutatorFunc) {
ctx.postDeps = append(ctx.postDeps, f)
}
func (ctx *TestContext) Register() {
registerMutators(ctx.Context, ctx.preArch, ctx.preDeps, ctx.postDeps)
ctx.RegisterSingletonType("env", EnvSingleton)
}
func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
var module Module
ctx.VisitAllModules(func(m blueprint.Module) {
if ctx.ModuleName(m) == name && ctx.ModuleSubDir(m) == variant {
module = m.(Module)
}
})
if module == nil {
panic(fmt.Errorf("failed to find module %q variant %q", name, variant))
}
return TestingModule{module}
}
type TestingModule struct {
module Module
}
func (m TestingModule) Module() Module {
return m.module
}
func (m TestingModule) Rule(rule string) ModuleBuildParams {
for _, p := range m.module.BuildParamsForTests() {
if strings.Contains(p.Rule.String(), rule) {
return p
}
}
panic(fmt.Errorf("couldn't find rule %q", rule))
}
func (m TestingModule) Output(file string) ModuleBuildParams {
for _, p := range m.module.BuildParamsForTests() {
outputs := append(WritablePaths(nil), p.Outputs...)
if p.Output != nil {
outputs = append(outputs, p.Output)
}
for _, f := range outputs {
if f.Base() == file {
return p
}
}
}
panic(fmt.Errorf("couldn't find output %q", file))
}

View File

@@ -23,8 +23,6 @@ import (
"android/soong/android" "android/soong/android"
"android/soong/genrule" "android/soong/genrule"
"github.com/google/blueprint"
) )
type dataFile struct { type dataFile struct {
@@ -123,8 +121,7 @@ func TestDataTests(t *testing.T) {
for _, test := range testDataTests { for _, test := range testDataTests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ctx := blueprint.NewContext() ctx := android.NewTestContext()
android.RegisterTestMutators(ctx)
ctx.MockFileSystem(map[string][]byte{ ctx.MockFileSystem(map[string][]byte{
"Blueprints": []byte(`subdirs = ["dir"]`), "Blueprints": []byte(`subdirs = ["dir"]`),
"dir/Blueprints": []byte(test.modules), "dir/Blueprints": []byte(test.modules),
@@ -135,18 +132,16 @@ func TestDataTests(t *testing.T) {
android.ModuleFactoryAdaptor(genrule.FileGroupFactory)) android.ModuleFactoryAdaptor(genrule.FileGroupFactory))
ctx.RegisterModuleType("test", ctx.RegisterModuleType("test",
android.ModuleFactoryAdaptor(newTest)) android.ModuleFactoryAdaptor(newTest))
ctx.Register()
_, errs := ctx.ParseBlueprintsFiles("Blueprints") _, errs := ctx.ParseBlueprintsFiles("Blueprints")
fail(t, errs) fail(t, errs)
_, errs = ctx.PrepareBuildActions(config) _, errs = ctx.PrepareBuildActions(config)
fail(t, errs) fail(t, errs)
foo := findModule(ctx, "foo") foo := ctx.ModuleForTests("foo", "")
if foo == nil {
t.Fatalf("failed to find module foo")
}
got := foo.(*testDataTest).data got := foo.Module().(*testDataTest).data
if len(got) != len(test.data) { if len(got) != len(test.data) {
t.Errorf("expected %d data files, got %d", t.Errorf("expected %d data files, got %d",
len(test.data), len(got)) len(test.data), len(got))
@@ -192,16 +187,6 @@ func (test *testDataTest) GenerateAndroidBuildActions(ctx android.ModuleContext)
test.data = ctx.ExpandSources(test.Properties.Data, nil) test.data = ctx.ExpandSources(test.Properties.Data, nil)
} }
func findModule(ctx *blueprint.Context, name string) blueprint.Module {
var ret blueprint.Module
ctx.VisitAllModules(func(m blueprint.Module) {
if ctx.ModuleName(m) == name {
ret = m
}
})
return ret
}
func fail(t *testing.T, errs []error) { func fail(t *testing.T, errs []error) {
if len(errs) > 0 { if len(errs) > 0 {
for _, err := range errs { for _, err := range errs {

View File

@@ -32,6 +32,7 @@ func main() {
srcDir := filepath.Dir(flag.Arg(0)) srcDir := filepath.Dir(flag.Arg(0))
ctx := android.NewContext() ctx := android.NewContext()
ctx.Register()
configuration, err := android.NewConfig(srcDir, bootstrap.BuildDir) configuration, err := android.NewConfig(srcDir, bootstrap.BuildDir)
if err != nil { if err != nil {
@@ -44,5 +45,5 @@ func main() {
ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies()) ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
bootstrap.Main(ctx, configuration, configuration.ConfigFileName, configuration.ProductVariablesFileName) bootstrap.Main(ctx.Context, configuration, configuration.ConfigFileName, configuration.ProductVariablesFileName)
} }

View File

@@ -26,8 +26,6 @@ import (
"testing" "testing"
"android/soong/android" "android/soong/android"
"github.com/google/blueprint"
) )
type pyBinary struct { type pyBinary struct {
@@ -306,17 +304,17 @@ var (
func TestPythonModule(t *testing.T) { func TestPythonModule(t *testing.T) {
config, buildDir := setupBuildEnv(t) config, buildDir := setupBuildEnv(t)
defer tearDownBuildEnv(buildDir) defer tearDownBuildEnv(buildDir)
android.TestPreDepsMutators(func(ctx android.RegisterMutatorsContext) {
ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
})
for _, d := range data { for _, d := range data {
t.Run(d.desc, func(t *testing.T) { t.Run(d.desc, func(t *testing.T) {
ctx := blueprint.NewContext() ctx := android.NewTestContext()
android.RegisterTestMutators(ctx) ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
})
ctx.RegisterModuleType("python_library_host", ctx.RegisterModuleType("python_library_host",
android.ModuleFactoryAdaptor(PythonLibraryHostFactory)) android.ModuleFactoryAdaptor(PythonLibraryHostFactory))
ctx.RegisterModuleType("python_binary_host", ctx.RegisterModuleType("python_binary_host",
android.ModuleFactoryAdaptor(PythonBinaryHostFactory)) android.ModuleFactoryAdaptor(PythonBinaryHostFactory))
ctx.Register()
ctx.MockFileSystem(d.mockFiles) ctx.MockFileSystem(d.mockFiles)
_, testErrs := ctx.ParseBlueprintsFiles(bpFile) _, testErrs := ctx.ParseBlueprintsFiles(bpFile)
fail(t, testErrs) fail(t, testErrs)
@@ -360,15 +358,12 @@ func expectErrors(t *testing.T, actErrs []error, expErrs []string) (testErrs []e
return return
} }
func expectModule(t *testing.T, ctx *blueprint.Context, buildDir, name, variant string, func expectModule(t *testing.T, ctx *android.TestContext, buildDir, name, variant string,
expPyRunfiles, expDepsPyRunfiles []string, expPyRunfiles, expDepsPyRunfiles []string,
expParSpec string, expDepsParSpecs []string) (testErrs []error) { expParSpec string, expDepsParSpecs []string) (testErrs []error) {
module := findModule(ctx, name, variant) module := ctx.ModuleForTests(name, variant)
if module == nil {
t.Fatalf("failed to find module %s!", name)
}
base, baseOk := module.(*pythonBaseModule) base, baseOk := module.Module().(*pythonBaseModule)
if !baseOk { if !baseOk {
t.Fatalf("%s is not Python module!", name) t.Fatalf("%s is not Python module!", name)
} }
@@ -438,16 +433,6 @@ func tearDownBuildEnv(buildDir string) {
os.RemoveAll(buildDir) os.RemoveAll(buildDir)
} }
func findModule(ctx *blueprint.Context, name, variant string) blueprint.Module {
var ret blueprint.Module
ctx.VisitAllModules(func(m blueprint.Module) {
if ctx.ModuleName(m) == name && ctx.ModuleSubDir(m) == variant {
ret = m
}
})
return ret
}
func fail(t *testing.T, errs []error) { func fail(t *testing.T, errs []error) {
if len(errs) > 0 { if len(errs) > 0 {
for _, err := range errs { for _, err := range errs {