summaryrefslogtreecommitdiff
path: root/src/cmd/gotest/gotest.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/gotest/gotest.go')
-rw-r--r--src/cmd/gotest/gotest.go415
1 files changed, 415 insertions, 0 deletions
diff --git a/src/cmd/gotest/gotest.go b/src/cmd/gotest/gotest.go
new file mode 100644
index 000000000..138216e68
--- /dev/null
+++ b/src/cmd/gotest/gotest.go
@@ -0,0 +1,415 @@
+// 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"
+ "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
+)
+
+// 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")
+ }
+}
+
+// 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"
+ }
+ 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: %s\n", strings.Join(argv, " "))
+ }
+ 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 + `"`
+ }
+ argv = []string{"cmd", "/c", "sh", "-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.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)
+}`