]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: write coverage to file, add percentage statistic
authorRob Pike <r@golang.org>
Tue, 18 Jun 2013 21:18:25 +0000 (14:18 -0700)
committerRob Pike <r@golang.org>
Tue, 18 Jun 2013 21:18:25 +0000 (14:18 -0700)
Move the data dumper to the testing package, where it has access
to file I/O.
Print a percentage value at the end of the run.

R=rsc, adg
CC=golang-dev
https://golang.org/cl/10264045

src/cmd/go/test.go
src/cmd/go/testflag.go
src/pkg/testing/cover.go [new file with mode: 0644]
src/pkg/testing/testing.go

index f33e56e0f3b5d0da156203991f5a478969b5dc1b..f20e1efad3230cf93f9ab5d16b290bab9edfcf6f 100644 (file)
@@ -126,8 +126,6 @@ control the execution of any test:
 
        -cover set,count,atomic
            TODO: This feature is not yet fully implemented.
-           TODO: Must run with -v to see output.
-           TODO: Need control over output format,
            Set the mode for coverage analysis for the package[s] being tested.
            The default is to do none.
            The values:
@@ -135,6 +133,11 @@ control the execution of any test:
                count: integer: how many times does this statement execute?
                atomic: integer: like count, but correct in multithreaded tests;
                        significantly more expensive.
+           Sets -v. TODO: This will change.
+
+       -coverprofile cover.out
+           Write a coverage profile to the specified file after all tests
+           have passed.
 
        -cpu 1,2,4
            Specify a list of GOMAXPROCS values for which the tests or
