Commit 8e8b8b85 authored by Rob Pike's avatar Rob Pike

cmd/go: write coverage to file, add percentage statistic

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
parent 6154ae8e
...@@ -126,8 +126,6 @@ control the execution of any test: ...@@ -126,8 +126,6 @@ control the execution of any test:
-cover set,count,atomic -cover set,count,atomic
TODO: This feature is not yet fully implemented. 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. Set the mode for coverage analysis for the package[s] being tested.
The default is to do none. The default is to do none.
The values: The values:
...@@ -135,6 +133,11 @@ control the execution of any test: ...@@ -135,6 +133,11 @@ control the execution of any test:
count: integer: how many times does this statement execute? count: integer: how many times does this statement execute?
atomic: integer: like count, but correct in multithreaded tests; atomic: integer: like count, but correct in multithreaded tests;
significantly more expensive. 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 -cpu 1,2,4
Specify a list of GOMAXPROCS values for which the tests or 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, ...@@ -534,7 +537,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
if testCover != "" { if testCover != "" {
p.coverMode = 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 { if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), p, p.coverVars); err != nil {
...@@ -678,11 +681,11 @@ var coverIndex = 0 ...@@ -678,11 +681,11 @@ var coverIndex = 0
// declareCoverVars attaches the required cover variables names // declareCoverVars attaches the required cover variables names
// to the files, to be used when annotating the files. // 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) coverVars := make(map[string]*CoverVar)
for _, file := range files { for _, file := range files {
coverVars[file] = &CoverVar{ coverVars[file] = &CoverVar{
File: file, File: filepath.Join(importPath, file),
Var: fmt.Sprintf("GoCover_%d", coverIndex), Var: fmt.Sprintf("GoCover_%d", coverIndex),
} }
coverIndex++ coverIndex++
...@@ -934,9 +937,6 @@ import ( ...@@ -934,9 +937,6 @@ import (
{{if .NeedXtest}} {{if .NeedXtest}}
_xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}} _xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}} {{end}}
{{if .CoverEnabled}}
_fmt "fmt"
{{end}}
) )
var tests = []testing.InternalTest{ var tests = []testing.InternalTest{
...@@ -972,66 +972,46 @@ func matchString(pat, str string) (result bool, err error) { ...@@ -972,66 +972,46 @@ func matchString(pat, str string) (result bool, err error) {
} }
{{if .CoverEnabled}} {{if .CoverEnabled}}
type coverBlock struct {
line0 uint32
col0 uint16
line1 uint32
col1 uint16
}
// Only updated by init functions, so no need for atomicity. // Only updated by init functions, so no need for atomicity.
var ( var (
coverCounters = make(map[string][]uint32) coverCounters = make(map[string][]uint32)
coverBlocks = make(map[string][]coverBlock) coverBlocks = make(map[string][]testing.CoverBlock)
) )
func init() { func init() {
{{range $file, $cover := .CoverVars}} {{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}} {{end}}
} }
func coverRegisterFile(fileName string, counter []uint32, pos ...uint32) { func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) {
if 3*len(counter) != len(pos) { if 3*len(counter) != len(pos) || len(counter) != len(numStmts) {
panic("coverage: mismatched sizes") panic("coverage: mismatched sizes")
} }
if coverCounters[fileName] != nil { if coverCounters[fileName] != nil {
panic("coverage: duplicate counter array for " + fileName) panic("coverage: duplicate counter array for " + fileName)
} }
coverCounters[fileName] = counter coverCounters[fileName] = counter
block := make([]coverBlock, len(counter)) block := make([]testing.CoverBlock, len(counter))
for i := range counter { for i := range counter {
block[i] = coverBlock{ block[i] = testing.CoverBlock{
line0: pos[3*i+0], Line0: pos[3*i+0],
col0: uint16(pos[3*i+2]), Col0: uint16(pos[3*i+2]),
line1: pos[3*i+1], Line1: pos[3*i+1],
col1: uint16(pos[3*i+2]>>16), Col1: uint16(pos[3*i+2]>>16),
Stmts: numStmts[i],
} }
} }
coverBlocks[fileName] = block 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}} {{end}}
func main() { func main() {
testing.Main(matchString, tests, benchmarks, examples)
{{if .CoverEnabled}} {{if .CoverEnabled}}
coverDump() testing.RegisterCover(coverCounters, coverBlocks)
{{end}} {{end}}
testing.Main(matchString, tests, benchmarks, examples)
} }
`)) `))
...@@ -27,6 +27,8 @@ var usageMessage = `Usage of go test: ...@@ -27,6 +27,8 @@ var usageMessage = `Usage of go test:
-bench="": passes -test.bench to test -bench="": passes -test.bench to test
-benchmem=false: print memory allocation statistics for benchmarks -benchmem=false: print memory allocation statistics for benchmarks
-benchtime=1s: passes -test.benchtime to test -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 -cpu="": passes -test.cpu to test
-cpuprofile="": passes -test.cpuprofile to test -cpuprofile="": passes -test.cpuprofile to test
-memprofile="": passes -test.memprofile to test -memprofile="": passes -test.memprofile to test
...@@ -63,7 +65,6 @@ var testFlagDefn = []*testFlagSpec{ ...@@ -63,7 +65,6 @@ var testFlagDefn = []*testFlagSpec{
{name: "c", boolVar: &testC}, {name: "c", boolVar: &testC},
{name: "file", multiOK: true}, {name: "file", multiOK: true},
{name: "i", boolVar: &testI}, {name: "i", boolVar: &testI},
{name: "cover"},
// build flags. // build flags.
{name: "a", boolVar: &buildA}, {name: "a", boolVar: &buildA},
...@@ -82,6 +83,8 @@ var testFlagDefn = []*testFlagSpec{ ...@@ -82,6 +83,8 @@ var testFlagDefn = []*testFlagSpec{
{name: "bench", passToTest: true}, {name: "bench", passToTest: true},
{name: "benchmem", boolVar: new(bool), passToTest: true}, {name: "benchmem", boolVar: new(bool), passToTest: true},
{name: "benchtime", passToTest: true}, {name: "benchtime", passToTest: true},
{name: "cover", passToTest: true},
{name: "coverprofile", passToTest: true},
{name: "cpu", passToTest: true}, {name: "cpu", passToTest: true},
{name: "cpuprofile", passToTest: true}, {name: "cpuprofile", passToTest: true},
{name: "memprofile", passToTest: true}, {name: "memprofile", passToTest: true},
...@@ -171,7 +174,7 @@ func testFlags(args []string) (packageNames, passToTest []string) { ...@@ -171,7 +174,7 @@ func testFlags(args []string) (packageNames, passToTest []string) {
testBench = true testBench = true
case "timeout": case "timeout":
testTimeout = value testTimeout = value
case "blockprofile", "cpuprofile", "memprofile": case "blockprofile", "coverprofile", "cpuprofile", "memprofile":
testProfile = true testProfile = true
case "outputdir": case "outputdir":
outputDir = value outputDir = value
...@@ -182,6 +185,8 @@ func testFlags(args []string) (packageNames, passToTest []string) { ...@@ -182,6 +185,8 @@ func testFlags(args []string) (packageNames, passToTest []string) {
default: default:
fatalf("invalid flag argument for -cover: %q", value) 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 { if extraWord {
i++ i++
......
// 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))
}
...@@ -122,6 +122,8 @@ var ( ...@@ -122,6 +122,8 @@ var (
// Report as tests are run; default is silent for success. // Report as tests are run; default is silent for success.
chatty = flag.Bool("test.v", false, "verbose: print additional output") 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") 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") 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") memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate")
...@@ -518,6 +520,9 @@ func after() { ...@@ -518,6 +520,9 @@ func after() {
} }
f.Close() f.Close()
} }
if *cover != "" {
coverReport()
}
} }
// toOutputDir returns the file name relocated, if required, to outputDir. // toOutputDir returns the file name relocated, if required, to outputDir.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment