mirror of
https://github.com/golang/go
synced 2024-11-23 04:00:03 -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:
parent
c5f0104daf
commit
296e676575
@ -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'.
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}}
|
||||
}
|
||||
|
||||
`))
|
||||
|
@ -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}}
|
||||
}
|
||||
|
||||
`))
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user