Add option to build and run tests during bootstrap
Users that want to enable this option can use the '-t' option to
bootstrap.bash when passing '-r'. Builders that want to enable this can
set the RUN_TESTS environment variable in their bootstrap.bash.
The gotestmain tools is needed to write the main functions for the test
binaries, since 'go test' doesn't work well in this environment.
Change-Id: Iec5c2b5c9c3f5e3ba0ac8677fb88f5e963f9bd3f
diff --git a/Blueprints b/Blueprints
index c254136..c22bb5a 100644
--- a/Blueprints
+++ b/Blueprints
@@ -19,6 +19,13 @@
"singleton_ctx.go",
"unpack.go",
],
+ testSrcs = [
+ "context_test.go",
+ "ninja_strings_test.go",
+ "ninja_writer_test.go",
+ "splice_modules_test.go",
+ "unpack_test.go",
+ ],
)
bootstrap_go_package(
@@ -30,6 +37,10 @@
"parser/printer.go",
"parser/sort.go",
],
+ testSrcs = [
+ "parser/parser_test.go",
+ "parser/printer_test.go",
+ ],
)
bootstrap_go_package(
@@ -45,6 +56,9 @@
"pathtools/lists.go",
"pathtools/glob.go",
],
+ testSrcs = [
+ "pathtools/glob_test.go",
+ ],
)
bootstrap_go_package(
@@ -90,3 +104,8 @@
deps = ["blueprint-parser"],
srcs = ["bpmodify/bpmodify.go"],
)
+
+bootstrap_go_binary(
+ name = "gotestmain",
+ srcs = ["gotestmain/gotestmain.go"],
+)
diff --git a/bootstrap.bash b/bootstrap.bash
index 74166b6..ee5ddb7 100755
--- a/bootstrap.bash
+++ b/bootstrap.bash
@@ -22,6 +22,8 @@
set -e
+EXTRA_ARGS=""
+
# BOOTSTRAP should be set to the path of the bootstrap script. It can be
# either an absolute path or one relative to the build directory (which of
# these is used should probably match what's used for SRCDIR).
@@ -49,16 +51,20 @@
[ -z "$GOARCH" ] && GOARCH=`go env GOHOSTARCH`
[ -z "$GOCHAR" ] && GOCHAR=`go env GOCHAR`
+# If RUN_TESTS is set, behave like -t was passed in as an option.
+[ ! -z "$RUN_TESTS" ] && EXTRA_ARGS="$EXTRA_ARGS -t"
+
usage() {
echo "Usage of ${BOOTSTRAP}:"
echo " -h: print a help message and exit"
echo " -r: regenerate ${BOOTSTRAP_MANIFEST}"
+ echo " -t: include tests when regenerating manifest"
}
# Parse the command line flags.
IN="$BOOTSTRAP_MANIFEST"
REGEN_BOOTSTRAP_MANIFEST=false
-while getopts ":hi:r" opt; do
+while getopts ":hi:rt" opt; do
case $opt in
h)
usage
@@ -66,6 +72,7 @@
;;
i) IN="$OPTARG";;
r) REGEN_BOOTSTRAP_MANIFEST=true;;
+ t) EXTRA_ARGS="$EXTRA_ARGS -t";;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
@@ -83,7 +90,7 @@
# that has been built in the past.
if [ -x .bootstrap/bin/minibp ]; then
echo "Regenerating $BOOTSTRAP_MANIFEST"
- ./.bootstrap/bin/minibp -o $BOOTSTRAP_MANIFEST $SRCDIR/$TOPNAME
+ ./.bootstrap/bin/minibp $EXTRA_ARGS -o $BOOTSTRAP_MANIFEST $SRCDIR/$TOPNAME
else
echo "Executable minibp not found at .bootstrap/bin/minibp" >&2
exit 1
@@ -97,4 +104,4 @@
-e "s|@@GoChar@@|$GOCHAR|g" \
-e "s|@@Bootstrap@@|$BOOTSTRAP|g" \
-e "s|@@BootstrapManifest@@|$BOOTSTRAP_MANIFEST|g" \
- $IN > build.ninja
\ No newline at end of file
+ $IN > build.ninja
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index 5b02032..ec6642e 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -29,8 +29,9 @@
var (
pctx = blueprint.NewPackageContext("github.com/google/blueprint/bootstrap")
- gcCmd = pctx.StaticVariable("gcCmd", "$goToolDir/${goChar}g")
- linkCmd = pctx.StaticVariable("linkCmd", "$goToolDir/${goChar}l")
+ gcCmd = pctx.StaticVariable("gcCmd", "$goToolDir/${goChar}g")
+ linkCmd = pctx.StaticVariable("linkCmd", "$goToolDir/${goChar}l")
+ goTestMainCmd = pctx.StaticVariable("goTestMainCmd", filepath.Join(bootstrapDir, "bin", "gotestmain"))
// Ninja only reinvokes itself once when it regenerates a .ninja file. For
// the re-bootstrap process we need that to happen more than once, so we
@@ -69,6 +70,20 @@
},
"libDirFlags")
+ goTestMain = pctx.StaticRule("gotestmain",
+ blueprint.RuleParams{
+ Command: "$goTestMainCmd -o $out -pkg $pkg $in",
+ Description: "gotestmain $out",
+ },
+ "pkg")
+
+ test = pctx.StaticRule("test",
+ blueprint.RuleParams{
+ Command: "(cd $pkgSrcDir && $$OLDPWD/$in -test.short) && touch $out",
+ Description: "test $pkg",
+ },
+ "pkg", "pkgSrcDir")
+
cp = pctx.StaticRule("cp",
blueprint.RuleParams{
Command: "cp $in $out",
@@ -113,6 +128,15 @@
return ok
}
+type goTestProducer interface {
+ GoTestTarget() string
+}
+
+func isGoTestProducer(module blueprint.Module) bool {
+ _, ok := module.(goTestProducer)
+ return ok
+}
+
func isBootstrapModule(module blueprint.Module) bool {
_, isPackage := module.(*goPackage)
_, isBinary := module.(*goBinary)
@@ -138,8 +162,9 @@
// A goPackage is a module for building Go packages.
type goPackage struct {
properties struct {
- PkgPath string
- Srcs []string
+ PkgPath string
+ Srcs []string
+ TestSrcs []string
}
// The root dir in which the package .a file is located. The full .a file
@@ -149,6 +174,9 @@
// The path of the .a file that is to be built.
archiveFile string
+ // The path of the test .a file that is to be built.
+ testArchiveFile string
+
// The bootstrap Config
config *Config
}
@@ -172,6 +200,10 @@
return g.archiveFile
}
+func (g *goPackage) GoTestTarget() string {
+ return g.testArchiveFile
+}
+
func (g *goPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
name := ctx.ModuleName()
@@ -181,8 +213,12 @@
}
g.pkgRoot = packageRoot(ctx)
- g.archiveFile = filepath.Clean(filepath.Join(g.pkgRoot,
- filepath.FromSlash(g.properties.PkgPath)+".a"))
+ g.archiveFile = filepath.Join(g.pkgRoot,
+ filepath.FromSlash(g.properties.PkgPath)+".a")
+ if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
+ g.testArchiveFile = filepath.Join(testRoot(ctx),
+ filepath.FromSlash(g.properties.PkgPath)+".a")
+ }
// We only actually want to build the builder modules if we're running as
// minibp (i.e. we're generating a bootstrap Ninja file). This is to break
@@ -190,9 +226,20 @@
// file to be built, but building a new ninja file requires the builder to
// be built.
if g.config.generatingBootstrapper {
+ var deps []string
+
+ if g.config.runGoTests {
+ deps = buildGoTest(ctx, testRoot(ctx), g.testArchiveFile,
+ g.properties.PkgPath, g.properties.Srcs,
+ g.properties.TestSrcs)
+ }
+
buildGoPackage(ctx, g.pkgRoot, g.properties.PkgPath, g.archiveFile,
- g.properties.Srcs)
+ g.properties.Srcs, deps)
} else {
+ if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
+ phonyGoTarget(ctx, g.testArchiveFile, g.properties.TestSrcs, nil)
+ }
phonyGoTarget(ctx, g.archiveFile, g.properties.Srcs, nil)
}
}
@@ -201,9 +248,13 @@
type goBinary struct {
properties struct {
Srcs []string
+ TestSrcs []string
PrimaryBuilder bool
}
+ // The path of the test .a file that is to be built.
+ testArchiveFile string
+
// The bootstrap Config
config *Config
}
@@ -217,6 +268,10 @@
}
}
+func (g *goBinary) GoTestTarget() string {
+ return g.testArchiveFile
+}
+
func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
var (
name = ctx.ModuleName()
@@ -226,13 +281,24 @@
binaryFile = filepath.Join(BinDir, name)
)
+ if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
+ g.testArchiveFile = filepath.Join(testRoot(ctx), name+".a")
+ }
+
// We only actually want to build the builder modules if we're running as
// minibp (i.e. we're generating a bootstrap Ninja file). This is to break
// the circular dependence that occurs when the builder requires a new Ninja
// file to be built, but building a new ninja file requires the builder to
// be built.
if g.config.generatingBootstrapper {
- buildGoPackage(ctx, objDir, name, archiveFile, g.properties.Srcs)
+ var deps []string
+
+ if g.config.runGoTests {
+ deps = buildGoTest(ctx, testRoot(ctx), g.testArchiveFile,
+ name, g.properties.Srcs, g.properties.TestSrcs)
+ }
+
+ buildGoPackage(ctx, objDir, name, archiveFile, g.properties.Srcs, deps)
var libDirFlags []string
ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
@@ -261,13 +327,17 @@
Inputs: []string{aoutFile},
})
} else {
+ if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
+ phonyGoTarget(ctx, g.testArchiveFile, g.properties.TestSrcs, nil)
+ }
+
intermediates := []string{aoutFile, archiveFile}
phonyGoTarget(ctx, binaryFile, g.properties.Srcs, intermediates)
}
}
func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
- pkgPath string, archiveFile string, srcs []string) {
+ pkgPath string, archiveFile string, srcs []string, orderDeps []string) {
srcDir := moduleSrcDir(ctx)
srcFiles := pathtools.PrefixPaths(srcs, srcDir)
@@ -295,11 +365,83 @@
Rule: gc,
Outputs: []string{archiveFile},
Inputs: srcFiles,
+ OrderOnly: orderDeps,
Implicits: deps,
Args: gcArgs,
})
}
+func buildGoTest(ctx blueprint.ModuleContext, testRoot string,
+ testPkgArchive string, pkgPath string, srcs []string,
+ testSrcs []string) []string {
+
+ if len(testSrcs) == 0 {
+ return nil
+ }
+
+ srcDir := moduleSrcDir(ctx)
+ testFiles := pathtools.PrefixPaths(testSrcs, srcDir)
+
+ mainFile := filepath.Join(testRoot, "test.go")
+ testArchive := filepath.Join(testRoot, "test.a")
+ testFile := filepath.Join(testRoot, "test")
+ testPassed := filepath.Join(testRoot, "test.passed")
+
+ buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive,
+ append(srcs, testSrcs...), nil)
+
+ ctx.Build(pctx, blueprint.BuildParams{
+ Rule: goTestMain,
+ Outputs: []string{mainFile},
+ Inputs: testFiles,
+ Implicits: []string{"$goTestMainCmd"},
+ Args: map[string]string{
+ "pkg": pkgPath,
+ },
+ })
+
+ libDirFlags := []string{"-L " + testRoot}
+ ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
+ func(module blueprint.Module) {
+ dep := module.(goPackageProducer)
+ libDir := dep.GoPkgRoot()
+ libDirFlags = append(libDirFlags, "-L "+libDir)
+ })
+
+ ctx.Build(pctx, blueprint.BuildParams{
+ Rule: gc,
+ Outputs: []string{testArchive},
+ Inputs: []string{mainFile},
+ Implicits: []string{testPkgArchive},
+ Args: map[string]string{
+ "pkgPath": "main",
+ "incFlags": "-I " + testRoot,
+ },
+ })
+
+ ctx.Build(pctx, blueprint.BuildParams{
+ Rule: link,
+ Outputs: []string{testFile},
+ Inputs: []string{testArchive},
+ Implicits: []string{"$linkCmd"},
+ Args: map[string]string{
+ "libDirFlags": strings.Join(libDirFlags, " "),
+ },
+ })
+
+ ctx.Build(pctx, blueprint.BuildParams{
+ Rule: test,
+ Outputs: []string{testPassed},
+ Inputs: []string{testFile},
+ Args: map[string]string{
+ "pkg": pkgPath,
+ "pkgSrcDir": filepath.Dir(testFiles[0]),
+ },
+ })
+
+ return []string{testPassed}
+}
+
func phonyGoTarget(ctx blueprint.ModuleContext, target string, srcs []string,
intermediates []string) {
@@ -398,6 +540,10 @@
primaryBuilderFile := filepath.Join(BinDir, primaryBuilderName)
+ if s.config.runGoTests {
+ primaryBuilderExtraFlags += " -t"
+ }
+
// Get the filename of the top-level Blueprints file to pass to minibp.
// This comes stored in a global variable that's set by Main.
topLevelBlueprints := filepath.Join("$srcDir",
@@ -469,24 +615,40 @@
// and it will trigger a reboostrap by the non-boostrap build manifest.
minibp := ctx.Rule(pctx, "minibp",
blueprint.RuleParams{
- Command: fmt.Sprintf("%s -c $checkFile -m $bootstrapManifest "+
+ Command: fmt.Sprintf("%s $runTests -c $checkFile -m $bootstrapManifest "+
"-d $out.d -o $out $in", minibpFile),
Description: "minibp $out",
Generator: true,
Depfile: "$out.d",
},
- "checkFile")
+ "checkFile", "runTests")
+
+ args := map[string]string{
+ "checkFile": "$bootstrapManifest",
+ }
+
+ if s.config.runGoTests {
+ args["runTests"] = "-t"
+ }
ctx.Build(pctx, blueprint.BuildParams{
Rule: minibp,
Outputs: []string{bootstrapNinjaFile},
Inputs: []string{topLevelBlueprints},
Implicits: []string{minibpFile},
- Args: map[string]string{
- "checkFile": "$bootstrapManifest",
- },
+ Args: args,
})
} else {
+ var allGoTests []string
+ ctx.VisitAllModulesIf(isGoTestProducer,
+ func(module blueprint.Module) {
+ testModule := module.(goTestProducer)
+ target := testModule.GoTestTarget()
+ if target != "" {
+ allGoTests = append(allGoTests, target)
+ }
+ })
+
// We're generating a non-bootstrapper Ninja file, so we need to set it
// up to depend on the bootstrapper Ninja file. The build.ninja target
// also has an implicit dependency on the primary builder and all other
@@ -500,6 +662,7 @@
// phony rule to generate it that uses the depfile.
buildNinjaDeps := []string{"$bootstrapCmd", mainNinjaFile}
buildNinjaDeps = append(buildNinjaDeps, allGoBinaries...)
+ buildNinjaDeps = append(buildNinjaDeps, allGoTests...)
ctx.Build(pctx, blueprint.BuildParams{
Rule: rebootstrap,
Outputs: []string{"build.ninja"},
@@ -550,6 +713,13 @@
return filepath.Join(bootstrapDir, ctx.ModuleName(), "pkg")
}
+// testRoot returns the module-specific package root directory path used for
+// building tests. The .a files generated here will include everything from
+// packageRoot, plus the test-only code.
+func testRoot(ctx blueprint.ModuleContext) string {
+ return filepath.Join(bootstrapDir, ctx.ModuleName(), "test")
+}
+
// moduleSrcDir returns the path of the directory that all source file paths are
// specified relative to.
func moduleSrcDir(ctx blueprint.ModuleContext) string {
diff --git a/bootstrap/command.go b/bootstrap/command.go
index 354a692..207d56e 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -34,6 +34,7 @@
checkFile string
manifestFile string
cpuprofile string
+ runGoTests bool
)
func init() {
@@ -42,6 +43,7 @@
flag.StringVar(&checkFile, "c", "", "the existing file to check against")
flag.StringVar(&manifestFile, "m", "", "the bootstrap manifest file")
flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to file")
+ flag.BoolVar(&runGoTests, "t", false, "build and run go tests during bootstrap")
}
func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...string) {
@@ -73,6 +75,7 @@
bootstrapConfig := &Config{
generatingBootstrapper: generatingBootstrapper,
topLevelBlueprintsFile: flag.Arg(0),
+ runGoTests: runGoTests,
}
ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory(bootstrapConfig))
diff --git a/bootstrap/config.go b/bootstrap/config.go
index a47bc11..ce96318 100644
--- a/bootstrap/config.go
+++ b/bootstrap/config.go
@@ -45,4 +45,6 @@
generatingBootstrapper bool
topLevelBlueprintsFile string
+
+ runGoTests bool
}
diff --git a/build.ninja.in b/build.ninja.in
index d72f8ef..15e0886 100644
--- a/build.ninja.in
+++ b/build.ninja.in
@@ -76,7 +76,7 @@
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.func·002
-# Defined: Blueprints:56:1
+# Defined: Blueprints:70:1
build $
.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
@@ -100,7 +100,7 @@
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.func·002
-# Defined: Blueprints:35:1
+# Defined: Blueprints:46:1
build .bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
: g.bootstrap.gc ${g.bootstrap.srcDir}/deptools/depfile.go | $
@@ -114,7 +114,7 @@
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.func·002
-# Defined: Blueprints:24:1
+# Defined: Blueprints:31:1
build .bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a: $
g.bootstrap.gc ${g.bootstrap.srcDir}/parser/modify.go $
@@ -129,7 +129,7 @@
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.func·002
-# Defined: Blueprints:41:1
+# Defined: Blueprints:52:1
build $
.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
@@ -144,7 +144,7 @@
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.func·002
-# Defined: Blueprints:50:1
+# Defined: Blueprints:64:1
build $
.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
@@ -159,7 +159,7 @@
# Variant:
# Type: bootstrap_go_binary
# Factory: github.com/google/blueprint/bootstrap.func·003
-# Defined: Blueprints:82:1
+# Defined: Blueprints:96:1
build .bootstrap/bpfmt/obj/bpfmt.a: g.bootstrap.gc $
${g.bootstrap.srcDir}/bpfmt/bpfmt.go | ${g.bootstrap.gcCmd} $
@@ -181,7 +181,7 @@
# Variant:
# Type: bootstrap_go_binary
# Factory: github.com/google/blueprint/bootstrap.func·003
-# Defined: Blueprints:88:1
+# Defined: Blueprints:102:1
build .bootstrap/bpmodify/obj/bpmodify.a: g.bootstrap.gc $
${g.bootstrap.srcDir}/bpmodify/bpmodify.go | ${g.bootstrap.gcCmd} $
@@ -199,11 +199,30 @@
default .bootstrap/bin/bpmodify
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: gotestmain
+# Variant:
+# Type: bootstrap_go_binary
+# Factory: github.com/google/blueprint/bootstrap.func·003
+# Defined: Blueprints:108:1
+
+build .bootstrap/gotestmain/obj/gotestmain.a: g.bootstrap.gc $
+ ${g.bootstrap.srcDir}/gotestmain/gotestmain.go | ${g.bootstrap.gcCmd}
+ pkgPath = gotestmain
+default .bootstrap/gotestmain/obj/gotestmain.a
+
+build .bootstrap/gotestmain/obj/a.out: g.bootstrap.link $
+ .bootstrap/gotestmain/obj/gotestmain.a | ${g.bootstrap.linkCmd}
+default .bootstrap/gotestmain/obj/a.out
+build .bootstrap/bin/gotestmain: g.bootstrap.cp $
+ .bootstrap/gotestmain/obj/a.out
+default .bootstrap/bin/gotestmain
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module: minibp
# Variant:
# Type: bootstrap_go_binary
# Factory: github.com/google/blueprint/bootstrap.func·003
-# Defined: Blueprints:73:1
+# Defined: Blueprints:87:1
build .bootstrap/minibp/obj/minibp.a: g.bootstrap.gc $
${g.bootstrap.srcDir}/bootstrap/minibp/main.go | ${g.bootstrap.gcCmd} $
@@ -227,7 +246,7 @@
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Singleton: bootstrap
-# Factory: github.com/google/blueprint/bootstrap.func·007
+# Factory: github.com/google/blueprint/bootstrap.func·008
rule s.bootstrap.bigbp
command = .bootstrap/bin/minibp -p -d .bootstrap/main.ninja.in.d -m ${g.bootstrap.bootstrapManifest} -o ${out} ${in}
@@ -235,14 +254,15 @@
description = minibp ${out}
rule s.bootstrap.minibp
- command = .bootstrap/bin/minibp -c ${checkFile} -m ${g.bootstrap.bootstrapManifest} -d ${out}.d -o ${out} ${in}
+ command = .bootstrap/bin/minibp ${runTests} -c ${checkFile} -m ${g.bootstrap.bootstrapManifest} -d ${out}.d -o ${out} ${in}
depfile = ${out}.d
description = minibp ${out}
generator = true
build .bootstrap/main.ninja.in: s.bootstrap.bigbp $
${g.bootstrap.srcDir}/Blueprints | .bootstrap/bin/bpfmt $
- .bootstrap/bin/bpmodify .bootstrap/bin/minibp
+ .bootstrap/bin/bpmodify .bootstrap/bin/gotestmain $
+ .bootstrap/bin/minibp
default .bootstrap/main.ninja.in
build .bootstrap/notAFile: phony
default .bootstrap/notAFile
diff --git a/gotestmain/gotestmain.go b/gotestmain/gotestmain.go
new file mode 100644
index 0000000..0c2ca3d
--- /dev/null
+++ b/gotestmain/gotestmain.go
@@ -0,0 +1,106 @@
+// Copyright 2015 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 gotestmain
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io/ioutil"
+ "os"
+ "strings"
+ "text/template"
+)
+
+var (
+ output = flag.String("o", "", "output filename")
+ pkg = flag.String("pkg", "", "test package")
+ exitCode = 0
+)
+
+type data struct {
+ Package string
+ Tests []string
+}
+
+func findTests(srcs []string) (tests []string) {
+ for _, src := range srcs {
+ f, err := parser.ParseFile(token.NewFileSet(), src, nil, 0)
+ if err != nil {
+ panic(err)
+ }
+ for _, obj := range f.Scope.Objects {
+ if obj.Kind != ast.Fun || !strings.HasPrefix(obj.Name, "Test") {
+ continue
+ }
+ tests = append(tests, obj.Name)
+ }
+ }
+ return
+}
+
+func main() {
+ flag.Parse()
+
+ if flag.NArg() == 0 {
+ fmt.Fprintln(os.Stderr, "error: must pass at least one input")
+ exitCode = 1
+ return
+ }
+
+ buf := &bytes.Buffer{}
+
+ d := data{
+ Package: *pkg,
+ Tests: findTests(flag.Args()),
+ }
+
+ err := testMainTmpl.Execute(buf, d)
+ if err != nil {
+ panic(err)
+ }
+
+ err = ioutil.WriteFile(*output, buf.Bytes(), 0666)
+ if err != nil {
+ panic(err)
+ }
+}
+
+var testMainTmpl = template.Must(template.New("testMain").Parse(`
+package main
+
+import (
+ "testing"
+
+ pkg "{{.Package}}"
+)
+
+var t = []testing.InternalTest{
+{{range .Tests}}
+ {"{{.}}", pkg.{{.}}},
+{{end}}
+}
+
+func matchString(pat, str string) (bool, error) {
+ return true, nil
+}
+
+func main() {
+ testing.Main(matchString, t, nil, nil)
+}
+`))