summaryrefslogtreecommitdiff
path: root/src/cmd/gotest
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/gotest')
-rw-r--r--src/cmd/gotest/Makefile12
-rw-r--r--src/cmd/gotest/doc.go113
-rw-r--r--src/cmd/gotest/flag.go159
-rw-r--r--src/cmd/gotest/gotest.go435
4 files changed, 719 insertions, 0 deletions
diff --git a/src/cmd/gotest/Makefile b/src/cmd/gotest/Makefile
new file mode 100644
index 000000000..5c1154537
--- /dev/null
+++ b/src/cmd/gotest/Makefile
@@ -0,0 +1,12 @@
+# Copyright 2010 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.
+
+include ../../Make.inc
+
+TARG=gotest
+GOFILES=\
+ flag.go\
+ gotest.go\
+
+include ../../Make.cmd
diff --git a/src/cmd/gotest/doc.go b/src/cmd/gotest/doc.go
new file mode 100644
index 000000000..5be06f817
--- /dev/null
+++ b/src/cmd/gotest/doc.go
@@ -0,0 +1,113 @@
+// Copyright 2009 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.
+
+/*
+
+Gotest is an automated testing tool for Go packages.
+
+Normally a Go package is compiled without its test files. Gotest is a
+tool that recompiles the package whose source is in the current
+directory, along with any files whose names match the pattern
+"[^.]*_test.go". Functions in the test source named TestXXX (where
+XXX is any alphanumeric string not starting with a lower case letter)
+will be run when the binary is executed. Gotest requires that the
+package have a standard package Makefile, one that includes
+go/src/Make.pkg.
+
+The test functions are run in the order they appear in the source.
+They should have the signature,
+
+ func TestXXX(t *testing.T) { ... }
+
+Benchmark functions can be written as well; they will be run only when
+the -test.bench flag is provided. Benchmarks should have the
+signature,
+
+ func BenchmarkXXX(b *testing.B) { ... }
+
+See the documentation of the testing package for more information.
+
+By default, gotest needs no arguments. It compiles all the .go files
+in the directory, including tests, and runs the tests. If file names
+are given (with flag -file=test.go, one per extra test source file),
+only those test files are added to the package. (The non-test files
+are always compiled.)
+
+The package is built in a special subdirectory so it does not
+interfere with the non-test installation.
+
+Usage:
+ gotest [-file a.go -file b.go ...] [-c] [-x] [args for test binary]
+
+The flags specific to gotest are:
+ -c Compile the test binary but do not run it.
+ -file a.go Use only the tests in the source file a.go.
+ Multiple -file flags may be provided.
+ -x Print each subcommand gotest executes.
+
+Everything else on the command line is passed to the test binary.
+
+The resulting test binary, called (for amd64) 6.out, has several flags.
+
+Usage:
+ 6.out [-test.v] [-test.run pattern] [-test.bench pattern] \
+ [-test.cpuprofile=cpu.out] \
+ [-test.memprofile=mem.out] [-test.memprofilerate=1] \
+ [-test.timeout=10] [-test.short] \
+ [-test.benchtime=3] [-test.cpu=1,2,3,4]
+
+The -test.v flag causes the tests to be logged as they run. The
+-test.run flag causes only those tests whose names match the regular
+expression pattern to be run. By default all tests are run silently.
+
+If all specified tests pass, 6.out prints the word PASS and exits with
+a 0 exit code. If any tests fail, it prints error details, the word
+FAIL, and exits with a non-zero code. The -test.bench flag is
+analogous to the -test.run flag, but applies to benchmarks. No
+benchmarks run by default.
+
+The -test.cpuprofile flag causes the testing software to write a CPU
+profile to the specified file before exiting.
+
+The -test.memprofile flag causes the testing software to write a
+memory profile to the specified file when all tests are complete. The
+-test.memprofilerate flag enables more precise (and expensive)
+profiles by setting runtime.MemProfileRate; run
+ godoc runtime MemProfileRate
+for details. The defaults are no memory profile and the standard
+setting of MemProfileRate. The memory profile records a sampling of
+the memory in use at the end of the test. To profile all memory
+allocations, use -test.memprofilerate=1 to sample every byte and set
+the environment variable GOGC=off to disable the garbage collector,
+provided the test can run in the available memory without garbage
+collection.
+
+Use -test.run or -test.bench to limit profiling to a particular test
+or benchmark.
+
+The -test.short flag tells long-running tests to shorten their run
+time. It is off by default but set by all.bash so installations of
+the Go tree can do a sanity check but not spend time running
+exhaustive tests.
+
+The -test.timeout flag sets a timeout for the test in seconds. If the
+test runs for longer than that, it will panic, dumping a stack trace
+of all existing goroutines.
+
+The -test.benchtime flag specifies the number of seconds to run each benchmark.
+The default is one second.
+
+The -test.cpu flag specifies a list of GOMAXPROCS values for which
+the tests or benchmarks are executed. The default is the current
+value of GOMAXPROCS.
+
+For convenience, each of these -test.X flags of the test binary is
+also available as the flag -X in gotest itself. Flags not listed here
+are unaffected. For instance, the command
+ gotest -x -v -cpuprofile=prof.out -dir=testdata -update -file x_test.go
+will compile the test binary using x_test.go and then run it as
+ 6.out -test.v -test.cpuprofile=prof.out -dir=testdata -update
+
+*/
+package documentation
diff --git a/src/cmd/gotest/flag.go b/src/cmd/gotest/flag.go
new file mode 100644
index 000000000..c3a28f9a3
--- /dev/null
+++ b/src/cmd/gotest/flag.go
@@ -0,0 +1,159 @@
+// 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 (
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+)
+
+// The flag handling part of gotest is large and distracting.
+// We can't use the flag package because some of the flags from
+// our command line are for us, and some are for 6.out, and
+// some are for both.
+
+var usageMessage = `Usage of %s:
+ -c=false: compile but do not run the test binary
+ -file=file:
+ -x=false: print command lines as they are executed
+
+ // These flags can be passed with or without a "test." prefix: -v or -test.v.
+ -bench="": passes -test.bench to test
+ -benchtime=1: passes -test.benchtime to test
+ -cpu="": passes -test.cpu to test
+ -cpuprofile="": passes -test.cpuprofile to test
+ -memprofile="": passes -test.memprofile to test
+ -memprofilerate=0: passes -test.memprofilerate to test
+ -run="": passes -test.run to test
+ -short=false: passes -test.short to test
+ -timeout=0: passes -test.timeout to test
+ -v=false: passes -test.v to test
+`
+
+// usage prints a usage message and exits.
+func usage() {
+ fmt.Fprintf(os.Stdout, usageMessage, os.Args[0])
+ os.Exit(2)
+}
+
+// flagSpec defines a flag we know about.
+type flagSpec struct {
+ name string
+ isBool bool
+ passToTest bool // pass to Test
+ multiOK bool // OK to have multiple instances
+ present bool // flag has been seen
+}
+
+// flagDefn is the set of flags we process.
+var flagDefn = []*flagSpec{
+ // gotest-local.
+ &flagSpec{name: "c", isBool: true},
+ &flagSpec{name: "file", multiOK: true},
+ &flagSpec{name: "x", isBool: true},
+
+ // passed to 6.out, adding a "test." prefix to the name if necessary: -v becomes -test.v.
+ &flagSpec{name: "bench", passToTest: true},
+ &flagSpec{name: "benchtime", passToTest: true},
+ &flagSpec{name: "cpu", passToTest: true},
+ &flagSpec{name: "cpuprofile", passToTest: true},
+ &flagSpec{name: "memprofile", passToTest: true},
+ &flagSpec{name: "memprofilerate", passToTest: true},
+ &flagSpec{name: "run", passToTest: true},
+ &flagSpec{name: "short", isBool: true, passToTest: true},
+ &flagSpec{name: "timeout", passToTest: true},
+ &flagSpec{name: "v", isBool: true, passToTest: true},
+}
+
+// flags processes the command line, grabbing -x and -c, rewriting known flags
+// to have "test" before them, and reading the command line for the 6.out.
+// Unfortunately for us, we need to do our own flag processing because gotest
+// grabs some flags but otherwise its command line is just a holding place for
+// 6.out's arguments.
+func flags() {
+ for i := 1; i < len(os.Args); i++ {
+ arg := os.Args[i]
+ f, value, extraWord := flag(i)
+ if f == nil {
+ args = append(args, arg)
+ continue
+ }
+ switch f.name {
+ case "c":
+ setBoolFlag(&cFlag, value)
+ case "x":
+ setBoolFlag(&xFlag, value)
+ case "file":
+ fileNames = append(fileNames, value)
+ }
+ if extraWord {
+ i++
+ }
+ if f.passToTest {
+ args = append(args, "-test."+f.name+"="+value)
+ }
+ }
+}
+
+// flag sees if argument i is a known flag and returns its definition, value, and whether it consumed an extra word.
+func flag(i int) (f *flagSpec, value string, extra bool) {
+ arg := os.Args[i]
+ if strings.HasPrefix(arg, "--") { // reduce two minuses to one
+ arg = arg[1:]
+ }
+ if arg == "" || arg[0] != '-' {
+ return
+ }
+ name := arg[1:]
+ // If there's already "test.", drop it for now.
+ if strings.HasPrefix(name, "test.") {
+ name = name[5:]
+ }
+ equals := strings.Index(name, "=")
+ if equals >= 0 {
+ value = name[equals+1:]
+ name = name[:equals]
+ }
+ for _, f = range flagDefn {
+ if name == f.name {
+ // Booleans are special because they have modes -x, -x=true, -x=false.
+ if f.isBool {
+ if equals < 0 { // otherwise, it's been set and will be verified in setBoolFlag
+ value = "true"
+ } else {
+ // verify it parses
+ setBoolFlag(new(bool), value)
+ }
+ } else { // Non-booleans must have a value.
+ extra = equals < 0
+ if extra {
+ if i+1 >= len(os.Args) {
+ usage()
+ }
+ value = os.Args[i+1]
+ }
+ }
+ if f.present && !f.multiOK {
+ usage()
+ }
+ f.present = true
+ return
+ }
+ }
+ f = nil
+ return
+}
+
+// setBoolFlag sets the addressed boolean to the value.
+func setBoolFlag(flag *bool, value string) {
+ x, err := strconv.Atob(value)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "gotest: illegal bool flag value %s\n", value)
+ usage()
+ }
+ *flag = x
+}
diff --git a/src/cmd/gotest/gotest.go b/src/cmd/gotest/gotest.go
new file mode 100644
index 000000000..4cb3da23c
--- /dev/null
+++ b/src/cmd/gotest/gotest.go
@@ -0,0 +1,435 @@
+// 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"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+ "unicode"
+ "utf8"
+)
+
+// Environment for commands.
+var (
+ XGC []string // 6g -I _test -o _xtest_.6
+ GC []string // 6g -I _test _testmain.go
+ GL []string // 6l -L _test _testmain.6
+ GOARCH string
+ GOROOT string
+ GORUN string
+ O string
+ args []string // arguments passed to gotest; also passed to the binary
+ fileNames []string
+ env = os.Environ()
+)
+
+// These strings are created by getTestNames.
+var (
+ insideFileNames []string // list of *.go files inside the package.
+ outsideFileNames []string // list of *.go files outside the package (in package foo_test).
+)
+
+var (
+ files []*File
+ importPath string
+)
+
+// Flags for our own purposes. We do our own flag processing.
+var (
+ cFlag bool
+ xFlag bool
+)
+
+// elapsed returns the number of seconds since gotest started.
+func elapsed() float64 {
+ return float64(time.Nanoseconds()-start) / 1e9
+}
+
+var start = time.Nanoseconds()
+
+// 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() {
+ flags()
+ needMakefile()
+ setEnvironment()
+ getTestFileNames()
+ parseFiles()
+ getTestNames()
+ run("gomake", "testpackage-clean")
+ run("gomake", "testpackage", fmt.Sprintf("GOTESTFILES=%s", strings.Join(insideFileNames, " ")))
+ if len(outsideFileNames) > 0 {
+ run(append(XGC, outsideFileNames...)...)
+ }
+ importPath = runWithStdout("gomake", "-s", "importpath")
+ writeTestmainGo()
+ run(GC...)
+ run(GL...)
+ if !cFlag {
+ runTestWithArgs("./" + O + ".out")
+ }
+ if xFlag {
+ fmt.Printf("gotest %.2fs: done\n", elapsed())
+ }
+}
+
+// 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 = os.Getenv("GOARCH")
+ if GOARCH == "" {
+ 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"
+ }
+ XGC = []string{gc, "-I", "_test", "-o", "_xtest_." + O}
+ 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 for file names matching "[^.]*_test.go".
+func getTestFileNames() {
+ names := fileNames
+ if len(names) == 0 {
+ var err os.Error
+ names, err = filepath.Glob("[^.]*_test.go")
+ if err != nil {
+ Fatalf("Glob pattern error: %s", err)
+ }
+ if len(names) == 0 {
+ Fatalf(`no test files found: no match for "[^.]*_test.go"`)
+ }
+ }
+ for _, n := range names {
+ fd, err := os.Open(n)
+ 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 {
+ // Report declaration errors so we can abort if the files are incorrect Go.
+ file, err := parser.ParseFile(fileSet, f.name, nil, parser.DeclarationErrors)
+ if err != nil {
+ Fatalf("parse error: %s", 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.
+ }
+ if strings.HasSuffix(f.pkg, "_test") {
+ outsideFileNames = append(outsideFileNames, f.name)
+ } else {
+ insideFileNames = append(insideFileNames, f.name)
+ }
+ }
+}
+
+// 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) {
+ return false
+ }
+ if len(name) == len(prefix) { // "Test" is ok
+ return true
+ }
+ rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
+ return !unicode.IsLower(rune)
+}
+
+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 strings.HasSuffix(s, "\r\n") {
+ s = s[:len(s)-2]
+ } else if strings.HasSuffix(s, "\n") {
+ s = s[:len(s)-1]
+ }
+ if len(s) == 0 {
+ Fatalf("no output from command %s", strings.Join(argv, " "))
+ }
+ return s
+}
+
+// runTestWithArgs appends gotest's runs the provided binary with the args passed on the command line.
+func runTestWithArgs(binary string) {
+ doRun(append([]string{binary}, args...), 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 %.2fs: %s\n", elapsed(), strings.Join(argv, " "))
+ t := -time.Nanoseconds()
+ defer func() {
+ t += time.Nanoseconds()
+ fmt.Printf(" [+%.2fs]\n", float64(t)/1e9)
+ }()
+ }
+ command := argv[0]
+ if runtime.GOOS == "windows" && command == "gomake" {
+ // gomake is a shell script and it cannot be executed directly on Windows.
+ cmd := ""
+ for i, v := range argv {
+ if i > 0 {
+ cmd += " "
+ }
+ cmd += `"` + v + `"`
+ }
+ command = "bash"
+ argv = []string{"bash", "-c", cmd}
+ }
+ var err os.Error
+ argv[0], err = exec.LookPath(argv[0])
+ if err != nil {
+ Fatalf("can't find %s: %s", command, 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("%s failed to start: %s", command, err)
+ }
+ if returnStdout {
+ defer r.Close()
+ w.Close()
+ }
+ waitMsg, err := proc.Wait(0)
+ if err != nil || waitMsg == nil {
+ Fatalf("%s failed: %s", command, 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.Create("_testmain.go")
+ 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?
+ // We can't just use file names because some of the things we compiled
+ // contain no tests.
+ 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.Fprintf(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)
+}`