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:
-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)
}
`))
......@@ -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++
......
// 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 (
// 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.
......
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