From 4c83e5ccd4096e57ec23916051895aba42f64fdb Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 25 Feb 2019 14:54:28 -0800 Subject: [PATCH] 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 --- android/module.go | 20 ++- android/register.go | 2 +- android/rule_builder_test.go | 27 ++-- android/singleton.go | 67 +++++++--- android/testing.go | 240 +++++++++++++++++++++++++---------- 5 files changed, 263 insertions(+), 93 deletions(-) diff --git a/android/module.go b/android/module.go index 516fa7878..d1a779d5b 100644 --- a/android/module.go +++ b/android/module.go @@ -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) { diff --git a/android/register.go b/android/register.go index 93c287032..d79982a31 100644 --- a/android/register.go +++ b/android/register.go @@ -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} } } diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go index f738fafcf..f157ab7d9 100644 --- a/android/rule_builder_test.go +++ b/android/rule_builder_test.go @@ -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")) + }) } diff --git a/android/singleton.go b/android/singleton.go index 05ec6b542..a59d54aa2 100644 --- a/android/singleton.go +++ b/android/singleton.go @@ -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) } diff --git a/android/testing.go b/android/testing.go index b7a043e58..b4008af37 100644 --- a/android/testing.go +++ b/android/testing.go @@ -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) {