diff options
Diffstat (limited to 'src/cmd/go/test.go')
-rw-r--r-- | src/cmd/go/test.go | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go new file mode 100644 index 000000000..870ab190f --- /dev/null +++ b/src/cmd/go/test.go @@ -0,0 +1,815 @@ +// 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 ( + "bytes" + "fmt" + "go/ast" + "go/build" + "go/doc" + "go/parser" + "go/token" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "sort" + "strings" + "text/template" + "time" + "unicode" + "unicode/utf8" +) + +// Break init loop. +func init() { + cmdTest.Run = runTest +} + +var cmdTest = &Command{ + CustomFlags: true, + UsageLine: "test [-c] [-i] [build flags] [packages] [flags for test binary]", + Short: "test packages", + Long: ` +'Go test' automates testing the packages named by the import paths. +It prints a summary of the test results in the format: + + ok archive/tar 0.011s + FAIL archive/zip 0.022s + ok compress/gzip 0.033s + ... + +followed by detailed output for each failed package. + +'Go test' recompiles each package along with any files with names matching +the file pattern "*_test.go". These additional files can contain test functions, +benchmark functions, and example functions. See 'go help testfunc' for more. + +By default, go test needs no arguments. It compiles and tests the package +with source in the current directory, including tests, and runs the tests. + +The package is built in a temporary directory so it does not interfere with the +non-test installation. + +In addition to the build flags, the flags handled by 'go test' itself are: + + -c Compile the test binary to pkg.test but do not run it. + + -i + Install packages that are dependencies of the test. + Do not run the test. + +The test binary also accepts flags that control execution of the test; these +flags are also accessible by 'go test'. See 'go help testflag' for details. + +For more about build flags, see 'go help build'. +For more about specifying packages, see 'go help packages'. + +See also: go build, go vet. +`, +} + +var helpTestflag = &Command{ + UsageLine: "testflag", + Short: "description of testing flags", + Long: ` +The 'go test' command takes both flags that apply to 'go test' itself +and flags that apply to the resulting test binary. + +The test binary, called pkg.test, where pkg is the name of the +directory containing the package sources, has its own flags: + + -test.v + Verbose output: log all tests as they are run. + + -test.run pattern + Run only those tests and examples matching the regular + expression. + + -test.bench pattern + Run benchmarks matching the regular expression. + By default, no benchmarks run. + + -test.cpuprofile cpu.out + Write a CPU profile to the specified file before exiting. + + -test.memprofile mem.out + Write a memory profile to the specified file when all tests + are complete. + + -test.memprofilerate n + Enable more precise (and expensive) memory profiles by setting + runtime.MemProfileRate. See 'godoc runtime MemProfileRate'. + To profile all memory allocations, use -test.memprofilerate=1 + and set the environment variable GOGC=off to disable the + garbage collector, provided the test can run in the available + memory without garbage collection. + + -test.parallel n + Allow parallel execution of test functions that call t.Parallel. + The value of this flag is the maximum number of tests to run + simultaneously; by default, it is set to the value of GOMAXPROCS. + + -test.short + Tell long-running tests to shorten their run time. + It is off by default but set during all.bash so that installing + the Go tree can run a sanity check but not spend time running + exhaustive tests. + + -test.timeout t + If a test runs longer than t, panic. + + -test.benchtime n + Run enough iterations of each benchmark to take n seconds. + The default is 1 second. + + -test.cpu 1,2,4 + Specify a list of GOMAXPROCS values for which the tests or + benchmarks should be 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 'go test' itself. Flags not listed +here are passed through unaltered. For instance, the command + + go test -x -v -cpuprofile=prof.out -dir=testdata -update + +will compile the test binary and then run it as + + pkg.test -test.v -test.cpuprofile=prof.out -dir=testdata -update +`, +} + +var helpTestfunc = &Command{ + UsageLine: "testfunc", + Short: "description of testing functions", + Long: ` +The 'go test' command expects to find test, benchmark, and example functions +in the "*_test.go" files corresponding to the package under test. + +A test function is one named TestXXX (where XXX is any alphanumeric string +not starting with a lower case letter) and should have the signature, + + func TestXXX(t *testing.T) { ... } + +A benchmark function is one named BenchmarkXXX and should have the signature, + + func BenchmarkXXX(b *testing.B) { ... } + +An example function is similar to a test function but, instead of using *testing.T +to report success or failure, prints output to os.Stdout and os.Stderr. +That output is compared against the function's "Output:" comment, which +must be the last comment in the function body (see example below). An +example with no such comment, or with no text after "Output:" is compiled +but not executed. + +Godoc displays the body of ExampleXXX to demonstrate the use +of the function, constant, or variable XXX. An example of a method M with +receiver type T or *T is named ExampleT_M. There may be multiple examples +for a given function, constant, or variable, distinguished by a trailing _xxx, +where xxx is a suffix not beginning with an upper case letter. + +Here is an example of an example: + + func ExamplePrintln() { + Println("The output of\nthis example.") + // Output: The output of + // this example. + } + +The entire test file is presented as the example when it contains a single +example function, at least one other function, type, variable, or constant +declaration, and no test or benchmark functions. + +See the documentation of the testing package for more information. +`, +} + +var ( + testC bool // -c flag + testI bool // -i flag + testV bool // -v flag + testFiles []string // -file flag(s) TODO: not respected + testTimeout string // -timeout flag + testArgs []string + testBench bool + testStreamOutput bool // show output as it is generated + testShowPass bool // show passing output + + testKillTimeout = 10 * time.Minute +) + +func runTest(cmd *Command, args []string) { + var pkgArgs []string + pkgArgs, testArgs = testFlags(args) + + pkgs := packagesForBuild(pkgArgs) + if len(pkgs) == 0 { + fatalf("no packages to test") + } + + if testC && len(pkgs) != 1 { + fatalf("cannot use -c flag with multiple packages") + } + + // If a test timeout was given and is parseable, set our kill timeout + // to that timeout plus one minute. This is a backup alarm in case + // the test wedges with a goroutine spinning and its background + // timer does not get a chance to fire. + if dt, err := time.ParseDuration(testTimeout); err == nil { + testKillTimeout = dt + 1*time.Minute + } + + // show passing test output (after buffering) with -v flag. + // must buffer because tests are running in parallel, and + // otherwise the output will get mixed. + testShowPass = testV + + // stream test output (no buffering) when no package has + // been given on the command line (implicit current directory) + // or when benchmarking. + // Also stream if we're showing output anyway with a + // single package under test. In that case, streaming the + // output produces the same result as not streaming, + // just more immediately. + testStreamOutput = len(pkgArgs) == 0 || testBench || + (len(pkgs) <= 1 && testShowPass) + + var b builder + b.init() + + if testI { + buildV = testV + + deps := map[string]bool{ + // Dependencies for testmain. + "testing": true, + "regexp": true, + } + for _, p := range pkgs { + // Dependencies for each test. + for _, path := range p.Imports { + deps[path] = true + } + for _, path := range p.TestImports { + deps[path] = true + } + for _, path := range p.XTestImports { + deps[path] = true + } + } + + // translate C to runtime/cgo + if deps["C"] { + delete(deps, "C") + deps["runtime/cgo"] = true + if buildContext.GOOS == runtime.GOOS && buildContext.GOARCH == runtime.GOARCH { + deps["cmd/cgo"] = true + } + } + // Ignore pseudo-packages. + delete(deps, "unsafe") + + all := []string{} + for path := range deps { + all = append(all, path) + } + sort.Strings(all) + + a := &action{} + for _, p := range packagesForBuild(all) { + a.deps = append(a.deps, b.action(modeInstall, modeInstall, p)) + } + b.do(a) + if !testC { + return + } + b.init() + } + + var builds, runs, prints []*action + + // Prepare build + run + print actions for all packages being tested. + for _, p := range pkgs { + buildTest, runTest, printTest, err := b.test(p) + if err != nil { + str := err.Error() + if strings.HasPrefix(str, "\n") { + str = str[1:] + } + if p.ImportPath != "" { + errorf("# %s\n%s", p.ImportPath, str) + } else { + errorf("%s", str) + } + continue + } + builds = append(builds, buildTest) + runs = append(runs, runTest) + prints = append(prints, printTest) + } + + // Ultimately the goal is to print the output. + root := &action{deps: prints} + + // Force the printing of results to happen in order, + // one at a time. + for i, a := range prints { + if i > 0 { + a.deps = append(a.deps, prints[i-1]) + } + } + + // If we are benchmarking, force everything to + // happen in serial. Could instead allow all the + // builds to run before any benchmarks start, + // but try this for now. + if testBench { + for i, a := range builds { + if i > 0 { + // Make build of test i depend on + // completing the run of test i-1. + a.deps = append(a.deps, runs[i-1]) + } + } + } + + // If we are building any out-of-date packages other + // than those under test, warn. + okBuild := map[*Package]bool{} + for _, p := range pkgs { + okBuild[p] = true + } + + warned := false + for _, a := range actionList(root) { + if a.p != nil && a.f != nil && !okBuild[a.p] && !a.p.fake && !a.p.local { + okBuild[a.p] = true // don't warn again + if !warned { + fmt.Fprintf(os.Stderr, "warning: building out-of-date packages:\n") + warned = true + } + fmt.Fprintf(os.Stderr, "\t%s\n", a.p.ImportPath) + } + } + if warned { + args := strings.Join(pkgArgs, " ") + if args != "" { + args = " " + args + } + fmt.Fprintf(os.Stderr, "installing these packages with 'go test -i%s' will speed future tests.\n\n", args) + } + + b.do(root) +} + +func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, err error) { + if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { + build := &action{p: p} + run := &action{p: p} + print := &action{f: (*builder).notest, p: p, deps: []*action{build}} + return build, run, print, nil + } + + // Build Package structs describing: + // ptest - package + test files + // pxtest - package of external test files + // pmain - pkg.test binary + var ptest, pxtest, pmain *Package + + var imports, ximports []*Package + var stk importStack + stk.push(p.ImportPath + "_test") + for _, path := range p.TestImports { + p1 := loadImport(path, p.Dir, &stk, p.build.TestImportPos[path]) + if p1.Error != nil { + return nil, nil, nil, p1.Error + } + imports = append(imports, p1) + } + for _, path := range p.XTestImports { + if path == p.ImportPath { + continue + } + p1 := loadImport(path, p.Dir, &stk, p.build.XTestImportPos[path]) + if p1.Error != nil { + return nil, nil, nil, p1.Error + } + ximports = append(ximports, p1) + } + stk.pop() + + // Use last element of import path, not package name. + // They differ when package name is "main". + _, elem := path.Split(p.ImportPath) + testBinary := elem + ".test" + + // The ptest package needs to be importable under the + // same import path that p has, but we cannot put it in + // the usual place in the temporary tree, because then + // other tests will see it as the real package. + // Instead we make a _test directory under the import path + // and then repeat the import path there. We tell the + // compiler and linker to look in that _test directory first. + // + // That is, if the package under test is unicode/utf8, + // then the normal place to write the package archive is + // $WORK/unicode/utf8.a, but we write the test package archive to + // $WORK/unicode/utf8/_test/unicode/utf8.a. + // We write the external test package archive to + // $WORK/unicode/utf8/_test/unicode/utf8_test.a. + testDir := filepath.Join(b.work, filepath.FromSlash(p.ImportPath+"/_test")) + ptestObj := buildToolchain.pkgpath(testDir, p) + + // Create the directory for the .a files. + ptestDir, _ := filepath.Split(ptestObj) + if err := b.mkdir(ptestDir); err != nil { + return nil, nil, nil, err + } + if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), p); err != nil { + return nil, nil, nil, err + } + + // Test package. + if len(p.TestGoFiles) > 0 { + ptest = new(Package) + *ptest = *p + ptest.GoFiles = nil + ptest.GoFiles = append(ptest.GoFiles, p.GoFiles...) + ptest.GoFiles = append(ptest.GoFiles, p.TestGoFiles...) + ptest.target = "" + ptest.Imports = stringList(p.Imports, p.TestImports) + ptest.imports = append(append([]*Package{}, p.imports...), imports...) + ptest.pkgdir = testDir + ptest.fake = true + ptest.forceLibrary = true + ptest.Stale = true + ptest.build = new(build.Package) + *ptest.build = *p.build + m := map[string][]token.Position{} + for k, v := range p.build.ImportPos { + m[k] = append(m[k], v...) + } + for k, v := range p.build.TestImportPos { + m[k] = append(m[k], v...) + } + ptest.build.ImportPos = m + } else { + ptest = p + } + + // External test package. + if len(p.XTestGoFiles) > 0 { + pxtest = &Package{ + Name: p.Name + "_test", + ImportPath: p.ImportPath + "_test", + localPrefix: p.localPrefix, + Root: p.Root, + Dir: p.Dir, + GoFiles: p.XTestGoFiles, + Imports: p.XTestImports, + build: &build.Package{ + ImportPos: p.build.XTestImportPos, + }, + imports: append(ximports, ptest), + pkgdir: testDir, + fake: true, + Stale: true, + } + } + + // Action for building pkg.test. + pmain = &Package{ + Name: "main", + Dir: testDir, + GoFiles: []string{"_testmain.go"}, + ImportPath: "testmain", + Root: p.Root, + imports: []*Package{ptest}, + build: &build.Package{Name: "main"}, + fake: true, + Stale: true, + } + if pxtest != nil { + pmain.imports = append(pmain.imports, pxtest) + } + + // The generated main also imports testing and regexp. + stk.push("testmain") + ptesting := loadImport("testing", "", &stk, nil) + if ptesting.Error != nil { + return nil, nil, nil, ptesting.Error + } + pregexp := loadImport("regexp", "", &stk, nil) + if pregexp.Error != nil { + return nil, nil, nil, pregexp.Error + } + pmain.imports = append(pmain.imports, ptesting, pregexp) + computeStale(pmain) + + if ptest != p { + a := b.action(modeBuild, modeBuild, ptest) + a.objdir = testDir + string(filepath.Separator) + a.objpkg = ptestObj + a.target = ptestObj + a.link = false + } + + if pxtest != nil { + a := b.action(modeBuild, modeBuild, pxtest) + a.objdir = testDir + string(filepath.Separator) + a.objpkg = buildToolchain.pkgpath(testDir, pxtest) + a.target = a.objpkg + } + + a := b.action(modeBuild, modeBuild, pmain) + a.objdir = testDir + string(filepath.Separator) + a.objpkg = filepath.Join(testDir, "main.a") + a.target = filepath.Join(testDir, testBinary) + exeSuffix + pmainAction := a + + if testC { + // -c flag: create action to copy binary to ./test.out. + runAction = &action{ + f: (*builder).install, + deps: []*action{pmainAction}, + p: pmain, + target: testBinary + exeSuffix, + } + printAction = &action{p: p, deps: []*action{runAction}} // nop + } else { + // run test + runAction = &action{ + f: (*builder).runTest, + deps: []*action{pmainAction}, + p: p, + ignoreFail: true, + } + cleanAction := &action{ + f: (*builder).cleanTest, + deps: []*action{runAction}, + p: p, + } + printAction = &action{ + f: (*builder).printTest, + deps: []*action{cleanAction}, + p: p, + } + } + + return pmainAction, runAction, printAction, nil +} + +// runTest is the action for running a test binary. +func (b *builder) runTest(a *action) error { + args := stringList(a.deps[0].target, testArgs) + a.testOutput = new(bytes.Buffer) + + if buildN || buildX { + b.showcmd("", "%s", strings.Join(args, " ")) + if buildN { + return nil + } + } + + if a.failed { + // We were unable to build the binary. + a.failed = false + fmt.Fprintf(a.testOutput, "FAIL\t%s [build failed]\n", a.p.ImportPath) + setExitStatus(1) + return nil + } + + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = a.p.Dir + var buf bytes.Buffer + if testStreamOutput { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = &buf + cmd.Stderr = &buf + } + + t0 := time.Now() + err := cmd.Start() + + // This is a last-ditch deadline to detect and + // stop wedged test binaries, to keep the builders + // running. + tick := time.NewTimer(testKillTimeout) + if err == nil { + done := make(chan error) + go func() { + done <- cmd.Wait() + }() + select { + case err = <-done: + // ok + case <-tick.C: + cmd.Process.Kill() + err = <-done + fmt.Fprintf(&buf, "*** Test killed: ran too long.\n") + } + tick.Stop() + } + out := buf.Bytes() + t1 := time.Now() + t := fmt.Sprintf("%.3fs", t1.Sub(t0).Seconds()) + if err == nil { + if testShowPass { + a.testOutput.Write(out) + } + fmt.Fprintf(a.testOutput, "ok \t%s\t%s\n", a.p.ImportPath, t) + return nil + } + + setExitStatus(1) + if len(out) > 0 { + a.testOutput.Write(out) + // assume printing the test binary's exit status is superfluous + } else { + fmt.Fprintf(a.testOutput, "%s\n", err) + } + fmt.Fprintf(a.testOutput, "FAIL\t%s\t%s\n", a.p.ImportPath, t) + + return nil +} + +// cleanTest is the action for cleaning up after a test. +func (b *builder) cleanTest(a *action) error { + if buildWork { + return nil + } + run := a.deps[0] + testDir := filepath.Join(b.work, filepath.FromSlash(run.p.ImportPath+"/_test")) + os.RemoveAll(testDir) + return nil +} + +// printTest is the action for printing a test result. +func (b *builder) printTest(a *action) error { + clean := a.deps[0] + run := clean.deps[0] + os.Stdout.Write(run.testOutput.Bytes()) + run.testOutput = nil + return nil +} + +// notest is the action for testing a package with no test files. +func (b *builder) notest(a *action) error { + fmt.Printf("? \t%s\t[no test files]\n", a.p.ImportPath) + return nil +} + +// 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) +} + +// writeTestmain writes the _testmain.go file for package p to +// the file named out. +func writeTestmain(out string, p *Package) error { + t := &testFuncs{ + Package: p, + } + for _, file := range p.TestGoFiles { + if err := t.load(filepath.Join(p.Dir, file), "_test", &t.NeedTest); err != nil { + return err + } + } + for _, file := range p.XTestGoFiles { + if err := t.load(filepath.Join(p.Dir, file), "_xtest", &t.NeedXtest); err != nil { + return err + } + } + + f, err := os.Create(out) + if err != nil { + return err + } + defer f.Close() + + if err := testmainTmpl.Execute(f, t); err != nil { + return err + } + + return nil +} + +type testFuncs struct { + Tests []testFunc + Benchmarks []testFunc + Examples []testFunc + Package *Package + NeedTest bool + NeedXtest bool +} + +type testFunc struct { + Package string // imported package name (_test or _xtest) + Name string // function name + Output string // output, for examples +} + +var testFileSet = token.NewFileSet() + +func (t *testFuncs) load(filename, pkg string, seen *bool) error { + f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments) + if err != nil { + return expandScanner(err) + } + for _, d := range f.Decls { + n, ok := d.(*ast.FuncDecl) + if !ok { + continue + } + if n.Recv != nil { + continue + } + name := n.Name.String() + switch { + case isTest(name, "Test"): + t.Tests = append(t.Tests, testFunc{pkg, name, ""}) + *seen = true + case isTest(name, "Benchmark"): + t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""}) + *seen = true + } + } + for _, e := range doc.Examples(f) { + if e.Output == "" { + // Don't run examples with no output. + continue + } + t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output}) + *seen = true + } + return nil +} + +var testmainTmpl = template.Must(template.New("main").Parse(` +package main + +import ( + "regexp" + "testing" + +{{if .NeedTest}} + _test {{.Package.ImportPath | printf "%q"}} +{{end}} +{{if .NeedXtest}} + _xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}} +{{end}} +) + +var tests = []testing.InternalTest{ +{{range .Tests}} + {"{{.Name}}", {{.Package}}.{{.Name}}}, +{{end}} +} + +var benchmarks = []testing.InternalBenchmark{ +{{range .Benchmarks}} + {"{{.Name}}", {{.Package}}.{{.Name}}}, +{{end}} +} + +var examples = []testing.InternalExample{ +{{range .Examples}} + {"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}}, +{{end}} +} + +var matchPat string +var matchRe *regexp.Regexp + +func matchString(pat, str string) (result bool, err 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, examples) +} + +`)) |