Merge pull request #37 from danw/runtests

Add option to build and run tests during bootstrap
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 ead4ceb..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)
@@ -124,14 +148,6 @@
 	return isBinary
 }
 
-func generatingBootstrapper(config interface{}) bool {
-	bootstrapConfig, ok := config.(Config)
-	if ok {
-		return bootstrapConfig.GeneratingBootstrapper()
-	}
-	return false
-}
-
 // ninjaHasMultipass returns true if Ninja will perform multiple passes
 // that can regenerate the build manifest.
 func ninjaHasMultipass(config interface{}) bool {
@@ -146,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
@@ -156,13 +173,23 @@
 
 	// 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
 }
 
 var _ goPackageProducer = (*goPackage)(nil)
 
-func newGoPackageModule() (blueprint.Module, []interface{}) {
-	module := &goPackage{}
-	return module, []interface{}{&module.properties}
+func newGoPackageModuleFactory(config *Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &goPackage{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
 }
 
 func (g *goPackage) GoPkgRoot() string {
@@ -173,6 +200,10 @@
 	return g.archiveFile
 }
 
+func (g *goPackage) GoTestTarget() string {
+	return g.testArchiveFile
+}
+
 func (g *goPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
 	name := ctx.ModuleName()
 
@@ -182,18 +213,33 @@
 	}
 
 	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
 	// 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 generatingBootstrapper(ctx.Config()) {
+	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)
 	}
 }
@@ -202,13 +248,28 @@
 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
+}
+
+func newGoBinaryModuleFactory(config *Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &goBinary{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
 }
 
-func newGoBinaryModule() (blueprint.Module, []interface{}) {
-	module := &goBinary{}
-	return module, []interface{}{&module.properties}
+func (g *goBinary) GoTestTarget() string {
+	return g.testArchiveFile
 }
 
 func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
@@ -220,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 generatingBootstrapper(ctx.Config()) {
-		buildGoPackage(ctx, objDir, name, archiveFile, g.properties.Srcs)
+	if g.config.generatingBootstrapper {
+		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,
@@ -255,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)
@@ -289,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) {
 
@@ -339,10 +487,17 @@
 
 }
 
-type singleton struct{}
+type singleton struct {
+	// The bootstrap Config
+	config *Config
+}
 
-func newSingleton() blueprint.Singleton {
-	return &singleton{}
+func newSingletonFactory(config *Config) func() blueprint.Singleton {
+	return func() blueprint.Singleton {
+		return &singleton{
+			config: config,
+		}
+	}
 }
 
 func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
@@ -385,16 +540,20 @@
 
 	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",
-		filepath.Base(topLevelBlueprintsFile))
+		filepath.Base(s.config.topLevelBlueprintsFile))
 
 	mainNinjaFile := filepath.Join(bootstrapDir, "main.ninja.in")
 	mainNinjaDepFile := mainNinjaFile + ".d"
 	bootstrapNinjaFile := filepath.Join(bootstrapDir, "bootstrap.ninja.in")
 
-	if generatingBootstrapper(ctx.Config()) {
+	if s.config.generatingBootstrapper {
 		// We're generating a bootstrapper Ninja file, so we need to set things
 		// up to rebuild the build.ninja file using the primary builder.
 
@@ -456,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
@@ -487,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"},
@@ -537,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/cleanup.go b/bootstrap/cleanup.go
index 5f53d5d..f3cb634 100644
--- a/bootstrap/cleanup.go
+++ b/bootstrap/cleanup.go
@@ -29,11 +29,11 @@
 
 // removeAbandonedFiles removes any files that appear in the Ninja log that are
 // not currently build targets.
-func removeAbandonedFiles(ctx *blueprint.Context, config interface{},
+func removeAbandonedFiles(ctx *blueprint.Context, config *Config,
 	srcDir, manifestFile string) error {
 
 	buildDir := "."
-	if generatingBootstrapper(config) {
+	if config.generatingBootstrapper {
 		buildDir = bootstrapDir
 	}
 
diff --git a/bootstrap/command.go b/bootstrap/command.go
index 05a3e9d..207d56e 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -34,20 +34,16 @@
 	checkFile    string
 	manifestFile string
 	cpuprofile   string
+	runGoTests   bool
 )
 
-// topLevelBlueprintsFile is set by Main as a way to pass this information on to
-// the bootstrap build manifest generators.  This information was not passed via
-// the config object so as to allow the caller of Main to use whatever Config
-// object it wants.
-var topLevelBlueprintsFile string
-
 func init() {
 	flag.StringVar(&outFile, "o", "build.ninja.in", "the Ninja file to output")
 	flag.StringVar(&depFile, "d", "", "the dependency file to output")
 	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) {
@@ -67,17 +63,26 @@
 		defer pprof.StopCPUProfile()
 	}
 
-	ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModule)
-	ctx.RegisterModuleType("bootstrap_go_binary", newGoBinaryModule)
-	ctx.RegisterSingletonType("bootstrap", newSingleton)
-
 	if flag.NArg() != 1 {
 		fatalf("no Blueprints file specified")
 	}
 
-	topLevelBlueprintsFile = flag.Arg(0)
+	generatingBootstrapper := false
+	if c, ok := config.(ConfigInterface); ok {
+		generatingBootstrapper = c.GeneratingBootstrapper()
+	}
 
-	deps, errs := ctx.ParseBlueprintsFiles(topLevelBlueprintsFile)
+	bootstrapConfig := &Config{
+		generatingBootstrapper: generatingBootstrapper,
+		topLevelBlueprintsFile: flag.Arg(0),
+		runGoTests:             runGoTests,
+	}
+
+	ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory(bootstrapConfig))
+	ctx.RegisterModuleType("bootstrap_go_binary", newGoBinaryModuleFactory(bootstrapConfig))
+	ctx.RegisterSingletonType("bootstrap", newSingletonFactory(bootstrapConfig))
+
+	deps, errs := ctx.ParseBlueprintsFiles(bootstrapConfig.topLevelBlueprintsFile)
 	if len(errs) > 0 {
 		fatalErrors(errs)
 	}
@@ -142,8 +147,8 @@
 		}
 	}
 
-	srcDir := filepath.Dir(topLevelBlueprintsFile)
-	err = removeAbandonedFiles(ctx, config, srcDir, manifestFile)
+	srcDir := filepath.Dir(bootstrapConfig.topLevelBlueprintsFile)
+	err = removeAbandonedFiles(ctx, bootstrapConfig, srcDir, manifestFile)
 	if err != nil {
 		fatalf("error removing abandoned files: %s", err)
 	}
diff --git a/bootstrap/config.go b/bootstrap/config.go
index 4763f50..ce96318 100644
--- a/bootstrap/config.go
+++ b/bootstrap/config.go
@@ -31,9 +31,20 @@
 		"$goRoot/pkg/tool/${goOS}_$goArch")
 )
 
