mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00: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
4 changed files with 124 additions and 43 deletions
|
|
@ -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:
|
||||||
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,
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
// 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 (
|
||||||
{{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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{{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:
|
||||||
-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{
|
||||||
{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{
|
||||||
{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) {
|
||||||
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) {
|
||||||
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++
|
||||||
|
|
|
||||||
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.
|
// 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() {
|
||||||
}
|
}
|
||||||
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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue