Merge "Add integration testing infrastructure"
am: dddf50039a
Change-Id: I7e378c07ae859e45ad461545401f69fade6137f0
This commit is contained in:
commit
daf9154992
12 changed files with 193 additions and 122 deletions
|
@ -51,6 +51,7 @@ bootstrap_go_package {
|
|||
"android/paths.go",
|
||||
"android/prebuilt.go",
|
||||
"android/register.go",
|
||||
"android/testing.go",
|
||||
"android/util.go",
|
||||
"android/variable.go",
|
||||
|
||||
|
|
|
@ -84,6 +84,8 @@ type config struct {
|
|||
|
||||
inMake bool
|
||||
|
||||
captureBuild bool // true for tests, saves build parameters for each module
|
||||
|
||||
OncePer
|
||||
}
|
||||
|
||||
|
@ -171,7 +173,8 @@ func TestConfig(buildDir string) Config {
|
|||
DeviceName: stringPtr("test_device"),
|
||||
},
|
||||
|
||||
buildDir: buildDir,
|
||||
buildDir: buildDir,
|
||||
captureBuild: true,
|
||||
}
|
||||
config.deviceConfig = &deviceConfig{
|
||||
config: config,
|
||||
|
|
|
@ -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) {
|
||||
if defaultable, ok := ctx.Module().(Defaultable); ok {
|
||||
ctx.AddDependency(ctx.Module(), DefaultsDepTag, defaultable.defaults().Defaults...)
|
||||
|
|
|
@ -111,6 +111,8 @@ type Module interface {
|
|||
|
||||
AddProperties(props ...interface{})
|
||||
GetProperties() []interface{}
|
||||
|
||||
BuildParamsForTests() []ModuleBuildParams
|
||||
}
|
||||
|
||||
type nameProperties struct {
|
||||
|
@ -291,6 +293,9 @@ type ModuleBase struct {
|
|||
hooks hooks
|
||||
|
||||
registerProps []interface{}
|
||||
|
||||
// For tests
|
||||
buildParams []ModuleBuildParams
|
||||
}
|
||||
|
||||
func (a *ModuleBase) AddProperties(props ...interface{}) {
|
||||
|
@ -301,6 +306,10 @@ func (a *ModuleBase) GetProperties() []interface{} {
|
|||
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
|
||||
// example prebuilts will prepend prebuilt_ to the name.
|
||||
func (a *ModuleBase) Name() string {
|
||||
|
@ -520,6 +529,8 @@ func (a *ModuleBase) GenerateBuildActions(ctx blueprint.ModuleContext) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
a.buildParams = androidCtx.buildParams
|
||||
}
|
||||
|
||||
type androidBaseContextImpl struct {
|
||||
|
@ -538,6 +549,9 @@ type androidModuleContext struct {
|
|||
checkbuildFiles Paths
|
||||
missingDeps []string
|
||||
module Module
|
||||
|
||||
// For tests
|
||||
buildParams []ModuleBuildParams
|
||||
}
|
||||
|
||||
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) {
|
||||
if a.config.captureBuild {
|
||||
a.buildParams = append(a.buildParams, params)
|
||||
}
|
||||
|
||||
bparams := blueprint.BuildParams{
|
||||
Rule: params.Rule,
|
||||
Deps: params.Deps,
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
package android
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/blueprint"
|
||||
)
|
||||
|
||||
|
@ -27,9 +25,6 @@ import (
|
|||
// Deps
|
||||
// PostDeps
|
||||
|
||||
var registerMutatorsOnce sync.Once
|
||||
var registeredMutators []*mutator
|
||||
|
||||
func registerMutatorsToContext(ctx *blueprint.Context, mutators []*mutator) {
|
||||
for _, t := range mutators {
|
||||
var handle blueprint.MutatorHandle
|
||||
|
@ -44,56 +39,24 @@ func registerMutatorsToContext(ctx *blueprint.Context, mutators []*mutator) {
|
|||
}
|
||||
}
|
||||
|
||||
func registerMutators(ctx *blueprint.Context) {
|
||||
|
||||
registerMutatorsOnce.Do(func() {
|
||||
ctx := ®isterMutatorsContext{}
|
||||
|
||||
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 := ®isterMutatorsContext{}
|
||||
func registerMutators(ctx *blueprint.Context, preArch, preDeps, postDeps []RegisterMutatorFunc) {
|
||||
mctx := ®isterMutatorsContext{}
|
||||
|
||||
register := func(funcs []RegisterMutatorFunc) {
|
||||
for _, f := range funcs {
|
||||
f(mutators)
|
||||
f(mctx)
|
||||
}
|
||||
}
|
||||
|
||||
register(testPreDeps)
|
||||
mutators.BottomUp("deps", depsMutator).Parallel()
|
||||
register(testPostDeps)
|
||||
register(preArch)
|
||||
|
||||
registerMutatorsToContext(ctx, mutators.mutators)
|
||||
register(preDeps)
|
||||
|
||||
mctx.BottomUp("deps", depsMutator).Parallel()
|
||||
|
||||
register(postDeps)
|
||||
|
||||
registerMutatorsToContext(ctx, mctx.mutators)
|
||||
}
|
||||
|
||||
type registerMutatorsContext struct {
|
||||
|
@ -107,7 +70,24 @@ type RegisterMutatorsContext interface {
|
|||
|
||||
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) {
|
||||
preArch = append(preArch, f)
|
||||
|
@ -121,14 +101,6 @@ func PostDepsMutators(f RegisterMutatorFunc) {
|
|||
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 TopDownMutatorContext interface {
|
||||
|
|
|
@ -61,6 +61,15 @@ type PrebuiltInterface interface {
|
|||
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
|
||||
// prebuilt modules that have both a prebuilt and a source module.
|
||||
func prebuiltMutator(ctx BottomUpMutatorContext) {
|
||||
|
|
|
@ -122,9 +122,12 @@ func TestPrebuilts(t *testing.T) {
|
|||
|
||||
for _, test := range prebuiltsTests {
|
||||
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("source", ModuleFactoryAdaptor(newSourceModule))
|
||||
ctx.Register()
|
||||
ctx.MockFileSystem(map[string][]byte{
|
||||
"Blueprints": []byte(`
|
||||
source {
|
||||
|
@ -139,13 +142,10 @@ func TestPrebuilts(t *testing.T) {
|
|||
_, errs = ctx.PrepareBuildActions(config)
|
||||
fail(t, errs)
|
||||
|
||||
foo := findModule(ctx, "foo")
|
||||
if foo == nil {
|
||||
t.Fatalf("failed to find module foo")
|
||||
}
|
||||
foo := ctx.ModuleForTests("foo", "")
|
||||
|
||||
var dependsOnSourceModule, dependsOnPrebuiltModule bool
|
||||
ctx.VisitDirectDeps(foo, func(m blueprint.Module) {
|
||||
ctx.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
|
||||
if _, ok := m.(*sourceModule); ok {
|
||||
dependsOnSourceModule = true
|
||||
}
|
||||
|
@ -228,16 +228,6 @@ func (s *sourceModule) DepsMutator(ctx BottomUpMutatorContext) {
|
|||
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) {
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
|
|
|
@ -60,9 +60,15 @@ func RegisterSingletonType(name string, factory blueprint.SingletonFactory) {
|
|||
singletons = append(singletons, singleton{name, factory})
|
||||
}
|
||||
|
||||
func NewContext() *blueprint.Context {
|
||||
ctx := blueprint.NewContext()
|
||||
type Context struct {
|
||||
*blueprint.Context
|
||||
}
|
||||
|
||||
func NewContext() *Context {
|
||||
return &Context{blueprint.NewContext()}
|
||||
}
|
||||
|
||||
func (ctx *Context) Register() {
|
||||
for _, t := range moduleTypes {
|
||||
ctx.RegisterModuleType(t.name, t.factory)
|
||||
}
|
||||
|
@ -71,9 +77,7 @@ func NewContext() *blueprint.Context {
|
|||
ctx.RegisterSingletonType(t.name, t.factory)
|
||||
}
|
||||
|
||||
registerMutators(ctx)
|
||||
registerMutators(ctx.Context, preArch, preDeps, postDeps)
|
||||
|
||||
ctx.RegisterSingletonType("env", EnvSingleton)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
|
98
android/testing.go
Normal file
98
android/testing.go
Normal 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))
|
||||
}
|
|
@ -23,8 +23,6 @@ import (
|
|||
|
||||
"android/soong/android"
|
||||
"android/soong/genrule"
|
||||
|
||||
"github.com/google/blueprint"
|
||||
)
|
||||
|
||||
type dataFile struct {
|
||||
|
@ -123,8 +121,7 @@ func TestDataTests(t *testing.T) {
|
|||
|
||||
for _, test := range testDataTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := blueprint.NewContext()
|
||||
android.RegisterTestMutators(ctx)
|
||||
ctx := android.NewTestContext()
|
||||
ctx.MockFileSystem(map[string][]byte{
|
||||
"Blueprints": []byte(`subdirs = ["dir"]`),
|
||||
"dir/Blueprints": []byte(test.modules),
|
||||
|
@ -135,18 +132,16 @@ func TestDataTests(t *testing.T) {
|
|||
android.ModuleFactoryAdaptor(genrule.FileGroupFactory))
|
||||
ctx.RegisterModuleType("test",
|
||||
android.ModuleFactoryAdaptor(newTest))
|
||||
ctx.Register()
|
||||
|
||||
_, errs := ctx.ParseBlueprintsFiles("Blueprints")
|
||||
fail(t, errs)
|
||||
_, errs = ctx.PrepareBuildActions(config)
|
||||
fail(t, errs)
|
||||
|
||||
foo := findModule(ctx, "foo")
|
||||
if foo == nil {
|
||||
t.Fatalf("failed to find module foo")
|
||||
}
|
||||
foo := ctx.ModuleForTests("foo", "")
|
||||
|
||||
got := foo.(*testDataTest).data
|
||||
got := foo.Module().(*testDataTest).data
|
||||
if len(got) != len(test.data) {
|
||||
t.Errorf("expected %d data files, got %d",
|
||||
len(test.data), len(got))
|
||||
|
@ -192,16 +187,6 @@ func (test *testDataTest) GenerateAndroidBuildActions(ctx android.ModuleContext)
|
|||
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) {
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
|
|
|
@ -32,6 +32,7 @@ func main() {
|
|||
srcDir := filepath.Dir(flag.Arg(0))
|
||||
|
||||
ctx := android.NewContext()
|
||||
ctx.Register()
|
||||
|
||||
configuration, err := android.NewConfig(srcDir, bootstrap.BuildDir)
|
||||
if err != nil {
|
||||
|
@ -44,5 +45,5 @@ func main() {
|
|||
|
||||
ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
|
||||
|
||||
bootstrap.Main(ctx, configuration, configuration.ConfigFileName, configuration.ProductVariablesFileName)
|
||||
bootstrap.Main(ctx.Context, configuration, configuration.ConfigFileName, configuration.ProductVariablesFileName)
|
||||
}
|
||||
|
|
|
@ -26,8 +26,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"android/soong/android"
|
||||
|
||||
"github.com/google/blueprint"
|
||||
)
|
||||
|
||||
type pyBinary struct {
|
||||
|
@ -306,17 +304,17 @@ var (
|
|||
func TestPythonModule(t *testing.T) {
|
||||
config, buildDir := setupBuildEnv(t)
|
||||
defer tearDownBuildEnv(buildDir)
|
||||
android.TestPreDepsMutators(func(ctx android.RegisterMutatorsContext) {
|
||||
ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
|
||||
})
|
||||
for _, d := range data {
|
||||
t.Run(d.desc, func(t *testing.T) {
|
||||
ctx := blueprint.NewContext()
|
||||
android.RegisterTestMutators(ctx)
|
||||
ctx := android.NewTestContext()
|
||||
ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
|
||||
ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
|
||||
})
|
||||
ctx.RegisterModuleType("python_library_host",
|
||||
android.ModuleFactoryAdaptor(PythonLibraryHostFactory))
|
||||
ctx.RegisterModuleType("python_binary_host",
|
||||
android.ModuleFactoryAdaptor(PythonBinaryHostFactory))
|
||||
ctx.Register()
|
||||
ctx.MockFileSystem(d.mockFiles)
|
||||
_, testErrs := ctx.ParseBlueprintsFiles(bpFile)
|
||||
fail(t, testErrs)
|
||||
|
@ -360,15 +358,12 @@ func expectErrors(t *testing.T, actErrs []error, expErrs []string) (testErrs []e
|
|||
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,
|
||||
expParSpec string, expDepsParSpecs []string) (testErrs []error) {
|
||||
module := findModule(ctx, name, variant)
|
||||
if module == nil {
|
||||
t.Fatalf("failed to find module %s!", name)
|
||||
}
|
||||
module := ctx.ModuleForTests(name, variant)
|
||||
|
||||
base, baseOk := module.(*pythonBaseModule)
|
||||
base, baseOk := module.Module().(*pythonBaseModule)
|
||||
if !baseOk {
|
||||
t.Fatalf("%s is not Python module!", name)
|
||||
}
|
||||
|
@ -438,16 +433,6 @@ func tearDownBuildEnv(buildDir string) {
|
|||
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) {
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
|
|
Loading…
Reference in a new issue