mirror of
https://github.com/golang/go
synced 2024-11-07 08:56:15 -07:00
cmd/cover: invoke go command to find packages
cmd/cover has always assumed that package x/y/z can be found in $GOPATH/src/x/y/z (roughly; by using go/build). That won't be true for too much longer. Instead, run the go command to find out where packages are. This will make 'go tool cover' safe for use with Go modules when they are in use in Go 1.11, and it continues to work with the existing Go toolchains too. An alternative would be to modify the cover profile format to record file names directly, but that would require also updating golang.org/x/tools/cover/profile and any tools that use it, which seems not worth the trouble. (That fork of the code does not contain any code to resolve package names to directory locations, so it's unaffected.) No new test here: cmd/go's TestCoverageFunc tests this code. Fixes #25318 (when people use Go 1.11 instead of vgo). Change-Id: I8769b15107aecf25f7aaf8692b724cf7d0f073d0 Reviewed-on: https://go-review.googlesource.com/122478 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
5a8b652c6e
commit
fd263ccefe
@ -8,13 +8,20 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,6 +43,11 @@ func funcOutput(profile, outputFile string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dirs, err := findPkgs(profiles)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var out *bufio.Writer
|
var out *bufio.Writer
|
||||||
if outputFile == "" {
|
if outputFile == "" {
|
||||||
out = bufio.NewWriter(os.Stdout)
|
out = bufio.NewWriter(os.Stdout)
|
||||||
@ -55,7 +67,7 @@ func funcOutput(profile, outputFile string) error {
|
|||||||
var total, covered int64
|
var total, covered int64
|
||||||
for _, profile := range profiles {
|
for _, profile := range profiles {
|
||||||
fn := profile.FileName
|
fn := profile.FileName
|
||||||
file, err := findFile(fn)
|
file, err := findFile(dirs, fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -154,14 +166,72 @@ func (f *FuncExtent) coverage(profile *Profile) (num, den int64) {
|
|||||||
return covered, total
|
return covered, total
|
||||||
}
|
}
|
||||||
|
|
||||||
// findFile finds the location of the named file in GOROOT, GOPATH etc.
|
// Pkg describes a single package, compatible with the JSON output from 'go list'; see 'go help list'.
|
||||||
func findFile(file string) (string, error) {
|
type Pkg struct {
|
||||||
dir, file := filepath.Split(file)
|
ImportPath string
|
||||||
pkg, err := build.Import(dir, ".", build.FindOnly)
|
Dir string
|
||||||
if err != nil {
|
Error *struct {
|
||||||
return "", fmt.Errorf("can't find %q: %v", file, err)
|
Err string
|
||||||
}
|
}
|
||||||
return filepath.Join(pkg.Dir, file), nil
|
}
|
||||||
|
|
||||||
|
func findPkgs(profiles []*Profile) (map[string]*Pkg, error) {
|
||||||
|
// Run go list to find the location of every package we care about.
|
||||||
|
pkgs := make(map[string]*Pkg)
|
||||||
|
var list []string
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if strings.HasPrefix(profile.FileName, ".") || filepath.IsAbs(profile.FileName) {
|
||||||
|
// Relative or absolute path.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkg := path.Dir(profile.FileName)
|
||||||
|
if _, ok := pkgs[pkg]; !ok {
|
||||||
|
pkgs[pkg] = nil
|
||||||
|
list = append(list, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: usually run as "go tool cover" in which case $GOROOT is set,
|
||||||
|
// in which case runtime.GOROOT() does exactly what we want.
|
||||||
|
goTool := filepath.Join(runtime.GOROOT(), "bin/go")
|
||||||
|
cmd := exec.Command(goTool, append([]string{"list", "-e", "-json"}, list...)...)
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
stdout, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot run go list: %v\n%s", err, stderr.Bytes())
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(bytes.NewReader(stdout))
|
||||||
|
for {
|
||||||
|
var pkg Pkg
|
||||||
|
err := dec.Decode(&pkg)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding go list json: %v", err)
|
||||||
|
}
|
||||||
|
pkgs[pkg.ImportPath] = &pkg
|
||||||
|
}
|
||||||
|
return pkgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findFile finds the location of the named file in GOROOT, GOPATH etc.
|
||||||
|
func findFile(pkgs map[string]*Pkg, file string) (string, error) {
|
||||||
|
if strings.HasPrefix(file, ".") || filepath.IsAbs(file) {
|
||||||
|
// Relative or absolute path.
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
pkg := pkgs[path.Dir(file)]
|
||||||
|
if pkg != nil {
|
||||||
|
if pkg.Dir != "" {
|
||||||
|
return filepath.Join(pkg.Dir, path.Base(file)), nil
|
||||||
|
}
|
||||||
|
if pkg.Error != nil {
|
||||||
|
return "", errors.New(pkg.Error.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("did not find package for %s in go list output", file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func percent(covered, total int64) float64 {
|
func percent(covered, total int64) float64 {
|
||||||
|
@ -29,12 +29,17 @@ func htmlOutput(profile, outfile string) error {
|
|||||||
|
|
||||||
var d templateData
|
var d templateData
|
||||||
|
|
||||||
|
dirs, err := findPkgs(profiles)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, profile := range profiles {
|
for _, profile := range profiles {
|
||||||
fn := profile.FileName
|
fn := profile.FileName
|
||||||
if profile.Mode == "set" {
|
if profile.Mode == "set" {
|
||||||
d.Set = true
|
d.Set = true
|
||||||
}
|
}
|
||||||
file, err := findFile(fn)
|
file, err := findFile(dirs, fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ type TestCover struct {
|
|||||||
Pkgs []*Package
|
Pkgs []*Package
|
||||||
Paths []string
|
Paths []string
|
||||||
Vars []coverInfo
|
Vars []coverInfo
|
||||||
DeclVars func(string, ...string) map[string]*CoverVar
|
DeclVars func(*Package, ...string) map[string]*CoverVar
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPackagesFor returns three packages:
|
// TestPackagesFor returns three packages:
|
||||||
@ -264,7 +264,7 @@ func TestPackagesFor(p *Package, cover *TestCover) (pmain, ptest, pxtest *Packag
|
|||||||
var coverFiles []string
|
var coverFiles []string
|
||||||
coverFiles = append(coverFiles, ptest.GoFiles...)
|
coverFiles = append(coverFiles, ptest.GoFiles...)
|
||||||
coverFiles = append(coverFiles, ptest.CgoFiles...)
|
coverFiles = append(coverFiles, ptest.CgoFiles...)
|
||||||
ptest.Internal.CoverVars = cover.DeclVars(ptest.ImportPath, coverFiles...)
|
ptest.Internal.CoverVars = cover.DeclVars(ptest, coverFiles...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cp := range pmain.Internal.Imports {
|
for _, cp := range pmain.Internal.Imports {
|
||||||
|
@ -698,7 +698,7 @@ func runTest(cmd *base.Command, args []string) {
|
|||||||
coverFiles = append(coverFiles, p.GoFiles...)
|
coverFiles = append(coverFiles, p.GoFiles...)
|
||||||
coverFiles = append(coverFiles, p.CgoFiles...)
|
coverFiles = append(coverFiles, p.CgoFiles...)
|
||||||
coverFiles = append(coverFiles, p.TestGoFiles...)
|
coverFiles = append(coverFiles, p.TestGoFiles...)
|
||||||
p.Internal.CoverVars = declareCoverVars(p.ImportPath, coverFiles...)
|
p.Internal.CoverVars = declareCoverVars(p, coverFiles...)
|
||||||
if testCover && testCoverMode == "atomic" {
|
if testCover && testCoverMode == "atomic" {
|
||||||
ensureImport(p, "sync/atomic")
|
ensureImport(p, "sync/atomic")
|
||||||
}
|
}
|
||||||
@ -966,7 +966,7 @@ func isTestFile(file string) bool {
|
|||||||
|
|
||||||
// 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(importPath string, files ...string) map[string]*load.CoverVar {
|
func declareCoverVars(p *load.Package, files ...string) map[string]*load.CoverVar {
|
||||||
coverVars := make(map[string]*load.CoverVar)
|
coverVars := make(map[string]*load.CoverVar)
|
||||||
coverIndex := 0
|
coverIndex := 0
|
||||||
// We create the cover counters as new top-level variables in the package.
|
// We create the cover counters as new top-level variables in the package.
|
||||||
@ -975,14 +975,25 @@ func declareCoverVars(importPath string, files ...string) map[string]*load.Cover
|
|||||||
// so we append 12 hex digits from the SHA-256 of the import path.
|
// so we append 12 hex digits from the SHA-256 of the import path.
|
||||||
// The point is only to avoid accidents, not to defeat users determined to
|
// The point is only to avoid accidents, not to defeat users determined to
|
||||||
// break things.
|
// break things.
|
||||||
sum := sha256.Sum256([]byte(importPath))
|
sum := sha256.Sum256([]byte(p.ImportPath))
|
||||||
h := fmt.Sprintf("%x", sum[:6])
|
h := fmt.Sprintf("%x", sum[:6])
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if isTestFile(file) {
|
if isTestFile(file) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// For a package that is "local" (imported via ./ import or command line, outside GOPATH),
|
||||||
|
// we record the full path to the file name.
|
||||||
|
// Otherwise we record the import path, then a forward slash, then the file name.
|
||||||
|
// This makes profiles within GOPATH file system-independent.
|
||||||
|
// These names appear in the cmd/cover HTML interface.
|
||||||
|
var longFile string
|
||||||
|
if p.Internal.Local {
|
||||||
|
longFile = filepath.Join(p.Dir, file)
|
||||||
|
} else {
|
||||||
|
longFile = path.Join(p.ImportPath, file)
|
||||||
|
}
|
||||||
coverVars[file] = &load.CoverVar{
|
coverVars[file] = &load.CoverVar{
|
||||||
File: filepath.Join(importPath, file),
|
File: longFile,
|
||||||
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
|
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
|
||||||
}
|
}
|
||||||
coverIndex++
|
coverIndex++
|
||||||
|
Loading…
Reference in New Issue
Block a user