-type Config interface {
+type ConfigInterface interface {
 	// GeneratingBootstrapper should return true if this build invocation is
 	// creating a build.ninja.in file to be used in a build bootstrapping
 	// sequence.
 	GeneratingBootstrapper() bool
 }
+
+type Config struct {
+	// generatingBootstrapper should be true if this build invocation is
+	// creating a build.ninja.in file to be used in a build bootstrapping
+	// sequence.
+	generatingBootstrapper bool
+
+	topLevelBlueprintsFile string
+
+	runGoTests bool
+}
diff --git a/build.ninja.in b/build.ninja.in
index ab32834..15e0886 100644
--- a/build.ninja.in
+++ b/build.ninja.in
@@ -52,7 +52,7 @@
 # Module:  blueprint
 # Variant:
 # Type:    bootstrap_go_package
-# Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
+# Factory: github.com/google/blueprint/bootstrap.func·002
 # Defined: Blueprints:1:1
 
 build .bootstrap/blueprint/pkg/github.com/google/blueprint.a: g.bootstrap.gc $
@@ -75,8 +75,8 @@
 # Module:  blueprint-bootstrap
 # Variant:
 # Type:    bootstrap_go_package
-# Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:56:1
+# Factory: github.com/google/blueprint/bootstrap.func·002
+# Defined: Blueprints:70:1
 
 build $
         .bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
@@ -99,8 +99,8 @@
 # Module:  blueprint-deptools
 # Variant:
 # Type:    bootstrap_go_package
-# Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:35:1
+# Factory: github.com/google/blueprint/bootstrap.func·002
+# Defined: Blueprints:46:1
 
 build .bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
         : g.bootstrap.gc ${g.bootstrap.srcDir}/deptools/depfile.go | $
@@ -113,8 +113,8 @@
 # Module:  blueprint-parser
 # Variant:
 # Type:    bootstrap_go_package
-# Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:24:1
+# Factory: github.com/google/blueprint/bootstrap.func·002
+# Defined: Blueprints:31:1
 
 build .bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a: $
         g.bootstrap.gc ${g.bootstrap.srcDir}/parser/modify.go $
@@ -128,8 +128,8 @@
 # Module:  blueprint-pathtools
 # Variant:
 # Type:    bootstrap_go_package
-# Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:41:1
+# Factory: github.com/google/blueprint/bootstrap.func·002
+# Defined: Blueprints:52:1
 
 build $
         .bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
@@ -143,8 +143,8 @@
 # Module:  blueprint-proptools
 # Variant:
 # Type:    bootstrap_go_package
-# Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:50:1
+# Factory: github.com/google/blueprint/bootstrap.func·002
+# Defined: Blueprints:64:1
 
 build $
         .bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
@@ -158,8 +158,8 @@
 # Module:  bpfmt
 # Variant:
 # Type:    bootstrap_go_binary
-# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModule
-# Defined: Blueprints:82:1
+# Factory: github.com/google/blueprint/bootstrap.func·003
+# Defined: Blueprints:96:1
 
 build .bootstrap/bpfmt/obj/bpfmt.a: g.bootstrap.gc $
         ${g.bootstrap.srcDir}/bpfmt/bpfmt.go | ${g.bootstrap.gcCmd} $
@@ -180,8 +180,8 @@
 # Module:  bpmodify
 # Variant:
 # Type:    bootstrap_go_binary
-# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModule
-# Defined: Blueprints:88:1
+# Factory: github.com/google/blueprint/bootstrap.func·003
+# 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.newGoBinaryModule
-# Defined: Blueprints:73:1
+# Factory: github.com/google/blueprint/bootstrap.func·003
+# 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.newSingleton
+# 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)
+}
+`))