diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 2f1108185f1..371f8ceb950 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -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'. diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index f46c7ab1ced..f5346570558 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -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) diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go index 842cd9627af..27477068060 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -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) } } diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 77c15c4c182..ff9243a320b 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -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 diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go index 848984ffec6..a9b47ce72dc 100644 --- a/src/cmd/go/internal/load/test.go +++ b/src/cmd/go/internal/load/test.go @@ -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}} +} + +`)) diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index b95a8c55aa0..31cb5179431 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -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}} -} - -`)) diff --git a/src/cmd/go/internal/vet/vet.go b/src/cmd/go/internal/vet/vet.go index c792a243bf7..9cb2aaa6565 100644 --- a/src/cmd/go/internal/vet/vet.go +++ b/src/cmd/go/internal/vet/vet.go @@ -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