Support testing Rules in Modules and Rules and Builds in Singletons

Add support for TestingModule to return RuleParams for rules created
by the module.

Refactor TestingModule to use helpers, and use the helpers to
implement a similar TestingSingleton.

Use the new functionality to test RuleBuilder's module and singleton
rules.

Test: none
Change-Id: I8348c56ff5086d0c49401f5a00faf7c864e6b6f3
This commit is contained in:
Colin Cross
2019-02-25 14:54:28 -08:00
parent 87ecbfe107
commit 4c83e5ccd4
5 changed files with 263 additions and 93 deletions

View File

@@ -193,6 +193,7 @@ type Module interface {
GetProperties() []interface{}
BuildParamsForTests() []BuildParams
RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
VariablesForTests() map[string]string
}
@@ -477,6 +478,7 @@ type ModuleBase struct {
// For tests
buildParams []BuildParams
ruleParams map[blueprint.Rule]blueprint.RuleParams
variables map[string]string
prefer32 func(ctx BaseModuleContext, base *ModuleBase, class OsClass) bool
@@ -496,6 +498,10 @@ func (a *ModuleBase) BuildParamsForTests() []BuildParams {
return a.buildParams
}
func (a *ModuleBase) RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams {
return a.ruleParams
}
func (a *ModuleBase) VariablesForTests() map[string]string {
return a.variables
}
@@ -795,6 +801,10 @@ func (a *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext)
variables: make(map[string]string),
}
if ctx.config.captureBuild {
ctx.ruleParams = make(map[blueprint.Rule]blueprint.RuleParams)
}
desc := "//" + ctx.ModuleDir() + ":" + ctx.ModuleName() + " "
var suffix []string
if ctx.Os().Class != Device && ctx.Os().Class != Generic {
@@ -854,6 +864,7 @@ func (a *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext)
}
a.buildParams = ctx.buildParams
a.ruleParams = ctx.ruleParams
a.variables = ctx.variables
}
@@ -877,6 +888,7 @@ type androidModuleContext struct {
// For tests
buildParams []BuildParams
ruleParams map[blueprint.Rule]blueprint.RuleParams
variables map[string]string
}
@@ -952,7 +964,13 @@ func (a *androidModuleContext) Variable(pctx PackageContext, name, value string)
func (a *androidModuleContext) Rule(pctx PackageContext, name string, params blueprint.RuleParams,
argNames ...string) blueprint.Rule {
return a.ModuleContext.Rule(pctx.PackageContext, name, params, argNames...)
rule := a.ModuleContext.Rule(pctx.PackageContext, name, params, argNames...)
if a.config.captureBuild {
a.ruleParams[rule] = params
}
return rule
}
func (a *androidModuleContext) Build(pctx PackageContext, params BuildParams) {

View File

@@ -61,7 +61,7 @@ func SingletonFactoryAdaptor(factory SingletonFactory) blueprint.SingletonFactor
if makevars, ok := singleton.(SingletonMakeVarsProvider); ok {
registerSingletonMakeVarsProvider(makevars)
}
return singletonAdaptor{singleton}
return &singletonAdaptor{Singleton: singleton}
}
}

View File