@@ -534,7 +537,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
 
        if testCover != "" {
                p.coverMode = testCover
-               p.coverVars = declareCoverVars(p.GoFiles...)
+               p.coverVars = declareCoverVars(p.ImportPath, p.GoFiles...)
        }
 
        if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), p, p.coverVars); err != nil {
@@ -678,11 +681,11 @@ var coverIndex = 0
 
 // declareCoverVars attaches the required cover variables names
 // to the files, to be used when annotating the files.
-func declareCoverVars(files ...string) map[string]*CoverVar {
+func declareCoverVars(importPath string, files ...string) map[string]*CoverVar {
        coverVars := make(map[string]*CoverVar)
        for _, file := range files {
                coverVars[file] = &CoverVar{
-                       File: file,
+                       File: filepath.Join(importPath, file),
                        Var:  fmt.Sprintf("GoCover_%d", coverIndex),
                }
                coverIndex++
@@ -934,9 +937,6 @@ import (
 {{if .NeedXtest}}
        _xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
 {{end}}
-{{if .CoverEnabled}}
-       _fmt "fmt"
-{{end}}
 )
 
 var tests = []testing.InternalTest{
@@ -972,66 +972,46 @@ func matchString(pat, str string) (result bool, err error) {
 }
 
 {{if .CoverEnabled}}
-type coverBlock struct {
-       line0 uint32
-       col0 uint16
-       line1 uint32
-       col1 uint16
-}
 
 // Only updated by init functions, so no need for atomicity.
 var (
        coverCounters = make(map[string][]uint32)
-       coverBlocks = make(map[string][]coverBlock)
+       coverBlocks = make(map[string][]testing.CoverBlock)
 )
 
 func init() {
        {{range $file, $cover := .CoverVars}}
-       coverRegisterFile({{printf "%q" $file}}, _test.{{$cover.Var}}.Count[:], _test.{{$cover.Var}}.Pos[:]...)
+       coverRegisterFile({{printf "%q" $cover.File}}, _test.{{$cover.Var}}.Count[:], _test.{{$cover.Var}}.Pos[:], _test.{{$cover.Var}}.NumStmt[:])
        {{end}}
 }
 
-func coverRegisterFile(fileName string, counter []uint32, pos ...uint32) {
-       if 3*len(counter) != len(pos) {
+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 {
                panic("coverage: duplicate counter array for " + fileName)
        }
        coverCounters[fileName] = counter
-       block := make([]coverBlock, len(counter))
+       block := make([]testing.CoverBlock, len(counter))
        for i := range counter {
-               block[i] = coverBlock{
-                       line0: pos[3*i+0],
-                       col0: uint16(pos[3*i+2]),
-                       line1: pos[3*i+1],
-                       col1: uint16(pos[3*i+2]>>16),
+               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
 }
-
-func coverDump() {
-       for name, counts := range coverCounters {
-               blocks := coverBlocks[name]
-               for i, count := range counts {
-                       _, err := _fmt.Printf("%s:%d.%d,%d.%d %d\n", name,
-                               blocks[i].line0, blocks[i].col0,
-                               blocks[i].line1, blocks[i].col1,
-                               count)
-                       if err != nil {
-                               panic(err)
-                       }
-               }
-       }
-}
 {{end}}
 
 func main() {
-       testing.Main(matchString, tests, benchmarks, examples)
 {{if .CoverEnabled}}
-       coverDump()
+       testing.RegisterCover(coverCounters, coverBlocks)
 {{end}}
+       testing.Main(matchString, tests, benchmarks, examples)
 }
 
 `))
index 7326b9076225684598ce1d83973e13c335132cb6..cd90a2f174c0d294755bf51afc8b8e534fd42d0f 100644 (file)
@@ -27,6 +27,8 @@ var usageMessage = `Usage of go test:
   -bench="": passes -test.bench to test
   -benchmem=false: print memory allocation statistics for benchmarks
   -benchtime=1s: passes -test.benchtime to test
+  -cover="": passes -test.cover to test
+  -coverprofile="": passes -test.coverprofile to test
   -cpu="": passes -test.cpu to test
   -cpuprofile="": passes -test.cpuprofile to test
   -memprofile="": passes -test.memprofile to test
@@ -63,7 +65,6 @@ var testFlagDefn = []*testFlagSpec{
        {name: "c", boolVar: &testC},
        {name: "file", multiOK: true},
        {name: "i", boolVar: &testI},
-       {name: "cover"},
 
        // build flags.
        {name: "a", boolVar: &buildA},
@@ -82,6 +83,8 @@ var testFlagDefn = []*testFlagSpec{
        {name: "bench", passToTest: true},
        {name: "benchmem", boolVar: new(bool), passToTest: true},
        {name: "benchtime", passToTest: true},
+       {name: "cover", passToTest: true},
+       {name: "coverprofile", passToTest: true},
        {name: "cpu", passToTest: true},
        {name: "cpuprofile", passToTest: true},
        {name: "memprofile", passToTest: true},
@@ -171,7 +174,7 @@ func testFlags(args []string) (packageNames, passToTest []string) {
                        testBench = true
                case "timeout":
                        testTimeout = value
-               case "blockprofile", "cpuprofile", "memprofile":
+               case "blockprofile", "coverprofile", "cpuprofile", "memprofile":
                        testProfile = true
                case "outputdir":
                        outputDir = value
@@ -182,6 +185,8 @@ func testFlags(args []string) (packageNames, passToTest []string) {
                        default:
                                fatalf("invalid flag argument for -cover: %q", value)
                        }
+                       // Guarantee we see the coverage statistics. Doesn't turn -v on generally; tricky. TODO?
+                       testV = true
                }
                if extraWord {
                        i++
diff --git a/src/pkg/testing/cover.go b/src/pkg/testing/cover.go
new file mode 100644 (file)
index 0000000..2419870
--- /dev/null
@@ -0,0 +1,91 @@
+// Copyright 2013 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.
+
+// Support for test coverage.
+
+package testing
+
+import (
+       "fmt"
+       "os"
+)
+
+// CoverBlock records the coverage data for a single basic block.
+// NOTE: This struct is internal to the testing infrastructure and may change.
+// It is not covered (yet) by the Go 1 compatibility guidelines.
+type CoverBlock struct {
+       Line0 uint32
+       Col0  uint16
+       Line1 uint32
+       Col1  uint16
+       Stmts uint16
+}
+
+var (
+       coverCounters map[string][]uint32
+       coverBlocks   map[string][]CoverBlock
+)
+
+// RegisterCover records the coverage data accumulators for the tests.
+// NOTE: This struct is internal to the testing infrastructure and may change.
+// It is not covered (yet) by the Go 1 compatibility guidelines.
+func RegisterCover(c map[string][]uint32, b map[string][]CoverBlock) {
+       coverCounters = c
+       coverBlocks = b
+}
+
+// mustBeNil checks the error and, if present, reports it and exits.
+func mustBeNil(err error) {
+       if err != nil {
+               fmt.Fprintf(os.Stderr, "testing: %s\n", err)
+               os.Exit(2)
+       }
+}
+
+// coverReport reports the coverage percentage and writes a coverage profile if requested.
+func coverReport() {
+       var f *os.File
+       var err error
+       if *coverProfile != "" {
+               f, err = os.Create(toOutputDir(*coverProfile))
+               mustBeNil(err)
+               defer func() { mustBeNil(f.Close()) }()
+       }
+
+       var active, total int64
+       packageName := ""
+       for name, counts := range coverCounters {
+               if packageName == "" {
+                       // Package name ends at last slash.
+                       for i, c := range name {
+                               if c == '/' {
+                                       packageName = name[:i]
+                               }
+                       }
+               }
+               blocks := coverBlocks[name]
+               for i, count := range counts {
+                       stmts := int64(blocks[i].Stmts)
+                       total += stmts
+                       if count > 0 {
+                               active += stmts
+                       }
+                       if f != nil {
+                               _, err := fmt.Fprintf(f, "%s:%d.%d,%d.%d %d %d\n", name,
+                                       blocks[i].Line0, blocks[i].Col0,
+                                       blocks[i].Line1, blocks[i].Col1,
+                                       stmts,
+                                       count)
+                               mustBeNil(err)
+                       }
+               }
+       }
+       if total == 0 {
+               total = 1
+       }
+       if packageName == "" {
+               packageName = "package"
+       }
+       fmt.Printf("test coverage for %s: %.1f%% of statements\n", packageName, 100*float64(active)/float64(total))
+}
index 7ef47b6875ae17e40511ffde7f4b1a9d91507f5e..ef8c77b4976dfa63fbe1938edbd18a0e6d6e78ce 100644 (file)
@@ -122,6 +122,8 @@ var (
 
        // Report as tests are run; default is silent for success.
        chatty           = flag.Bool("test.v", false, "verbose: print additional output")
+       cover            = flag.String("test.cover", "", "cover mode: set, count, atomic; default is none")
+       coverProfile     = flag.String("test.coverprofile", "", "write a coveraage profile to the named file after execution")
        match            = flag.String("test.run", "", "regular expression to select tests and examples to run")
        memProfile       = flag.String("test.memprofile", "", "write a memory profile to the named file after execution")
        memProfileRate   = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate")
@@ -518,6 +520,9 @@ func after() {
                }
                f.Close()
        }
+       if *cover != "" {
+               coverReport()
+       }
 }
 
 // toOutputDir returns the file name relocated, if required, to outputDir.