--- /dev/null
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bufio"
+ "exec"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "unicode"
+ "utf8"
+)
+
+// Environment for commands.
+var (
+ GC []string // 6g -I _test _testmain.go
+ GL []string // 6l -L _test _testmain.6
+ GOARCH string
+ GOROOT string
+ GORUN string
+ O string
+ env = os.Environ()
+)
+
+var (
+ files = make([]*File, 0, 10)
+ importPath string
+)
+
+// Flags from package "testing" we will forward to 6.out. See documentation there
+// or by running "godoc gotest" - details are in ./doc.go.
+var (
+ test_short bool
+ test_v bool
+ test_run string
+ test_memprofile string
+ test_memprofilerate int
+ test_cpuprofile string
+)
+
+// Flags for our own purposes
+var (
+ xFlag = flag.Bool("x", false, "print command lines as they are executed")
+ cFlag = flag.Bool("c", false, "compile but do not run the test binary")
+)
+
+// File represents a file that contains tests.
+type File struct {
+ name string
+ pkg string
+ file *os.File
+ astFile *ast.File
+ tests []string // The names of the TestXXXs.
+ benchmarks []string // The names of the BenchmarkXXXs.
+}
+
+func main() {
+ flag.Parse()
+ needMakefile()
+ setEnvironment()
+ getTestFileNames()
+ parseFiles()
+ getTestNames()
+ run("gomake", "testpackage-clean")
+ run("gomake", "testpackage", fmt.Sprintf("GOTESTFILES=%s", insideFileNames()))
+ importPath = runWithStdout("gomake", "-s", "importpath")
+ writeTestmainGo()
+ run(GC...)
+ run(GL...)
+ if !*cFlag {
+ runWithTestFlags("./" + O + ".out")
+ }
+}
+
+// init sets up pairs of flags. Each pair contains a flag defined as in testing, and one with
+// the "test." prefix missing, for ease of use.
+func init() {
+ flag.BoolVar(&test_short, "test.short", false, "run smaller test suite to save time")
+ flag.BoolVar(&test_v, "test.v", false, "verbose: print additional output")
+ flag.StringVar(&test_run, "test.run", "", "regular expression to select tests to run")
+ flag.StringVar(&test_memprofile, "test.memprofile", "", "write a memory profile to the named file after execution")
+ flag.IntVar(&test_memprofilerate, "test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate")
+ flag.StringVar(&test_cpuprofile, "test.cpuprofile", "", "write a cpu profile to the named file during execution")
+ // Now the same flags again, but with shorter names that are forwarded.
+ flag.BoolVar(&test_short, "short", false, "passes -test.short to test")
+ flag.BoolVar(&test_v, "v", false, "passes -test.v to test")
+ flag.StringVar(&test_run, "run", "", "passes -test.run to test")
+ flag.StringVar(&test_memprofile, "memprofile", "", "passes -test.memprofile to test")
+ flag.IntVar(&test_memprofilerate, "memprofilerate", 0, "passes -test.memprofilerate to test")
+ flag.StringVar(&test_cpuprofile, "cpuprofile", "", "passes -test.cpuprofile to test")
+}
+
+// needMakefile tests that we have a Makefile in this directory.
+func needMakefile() {
+ if _, err := os.Stat("Makefile"); err != nil {
+ Fatalf("please create a Makefile for gotest; see http://golang.org/doc/code.html for details")
+ }
+}
+
+// Fatalf formats its arguments, prints the message with a final newline, and exits.
+func Fatalf(s string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, "gotest: "+s+"\n", args...)
+ os.Exit(2)
+}
+
+// theChar is the map from architecture to object character.
+var theChar = map[string]string{
+ "arm": "5",
+ "amd64": "6",
+ "386": "8",
+}
+
+// addEnv adds a name=value pair to the environment passed to subcommands.
+// If the item is already in the environment, addEnv replaces the value.
+func addEnv(name, value string) {
+ for i := 0; i < len(env); i++ {
+ if strings.HasPrefix(env[i], name+"=") {
+ env[i] = name + "=" + value
+ return
+ }
+ }
+ env = append(env, name+"="+value)
+}
+
+// setEnvironment assembles the configuration for gotest and its subcommands.
+func setEnvironment() {
+ // Basic environment.
+ GOROOT = runtime.GOROOT()
+ addEnv("GOROOT", GOROOT)
+ GOARCH = runtime.GOARCH
+ addEnv("GOARCH", GOARCH)
+ O = theChar[GOARCH]
+ if O == "" {
+ Fatalf("unknown architecture %s", GOARCH)
+ }
+
+ // Commands and their flags.
+ gc := os.Getenv("GC")
+ if gc == "" {
+ gc = O + "g"
+ }
+ GC = []string{gc, "-I", "_test", "_testmain.go"}
+ gl := os.Getenv("GL")
+ if gl == "" {
+ gl = O + "l"
+ }
+ GL = []string{gl, "-L", "_test", "_testmain." + O}
+
+ // Silence make on Linux
+ addEnv("MAKEFLAGS", "")
+ addEnv("MAKELEVEL", "")
+}
+
+// getTestFileNames gets the set of files we're looking at.
+// If gotest has no arguments, it scans the current directory for _test.go files.
+func getTestFileNames() {
+ names := flag.Args()
+ if len(names) == 0 {
+ names = filepath.Glob("*_test.go")
+ if len(names) == 0 {
+ Fatalf(`no test files found: no match for "*_test.go"`)
+ }
+ }
+ for _, n := range names {
+ fd, err := os.Open(n, os.O_RDONLY, 0)
+ if err != nil {
+ Fatalf("%s: %s", n, err)
+ }
+ f := &File{name: n, file: fd}
+ files = append(files, f)
+ }
+}
+
+// parseFiles parses the files and remembers the packages we find.
+func parseFiles() {
+ fileSet := token.NewFileSet()
+ for _, f := range files {
+ file, err := parser.ParseFile(fileSet, f.name, nil, 0)
+ if err != nil {
+ Fatalf("could not parse %s: %s", f.name, err)
+ }
+ f.astFile = file
+ f.pkg = file.Name.String()
+ if f.pkg == "" {
+ Fatalf("cannot happen: no package name in %s", f.name)
+ }
+ }
+}
+
+// getTestNames extracts the names of tests and benchmarks. They are all
+// top-level functions that are not methods.
+func getTestNames() {
+ for _, f := range files {
+ for _, d := range f.astFile.Decls {
+ n, ok := d.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ if n.Recv != nil { // a method, not a function.
+ continue
+ }
+ name := n.Name.String()
+ if isTest(name, "Test") {
+ f.tests = append(f.tests, name)
+ } else if isTest(name, "Benchmark") {
+ f.benchmarks = append(f.benchmarks, name)
+ }
+ // TODO: worth checking the signature? Probably not.
+ }
+ }
+}
+
+// isTest tells whether name looks like a test (or benchmark, according to prefix).
+// It is a Test (say) if there is a character after Test that is not a lower-case letter.
+// We don't want TesticularCancer.
+func isTest(name, prefix string) bool {
+ if !strings.HasPrefix(name, prefix) || len(name) == len(prefix) {
+ return false
+ }
+ rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
+ return !unicode.IsLower(rune)
+}
+
+// insideFileNames returns the list of files in package foo, not a package foo_test, as a space-separated string.
+func insideFileNames() (result string) {
+ for _, f := range files {
+ if !strings.HasSuffix(f.pkg, "_test") {
+ if len(result) > 0 {
+ result += " "
+ }
+ result += f.name
+ }
+ }
+ return
+}
+
+func run(args ...string) {
+ doRun(args, false)
+}
+
+// runWithStdout is like run, but returns the text of standard output with the last newline dropped.
+func runWithStdout(argv ...string) string {
+ s := doRun(argv, true)
+ if len(s) == 0 {
+ Fatalf("no output from command %s", strings.Join(argv, " "))
+ }
+ if s[len(s)-1] == '\n' {
+ s = s[:len(s)-1]
+ }
+ return s
+}
+
+// runWithTestFlags appends any flag settings to the command line before running it.
+func runWithTestFlags(argv ...string) {
+ if test_short {
+ argv = append(argv, "-test.short")
+ }
+
+ if test_v {
+ argv = append(argv, "-test.v")
+ }
+ if test_run != "" {
+ argv = append(argv, fmt.Sprintf("-test.run=%s", test_run))
+ }
+ if test_memprofile != "" {
+ argv = append(argv, fmt.Sprintf("-test.memprofile=%s", test_memprofile))
+ }
+ if test_memprofilerate > 0 {
+ argv = append(argv, fmt.Sprintf("-test.memprofilerate=%d", test_memprofilerate))
+ }
+ if test_cpuprofile != "" {
+ argv = append(argv, fmt.Sprintf("-test.cpuprofile=%s", test_cpuprofile))
+ }
+ doRun(argv, false)
+}
+
+// doRun is the general command runner. The flag says whether we want to
+// retrieve standard output.
+func doRun(argv []string, returnStdout bool) string {
+ if *xFlag {
+ fmt.Printf("gotest: %s\n", strings.Join(argv, " "))
+ }
+ var err os.Error
+ argv[0], err = exec.LookPath(argv[0])
+ if err != nil {
+ Fatalf("can't find %s: %s", argv[0], err)
+ }
+ procAttr := &os.ProcAttr{
+ Env: env,
+ Files: []*os.File{
+ os.Stdin,
+ os.Stdout,
+ os.Stderr,
+ },
+ }
+ var r, w *os.File
+ if returnStdout {
+ r, w, err = os.Pipe()
+ if err != nil {
+ Fatalf("can't create pipe: %s", err)
+ }
+ procAttr.Files[1] = w
+ }
+ proc, err := os.StartProcess(argv[0], argv, procAttr)
+ if err != nil {
+ Fatalf("make failed to start: %s", err)
+ }
+ if returnStdout {
+ defer r.Close()
+ w.Close()
+ }
+ waitMsg, err := proc.Wait(0)
+ if err != nil || waitMsg == nil {
+ Fatalf("%s failed: %s", argv[0], err)
+ }
+ if !waitMsg.Exited() || waitMsg.ExitStatus() != 0 {
+ Fatalf("%q failed: %s", strings.Join(argv, " "), waitMsg)
+ }
+ if returnStdout {
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ Fatalf("can't read output from command: %s", err)
+ }
+ return string(b)
+ }
+ return ""
+}
+
+// writeTestmainGo generates the test program to be compiled, "./_testmain.go".
+func writeTestmainGo() {
+ f, err := os.Open("_testmain.go", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ Fatalf("can't create _testmain.go: %s", err)
+ }
+ defer f.Close()
+ b := bufio.NewWriter(f)
+ defer b.Flush()
+
+ // Package and imports.
+ fmt.Fprint(b, "package main\n\n")
+ // Are there tests from a package other than the one we're testing?
+ outsideTests := false
+ insideTests := false
+ for _, f := range files {
+ //println(f.name, f.pkg)
+ if len(f.tests) == 0 && len(f.benchmarks) == 0 {
+ continue
+ }
+ if strings.HasSuffix(f.pkg, "_test") {
+ outsideTests = true
+ } else {
+ insideTests = true
+ }
+ }
+ if insideTests {
+ switch importPath {
+ case "testing":
+ case "main":
+ // Import path main is reserved, so import with
+ // explicit reference to ./_test/main instead.
+ // Also, the file we are writing defines a function named main,
+ // so rename this import to __main__ to avoid name conflict.
+ fmt.Fprintf(b, "import __main__ %q\n", "./_test/main")
+ default:
+ fmt.Fprintf(b, "import %q\n", importPath)
+ }
+ }
+ if outsideTests {
+ fmt.Fprintf(b, "import %q\n", "./_xtest_")
+ }
+ fmt.Fprintf(b, "import %q\n", "testing")
+ fmt.Fprintf(b, "import __os__ %q\n", "os") // rename in case tested package is called os
+ fmt.Fprintf(b, "import __regexp__ %q\n", "regexp") // rename in case tested package is called regexp
+ fmt.Fprintln(b) // for gofmt
+
+ // Tests.
+ fmt.Fprintln(b, "var tests = []testing.InternalTest{")
+ for _, f := range files {
+ for _, t := range f.tests {
+ fmt.Fprintf(b, "\t{\"%s.%s\", %s.%s},\n", f.pkg, t, notMain(f.pkg), t)
+ }
+ }
+ fmt.Fprintln(b, "}")
+ fmt.Fprintln(b)
+
+ // Benchmarks.
+ fmt.Fprintln(b, "var benchmarks = []testing.InternalBenchmark{")
+ for _, f := range files {
+ for _, bm := range f.benchmarks {
+ fmt.Fprintf(b, "\t{\"%s.%s\", %s.%s},\n", f.pkg, bm, notMain(f.pkg), bm)
+ }
+ }
+ fmt.Fprintln(b, "}")
+
+ // Body.
+ fmt.Fprintln(b, testBody)
+}
+
+// notMain returns the package, renaming as appropriate if it's "main".
+func notMain(pkg string) string {
+ if pkg == "main" {
+ return "__main__"
+ }
+ return pkg
+}
+
+// testBody is just copied to the output. It's the code that runs the tests.
+var testBody = `
+var matchPat string
+var matchRe *__regexp__.Regexp
+
+func matchString(pat, str string) (result bool, err __os__.Error) {
+ if matchRe == nil || matchPat != pat {
+ matchPat = pat
+ matchRe, err = __regexp__.Compile(matchPat)
+ if err != nil {
+ return
+ }
+ }
+ return matchRe.MatchString(str), nil
+}
+
+func main() {
+ testing.Main(matchString, tests, benchmarks)
+}`