@@ -364,17 +364,26 @@ func TestRuleBuilder_Build(t *testing.T) {
_, errs = ctx.PrepareBuildActions(config)
FailIfErrored(t, errs)
foo := ctx.ModuleForTests("foo", "").Rule("rule")
check := func(t *testing.T, params TestingBuildParams, wantOutput string) {
if len(params.RuleParams.CommandDeps) != 1 || params.RuleParams.CommandDeps[0] != "cp" {
t.Errorf("want RuleParams.CommandDeps = [%q], got %q", "cp", params.RuleParams.CommandDeps)
}
// TODO: make RuleParams accessible to tests and verify rule.Command().Tools() ends up in CommandDeps
if len(params.Implicits) != 1 || params.Implicits[0].String() != "bar" {
t.Errorf("want Implicits = [%q], got %q", "bar", params.Implicits.Strings())
}
if len(foo.Implicits) != 1 || foo.Implicits[0].String() != "bar" {
t.Errorf("want foo.Implicits = [%q], got %q", "bar", foo.Implicits.Strings())
}
wantOutput := filepath.Join(buildDir, ".intermediates", "foo", "foo")
if len(foo.Outputs) != 1 || foo.Outputs[0].String() != wantOutput {
t.Errorf("want foo.Outputs = [%q], got %q", wantOutput, foo.Outputs.Strings())
if len(params.Outputs) != 1 || params.Outputs[0].String() != wantOutput {
t.Errorf("want Outputs = [%q], got %q", wantOutput, params.Outputs.Strings())
}
}
t.Run("module", func(t *testing.T) {
check(t, ctx.ModuleForTests("foo", "").Rule("rule"),
filepath.Join(buildDir, ".intermediates", "foo", "foo"))
})
t.Run("singleton", func(t *testing.T) {
check(t, ctx.SingletonForTests("rule_builder_test").Rule("rule"),
filepath.Join(buildDir, "baz"))
})
}

View File

@@ -76,10 +76,31 @@ type SingletonContext interface {
type singletonAdaptor struct {
Singleton
buildParams []BuildParams
ruleParams map[blueprint.Rule]blueprint.RuleParams
}
func (s singletonAdaptor) GenerateBuildActions(ctx blueprint.SingletonContext) {
s.Singleton.GenerateBuildActions(singletonContextAdaptor{ctx})
var _ testBuildProvider = (*singletonAdaptor)(nil)
func (s *singletonAdaptor) GenerateBuildActions(ctx blueprint.SingletonContext) {
sctx := &singletonContextAdaptor{SingletonContext: ctx}
if sctx.Config().captureBuild {
sctx.ruleParams = make(map[blueprint.Rule]blueprint.RuleParams)
}
s.Singleton.GenerateBuildActions(sctx)
s.buildParams = sctx.buildParams
s.ruleParams = sctx.ruleParams
}
func (s *singletonAdaptor) BuildParamsForTests() []BuildParams {
return s.buildParams
}
func (s *singletonAdaptor) RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams {
return s.ruleParams
}
type Singleton interface {
@@ -88,35 +109,45 @@ type Singleton interface {
type singletonContextAdaptor struct {
blueprint.SingletonContext
buildParams []BuildParams
ruleParams map[blueprint.Rule]blueprint.RuleParams
}
func (s singletonContextAdaptor) Config() Config {
func (s *singletonContextAdaptor) Config() Config {
return s.SingletonContext.Config().(Config)
}
func (s singletonContextAdaptor) DeviceConfig() DeviceConfig {
func (s *singletonContextAdaptor) DeviceConfig() DeviceConfig {
return DeviceConfig{s.Config().deviceConfig}
}
func (s singletonContextAdaptor) Variable(pctx PackageContext, name, value string) {
func (s *singletonContextAdaptor) Variable(pctx PackageContext, name, value string) {
s.SingletonContext.Variable(pctx.PackageContext, name, value)
}
func (s singletonContextAdaptor) Rule(pctx PackageContext, name string, params blueprint.RuleParams, argNames ...string) blueprint.Rule {
return s.SingletonContext.Rule(pctx.PackageContext, name, params, argNames...)
func (s *singletonContextAdaptor) Rule(pctx PackageContext, name string, params blueprint.RuleParams, argNames ...string) blueprint.Rule {
rule := s.SingletonContext.Rule(pctx.PackageContext, name, params, argNames...)
if s.Config().captureBuild {
s.ruleParams[rule] = params
}
return rule
}
func (s singletonContextAdaptor) Build(pctx PackageContext, params BuildParams) {
func (s *singletonContextAdaptor) Build(pctx PackageContext, params BuildParams) {
if s.Config().captureBuild {
s.buildParams = append(s.buildParams, params)
}
bparams := convertBuildParams(params)
s.SingletonContext.Build(pctx.PackageContext, bparams)
}
func (s singletonContextAdaptor) SetNinjaBuildDir(pctx PackageContext, value string) {
func (s *singletonContextAdaptor) SetNinjaBuildDir(pctx PackageContext, value string) {
s.SingletonContext.SetNinjaBuildDir(pctx.PackageContext, value)
}
func (s singletonContextAdaptor) Eval(pctx PackageContext, ninjaStr string) (string, error) {
func (s *singletonContextAdaptor) Eval(pctx PackageContext, ninjaStr string) (string, error) {
return s.SingletonContext.Eval(pctx.PackageContext, ninjaStr)
}
@@ -144,34 +175,34 @@ func predAdaptor(pred func(Module) bool) func(blueprint.Module) bool {
}
}
func (s singletonContextAdaptor) VisitAllModulesBlueprint(visit func(blueprint.Module)) {
func (s *singletonContextAdaptor) VisitAllModulesBlueprint(visit func(blueprint.Module)) {
s.SingletonContext.VisitAllModules(visit)
}
func (s singletonContextAdaptor) VisitAllModules(visit func(Module)) {
func (s *singletonContextAdaptor) VisitAllModules(visit func(Module)) {
s.SingletonContext.VisitAllModules(visitAdaptor(visit))
}
func (s singletonContextAdaptor) VisitAllModulesIf(pred func(Module) bool, visit func(Module)) {
func (s *singletonContextAdaptor) VisitAllModulesIf(pred func(Module) bool, visit func(Module)) {
s.SingletonContext.VisitAllModulesIf(predAdaptor(pred), visitAdaptor(visit))
}
func (s singletonContextAdaptor) VisitDepsDepthFirst(module Module, visit func(Module)) {
func (s *singletonContextAdaptor) VisitDepsDepthFirst(module Module, visit func(Module)) {
s.SingletonContext.VisitDepsDepthFirst(module, visitAdaptor(visit))
}
func (s singletonContextAdaptor) VisitDepsDepthFirstIf(module Module, pred func(Module) bool, visit func(Module)) {
func (s *singletonContextAdaptor) VisitDepsDepthFirstIf(module Module, pred func(Module) bool, visit func(Module)) {
s.SingletonContext.VisitDepsDepthFirstIf(module, predAdaptor(pred), visitAdaptor(visit))
}
func (s singletonContextAdaptor) VisitAllModuleVariants(module Module, visit func(Module)) {
func (s *singletonContextAdaptor) VisitAllModuleVariants(module Module, visit func(Module)) {
s.SingletonContext.VisitAllModuleVariants(module, visitAdaptor(visit))
}
func (s singletonContextAdaptor) PrimaryModule(module Module) Module {
func (s *singletonContextAdaptor) PrimaryModule(module Module) Module {
return s.SingletonContext.PrimaryModule(module).(Module)
}
func (s singletonContextAdaptor) FinalModule(module Module) Module {
func (s *singletonContextAdaptor) FinalModule(module Module) Module {
return s.SingletonContext.FinalModule(module).(Module)
}

View File

@@ -31,7 +31,7 @@ func NewTestContext() *TestContext {
nameResolver := NewNameResolver(namespaceExportFilter)
ctx := &TestContext{
Context: blueprint.NewContext(),
Context: &Context{blueprint.NewContext()},
NameResolver: nameResolver,
}
@@ -47,7 +47,7 @@ func NewTestArchContext() *TestContext {
}
type TestContext struct {
*blueprint.Context
*Context
preArch, preDeps, postDeps []RegisterMutatorFunc
NameResolver *NameResolver
}
@@ -65,7 +65,7 @@ func (ctx *TestContext) PostDepsMutators(f RegisterMutatorFunc) {
}
func (ctx *TestContext) Register() {
registerMutators(ctx.Context, ctx.preArch, ctx.preDeps, ctx.postDeps)
registerMutators(ctx.Context.Context, ctx.preArch, ctx.preDeps, ctx.postDeps)
ctx.RegisterSingletonType("env", SingletonFactoryAdaptor(EnvSingleton))
}
@@ -102,6 +102,24 @@ func (ctx *TestContext) ModuleVariantsForTests(name string) []string {
return variants
}
// SingletonForTests returns a TestingSingleton for the singleton registered with the given name.
func (ctx *TestContext) SingletonForTests(name string) TestingSingleton {
allSingletonNames := []string{}
for _, s := range ctx.Singletons() {
n := ctx.SingletonName(s)
if n == name {
return TestingSingleton{
singleton: s.(*singletonAdaptor).Singleton,
provider: s.(testBuildProvider),
}
}
allSingletonNames = append(allSingletonNames, n)
}
panic(fmt.Errorf("failed to find singleton %q."+
"\nall singletons: %v", name, allSingletonNames))
}
// MockFileSystem causes the Context to replace all reads with accesses to the provided map of
// filenames to contents stored as a byte slice.
func (ctx *TestContext) MockFileSystem(files map[string][]byte) {
@@ -121,6 +139,95 @@ func (ctx *TestContext) MockFileSystem(files map[string][]byte) {
ctx.Context.MockFileSystem(files)
}
type testBuildProvider interface {
BuildParamsForTests() []BuildParams
RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
}
type TestingBuildParams struct {
BuildParams
RuleParams blueprint.RuleParams
}
func newTestingBuildParams(provider testBuildProvider, bparams BuildParams) TestingBuildParams {
return TestingBuildParams{
BuildParams: bparams,
RuleParams: provider.RuleParamsForTests()[bparams.Rule],
}
}
func maybeBuildParamsFromRule(provider testBuildProvider, rule string) TestingBuildParams {
for _, p := range provider.BuildParamsForTests() {
if strings.Contains(p.Rule.String(), rule) {
return newTestingBuildParams(provider, p)
}
}
return TestingBuildParams{}
}
func buildParamsFromRule(provider testBuildProvider, rule string) TestingBuildParams {
p := maybeBuildParamsFromRule(provider, rule)
if p.Rule == nil {
panic(fmt.Errorf("couldn't find rule %q", rule))
}
return p
}
func maybeBuildParamsFromDescription(provider testBuildProvider, desc string) TestingBuildParams {
for _, p := range provider.BuildParamsForTests() {
if p.Description == desc {
return newTestingBuildParams(provider, p)
}
}
return TestingBuildParams{}
}
func buildParamsFromDescription(provider testBuildProvider, desc string) TestingBuildParams {
p := maybeBuildParamsFromDescription(provider, desc)
if p.Rule == nil {
panic(fmt.Errorf("couldn't find description %q", desc))
}
return p
}
func maybeBuildParamsFromOutput(provider testBuildProvider, file string) (TestingBuildParams, []string) {
var searchedOutputs []string
for _, p := range provider.BuildParamsForTests() {
outputs := append(WritablePaths(nil), p.Outputs...)
if p.Output != nil {
outputs = append(outputs, p.Output)
}
for _, f := range outputs {
if f.String() == file || f.Rel() == file {
return newTestingBuildParams(provider, p), nil
}
searchedOutputs = append(searchedOutputs, f.Rel())
}
}
return TestingBuildParams{}, searchedOutputs
}
func buildParamsFromOutput(provider testBuildProvider, file string) TestingBuildParams {
p, searchedOutputs := maybeBuildParamsFromOutput(provider, file)
if p.Rule == nil {
panic(fmt.Errorf("couldn't find output %q.\nall outputs: %v",
file, searchedOutputs))
}
return p
}
func allOutputs(provider testBuildProvider) []string {
var outputFullPaths []string
for _, p := range provider.BuildParamsForTests() {
outputs := append(WritablePaths(nil), p.Outputs...)
if p.Output != nil {
outputs = append(outputs, p.Output)
}
outputFullPaths = append(outputFullPaths, outputs.Strings()...)
}
return outputFullPaths
}
// TestingModule is wrapper around an android.Module that provides methods to find information about individual
// ctx.Build parameters for verification in tests.
type TestingModule struct {
@@ -134,91 +241,96 @@ func (m TestingModule) Module() Module {
// MaybeRule finds a call to ctx.Build with BuildParams.Rule set to a rule with the given name. Returns an empty
// BuildParams if no rule is found.
func (m TestingModule) MaybeRule(rule string) BuildParams {
for _, p := range m.module.BuildParamsForTests() {
if strings.Contains(p.Rule.String(), rule) {
return p
}
}
return BuildParams{}
func (m TestingModule) MaybeRule(rule string) TestingBuildParams {
return maybeBuildParamsFromRule(m.module, rule)
}
// Rule finds a call to ctx.Build with BuildParams.Rule set to a rule with the given name. Panics if no rule is found.
func (m TestingModule) Rule(rule string) BuildParams {
p := m.MaybeRule(rule)
if p.Rule == nil {
panic(fmt.Errorf("couldn't find rule %q", rule))
}
return p
func (m TestingModule) Rule(rule string) TestingBuildParams {
return buildParamsFromRule(m.module, rule)
}
// MaybeDescription finds a call to ctx.Build with BuildParams.Description set to a the given string. Returns an empty
// BuildParams if no rule is found.
func (m TestingModule) MaybeDescription(desc string) BuildParams {
for _, p := range m.module.BuildParamsForTests() {
if p.Description == desc {
return p
}
}
return BuildParams{}
func (m TestingModule) MaybeDescription(desc string) TestingBuildParams {
return maybeBuildParamsFromDescription(m.module, desc)
}
// Description finds a call to ctx.Build with BuildParams.Description set to a the given string. Panics if no rule is
// found.
func (m TestingModule) Description(desc string) BuildParams {
p := m.MaybeDescription(desc)
if p.Rule == nil {
panic(fmt.Errorf("couldn't find description %q", desc))
}
return p
}
func (m TestingModule) maybeOutput(file string) (BuildParams, []string) {
var searchedOutputs []string
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.String() == file || f.Rel() == file {
return p, nil
}
searchedOutputs = append(searchedOutputs, f.Rel())
}
}
return BuildParams{}, searchedOutputs
func (m TestingModule) Description(desc string) TestingBuildParams {
return buildParamsFromDescription(m.module, desc)
}
// MaybeOutput finds a call to ctx.Build with a BuildParams.Output or BuildParams.Outputs whose String() or Rel()
// value matches the provided string. Returns an empty BuildParams if no rule is found.
func (m TestingModule) MaybeOutput(file string) BuildParams {
p, _ := m.maybeOutput(file)
func (m TestingModule) MaybeOutput(file string) TestingBuildParams {
p, _ := maybeBuildParamsFromOutput(m.module, file)
return p
}
// Output finds a call to ctx.Build with a BuildParams.Output or BuildParams.Outputs whose String() or Rel()
// value matches the provided string. Panics if no rule is found.
func (m TestingModule) Output(file string) BuildParams {
p, searchedOutputs := m.maybeOutput(file)
if p.Rule == nil {
panic(fmt.Errorf("couldn't find output %q.\nall outputs: %v",
file, searchedOutputs))
}
return p
func (m TestingModule) Output(file string) TestingBuildParams {
return buildParamsFromOutput(m.module, file)
}
// AllOutputs returns all 'BuildParams.Output's and 'BuildParams.Outputs's in their full path string forms.
func (m TestingModule) AllOutputs() []string {
var outputFullPaths []string
for _, p := range m.module.BuildParamsForTests() {
outputs := append(WritablePaths(nil), p.Outputs...)
if p.Output != nil {
outputs = append(outputs, p.Output)
}
outputFullPaths = append(outputFullPaths, outputs.Strings()...)
}
return outputFullPaths
return allOutputs(m.module)
}
// TestingSingleton is wrapper around an android.Singleton that provides methods to find information about individual
// ctx.Build parameters for verification in tests.
type TestingSingleton struct {
singleton Singleton
provider testBuildProvider
}
// Singleton returns the Singleton wrapped by the TestingSingleton.
func (s TestingSingleton) Singleton() Singleton {
return s.singleton
}
// MaybeRule finds a call to ctx.Build with BuildParams.Rule set to a rule with the given name. Returns an empty
// BuildParams if no rule is found.
func (s TestingSingleton) MaybeRule(rule string) TestingBuildParams {
return maybeBuildParamsFromRule(s.provider, rule)
}
// Rule finds a call to ctx.Build with BuildParams.Rule set to a rule with the given name. Panics if no rule is found.
func (s TestingSingleton) Rule(rule string) TestingBuildParams {
return buildParamsFromRule(s.provider, rule)
}
// MaybeDescription finds a call to ctx.Build with BuildParams.Description set to a the given string. Returns an empty
// BuildParams if no rule is found.
func (s TestingSingleton) MaybeDescription(desc string) TestingBuildParams {
return maybeBuildParamsFromDescription(s.provider, desc)
}
// Description finds a call to ctx.Build with BuildParams.Description set to a the given string. Panics if no rule is
// found.
func (s TestingSingleton) Description(desc string) TestingBuildParams {
return buildParamsFromDescription(s.provider, desc)
}
// MaybeOutput finds a call to ctx.Build with a BuildParams.Output or BuildParams.Outputs whose String() or Rel()
// value matches the provided string. Returns an empty BuildParams if no rule is found.
func (s TestingSingleton) MaybeOutput(file string) TestingBuildParams {
p, _ := maybeBuildParamsFromOutput(s.provider, file)
return p
}
// Output finds a call to ctx.Build with a BuildParams.Output or BuildParams.Outputs whose String() or Rel()
// value matches the provided string. Panics if no rule is found.
func (s TestingSingleton) Output(file string) TestingBuildParams {
return buildParamsFromOutput(s.provider, file)
}
// AllOutputs returns all 'BuildParams.Output's and 'BuildParams.Outputs's in their full path string forms.
func (s TestingSingleton) AllOutputs() []string {
return allOutputs(s.provider)
}
func FailIfErrored(t *testing.T, errs []error) {