}
var gofiles, cfiles, sfiles, objects, cgoObjects []string
- gofiles = append(gofiles, a.p.GoFiles...)
+
+ // If we're doing coverage, preprocess the .go files and put them in the work directory
+ if a.p.coverMode != "" {
+ for _, file := range a.p.GoFiles {
+ sourceFile := filepath.Join(a.p.Dir, file)
+ cover := a.p.coverVars[file]
+ if cover == nil {
+ // Not covering this file
+ gofiles = append(gofiles, file)
+ continue
+ }
+ coverFile := filepath.Join(obj, file)
+ if err := b.cover(a, coverFile, sourceFile, 0666, cover.Count, cover.Pos); err != nil {
+ return err
+ }
+ gofiles = append(gofiles, coverFile)
+ }
+ } else {
+ gofiles = append(gofiles, a.p.GoFiles...)
+ }
cfiles = append(cfiles, a.p.CFiles...)
sfiles = append(sfiles, a.p.SFiles...)
return nil
}
+// cover runs, in effect,
+// go tool cover -mode=b.coverMode -count="count" -pos="pos" src.go >dst.go
+func (b *builder) cover(a *action, dst, src string, perm os.FileMode, count, pos string) error {
+ out, err := b.runOut(a.objdir, "cover "+a.p.ImportPath, nil, tool("cover"), "-mode="+a.p.coverMode, "-count="+count, "-pos="+pos, src)
+ if err != nil {
+ return err
+ }
+ // Output is processed source code. Write it to destination.
+ return ioutil.WriteFile(dst, out, perm)
+}
+
var objectMagic = [][]byte{
{'!', '<', 'a', 'r', 'c', 'h', '>', '\n'}, // Package archive
{'\x7F', 'E', 'L', 'F'}, // ELF
if -test.blockprofile is set without this flag, all blocking events
are recorded, equivalent to -test.blockprofilerate=1.
+ -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:
+ set: boolean: does this statement execute?
+ count: integer: how many times does this statement execute?
+ atomic: integer: like count, but correct in multithreaded tests;
+ significantly more expensive.
+
-cpu 1,2,4
Specify a list of GOMAXPROCS values for which the tests or
benchmarks should be executed. The default is the current value
deps []*Package
gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths
sfiles []string
- allgofiles []string // gofiles + IgnoredGoFiles, absolute paths
- target string // installed file for this package (may be executable)
- fake bool // synthesized package
- forceBuild bool // this package must be rebuilt
- forceLibrary bool // this package is a library (even if named "main")
- local bool // imported via local path (./ or ../)
- localPrefix string // interpret ./ and ../ imports relative to this prefix
- exeName string // desired name for temporary executable
+ allgofiles []string // gofiles + IgnoredGoFiles, absolute paths
+ target string // installed file for this package (may be executable)
+ fake bool // synthesized package
+ forceBuild bool // this package must be rebuilt
+ forceLibrary bool // this package is a library (even if named "main")
+ local bool // imported via local path (./ or ../)
+ localPrefix string // interpret ./ and ../ imports relative to this prefix
+ exeName string // desired name for temporary executable
+ coverMode string // preprocess Go source files with the coverage tool in this mode
+ coverVars map[string]*CoverVar // variables created by coverage analysis
+}
+
+// CoverVar holds the name of the generated coverage variables targeting the named file.
+type CoverVar struct {
+ File string // local file name
+ Count string // name of count array
+ Pos string // name of position array
}
func (p *Package) copyBuild(pp *build.Package) {
// isGoTool is the list of directories for Go programs that are installed in
// $GOROOT/pkg/tool.
var isGoTool = map[string]bool{
- "cmd/api": true,
- "cmd/cgo": true,
- "cmd/fix": true,
- "cmd/yacc": true,
- "code.google.com/p/go.tools/cmd/vet": true,
+ "cmd/api": true,
+ "cmd/cgo": true,
+ "cmd/fix": true,
+ "cmd/yacc": true,
+ "code.google.com/p/go.tools/cmd/cover": true,
+ "code.google.com/p/go.tools/cmd/vet": true,
}
// expandScanner expands a scanner.List error into all the errors in the list.
if -test.blockprofile is set without this flag, all blocking events
are recorded, equivalent to -test.blockprofilerate=1.
+ -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:
+ set: boolean: does this statement execute?
+ count: integer: how many times does this statement execute?
+ atomic: integer: like count, but correct in multithreaded tests;
+ significantly more expensive.
+
-cpu 1,2,4
Specify a list of GOMAXPROCS values for which the tests or
benchmarks should be executed. The default is the current value
var (
testC bool // -c flag
+ testCover string // -cover flag
testProfile bool // some profiling flag
testI bool // -i flag
testV bool // -v flag
if err := b.mkdir(ptestDir); err != nil {
return nil, nil, nil, err
}
- if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), p); err != nil {
+
+ if testCover != "" {
+ p.coverMode = testCover
+ p.coverVars = declareCoverVars(p.GoFiles...)
+ }
+
+ if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), p, p.coverVars); err != nil {
return nil, nil, nil, err
}
// Test package.
- if len(p.TestGoFiles) > 0 {
+ if len(p.TestGoFiles) > 0 || testCover != "" {
ptest = new(Package)
*ptest = *p
ptest.GoFiles = nil
return pmainAction, runAction, printAction, nil
}
+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 {
+ coverVars := make(map[string]*CoverVar)
+ for _, file := range files {
+ coverVars[file] = &CoverVar{
+ File: file,
+ Count: fmt.Sprintf("GoCoverCount_%d", coverIndex),
+ Pos: fmt.Sprintf("GoCoverPos_%d", coverIndex),
+ }
+ coverIndex++
+ }
+ return coverVars
+}
+
// runTest is the action for running a test binary.
func (b *builder) runTest(a *action) error {
args := stringList(a.deps[0].target, testArgs)
// writeTestmain writes the _testmain.go file for package p to
// the file named out.
-func writeTestmain(out string, p *Package) error {
+func writeTestmain(out string, p *Package, coverVars map[string]*CoverVar) error {
t := &testFuncs{
- Package: p,
+ Package: p,
+ CoverVars: coverVars,
}
for _, file := range p.TestGoFiles {
if err := t.load(filepath.Join(p.Dir, file), "_test", &t.NeedTest); err != nil {
Package *Package
NeedTest bool
NeedXtest bool
+ CoverVars map[string]*CoverVar
+}
+
+func (t *testFuncs) CoverEnabled() bool {
+ return testCover != ""
}
type testFunc struct {
"regexp"
"testing"
-{{if .NeedTest}}
+{{if or .CoverEnabled .NeedTest}}
_test {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .NeedXtest}}
_xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
+{{if .CoverEnabled}}
+ _fmt "fmt"
+{{end}}
)
var tests = []testing.InternalTest{
return matchRe.MatchString(str), nil
}
+{{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)
+)
+
+func init() {
+ {{range $file, $cover := .CoverVars}}
+ coverRegisterFile({{printf "%q" $file}}, _test.{{$cover.Count}}[:], _test.{{$cover.Pos}}[:]...)
+ {{end}}
+}
+
+func coverRegisterFile(fileName string, counter []uint32, pos ...uint32) {
+ if 3*len(counter) != len(pos) {
+ panic("coverage: mismatched sizes")
+ }
+ if coverCounters[fileName] != nil {
+ panic("coverage: duplicate counter array for " + fileName)
+ }
+ coverCounters[fileName] = counter
+ block := make([]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),
+ }
+ }
+ 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()
+{{end}}
}
`))
{name: "c", boolVar: &testC},
{name: "file", multiOK: true},
{name: "i", boolVar: &testI},
+ {name: "cover"},
// build flags.
{name: "a", boolVar: &buildA},
testTimeout = value
case "blockprofile", "cpuprofile", "memprofile":
testProfile = true
+ case "cover":
+ switch value {
+ case "set", "count", "atomic":
+ testCover = value
+ default:
+ fatalf("invalid flag argument for -cover: %q", value)
+ }
}
if extraWord {
i++
func isInGoToolsRepo(toolName string) bool {
switch toolName {
- case "vet":
+ case "cover", "vet":
return true
}
return false