diff options
Diffstat (limited to 'src/cmd/go/test.go')
-rw-r--r-- | src/cmd/go/test.go | 424 |
1 files changed, 377 insertions, 47 deletions
diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go index b1db16f77..06ac9d206 100644 --- a/src/cmd/go/test.go +++ b/src/cmd/go/test.go @@ -12,10 +12,12 @@ import ( "go/doc" "go/parser" "go/token" + "log" "os" "os/exec" "path" "path/filepath" + "regexp" "runtime" "sort" "strings" @@ -32,7 +34,7 @@ func init() { var cmdTest = &Command{ CustomFlags: true, - UsageLine: "test [-c] [-i] [build flags] [packages] [flags for test binary]", + UsageLine: "test [-c] [-i] [build and test flags] [packages] [flags for test binary]", Short: "test packages", Long: ` 'Go test' automates testing the packages named by the import paths. @@ -46,8 +48,10 @@ It prints a summary of the test results in the format: 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. +the file pattern "*_test.go". +Files whose names begin with "_" (including "_test.go") or "." are ignored. +These additional files can contain test functions, benchmark functions, and +example functions. See 'go help testfunc' for more. Each listed package causes the execution of a separate test binary. Test files that declare a package with the suffix "_test" will be compiled as a @@ -71,6 +75,11 @@ In addition to the build flags, the flags handled by 'go test' itself are: 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. +If the test binary needs any other flags, they should be presented after the +package names. The go tool treats as a flag the first argument that begins with +a minus sign that it does not recognize itself; that argument and all subsequent +arguments are passed as arguments to the test binary. + For more about build flags, see 'go help build'. For more about specifying packages, see 'go help packages'. @@ -119,6 +128,30 @@ control the execution of any test: if -test.blockprofile is set without this flag, all blocking events are recorded, equivalent to -test.blockprofilerate=1. + -cover + Enable coverage analysis. + + -covermode set,count,atomic + Set the mode for coverage analysis for the package[s] + being tested. The default is "set". + The values: + set: bool: does this statement run? + count: int: how many times does this statement run? + atomic: int: count, but correct in multithreaded tests; + significantly more expensive. + Sets -cover. + + -coverpkg pkg1,pkg2,pkg3 + Apply coverage analysis in each test to the given list of packages. + The default is for each test to analyze only the package being tested. + Packages are specified as import paths. + Sets -cover. + + -coverprofile cover.out + Write a coverage profile to the specified file after all tests + have passed. + Sets -cover. + -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 @@ -139,6 +172,10 @@ control the execution of any test: garbage collector, provided the test can run in the available memory without garbage collection. + -outputdir directory + Place output files from profiling in the specified directory, + by default the directory in which "go test" is running. + -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 @@ -176,8 +213,8 @@ will compile the test binary and then run it as pkg.test -test.v -test.cpuprofile=prof.out -dir=testdata -update -The test flags that generate profiles also leave the test binary in pkg.test -for use when analyzing the profiles. +The test flags that generate profiles (other than for coverage) also +leave the test binary in pkg.test for use when analyzing the profiles. Flags not recognized by 'go test' must be placed after any specified packages. `, @@ -229,12 +266,17 @@ See the documentation of the testing package for more information. } var ( - testC bool // -c flag - testProfile bool // some profiling flag - testI bool // -i flag - testV bool // -v flag - testFiles []string // -file flag(s) TODO: not respected - testTimeout string // -timeout flag + testC bool // -c flag + testCover bool // -cover flag + testCoverMode string // -covermode flag + testCoverPaths []string // -coverpkg flag + testCoverPkgs []*Package // -coverpkg flag + testProfile bool // some profiling flag + testNeedBinary bool // profile needs to keep binary around + 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 @@ -243,6 +285,12 @@ var ( testKillTimeout = 10 * time.Minute ) +var testMainDeps = map[string]bool{ + // Dependencies for testmain. + "testing": true, + "regexp": true, +} + func runTest(cmd *Command, args []string) { var pkgArgs []string pkgArgs, testArgs = testFlags(args) @@ -289,11 +337,11 @@ func runTest(cmd *Command, args []string) { if testI { buildV = testV - deps := map[string]bool{ - // Dependencies for testmain. - "testing": true, - "regexp": true, + deps := make(map[string]bool) + for dep := range testMainDeps { + deps[dep] = true } + for _, p := range pkgs { // Dependencies for each test. for _, path := range p.Imports { @@ -331,7 +379,7 @@ func runTest(cmd *Command, args []string) { a.deps = append(a.deps, b.action(modeInstall, modeInstall, p)) } b.do(a) - if !testC { + if !testC || a.failed { return } b.init() @@ -339,6 +387,34 @@ func runTest(cmd *Command, args []string) { var builds, runs, prints []*action + if testCoverPaths != nil { + // Load packages that were asked about for coverage. + // packagesForBuild exits if the packages cannot be loaded. + testCoverPkgs = packagesForBuild(testCoverPaths) + + // Warn about -coverpkg arguments that are not actually used. + used := make(map[string]bool) + for _, p := range pkgs { + used[p.ImportPath] = true + for _, dep := range p.Deps { + used[dep] = true + } + } + for _, p := range testCoverPkgs { + if !used[p.ImportPath] { + log.Printf("warning: no packages being tested depend on %s", p.ImportPath) + } + } + + // Mark all the coverage packages for rebuilding with coverage. + for _, p := range testCoverPkgs { + p.Stale = true // rebuild + p.fake = true // do not warn about rebuild + p.coverMode = testCoverMode + p.coverVars = declareCoverVars(p.ImportPath, p.GoFiles...) + } + } + // Prepare build + run + print actions for all packages being tested. for _, p := range pkgs { buildTest, runTest, printTest, err := b.test(p) @@ -347,10 +423,12 @@ func runTest(cmd *Command, args []string) { if strings.HasPrefix(str, "\n") { str = str[1:] } + failed := fmt.Sprintf("FAIL\t%s [setup failed]\n", p.ImportPath) + if p.ImportPath != "" { - errorf("# %s\n%s", p.ImportPath, str) + errorf("# %s\n%s\n%s", p.ImportPath, str, failed) } else { - errorf("%s", str) + errorf("%s\n%s", str, failed) } continue } @@ -370,16 +448,15 @@ func runTest(cmd *Command, args []string) { } } - // 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]) + // Force benchmarks to run in serial. + if !testC && testBench { + // The first run must wait for all builds. + // Later runs must wait for the previous run's print. + for i, run := range runs { + if i == 0 { + run.deps = append(run.deps, builds...) + } else { + run.deps = append(run.deps, prints[i-1]) } } } @@ -390,11 +467,22 @@ func runTest(cmd *Command, args []string) { 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 a.p == nil || okBuild[a.p] { + continue + } + okBuild[a.p] = true // warn at most once + + // Don't warn about packages being rebuilt because of + // things like coverage analysis. + for _, p1 := range a.p.imports { + if p1.fake { + a.p.fake = true + } + } + + if a.f != nil && !okBuild[a.p] && !a.p.fake && !a.p.local { if !warned { fmt.Fprintf(os.Stderr, "warning: building out-of-date packages:\n") warned = true @@ -417,11 +505,20 @@ func runTest(cmd *Command, args []string) { b.do(root) } +func contains(x []string, s string) bool { + for _, t := range x { + if t == s { + return true + } + } + return false +} + 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}} + run := &action{p: p, deps: []*action{build}} + print := &action{f: (*builder).notest, p: p, deps: []*action{run}} return build, run, print, nil } @@ -487,12 +584,15 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, 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 - } + + // Should we apply coverage analysis locally, + // only for this package and only for this test? + // Yes, if -cover is on but -coverpkg has not specified + // a list of packages for global coverage. + localCover := testCover && testCoverPaths == nil // Test package. - if len(p.TestGoFiles) > 0 { + if len(p.TestGoFiles) > 0 || localCover || p.Name == "main" { ptest = new(Package) *ptest = *p ptest.GoFiles = nil @@ -515,6 +615,11 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, m[k] = append(m[k], v...) } ptest.build.ImportPos = m + + if localCover { + ptest.coverMode = testCoverMode + ptest.coverVars = declareCoverVars(ptest.ImportPath, ptest.GoFiles...) + } } else { ptest = p } @@ -548,6 +653,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, Root: p.Root, imports: []*Package{ptest}, build: &build.Package{Name: "main"}, + pkgdir: testDir, fake: true, Stale: true, } @@ -557,15 +663,50 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, // 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 + for dep := range testMainDeps { + if ptest.ImportPath != dep { + p1 := loadImport("testing", "", &stk, nil) + if p1.Error != nil { + return nil, nil, nil, p1.Error + } + pmain.imports = append(pmain.imports, p1) + } + } + + if testCoverPkgs != nil { + // Add imports, but avoid duplicates. + seen := map[*Package]bool{p: true, ptest: true} + for _, p1 := range pmain.imports { + seen[p1] = true + } + for _, p1 := range testCoverPkgs { + if !seen[p1] { + seen[p1] = true + pmain.imports = append(pmain.imports, p1) + } + } } - pregexp := loadImport("regexp", "", &stk, nil) - if pregexp.Error != nil { - return nil, nil, nil, pregexp.Error + + if ptest != p && localCover { + // We have made modifications to the package p being tested + // and are rebuilding p (as ptest), writing it to the testDir tree. + // Arrange to rebuild, writing to that same tree, all packages q + // such that the test depends on q, and q depends on p. + // This makes sure that q sees the modifications to p. + // Strictly speaking, the rebuild is only necessary if the + // modifications to p change its export metadata, but + // determining that is a bit tricky, so we rebuild always. + // + // This will cause extra compilation, so for now we only do it + // when testCover is set. The conditions are more general, though, + // and we may find that we need to do it always in the future. + recompileForTest(pmain, p, ptest, testDir) } - pmain.imports = append(pmain.imports, ptesting, pregexp) + + if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), pmain, ptest); err != nil { + return nil, nil, nil, err + } + computeStale(pmain) if ptest != p { @@ -589,7 +730,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, a.target = filepath.Join(testDir, testBinary) + exeSuffix pmainAction := a - if testC || testProfile { + if testC || testNeedBinary { // -c or profiling flag: create action to copy binary to ./test.out. runAction = &action{ f: (*builder).install, @@ -624,6 +765,75 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, return pmainAction, runAction, printAction, nil } +func recompileForTest(pmain, preal, ptest *Package, testDir string) { + // The "test copy" of preal is ptest. + // For each package that depends on preal, make a "test copy" + // that depends on ptest. And so on, up the dependency tree. + testCopy := map[*Package]*Package{preal: ptest} + for _, p := range packageList([]*Package{pmain}) { + // Copy on write. + didSplit := false + split := func() { + if didSplit { + return + } + didSplit = true + if p.pkgdir != testDir { + p1 := new(Package) + testCopy[p] = p1 + *p1 = *p + p1.imports = make([]*Package, len(p.imports)) + copy(p1.imports, p.imports) + p = p1 + p.pkgdir = testDir + p.target = "" + p.fake = true + p.Stale = true + } + } + + // Update p.deps and p.imports to use at test copies. + for i, dep := range p.deps { + if p1 := testCopy[dep]; p1 != nil && p1 != dep { + split() + p.deps[i] = p1 + } + } + for i, imp := range p.imports { + if p1 := testCopy[imp]; p1 != nil && p1 != imp { + split() + p.imports[i] = p1 + } + } + } +} + +var coverIndex = 0 + +// isTestFile reports whether the source file is a set of tests and should therefore +// be excluded from coverage analysis. +func isTestFile(file string) bool { + // We don't cover tests, only the code they test. + return strings.HasSuffix(file, "_test.go") +} + +// declareCoverVars attaches the required cover variables names +// to the files, to be used when annotating the files. +func declareCoverVars(importPath string, files ...string) map[string]*CoverVar { + coverVars := make(map[string]*CoverVar) + for _, file := range files { + if isTestFile(file) { + continue + } + coverVars[file] = &CoverVar{ + File: filepath.Join(importPath, file), + Var: fmt.Sprintf("GoCover_%d", coverIndex), + } + coverIndex++ + } + return coverVars +} + // runTest is the action for running a test binary. func (b *builder) runTest(a *action) error { args := stringList(a.deps[0].target, testArgs) @@ -688,10 +898,23 @@ func (b *builder) runTest(a *action) error { go func() { done <- cmd.Wait() }() + Outer: select { case err = <-done: // ok case <-tick.C: + if signalTrace != nil { + // Send a quit signal in the hope that the program will print + // a stack trace and exit. Give it five seconds before resorting + // to Kill. + cmd.Process.Signal(signalTrace) + select { + case err = <-done: + fmt.Fprintf(&buf, "*** Test killed with %v: ran too long (%v).\n", signalTrace, testKillTimeout) + break Outer + case <-time.After(5 * time.Second): + } + } cmd.Process.Kill() err = <-done fmt.Fprintf(&buf, "*** Test killed: ran too long (%v).\n", testKillTimeout) @@ -704,7 +927,7 @@ func (b *builder) runTest(a *action) error { if testShowPass { a.testOutput.Write(out) } - fmt.Fprintf(a.testOutput, "ok \t%s\t%s\n", a.p.ImportPath, t) + fmt.Fprintf(a.testOutput, "ok \t%s\t%s%s\n", a.p.ImportPath, t, coveragePercentage(out)) return nil } @@ -720,6 +943,25 @@ func (b *builder) runTest(a *action) error { return nil } +// coveragePercentage returns the coverage results (if enabled) for the +// test. It uncovers the data by scanning the output from the test run. +func coveragePercentage(out []byte) string { + if !testCover { + return "" + } + // The string looks like + // test coverage for encoding/binary: 79.9% of statements + // Extract the piece from the percentage to the end of the line. + re := regexp.MustCompile(`coverage: (.*)\n`) + matches := re.FindSubmatch(out) + if matches == nil { + // Probably running "go test -cover" not "go test -cover fmt". + // The coverage output will appear in the output directly. + return "" + } + return fmt.Sprintf("\tcoverage: %s", matches[1]) +} + // cleanTest is the action for cleaning up after a test. func (b *builder) cleanTest(a *action) error { if buildWork { @@ -760,11 +1002,24 @@ func isTest(name, prefix string) bool { return !unicode.IsLower(rune) } +type coverInfo struct { + Package *Package + Vars map[string]*CoverVar +} + // writeTestmain writes the _testmain.go file for package p to // the file named out. -func writeTestmain(out string, p *Package) error { +func writeTestmain(out string, pmain, p *Package) error { + var cover []coverInfo + for _, cp := range pmain.imports { + if len(cp.coverVars) > 0 { + cover = append(cover, coverInfo{cp, cp.coverVars}) + } + } + t := &testFuncs{ Package: p, + Cover: cover, } for _, file := range p.TestGoFiles { if err := t.load(filepath.Join(p.Dir, file), "_test", &t.NeedTest); err != nil { @@ -797,6 +1052,31 @@ type testFuncs struct { Package *Package NeedTest bool NeedXtest bool + Cover []coverInfo +} + +func (t *testFuncs) CoverMode() string { + return testCoverMode +} + +func (t *testFuncs) CoverEnabled() bool { + return testCover +} + +// Covered returns a string describing which packages are being tested for coverage. +// If the covered package is the same as the tested package, it returns the empty string. +// Otherwise it is a comma-separated human-readable list of packages beginning with +// " in", ready for use in the coverage message. +func (t *testFuncs) Covered() string { + if testCoverPaths == nil { + return "" + } + return " in " + strings.Join(testCoverPaths, ", ") +} + +// Tested returns the name of the package being tested. +func (t *testFuncs) Tested() string { + return t.Package.Name } type testFunc struct { @@ -862,6 +1142,9 @@ import ( {{if .NeedXtest}} _xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}} {{end}} +{{range $i, $p := .Cover}} + _cover{{$i}} {{$p.Package.ImportPath | printf "%q"}} +{{end}} ) var tests = []testing.InternalTest{ @@ -896,7 +1179,54 @@ func matchString(pat, str string) (result bool, err error) { return matchRe.MatchString(str), nil } +{{if .CoverEnabled}} + +// Only updated by init functions, so no need for atomicity. +var ( + coverCounters = make(map[string][]uint32) + coverBlocks = make(map[string][]testing.CoverBlock) +) + +func init() { + {{range $i, $p := .Cover}} + {{range $file, $cover := $p.Vars}} + coverRegisterFile({{printf "%q" $cover.File}}, _cover{{$i}}.{{$cover.Var}}.Count[:], _cover{{$i}}.{{$cover.Var}}.Pos[:], _cover{{$i}}.{{$cover.Var}}.NumStmt[:]) + {{end}} + {{end}} +} + +func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) { + if 3*len(counter) != len(pos) || len(counter) != len(numStmts) { + panic("coverage: mismatched sizes") + } + if coverCounters[fileName] != nil { + // Already registered. + return + } + coverCounters[fileName] = counter + block := make([]testing.CoverBlock, len(counter)) + for i := range counter { + block[i] = testing.CoverBlock{ + Line0: pos[3*i+0], + Col0: uint16(pos[3*i+2]), + Line1: pos[3*i+1], + Col1: uint16(pos[3*i+2]>>16), + Stmts: numStmts[i], + } + } + coverBlocks[fileName] = block +} +{{end}} + func main() { +{{if .CoverEnabled}} + testing.RegisterCover(testing.Cover{ + Mode: {{printf "%q" .CoverMode}}, + Counters: coverCounters, + Blocks: coverBlocks, + CoveredPackages: {{printf "%q" .Covered}}, + }) +{{end}} testing.Main(matchString, tests, benchmarks, examples) } |