]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add go list -test to describe test binaries
authorRuss Cox <rsc@golang.org>
Wed, 18 Apr 2018 18:42:47 +0000 (14:42 -0400)
committerRuss Cox <rsc@golang.org>
Thu, 26 Apr 2018 02:20:04 +0000 (02:20 +0000)
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>
src/cmd/go/alldocs.go
src/cmd/go/go_test.go
src/cmd/go/internal/list/list.go
src/cmd/go/internal/load/pkg.go
src/cmd/go/internal/load/test.go
src/cmd/go/internal/test/test.go
src/cmd/go/internal/vet/vet.go

index 2f1108185f1d40d6a64a7e8add6cf6cd50eb0aec..371f8ceb9504f9cc559e9c4e2815f5ed8241185e 100644 (file)
 // 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'.
index f46c7ab1ced141f39408b81334054ddce77e55e5..f5346570558bd759444f93aad925c9aa4288c84b 100644 (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)
index 842cd9627af2bef4fa353d0fbc717d5f28270113..274770680608a35a223318d5e85dfa2587fbe560 100644 (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)
+       }
+
+       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)
+               }
+       }
 
-               do(&pkg.PackagePublic)
+       for _, p := range pkgs {
+               do(&p.PackagePublic)
        }
 }
 
index 77c15c4c18246604801f6b351b9f1f45fb19b1bd..ff9243a320b367d1e366134ced7fb125e5355521 100644 (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
index 848984ffec65277bd5794181ec747e0d5e007025..a9b47ce72dc40560080f14c02edeb643efdf17b1 100644 (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)
+       }
+
+       // 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 ptest, pxtest, nil
+       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}}
+}
+
+`))
index b95a8c55aa0bd31e3f49a959067c9158a2975d73..31cb51794312edac8dd8ea54f96fa2c92178ba90 100644 (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}}
-}
-
-`))
index c792a243bf7285ee03ff36870963d03ed2f37042..9cb2aaa656556cd85273353c562b4e630c7233a1 100644 (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