gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/go/packages/golist_fallback_testmain.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file is largely based on the Go 1.10-era cmd/go/internal/test/test.go
     6  // testmain generation code.
     7  
     8  package packages
     9  
    10  import (
    11  	"errors"
    12  	"fmt"
    13  	"go/ast"
    14  	"go/doc"
    15  	"go/parser"
    16  	"go/token"
    17  	"os"
    18  	"sort"
    19  	"strings"
    20  	"text/template"
    21  	"unicode"
    22  	"unicode/utf8"
    23  )
    24  
    25  // TODO(matloob): Delete this file once Go 1.12 is released.
    26  
    27  // This file complements golist_fallback.go by providing
    28  // support for generating testmains.
    29  
    30  func generateTestmain(out string, testPkg, xtestPkg *Package) (extraimports, extradeps []string, err error) {
    31  	testFuncs, err := loadTestFuncs(testPkg, xtestPkg)
    32  	if err != nil {
    33  		return nil, nil, err
    34  	}
    35  	extraimports = []string{"testing", "testing/internal/testdeps"}
    36  	if testFuncs.TestMain == nil {
    37  		extraimports = append(extraimports, "os")
    38  	}
    39  	// Transitive dependencies of ("testing", "testing/internal/testdeps").
    40  	// os is part of the transitive closure so it and its transitive dependencies are
    41  	// included regardless of whether it's imported in the template below.
    42  	extradeps = []string{
    43  		"errors",
    44  		"internal/cpu",
    45  		"unsafe",
    46  		"internal/bytealg",
    47  		"internal/race",
    48  		"runtime/internal/atomic",
    49  		"runtime/internal/sys",
    50  		"runtime",
    51  		"sync/atomic",
    52  		"sync",
    53  		"io",
    54  		"unicode",
    55  		"unicode/utf8",
    56  		"bytes",
    57  		"math",
    58  		"syscall",
    59  		"time",
    60  		"internal/poll",
    61  		"internal/syscall/unix",
    62  		"internal/testlog",
    63  		"os",
    64  		"math/bits",
    65  		"strconv",
    66  		"reflect",
    67  		"fmt",
    68  		"sort",
    69  		"strings",
    70  		"flag",
    71  		"runtime/debug",
    72  		"context",
    73  		"runtime/trace",
    74  		"testing",
    75  		"bufio",
    76  		"regexp/syntax",
    77  		"regexp",
    78  		"compress/flate",
    79  		"encoding/binary",
    80  		"hash",
    81  		"hash/crc32",
    82  		"compress/gzip",
    83  		"path/filepath",
    84  		"io/ioutil",
    85  		"text/tabwriter",
    86  		"runtime/pprof",
    87  		"testing/internal/testdeps",
    88  	}
    89  	return extraimports, extradeps, writeTestmain(out, testFuncs)
    90  }
    91  
    92  // The following is adapted from the cmd/go testmain generation code.
    93  
    94  // isTestFunc tells whether fn has the type of a testing function. arg
    95  // specifies the parameter type we look for: B, M or T.
    96  func isTestFunc(fn *ast.FuncDecl, arg string) bool {
    97  	if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
    98  		fn.Type.Params.List == nil ||
    99  		len(fn.Type.Params.List) != 1 ||
   100  		len(fn.Type.Params.List[0].Names) > 1 {
   101  		return false
   102  	}
   103  	ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
   104  	if !ok {
   105  		return false
   106  	}
   107  	// We can't easily check that the type is *testing.M
   108  	// because we don't know how testing has been imported,
   109  	// but at least check that it's *M or *something.M.
   110  	// Same applies for B and T.
   111  	if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
   112  		return true
   113  	}
   114  	if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
   115  		return true
   116  	}
   117  	return false
   118  }
   119  
   120  // isTest tells whether name looks like a test (or benchmark, according to prefix).
   121  // It is a Test (say) if there is a character after Test that is not a lower-case letter.
   122  // We don't want TesticularCancer.
   123  func isTest(name, prefix string) bool {
   124  	if !strings.HasPrefix(name, prefix) {
   125  		return false
   126  	}
   127  	if len(name) == len(prefix) { // "Test" is ok
   128  		return true
   129  	}
   130  	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
   131  	return !unicode.IsLower(rune)
   132  }
   133  
   134  // loadTestFuncs returns the testFuncs describing the tests that will be run.
   135  func loadTestFuncs(ptest, pxtest *Package) (*testFuncs, error) {
   136  	t := &testFuncs{
   137  		TestPackage:  ptest,
   138  		XTestPackage: pxtest,
   139  	}
   140  	for _, file := range ptest.GoFiles {
   141  		if !strings.HasSuffix(file, "_test.go") {
   142  			continue
   143  		}
   144  		if err := t.load(file, "_test", &t.ImportTest, &t.NeedTest); err != nil {
   145  			return nil, err
   146  		}
   147  	}
   148  	if pxtest != nil {
   149  		for _, file := range pxtest.GoFiles {
   150  			if err := t.load(file, "_xtest", &t.ImportXtest, &t.NeedXtest); err != nil {
   151  				return nil, err
   152  			}
   153  		}
   154  	}
   155  	return t, nil
   156  }
   157  
   158  // writeTestmain writes the _testmain.go file for t to the file named out.
   159  func writeTestmain(out string, t *testFuncs) error {
   160  	f, err := os.Create(out)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	defer f.Close()
   165  
   166  	if err := testmainTmpl.Execute(f, t); err != nil {
   167  		return err
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  type testFuncs struct {
   174  	Tests        []testFunc
   175  	Benchmarks   []testFunc
   176  	Examples     []testFunc
   177  	TestMain     *testFunc
   178  	TestPackage  *Package
   179  	XTestPackage *Package
   180  	ImportTest   bool
   181  	NeedTest     bool
   182  	ImportXtest  bool
   183  	NeedXtest    bool
   184  }
   185  
   186  // Tested returns the name of the package being tested.
   187  func (t *testFuncs) Tested() string {
   188  	return t.TestPackage.Name
   189  }
   190  
   191  type testFunc struct {
   192  	Package   string // imported package name (_test or _xtest)
   193  	Name      string // function name
   194  	Output    string // output, for examples
   195  	Unordered bool   // output is allowed to be unordered.
   196  }
   197  
   198  func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
   199  	var fset = token.NewFileSet()
   200  
   201  	f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
   202  	if err != nil {
   203  		return errors.New("failed to parse test file " + filename)
   204  	}
   205  	for _, d := range f.Decls {
   206  		n, ok := d.(*ast.FuncDecl)
   207  		if !ok {
   208  			continue
   209  		}
   210  		if n.Recv != nil {
   211  			continue
   212  		}
   213  		name := n.Name.String()
   214  		switch {
   215  		case name == "TestMain":
   216  			if isTestFunc(n, "T") {
   217  				t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
   218  				*doImport, *seen = true, true
   219  				continue
   220  			}
   221  			err := checkTestFunc(fset, n, "M")
   222  			if err != nil {
   223  				return err
   224  			}
   225  			if t.TestMain != nil {
   226  				return errors.New("multiple definitions of TestMain")
   227  			}
   228  			t.TestMain = &testFunc{pkg, name, "", false}
   229  			*doImport, *seen = true, true
   230  		case isTest(name, "Test"):
   231  			err := checkTestFunc(fset, n, "T")
   232  			if err != nil {
   233  				return err
   234  			}
   235  			t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
   236  			*doImport, *seen = true, true
   237  		case isTest(name, "Benchmark"):
   238  			err := checkTestFunc(fset, n, "B")
   239  			if err != nil {
   240  				return err
   241  			}
   242  			t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
   243  			*doImport, *seen = true, true
   244  		}
   245  	}
   246  	ex := doc.Examples(f)
   247  	sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order })
   248  	for _, e := range ex {
   249  		*doImport = true // import test file whether executed or not
   250  		if e.Output == "" && !e.EmptyOutput {
   251  			// Don't run examples with no output.
   252  			continue
   253  		}
   254  		t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
   255  		*seen = true
   256  	}
   257  	return nil
   258  }
   259  
   260  func checkTestFunc(fset *token.FileSet, fn *ast.FuncDecl, arg string) error {
   261  	if !isTestFunc(fn, arg) {
   262  		name := fn.Name.String()
   263  		pos := fset.Position(fn.Pos())
   264  		return fmt.Errorf("%s: wrong signature for %s, must be: func %s(%s *testing.%s)", pos, name, name, strings.ToLower(arg), arg)
   265  	}
   266  	return nil
   267  }
   268  
   269  var testmainTmpl = template.Must(template.New("main").Parse(`
   270  package main
   271  
   272  import (
   273  {{if not .TestMain}}
   274  	"os"
   275  {{end}}
   276  	"testing"
   277  	"testing/internal/testdeps"
   278  
   279  {{if .ImportTest}}
   280  	{{if .NeedTest}}_test{{else}}_{{end}} {{.TestPackage.PkgPath | printf "%q"}}
   281  {{end}}
   282  {{if .ImportXtest}}
   283  	{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.XTestPackage.PkgPath | printf "%q"}}
   284  {{end}}
   285  )
   286  
   287  var tests = []testing.InternalTest{
   288  {{range .Tests}}
   289  	{"{{.Name}}", {{.Package}}.{{.Name}}},
   290  {{end}}
   291  }
   292  
   293  var benchmarks = []testing.InternalBenchmark{
   294  {{range .Benchmarks}}
   295  	{"{{.Name}}", {{.Package}}.{{.Name}}},
   296  {{end}}
   297  }
   298  
   299  var examples = []testing.InternalExample{
   300  {{range .Examples}}
   301  	{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
   302  {{end}}
   303  }
   304  
   305  func init() {
   306  	testdeps.ImportPath = {{.TestPackage.PkgPath | printf "%q"}}
   307  }
   308  
   309  func main() {
   310  	m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
   311  {{with .TestMain}}
   312  	{{.Package}}.{{.Name}}(m)
   313  {{else}}
   314  	os.Exit(m.Run())
   315  {{end}}
   316  }
   317  
   318  `))