1
0
mirror of https://github.com/golang/go synced 2024-11-23 00:50:05 -07:00

cmd/go: add go list -test to describe test binaries

Tools should be able to ask cmd/go about the dependency
graph for test binaries instead of reinventing it themselves.
Allow them to do so, with the new list -test flag.

This also fixes and tests for a bug introduced in CL 104315
that was not properly splitting dependencies on the path
between package main and the package being tested.

Change-Id: I29eb454c82893f5ee70252aaaecd9fa376eaf3c8
Reviewed-on: https://go-review.googlesource.com/107916
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
This commit is contained in:
Russ Cox 2018-04-18 14:42:47 -04:00
parent c5f0104daf
commit 296e676575
7 changed files with 618 additions and 439 deletions

View File

@ -697,6 +697,18 @@
// a non-nil Error field; other information may or may not be missing
// (zeroed).
//
// The -test flag causes list to add to its output test binaries for the
// named packages that have tests, to make information about test
// binary construction available to source code analysis tools.
// The reported import path for a test binary is the import path of
// the package followed by a ".test" suffix, as in "math/rand.test".
// When building a test, it is sometimes necessary to rebuild certain
// dependencies specially for that test (most commonly the tested
// package itself). The reported import path of a package recompiled
// for a particular test binary is followed by a space and the name of
// the test binary in brackets, as in "math/rand [math/rand.test]"
// or "regexp [sort.test]".
//
// For more about build flags, see 'go help build'.
//
// For more about specifying packages, see 'go help packages'.

View File

