go/packages: generate test main files from the golist fallback
Part of the testmain generation logic (sans coverage) has been taken from go build and put into golist_fallback_testmain.go. golist_fallback invokes this logic and builds up the package metadata for the testmain. The tests checking for testmain are now no longer skipped from packages_test.go. Change-Id: I487a947f087f3ad4161ea6c2bed06ebb2f833422 Reviewed-on: https://go-review.googlesource.com/134119 Run-TryBot: Michael Matloob <matloob@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
9f3b32b5c4
commit
90fa682c2a
|
@ -67,17 +67,32 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
|
|||
compiledGoFiles := absJoin(p.Dir, p.GoFiles)
|
||||
// Use a function to simplify control flow. It's just a bunch of gotos.
|
||||
var cgoErrors []error
|
||||
var outdir string
|
||||
getOutdir := func() (string, error) {
|
||||
if outdir != "" {
|
||||
return outdir, nil
|
||||
}
|
||||
if tmpdir == "" {
|
||||
if tmpdir, err = ioutil.TempDir("", "gopackages"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
// Add a "go-build" component to the path to make the tests think the files are in the cache.
|
||||
// This allows the same test to test the pre- and post-Go 1.11 go list logic because the Go 1.11
|
||||
// go list generates test mains in the cache, and the test code knows not to rely on paths in the
|
||||
// cache to stay stable.
|
||||
outdir = filepath.Join(tmpdir, "go-build", strings.Replace(p.ImportPath, "/", "_", -1))
|
||||
if err := os.MkdirAll(outdir, 0755); err != nil {
|
||||
outdir = ""
|
||||
return "", err
|
||||
}
|
||||
return outdir, nil
|
||||
}
|
||||
processCgo := func() bool {
|
||||
// Suppress any cgo errors. Any relevant errors will show up in typechecking.
|
||||
// TODO(matloob): Skip running cgo if Mode < LoadTypes.
|
||||
if tmpdir == "" {
|
||||
if tmpdir, err = ioutil.TempDir("", "gopackages"); err != nil {
|
||||
cgoErrors = append(cgoErrors, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
outdir := filepath.Join(tmpdir, strings.Replace(p.ImportPath, "/", "_", -1))
|
||||
if err := os.Mkdir(outdir, 0755); err != nil {
|
||||
outdir, err := getOutdir()
|
||||
if err != nil {
|
||||
cgoErrors = append(cgoErrors, err)
|
||||
return false
|
||||
}
|
||||
|
@ -111,7 +126,7 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
|
|||
if isRoot {
|
||||
response.Roots = append(response.Roots, testID)
|
||||
}
|
||||
response.Packages = append(response.Packages, &Package{
|
||||
testPkg := &Package{
|
||||
ID: testID,
|
||||
Name: p.Name,
|
||||
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles, p.TestGoFiles),
|
||||
|
@ -120,7 +135,9 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
|
|||
PkgPath: pkgpath,
|
||||
Imports: importMap(append(p.Imports, p.TestImports...)),
|
||||
// TODO(matloob): set errors on the Package to cgoErrors
|
||||
})
|
||||
}
|
||||
response.Packages = append(response.Packages, testPkg)
|
||||
var xtestPkg *Package
|
||||
if len(p.XTestGoFiles) > 0 {
|
||||
xtestID := fmt.Sprintf("%s_test [%s.test]", id, id)
|
||||
if isRoot {
|
||||
|
@ -134,15 +151,51 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
|
|||
break
|
||||
}
|
||||
}
|
||||
response.Packages = append(response.Packages, &Package{
|
||||
xtestPkg = &Package{
|
||||
ID: xtestID,
|
||||
Name: p.Name + "_test",
|
||||
GoFiles: absJoin(p.Dir, p.XTestGoFiles),
|
||||
CompiledGoFiles: absJoin(p.Dir, p.XTestGoFiles),
|
||||
PkgPath: pkgpath,
|
||||
PkgPath: pkgpath + "_test",
|
||||
Imports: imports,
|
||||
})
|
||||
}
|
||||
response.Packages = append(response.Packages, xtestPkg)
|
||||
}
|
||||
// testmain package
|
||||
testmainID := id + ".test"
|
||||
if isRoot {
|
||||
response.Roots = append(response.Roots, testmainID)
|
||||
}
|
||||
imports := map[string]*Package{}
|
||||
imports[testPkg.PkgPath] = &Package{ID: testPkg.ID}
|
||||
if xtestPkg != nil {
|
||||
imports[xtestPkg.PkgPath] = &Package{ID: xtestPkg.ID}
|
||||
}
|
||||
testmainPkg := &Package{
|
||||
ID: testmainID,
|
||||
Name: "main",
|
||||
PkgPath: testmainID,
|
||||
Imports: imports,
|
||||
}
|
||||
response.Packages = append(response.Packages, testmainPkg)
|
||||
outdir, err := getOutdir()
|
||||
if err != nil {
|
||||
testmainPkg.Errors = append(testmainPkg.Errors,
|
||||
Error{"-", fmt.Sprintf("failed to generate testmain: %v", err)})
|
||||
return
|
||||
}
|
||||
testmain := filepath.Join(outdir, "testmain.go")
|
||||
extradeps, err := generateTestmain(testmain, testPkg, xtestPkg)
|
||||
if err != nil {
|
||||
testmainPkg.Errors = append(testmainPkg.Errors,
|
||||
Error{"-", fmt.Sprintf("failed to generate testmain: %v", err)})
|
||||
}
|
||||
deps = append(deps, extradeps...)
|
||||
for _, imp := range extradeps { // testing, testing/internal/testdeps, and maybe os
|
||||
imports[imp] = &Package{ID: imp}
|
||||
}
|
||||
testmainPkg.GoFiles = []string{testmain}
|
||||
testmainPkg.CompiledGoFiles = []string{testmain}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +222,11 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
|
|||
addPackage(p)
|
||||
}
|
||||
|
||||
// TODO(matloob): Is this the right ordering?
|
||||
sort.SliceStable(response.Packages, func(i, j int) bool {
|
||||
return response.Packages[i].PkgPath < response.Packages[j].PkgPath
|
||||
})
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file is largely based on the Go 1.10-era cmd/go/internal/test/test.go
|
||||
// testmain generation code.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/doc"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// TODO(matloob): Delete this file once Go 1.12 is released.
|
||||
|
||||
// This file complements golist_fallback.go by providing
|
||||
// support for generating testmains.
|
||||
|
||||
func generateTestmain(out string, testPkg, xtestPkg *Package) (extradeps []string, err error) {
|
||||
testFuncs, err := loadTestFuncs(testPkg, xtestPkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extradeps = []string{"testing/internal/testdeps", "testing"}
|
||||
if testFuncs.TestMain == nil {
|
||||
extradeps = append(extradeps, "os")
|
||||
}
|
||||
return extradeps, writeTestmain(out, testFuncs)
|
||||
}
|
||||
|
||||
// The following is adapted from the cmd/go testmain generation code.
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// loadTestFuncs returns the testFuncs describing the tests that will be run.
|
||||
func loadTestFuncs(ptest, pxtest *Package) (*testFuncs, error) {
|
||||
t := &testFuncs{
|
||||
TestPackage: ptest,
|
||||
XTestPackage: pxtest,
|
||||
}
|
||||
for _, file := range ptest.GoFiles {
|
||||
if !strings.HasSuffix(file, "_test.go") {
|
||||
continue
|
||||
}
|
||||
if err := t.load(file, "_test", &t.ImportTest, &t.NeedTest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if pxtest != nil {
|
||||
for _, file := range pxtest.GoFiles {
|
||||
if err := t.load(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()
|
||||
|
||||
if err := testmainTmpl.Execute(f, t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type testFuncs struct {
|
||||
Tests []testFunc
|
||||
Benchmarks []testFunc
|
||||
Examples []testFunc
|
||||
TestMain *testFunc
|
||||
TestPackage *Package
|
||||
XTestPackage *Package
|
||||
ImportTest bool
|
||||
NeedTest bool
|
||||
ImportXtest bool
|
||||
NeedXtest bool
|
||||
}
|
||||
|
||||
// Tested returns the name of the package being tested.
|
||||
func (t *testFuncs) Tested() string {
|
||||
return t.TestPackage.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.
|
||||
}
|
||||
|
||||
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
|
||||
var fset = token.NewFileSet()
|
||||
|
||||
f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return errors.New("failed to parse test file " + filename)
|
||||
}
|
||||
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(fset, 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(fset, 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(fset, 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(fset *token.FileSet, fn *ast.FuncDecl, arg string) error {
|
||||
if !isTestFunc(fn, arg) {
|
||||
name := fn.Name.String()
|
||||
pos := fset.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}} {{.TestPackage.PkgPath | printf "%q"}}
|
||||
{{end}}
|
||||
{{if .ImportXtest}}
|
||||
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.XTestPackage.PkgPath | 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 = {{.TestPackage.PkgPath | printf "%q"}}
|
||||
}
|
||||
|
||||
func main() {
|
||||
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
|
||||
{{with .TestMain}}
|
||||
{{.Package}}.{{.Name}}(m)
|
||||
{{else}}
|
||||
os.Exit(m.Run())
|
||||
{{end}}
|
||||
}
|
||||
|
||||
`))
|
|
@ -6,51 +6,6 @@
|
|||
|
||||
package packages_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
func init() {
|
||||
usesOldGolist = true
|
||||
}
|
||||
|
||||
func TestXTestImports(t *testing.T) {
|
||||
tmp, cleanup := makeTree(t, map[string]string{
|
||||
"src/a/a_test.go": `package a_test; import "a"`,
|
||||
"src/a/a.go": `package a`,
|
||||
})
|
||||
defer cleanup()
|
||||
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.LoadImports,
|
||||
Dir: tmp,
|
||||
Env: append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
|
||||
Tests: true,
|
||||
}
|
||||
initial, err := packages.Load(cfg, "a")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var gotImports bytes.Buffer
|
||||
for _, pkg := range initial {
|
||||
var imports []string
|
||||
for imp, pkg := range pkg.Imports {
|
||||
imports = append(imports, fmt.Sprintf("%q: %q", imp, pkg.ID))
|
||||
}
|
||||
fmt.Fprintf(&gotImports, "%s {%s}\n", pkg.ID, strings.Join(imports, ", "))
|
||||
}
|
||||
wantImports := `a {}
|
||||
a [a.test] {}
|
||||
a_test [a.test] {"a": "a [a.test]"}
|
||||
`
|
||||
if gotImports.String() != wantImports {
|
||||
t.Fatalf("wrong imports: got <<%s>>, want <<%s>>", gotImports.String(), wantImports)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,32 +152,8 @@ func TestLoadImportsGraph(t *testing.T) {
|
|||
subdir/d_test [subdir/d.test] -> subdir/d [subdir/d.test]
|
||||
`[1:]
|
||||
|
||||
// Legacy go list support does not create test main package.
|
||||
wantOldGraph := `
|
||||
a
|
||||
b
|
||||
* c
|
||||
* e
|
||||
errors
|
||||
math/bits
|
||||
* subdir/d
|
||||
* subdir/d [subdir/d.test]
|
||||
* subdir/d_test [subdir/d.test]
|
||||
unsafe
|
||||
b -> a
|
||||
b -> errors
|
||||
c -> b
|
||||
c -> unsafe
|
||||
e -> b
|
||||
e -> c
|
||||
subdir/d [subdir/d.test] -> math/bits
|
||||
subdir/d_test [subdir/d.test] -> subdir/d [subdir/d.test]
|
||||
`[1:]
|
||||
|
||||
if graph != wantGraph && !usesOldGolist {
|
||||
if graph != wantGraph {
|
||||
t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
|
||||
} else if graph != wantOldGraph && usesOldGolist {
|
||||
t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantOldGraph)
|
||||
}
|
||||
|
||||
// Check node information: kind, name, srcs.
|
||||
|
@ -196,10 +172,6 @@ func TestLoadImportsGraph(t *testing.T) {
|
|||
{"subdir/d.test", "main", "command", "0.go"},
|
||||
{"unsafe", "unsafe", "package", ""},
|
||||
} {
|
||||
if usesOldGolist && test.id == "subdir/d.test" {
|
||||
// Legacy go list support does not create test main package.
|
||||
continue
|
||||
}
|
||||
p, ok := all[test.id]
|
||||
if !ok {
|
||||
t.Errorf("no package %s", test.id)
|
||||
|
@ -261,20 +233,8 @@ func TestLoadImportsGraph(t *testing.T) {
|
|||
subdir/d_test [subdir/d.test] -> subdir/d [subdir/d.test]
|
||||
`[1:]
|
||||
|
||||
// Legacy go list support does not create test main package.
|
||||
wantOldGraph = `
|
||||
math/bits
|
||||
* subdir/d
|
||||
* subdir/d [subdir/d.test]
|
||||
* subdir/d_test [subdir/d.test]
|
||||
* subdir/e
|
||||
subdir/d [subdir/d.test] -> math/bits
|
||||
subdir/d_test [subdir/d.test] -> subdir/d [subdir/d.test]
|
||||
`[1:]
|
||||
if graph != wantGraph && !usesOldGolist {
|
||||
if graph != wantGraph {
|
||||
t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
|
||||
} else if graph != wantOldGraph && usesOldGolist {
|
||||
t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantOldGraph)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue