319 lines
7.6 KiB
Go
319 lines
7.6 KiB
Go
// 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) (extraimports, extradeps []string, err error) {
|
|
testFuncs, err := loadTestFuncs(testPkg, xtestPkg)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
extraimports = []string{"testing", "testing/internal/testdeps"}
|
|
if testFuncs.TestMain == nil {
|
|
extraimports = append(extraimports, "os")
|
|
}
|
|
// Transitive dependencies of ("testing", "testing/internal/testdeps").
|
|
// os is part of the transitive closure so it and its transitive dependencies are
|
|
// included regardless of whether it's imported in the template below.
|
|
extradeps = []string{
|
|
"errors",
|
|
"internal/cpu",
|
|
"unsafe",
|
|
"internal/bytealg",
|
|
"internal/race",
|
|
"runtime/internal/atomic",
|
|
"runtime/internal/sys",
|
|
"runtime",
|
|
"sync/atomic",
|
|
"sync",
|
|
"io",
|
|
"unicode",
|
|
"unicode/utf8",
|
|
"bytes",
|
|
"math",
|
|
"syscall",
|
|
"time",
|
|
"internal/poll",
|
|
"internal/syscall/unix",
|
|
"internal/testlog",
|
|
"os",
|
|
"math/bits",
|
|
"strconv",
|
|
"reflect",
|
|
"fmt",
|
|
"sort",
|
|
"strings",
|
|
"flag",
|
|
"runtime/debug",
|
|
"context",
|
|
"runtime/trace",
|
|
"testing",
|
|
"bufio",
|
|
"regexp/syntax",
|
|
"regexp",
|
|
"compress/flate",
|
|
"encoding/binary",
|
|
"hash",
|
|
"hash/crc32",
|
|
"compress/gzip",
|
|
"path/filepath",
|
|
"io/ioutil",
|
|
"text/tabwriter",
|
|
"runtime/pprof",
|
|
"testing/internal/testdeps",
|
|
}
|
|
return extraimports, 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}}
|
|
}
|
|
|
|
`))
|