@ -1919,6 +1919,35 @@ func TestGoListDeps(t *testing.T) {
}
}
func TestGoListTest(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
tg.parallel()
tg.makeTempdir()
tg.setenv("GOCACHE", tg.tempdir)
tg.run("list", "-test", "-deps", "sort")
tg.grepStdout(`^sort.test$`, "missing test main")
tg.grepStdout(`^sort$`, "missing real sort")
tg.grepStdout(`^sort \[sort.test\]$`, "missing test copy of sort")
tg.grepStdout(`^testing \[sort.test\]$`, "missing test copy of testing")
tg.grepStdoutNot(`^testing$`, "unexpected real copy of testing")
tg.run("list", "-test", "sort")
tg.grepStdout(`^sort.test$`, "missing test main")
tg.grepStdout(`^sort$`, "missing real sort")
tg.grepStdoutNot(`^sort \[sort.test\]$`, "unexpected test copy of sort")
tg.grepStdoutNot(`^testing \[sort.test\]$`, "unexpected test copy of testing")
tg.grepStdoutNot(`^testing$`, "unexpected real copy of testing")
tg.run("list", "-test", "cmd/dist", "cmd/doc")
tg.grepStdout(`^cmd/dist$`, "missing cmd/dist")
tg.grepStdout(`^cmd/doc$`, "missing cmd/doc")
tg.grepStdout(`^cmd/doc\.test$`, "missing cmd/doc test")
tg.grepStdoutNot(`^cmd/dist\.test$`, "unexpected cmd/dist test")
tg.grepStdoutNot(`^testing`, "unexpected testing")
}
// Issue 4096. Validate the output of unsuccessful go install foo/quxx.
func TestUnsuccessfulGoInstallShouldMentionMissingPackage(t *testing.T) {
tg := testgo(t)

View File

@ -7,13 +7,16 @@ package list
import (
"bufio"
"bytes"
"encoding/json"
"io"
"os"
"sort"
"strings"
"text/template"
"cmd/go/internal/base"
"cmd/go/internal/cache"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/work"
@ -139,6 +142,18 @@ printing. Erroneous packages will have a non-empty ImportPath and
a non-nil Error field; other information may or may not be missing
(zeroed).
The -test flag causes list to report not only the named packages
but also their test binaries (for packages with tests), to convey to
source code analysis tools exactly how test binaries are constructed.
The reported import path for a test binary is the import path of
the package followed by a ".test" suffix, as in "math/rand.test".
When building a test, it is sometimes necessary to rebuild certain
dependencies specially for that test (most commonly the tested
package itself). The reported import path of a package recompiled
for a particular test binary is followed by a space and the name of
the test binary in brackets, as in "math/rand [math/rand.test]"
or "regexp [sort.test]".
For more about build flags, see 'go help build'.
For more about specifying packages, see 'go help packages'.
@ -154,6 +169,7 @@ var listDeps = CmdList.Flag.Bool("deps", false, "")
var listE = CmdList.Flag.Bool("e", false, "")
var listFmt = CmdList.Flag.String("f", "{{.ImportPath}}", "")
var listJson = CmdList.Flag.Bool("json", false, "")
var listTest = CmdList.Flag.Bool("test", false, "")
var nl = []byte{'\n'}
func runList(cmd *base.Command, args []string) {
@ -206,12 +222,60 @@ func runList(cmd *base.Command, args []string) {
pkgs = load.Packages(args)
}
if *listTest {
c := cache.Default()
if c == nil {
base.Fatalf("go list -test requires build cache")
}
// Add test binaries to packages to be listed.
for _, p := range pkgs {
if p.Error != nil {
continue
}
if len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 {
pmain, _, _, err := load.TestPackagesFor(p, nil)
if err != nil {
if !*listE {
base.Errorf("can't load test package: %s", err)
continue
}
pmain = &load.Package{
PackagePublic: load.PackagePublic{
ImportPath: p.ImportPath + ".test",
Error: &load.PackageError{Err: err.Error()},
},
}
}
pkgs = append(pkgs, pmain)
data := *pmain.Internal.TestmainGo
h := cache.NewHash("testmain")
h.Write([]byte("testmain\n"))
h.Write(data)
out, _, err := c.Put(h.Sum(), bytes.NewReader(data))
if err != nil {
base.Fatalf("%s", err)
}
pmain.GoFiles[0] = c.OutputFile(out)
}
}
}
// Remember which packages are named on the command line.
cmdline := make(map[*load.Package]bool)
for _, p := range pkgs {
cmdline[p] = true
}
if *listDeps {
// Note: This changes the order of the listed packages
// from "as written on the command line" to
// "a depth-first post-order traversal".
// (The dependency exploration order for a given node
// is alphabetical, same as listed in .Deps.)
// Note that -deps is applied after -test,
// so that you only get descriptions of tests for the things named
// explicitly on the command line, not for all dependencies.
pkgs = load.PackageList(pkgs)
}
@ -230,12 +294,53 @@ func runList(cmd *base.Command, args []string) {
b.Do(a)
}
for _, pkg := range pkgs {
for _, p := range pkgs {
// Show vendor-expanded paths in listing
pkg.TestImports = pkg.Vendored(pkg.TestImports)
pkg.XTestImports = pkg.Vendored(pkg.XTestImports)
p.TestImports = p.Vendored(p.TestImports)
p.XTestImports = p.Vendored(p.XTestImports)
}
do(&pkg.PackagePublic)
if *listTest {
all := pkgs
if !*listDeps {
all = load.PackageList(pkgs)
}
// Update import paths to distinguish the real package p
// from p recompiled for q.test.
// This must happen only once the build code is done
// looking at import paths, because it will get very confused
// if it sees these.
for _, p := range all {
if p.ForTest != "" {
p.ImportPath += " [" + p.ForTest + ".test]"
}
p.DepOnly = !cmdline[p]
}
// Update import path lists to use new strings.
for _, p := range all {
for i := range p.Imports {
p.Imports[i] = p.Internal.Imports[i].ImportPath
}
}
// Recompute deps lists using new strings, from the leaves up.
for _, p := range all {
deps := make(map[string]bool)
for _, p1 := range p.Internal.Imports {
deps[p1.ImportPath] = true
for _, d := range p1.Deps {
deps[d] = true
}
}
p.Deps = make([]string, 0, len(deps))
for d := range deps {
p.Deps = append(p.Deps, d)
}
sort.Strings(p.Deps)
}
}
for _, p := range pkgs {
do(&p.PackagePublic)
}
}

View File

@ -47,6 +47,8 @@ type PackagePublic struct {
Root string `json:",omitempty"` // Go root or Go path dir containing this package
ConflictDir string `json:",omitempty"` // Dir is hidden by this other directory
BinaryOnly bool `json:",omitempty"` // package cannot be recompiled
ForTest string `json:",omitempty"` // package is only for use in named test
DepOnly bool `json:",omitempty"` // package is only as a dependency, not explicitly listed
// Stale and StaleReason remain here *only* for the list command.
// They are only initialized in preparation for list execution.
@ -135,6 +137,7 @@ type PackageInternal struct {
CoverVars map[string]*CoverVar // variables created by coverage analysis
OmitDebug bool // tell linker not to write debug information
GobinSubdir bool // install target would be subdir of GOBIN
TestmainGo *[]byte // content for _testmain.go
Asmflags []string // -asmflags for this package
Gcflags []string // -gcflags for this package

View File

@ -5,16 +5,54 @@
package load
import (
"bytes"
"cmd/go/internal/base"
"cmd/go/internal/str"
"errors"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/token"
"path/filepath"
"sort"
"strings"
"text/template"
"unicode"
"unicode/utf8"
)
// TestPackagesFor returns package structs ptest, the package p plus
// its test files, and pxtest, the external tests of package p.
// pxtest may be nil. If there are no test files, forceTest decides
// whether this returns a new package struct or just returns p.
func TestPackagesFor(p *Package, forceTest bool) (ptest, pxtest *Package, err error) {
var TestMainDeps = []string{
// Dependencies for testmain.
"os",
"testing",
"testing/internal/testdeps",
}
type TestCover struct {
Mode string
Local bool
Pkgs []*Package
Paths []string
Vars []coverInfo
DeclVars func(string, ...string) map[string]*CoverVar
}
// TestPackagesFor returns three packages:
// - ptest, the package p compiled with added "package p" test files.
// - pxtest, the result of compiling any "package p_test" (external) test files.
// - pmain, the package main corresponding to the test binary (running tests in ptest and pxtest).
//
// If the package has no "package p_test" test files, pxtest will be nil.
// If the non-test compilation of package p can be reused
// (for example, if there are no "package p" test files and
// package p need not be instrumented for coverage or any other reason),
// then the returned ptest == p.
//
// The caller is expected to have checked that len(p.TestGoFiles)+len(p.XTestGoFiles) > 0,
// or else there's no point in any of this.
func TestPackagesFor(p *Package, cover *TestCover) (pmain, ptest, pxtest *Package, err error) {
var imports, ximports []*Package
var stk ImportStack
stk.Push(p.ImportPath + " (test)")
@ -22,12 +60,12 @@ func TestPackagesFor(p *Package, forceTest bool) (ptest, pxtest *Package, err er
for i, path := range p.TestImports {
p1 := LoadImport(path, p.Dir, p, &stk, p.Internal.Build.TestImportPos[path], UseVendor)
if p1.Error != nil {
return nil, nil, p1.Error
return nil, nil, nil, p1.Error
}
if len(p1.DepsErrors) > 0 {
err := p1.DepsErrors[0]
err.Pos = "" // show full import stack
return nil, nil, err
return nil, nil, nil, err
}
if str.Contains(p1.Deps, p.ImportPath) || p1.ImportPath == p.ImportPath {
// Same error that loadPackage returns (via reusePackage) in pkg.go.
@ -38,7 +76,7 @@ func TestPackagesFor(p *Package, forceTest bool) (ptest, pxtest *Package, err er
Err: "import cycle not allowed in test",
IsImportCycle: true,
}
return nil, nil, err
return nil, nil, nil, err
}
p.TestImports[i] = p1.ImportPath
imports = append(imports, p1)
@ -50,12 +88,12 @@ func TestPackagesFor(p *Package, forceTest bool) (ptest, pxtest *Package, err er
for i, path := range p.XTestImports {
p1 := LoadImport(path, p.Dir, p, &stk, p.Internal.Build.XTestImportPos[path], UseVendor)
if p1.Error != nil {
return nil, nil, p1.Error
return nil, nil, nil, p1.Error
}
if len(p1.DepsErrors) > 0 {
err := p1.DepsErrors[0]
err.Pos = "" // show full import stack
return nil, nil, err
return nil, nil, nil, err
}
if p1.ImportPath == p.ImportPath {
pxtestNeedsPtest = true
@ -67,9 +105,10 @@ func TestPackagesFor(p *Package, forceTest bool) (ptest, pxtest *Package, err er
stk.Pop()
// Test package.
if len(p.TestGoFiles) > 0 || forceTest {
if len(p.TestGoFiles) > 0 || p.Name == "main" || cover != nil && cover.Local {
ptest = new(Package)
*ptest = *p
ptest.ForTest = p.ImportPath
ptest.GoFiles = nil
ptest.GoFiles = append(ptest.GoFiles, p.GoFiles...)
ptest.GoFiles = append(ptest.GoFiles, p.TestGoFiles...)
@ -113,6 +152,7 @@ func TestPackagesFor(p *Package, forceTest bool) (ptest, pxtest *Package, err er
Dir: p.Dir,
GoFiles: p.XTestGoFiles,
Imports: p.XTestImports,
ForTest: p.ImportPath,
},
Internal: PackageInternal{
LocalPrefix: p.Internal.LocalPrefix,
@ -133,19 +173,113 @@ func TestPackagesFor(p *Package, forceTest bool) (ptest, pxtest *Package, err er
}
}
if p != ptest && pxtest != nil {
// Build main package.
pmain = &Package{
PackagePublic: PackagePublic{
Name: "main",
Dir: p.Dir,
GoFiles: []string{"_testmain.go"},
ImportPath: p.ImportPath + ".test",
Root: p.Root,
},
Internal: PackageInternal{
Build: &build.Package{Name: "main"},
Asmflags: p.Internal.Asmflags,
Gcflags: p.Internal.Gcflags,
Ldflags: p.Internal.Ldflags,
Gccgoflags: p.Internal.Gccgoflags,
},
}
// The generated main also imports testing, regexp, and os.
// Also the linker introduces implicit dependencies reported by LinkerDeps.
stk.Push("testmain")
deps := TestMainDeps // cap==len, so safe for append
for _, d := range LinkerDeps(p) {
deps = append(deps, d)
}
for _, dep := range deps {
if dep == ptest.ImportPath {
pmain.Internal.Imports = append(pmain.Internal.Imports, ptest)
} else {
p1 := LoadImport(dep, "", nil, &stk, nil, 0)
if p1.Error != nil {
return nil, nil, nil, p1.Error
}
pmain.Internal.Imports = append(pmain.Internal.Imports, p1)
}
}
stk.Pop()
if cover != nil && cover.Pkgs != nil {
// Add imports, but avoid duplicates.
seen := map[*Package]bool{p: true, ptest: true}
for _, p1 := range pmain.Internal.Imports {
seen[p1] = true
}
for _, p1 := range cover.Pkgs {
if !seen[p1] {
seen[p1] = true
pmain.Internal.Imports = append(pmain.Internal.Imports, p1)
}
}
}
// Do initial scan for metadata needed for writing _testmain.go
// Use that metadata to update the list of imports for package main.
// The list of imports is used by recompileForTest and by the loop
// afterward that gathers t.Cover information.
t, err := loadTestFuncs(ptest)
if err != nil {
return nil, nil, nil, err
}
t.Cover = cover
if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 {
pmain.Internal.Imports = append(pmain.Internal.Imports, ptest)
t.ImportTest = true
}
if pxtest != nil {
pmain.Internal.Imports = append(pmain.Internal.Imports, pxtest)
t.ImportXtest = true
}
if ptest != p {
// We have made modifications to the package p being tested
// and are rebuilding p (as ptest).
// Arrange to rebuild all packages q such that
// pxtest depends on q and q depends on p.
// the test depends on q and q depends on p.
// This makes sure that q sees the modifications to p.
// Strictly speaking, the rebuild is only necessary if the
// modifications to p change its export metadata, but
// determining that is a bit tricky, so we rebuild always.
recompileForTest(p, ptest, pxtest)
recompileForTest(pmain, p, ptest, pxtest)
}
return ptest, pxtest, nil
// Should we apply coverage analysis locally,
// only for this package and only for this test?
// Yes, if -cover is on but -coverpkg has not specified
// a list of packages for global coverage.
if cover != nil && cover.Local {
ptest.Internal.CoverMode = cover.Mode
var coverFiles []string
coverFiles = append(coverFiles, ptest.GoFiles...)
coverFiles = append(coverFiles, ptest.CgoFiles...)
ptest.Internal.CoverVars = cover.DeclVars(ptest.ImportPath, coverFiles...)
}
for _, cp := range pmain.Internal.Imports {
if len(cp.Internal.CoverVars) > 0 {
t.Cover.Vars = append(t.Cover.Vars, coverInfo{cp, cp.Internal.CoverVars})
}
}
data, err := formatTestmain(t)
if err != nil {
return nil, nil, nil, err
}
pmain.Internal.TestmainGo = &data
return pmain, ptest, pxtest, nil
}
func testImportStack(top string, p *Package, target string) []string {
@ -166,20 +300,17 @@ Search:
return stk
}
func recompileForTest(preal, ptest, pxtest *Package) {
func recompileForTest(pmain, preal, ptest, pxtest *Package) {
// The "test copy" of preal is ptest.
// For each package that depends on preal, make a "test copy"
// that depends on ptest. And so on, up the dependency tree.
testCopy := map[*Package]*Package{preal: ptest}
// Only pxtest and its dependencies can legally depend on p.
// If ptest or its dependencies depended on p, the dependency
// would be circular.
for _, p := range PackageList([]*Package{pxtest}) {
for _, p := range PackageList([]*Package{pmain}) {
if p == preal {
continue
}
// Copy on write.
didSplit := p == pxtest
didSplit := p == pmain || p == pxtest
split := func() {
if didSplit {
return
@ -191,6 +322,7 @@ func recompileForTest(preal, ptest, pxtest *Package) {
p1 := new(Package)
testCopy[p] = p1
*p1 = *p
p1.ForTest = preal.ImportPath
p1.Internal.Imports = make([]*Package, len(p.Internal.Imports))
copy(p1.Internal.Imports, p.Internal.Imports)
p = p1
@ -206,3 +338,298 @@ func recompileForTest(preal, ptest, pxtest *Package) {
}
}
}
// isTestFunc tells whether fn has the type of a testing function. arg
// specifies the parameter type we look for: B, M or T.
func isTestFunc(fn *ast.FuncDecl, arg string) bool {
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params.List == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) > 1 {
return false
}
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
if !ok {
return false
}
// We can't easily check that the type is *testing.M
// because we don't know how testing has been imported,
// but at least check that it's *M or *something.M.
// Same applies for B and T.
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
return true
}
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
return true
}
return false
}
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
func isTest(name, prefix string) bool {
if !strings.HasPrefix(name, prefix) {
return false
}
if len(name) == len(prefix) { // "Test" is ok
return true
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}
type coverInfo struct {
Package *Package
Vars map[string]*CoverVar
}
// loadTestFuncs returns the testFuncs describing the tests that will be run.
func loadTestFuncs(ptest *Package) (*testFuncs, error) {
t := &testFuncs{
Package: ptest,
}
for _, file := range ptest.TestGoFiles {
if err := t.load(filepath.Join(ptest.Dir, file), "_test", &t.ImportTest, &t.NeedTest); err != nil {
return nil, err
}
}
for _, file := range ptest.XTestGoFiles {
if err := t.load(filepath.Join(ptest.Dir, file), "_xtest", &t.ImportXtest, &t.NeedXtest); err != nil {
return nil, err
}
}
return t, nil
}
// formatTestmain returns the content of the _testmain.go file for t.
func formatTestmain(t *testFuncs) ([]byte, error) {
var buf bytes.Buffer
if err := testmainTmpl.Execute(&buf, t); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
type testFuncs struct {
Tests []testFunc
Benchmarks []testFunc
Examples []testFunc
TestMain *testFunc
Package *Package
ImportTest bool
NeedTest bool
ImportXtest bool
NeedXtest bool
Cover *TestCover
}
// ImportPath returns the import path of the package being tested, if it is within GOPATH.
// This is printed by the testing package when running benchmarks.
func (t *testFuncs) ImportPath() string {
pkg := t.Package.ImportPath
if strings.HasPrefix(pkg, "_/") {
return ""
}
if pkg == "command-line-arguments" {
return ""
}
return pkg
}
// Covered returns a string describing which packages are being tested for coverage.
// If the covered package is the same as the tested package, it returns the empty string.
// Otherwise it is a comma-separated human-readable list of packages beginning with
// " in", ready for use in the coverage message.
func (t *testFuncs) Covered() string {
if t.Cover == nil || t.Cover.Paths == nil {
return ""
}
return " in " + strings.Join(t.Cover.Paths, ", ")
}
// Tested returns the name of the package being tested.
func (t *testFuncs) Tested() string {
return t.Package.Name
}
type testFunc struct {
Package string // imported package name (_test or _xtest)
Name string // function name
Output string // output, for examples
Unordered bool // output is allowed to be unordered.
}
var testFileSet = token.NewFileSet()
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments)
if err != nil {
return base.ExpandScanner(err)
}
for _, d := range f.Decls {
n, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
if n.Recv != nil {
continue
}
name := n.Name.String()
switch {
case name == "TestMain":
if isTestFunc(n, "T") {
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
continue
}
err := checkTestFunc(n, "M")
if err != nil {
return err
}
if t.TestMain != nil {
return errors.New("multiple definitions of TestMain")
}
t.TestMain = &testFunc{pkg, name, "", false}
*doImport, *seen = true, true
case isTest(name, "Test"):
err := checkTestFunc(n, "T")
if err != nil {
return err
}
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
case isTest(name, "Benchmark"):
err := checkTestFunc(n, "B")
if err != nil {
return err
}
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
}
}
ex := doc.Examples(f)
sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order })
for _, e := range ex {
*doImport = true // import test file whether executed or not
if e.Output == "" && !e.EmptyOutput {
// Don't run examples with no output.
continue
}
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
*seen = true
}
return nil
}
func checkTestFunc(fn *ast.FuncDecl, arg string) error {
if !isTestFunc(fn, arg) {
name := fn.Name.String()
pos := testFileSet.Position(fn.Pos())
return fmt.Errorf("%s: wrong signature for %s, must be: func %s(%s *testing.%s)", pos, name, name, strings.ToLower(arg), arg)
}
return nil
}
var testmainTmpl = template.Must(template.New("main").Parse(`
package main
import (
{{if not .TestMain}}
"os"
{{end}}
"testing"
"testing/internal/testdeps"
{{if .ImportTest}}
{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .ImportXtest}}
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
{{if .Cover}}
{{range $i, $p := .Cover.Vars}}
_cover{{$i}} {{$p.Package.ImportPath | printf "%q"}}
{{end}}
{{end}}
)
var tests = []testing.InternalTest{
{{range .Tests}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var benchmarks = []testing.InternalBenchmark{
{{range .Benchmarks}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var examples = []testing.InternalExample{
{{range .Examples}}
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}
func init() {
testdeps.ImportPath = {{.ImportPath | printf "%q"}}
}
{{if .Cover}}
// Only updated by init functions, so no need for atomicity.
var (
coverCounters = make(map[string][]uint32)
coverBlocks = make(map[string][]testing.CoverBlock)
)
func init() {
{{range $i, $p := .Cover.Vars}}
{{range $file, $cover := $p.Vars}}
coverRegisterFile({{printf "%q" $cover.File}}, _cover{{$i}}.{{$cover.Var}}.Count[:], _cover{{$i}}.{{$cover.Var}}.Pos[:], _cover{{$i}}.{{$cover.Var}}.NumStmt[:])
{{end}}
{{end}}
}
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 {
// Already registered.
return
}
coverCounters[fileName] = counter
block := make([]testing.CoverBlock, len(counter))
for i := range counter {
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
}
{{end}}
func main() {
{{if .Cover}}
testing.RegisterCover(testing.Cover{
Mode: {{printf "%q" .Cover.Mode}},
Counters: coverCounters,
Blocks: coverBlocks,
CoveredPackages: {{printf "%q" .Covered}},
})
{{end}}
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
{{else}}
os.Exit(m.Run())
{{end}}
}
`))

View File

@ -9,11 +9,7 @@ import (
"crypto/sha256"
"errors"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/token"
"io"
"io/ioutil"
"os"
@ -25,10 +21,7 @@ import (
"strconv"
"strings"
"sync"
"text/template"
"time"
"unicode"
"unicode/utf8"
"cmd/go/internal/base"
"cmd/go/internal/cache"
@ -507,13 +500,6 @@ var (
testCacheExpire time.Time // ignore cached test results before this time
)
var testMainDeps = []string{
// Dependencies for testmain.
"os",
"testing",
"testing/internal/testdeps",
}
// testVetFlags is the list of flags to pass to vet when invoked automatically during go test.
var testVetFlags = []string{
// TODO(rsc): Decide which tests are enabled by default.
@ -605,7 +591,7 @@ func runTest(cmd *base.Command, args []string) {
cfg.BuildV = testV
deps := make(map[string]bool)
for _, dep := range testMainDeps {
for _, dep := range load.TestMainDeps {
deps[dep] = true
}
@ -799,14 +785,20 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
}
// Build Package structs describing:
// pmain - pkg.test binary
// ptest - package + test files
// pxtest - package of external test files
// pmain - pkg.test binary
var ptest, pxtest, pmain *load.Package
localCover := testCover && testCoverPaths == nil
ptest, pxtest, err = load.TestPackagesFor(p, localCover || p.Name == "main")
var cover *load.TestCover
if testCover {
cover = &load.TestCover{
Mode: testCoverMode,
Local: testCover && testCoverPaths == nil,
Pkgs: testCoverPkgs,
Paths: testCoverPaths,
DeclVars: declareCoverVars,
}
}
pmain, ptest, pxtest, err := load.TestPackagesFor(p, cover)
if err != nil {
return nil, nil, nil, err
}
@ -823,104 +815,18 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
}
testBinary := elem + ".test"
// Should we apply coverage analysis locally,
// only for this package and only for this test?
// Yes, if -cover is on but -coverpkg has not specified
// a list of packages for global coverage.
if localCover {
ptest.Internal.CoverMode = testCoverMode
var coverFiles []string
coverFiles = append(coverFiles, ptest.GoFiles...)
coverFiles = append(coverFiles, ptest.CgoFiles...)
ptest.Internal.CoverVars = declareCoverVars(ptest.ImportPath, coverFiles...)
}
testDir := b.NewObjdir()
if err := b.Mkdir(testDir); err != nil {
return nil, nil, nil, err
}
// Action for building pkg.test.
pmain = &load.Package{
PackagePublic: load.PackagePublic{
Name: "main",
Dir: testDir,
GoFiles: []string{"_testmain.go"},
ImportPath: p.ImportPath + " (testmain)",
Root: p.Root,
},
Internal: load.PackageInternal{
Build: &build.Package{Name: "main"},
OmitDebug: !testC && !testNeedBinary,
Asmflags: p.Internal.Asmflags,
Gcflags: p.Internal.Gcflags,
Ldflags: p.Internal.Ldflags,
Gccgoflags: p.Internal.Gccgoflags,
},
}
// The generated main also imports testing, regexp, and os.
// Also the linker introduces implicit dependencies reported by LinkerDeps.
var stk load.ImportStack
stk.Push("testmain")
deps := testMainDeps // cap==len, so safe for append
for _, d := range load.LinkerDeps(p) {
deps = append(deps, d)
}
for _, dep := range deps {
if dep == ptest.ImportPath {
pmain.Internal.Imports = append(pmain.Internal.Imports, ptest)
} else {
p1 := load.LoadImport(dep, "", nil, &stk, nil, 0)
if p1.Error != nil {
return nil, nil, nil, p1.Error
}
pmain.Internal.Imports = append(pmain.Internal.Imports, p1)
}
}
if testCoverPkgs != nil {
// Add imports, but avoid duplicates.
seen := map[*load.Package]bool{p: true, ptest: true}
for _, p1 := range pmain.Internal.Imports {
seen[p1] = true
}
for _, p1 := range testCoverPkgs {
if !seen[p1] {
seen[p1] = true
pmain.Internal.Imports = append(pmain.Internal.Imports, p1)
}
}
}
// Do initial scan for metadata needed for writing _testmain.go
// Use that metadata to update the list of imports for package main.
// The list of imports is used by recompileForTest and by the loop
// afterward that gathers t.Cover information.
t, err := loadTestFuncs(ptest)
if err != nil {
return nil, nil, nil, err
}
if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 {
pmain.Internal.Imports = append(pmain.Internal.Imports, ptest)
t.ImportTest = true
}
if pxtest != nil {
pmain.Internal.Imports = append(pmain.Internal.Imports, pxtest)
t.ImportXtest = true
}
for _, cp := range pmain.Internal.Imports {
if len(cp.Internal.CoverVars) > 0 {
t.Cover = append(t.Cover, coverInfo{cp, cp.Internal.CoverVars})
}
}
pmain.Dir = testDir
pmain.Internal.OmitDebug = !testC && !testNeedBinary
if !cfg.BuildN {
// writeTestmain writes _testmain.go,
// using the test description gathered in t.
if err := writeTestmain(testDir+"_testmain.go", t); err != nil {
if err := ioutil.WriteFile(testDir+"_testmain.go", *pmain.Internal.TestmainGo, 0666); err != nil {
return nil, nil, nil, err
}
}
@ -1697,306 +1603,3 @@ func builderNoTest(b *work.Builder, a *work.Action) error {
fmt.Fprintf(stdout, "? \t%s\t[no test files]\n", a.Package.ImportPath)
return nil
}
// isTestFunc tells whether fn has the type of a testing function. arg
// specifies the parameter type we look for: B, M or T.
func isTestFunc(fn *ast.FuncDecl, arg string) bool {
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params.List == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) > 1 {
return false
}
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
if !ok {
return false
}
// We can't easily check that the type is *testing.M
// because we don't know how testing has been imported,
// but at least check that it's *M or *something.M.
// Same applies for B and T.
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
return true
}
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
return true
}
return false
}
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
func isTest(name, prefix string) bool {
if !strings.HasPrefix(name, prefix) {
return false
}
if len(name) == len(prefix) { // "Test" is ok
return true
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}
type coverInfo struct {
Package *load.Package
Vars map[string]*load.CoverVar
}
// loadTestFuncs returns the testFuncs describing the tests that will be run.
func loadTestFuncs(ptest *load.Package) (*testFuncs, error) {
t := &testFuncs{
Package: ptest,
}
for _, file := range ptest.TestGoFiles {
if err := t.load(filepath.Join(ptest.Dir, file), "_test", &t.ImportTest, &t.NeedTest); err != nil {
return nil, err
}
}
for _, file := range ptest.XTestGoFiles {
if err := t.load(filepath.Join(ptest.Dir, file), "_xtest", &t.ImportXtest, &t.NeedXtest); err != nil {
return nil, err
}
}
return t, nil
}
// writeTestmain writes the _testmain.go file for t to the file named out.
func writeTestmain(out string, t *testFuncs) error {
f, err := os.Create(out)
if err != nil {
return err
}
defer f.Close()
return testmainTmpl.Execute(f, t)
}
type testFuncs struct {
Tests []testFunc
Benchmarks []testFunc
Examples []testFunc
TestMain *testFunc
Package *load.Package
ImportTest bool
NeedTest bool
ImportXtest bool
NeedXtest bool
Cover []coverInfo
}
func (t *testFuncs) CoverMode() string {
return testCoverMode
}
func (t *testFuncs) CoverEnabled() bool {
return testCover
}
// ImportPath returns the import path of the package being tested, if it is within GOPATH.
// This is printed by the testing package when running benchmarks.
func (t *testFuncs) ImportPath() string {
pkg := t.Package.ImportPath
if strings.HasPrefix(pkg, "_/") {
return ""
}
if pkg == "command-line-arguments" {
return ""
}
return pkg
}
// Covered returns a string describing which packages are being tested for coverage.
// If the covered package is the same as the tested package, it returns the empty string.
// Otherwise it is a comma-separated human-readable list of packages beginning with
// " in", ready for use in the coverage message.
func (t *testFuncs) Covered() string {
if testCoverPaths == nil {
return ""
}
return " in " + strings.Join(testCoverPaths, ", ")
}
// Tested returns the name of the package being tested.
func (t *testFuncs) Tested() string {
return t.Package.Name
}
type testFunc struct {
Package string // imported package name (_test or _xtest)
Name string // function name
Output string // output, for examples
Unordered bool // output is allowed to be unordered.
}
var testFileSet = token.NewFileSet()
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments)
if err != nil {
return base.ExpandScanner(err)
}
for _, d := range f.Decls {
n, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
if n.Recv != nil {
continue
}
name := n.Name.String()
switch {
case name == "TestMain":
if isTestFunc(n, "T") {
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
continue
}
err := checkTestFunc(n, "M")
if err != nil {
return err
}
if t.TestMain != nil {
return errors.New("multiple definitions of TestMain")
}
t.TestMain = &testFunc{pkg, name, "", false}
*doImport, *seen = true, true
case isTest(name, "Test"):
err := checkTestFunc(n, "T")
if err != nil {
return err
}
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
case isTest(name, "Benchmark"):
err := checkTestFunc(n, "B")
if err != nil {
return err
}
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
}
}
ex := doc.Examples(f)
sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order })
for _, e := range ex {
*doImport = true // import test file whether executed or not
if e.Output == "" && !e.EmptyOutput {
// Don't run examples with no output.
continue
}
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
*seen = true
}
return nil
}
func checkTestFunc(fn *ast.FuncDecl, arg string) error {
if !isTestFunc(fn, arg) {
name := fn.Name.String()
pos := testFileSet.Position(fn.Pos())
return fmt.Errorf("%s: wrong signature for %s, must be: func %s(%s *testing.%s)", pos, name, name, strings.ToLower(arg), arg)
}
return nil
}
var testmainTmpl = template.Must(template.New("main").Parse(`
package main
import (
{{if not .TestMain}}
"os"
{{end}}
"testing"
"testing/internal/testdeps"
{{if .ImportTest}}
{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .ImportXtest}}
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
{{range $i, $p := .Cover}}
_cover{{$i}} {{$p.Package.ImportPath | printf "%q"}}
{{end}}
)
var tests = []testing.InternalTest{
{{range .Tests}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var benchmarks = []testing.InternalBenchmark{
{{range .Benchmarks}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var examples = []testing.InternalExample{
{{range .Examples}}
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}
func init() {
testdeps.ImportPath = {{.ImportPath | printf "%q"}}
}
{{if .CoverEnabled}}
// Only updated by init functions, so no need for atomicity.
var (
coverCounters = make(map[string][]uint32)
coverBlocks = make(map[string][]testing.CoverBlock)
)
func init() {
{{range $i, $p := .Cover}}
{{range $file, $cover := $p.Vars}}
coverRegisterFile({{printf "%q" $cover.File}}, _cover{{$i}}.{{$cover.Var}}.Count[:], _cover{{$i}}.{{$cover.Var}}.Pos[:], _cover{{$i}}.{{$cover.Var}}.NumStmt[:])
{{end}}
{{end}}
}
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 {
// Already registered.
return
}
coverCounters[fileName] = counter
block := make([]testing.CoverBlock, len(counter))
for i := range counter {
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
}
{{end}}
func main() {
{{if .CoverEnabled}}
testing.RegisterCover(testing.Cover{
Mode: {{printf "%q" .CoverMode}},
Counters: coverCounters,
Blocks: coverBlocks,
CoveredPackages: {{printf "%q" .Covered}},
})
{{end}}
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
{{else}}
os.Exit(m.Run())
{{end}}
}
`))

View File

@ -57,7 +57,7 @@ func runVet(cmd *base.Command, args []string) {
root := &work.Action{Mode: "go vet"}
for _, p := range pkgs {
ptest, pxtest, err := load.TestPackagesFor(p, false)
_, ptest, pxtest, err := load.TestPackagesFor(p, nil)
if err != nil {
base.Errorf("%v", err)
continue