mirror of
https://github.com/golang/go
synced 2024-11-22 22:10:03 -07:00
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
This commit is contained in:
parent
6154ae8e24
commit
8e8b8b85c2
@ -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++
|
||||
|
91
src/pkg/testing/cover.go
Normal file
91
src/pkg/testing/cover.go
Normal file
@ -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))
|
||||
}
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user