| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- // Copyright 2013 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.
- package ssa
- // CreateTestMainPackage synthesizes a main package that runs all the
- // tests of the supplied packages.
- // It is closely coupled to $GOROOT/src/cmd/go/test.go and $GOROOT/src/testing.
- //
- // TODO(adonovan): throws this all away now that x/tools/go/packages
- // provides access to the actual synthetic test main files.
- import (
- "bytes"
- "fmt"
- "go/ast"
- "go/parser"
- "go/types"
- "log"
- "os"
- "strings"
- "text/template"
- )
- // FindTests returns the Test, Benchmark, and Example functions
- // (as defined by "go test") defined in the specified package,
- // and its TestMain function, if any.
- //
- // Deprecated: use x/tools/go/packages to access synthetic testmain packages.
- func FindTests(pkg *Package) (tests, benchmarks, examples []*Function, main *Function) {
- prog := pkg.Prog
- // The first two of these may be nil: if the program doesn't import "testing",
- // it can't contain any tests, but it may yet contain Examples.
- var testSig *types.Signature // func(*testing.T)
- var benchmarkSig *types.Signature // func(*testing.B)
- var exampleSig = types.NewSignature(nil, nil, nil, false) // func()
- // Obtain the types from the parameters of testing.MainStart.
- if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
- mainStart := testingPkg.Func("MainStart")
- params := mainStart.Signature.Params()
- testSig = funcField(params.At(1).Type())
- benchmarkSig = funcField(params.At(2).Type())
- // Does the package define this function?
- // func TestMain(*testing.M)
- if f := pkg.Func("TestMain"); f != nil {
- sig := f.Type().(*types.Signature)
- starM := mainStart.Signature.Results().At(0).Type() // *testing.M
- if sig.Results().Len() == 0 &&
- sig.Params().Len() == 1 &&
- types.Identical(sig.Params().At(0).Type(), starM) {
- main = f
- }
- }
- }
- // TODO(adonovan): use a stable order, e.g. lexical.
- for _, mem := range pkg.Members {
- if f, ok := mem.(*Function); ok &&
- ast.IsExported(f.Name()) &&
- strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") {
- switch {
- case testSig != nil && isTestSig(f, "Test", testSig):
- tests = append(tests, f)
- case benchmarkSig != nil && isTestSig(f, "Benchmark", benchmarkSig):
- benchmarks = append(benchmarks, f)
- case isTestSig(f, "Example", exampleSig):
- examples = append(examples, f)
- default:
- continue
- }
- }
- }
- return
- }
- // Like isTest, but checks the signature too.
- func isTestSig(f *Function, prefix string, sig *types.Signature) bool {
- return isTest(f.Name(), prefix) && types.Identical(f.Signature, sig)
- }
- // Given the type of one of the three slice parameters of testing.Main,
- // returns the function type.
- func funcField(slice types.Type) *types.Signature {
- return slice.(*types.Slice).Elem().Underlying().(*types.Struct).Field(1).Type().(*types.Signature)
- }
- // 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.
- // Plundered from $GOROOT/src/cmd/go/test.go
- func isTest(name, prefix string) bool {
- if !strings.HasPrefix(name, prefix) {
- return false
- }
- if len(name) == len(prefix) { // "Test" is ok
- return true
- }
- return ast.IsExported(name[len(prefix):])
- }
- // CreateTestMainPackage creates and returns a synthetic "testmain"
- // package for the specified package if it defines tests, benchmarks or
- // executable examples, or nil otherwise. The new package is named
- // "main" and provides a function named "main" that runs the tests,
- // similar to the one that would be created by the 'go test' tool.
- //
- // Subsequent calls to prog.AllPackages include the new package.
- // The package pkg must belong to the program prog.
- //
- // Deprecated: use x/tools/go/packages to access synthetic testmain packages.
- func (prog *Program) CreateTestMainPackage(pkg *Package) *Package {
- if pkg.Prog != prog {
- log.Fatal("Package does not belong to Program")
- }
- // Template data
- var data struct {
- Pkg *Package
- Tests, Benchmarks, Examples []*Function
- Main *Function
- Go18 bool
- }
- data.Pkg = pkg
- // Enumerate tests.
- data.Tests, data.Benchmarks, data.Examples, data.Main = FindTests(pkg)
- if data.Main == nil &&
- data.Tests == nil && data.Benchmarks == nil && data.Examples == nil {
- return nil
- }
- // Synthesize source for testmain package.
- path := pkg.Pkg.Path() + "$testmain"
- tmpl := testmainTmpl
- if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
- // In Go 1.8, testing.MainStart's first argument is an interface, not a func.
- data.Go18 = types.IsInterface(testingPkg.Func("MainStart").Signature.Params().At(0).Type())
- } else {
- // The program does not import "testing", but FindTests
- // returned non-nil, which must mean there were Examples
- // but no Test, Benchmark, or TestMain functions.
- // We'll simply call them from testmain.main; this will
- // ensure they don't panic, but will not check any
- // "Output:" comments.
- // (We should not execute an Example that has no
- // "Output:" comment, but it's impossible to tell here.)
- tmpl = examplesOnlyTmpl
- }
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- log.Fatalf("internal error expanding template for %s: %v", path, err)
- }
- if false { // debugging
- fmt.Fprintln(os.Stderr, buf.String())
- }
- // Parse and type-check the testmain package.
- f, err := parser.ParseFile(prog.Fset, path+".go", &buf, parser.Mode(0))
- if err != nil {
- log.Fatalf("internal error parsing %s: %v", path, err)
- }
- conf := types.Config{
- DisableUnusedImportCheck: true,
- Importer: importer{pkg},
- }
- files := []*ast.File{f}
- info := &types.Info{
- Types: make(map[ast.Expr]types.TypeAndValue),
- Defs: make(map[*ast.Ident]types.Object),
- Uses: make(map[*ast.Ident]types.Object),
- Implicits: make(map[ast.Node]types.Object),
- Scopes: make(map[ast.Node]*types.Scope),
- Selections: make(map[*ast.SelectorExpr]*types.Selection),
- }
- testmainPkg, err := conf.Check(path, prog.Fset, files, info)
- if err != nil {
- log.Fatalf("internal error type-checking %s: %v", path, err)
- }
- // Create and build SSA code.
- testmain := prog.CreatePackage(testmainPkg, files, info, false)
- testmain.SetDebugMode(false)
- testmain.Build()
- testmain.Func("main").Synthetic = "test main function"
- testmain.Func("init").Synthetic = "package initializer"
- return testmain
- }
- // An implementation of types.Importer for an already loaded SSA program.
- type importer struct {
- pkg *Package // package under test; may be non-importable
- }
- func (imp importer) Import(path string) (*types.Package, error) {
- if p := imp.pkg.Prog.ImportedPackage(path); p != nil {
- return p.Pkg, nil
- }
- if path == imp.pkg.Pkg.Path() {
- return imp.pkg.Pkg, nil
- }
- return nil, fmt.Errorf("not found") // can't happen
- }
- var testmainTmpl = template.Must(template.New("testmain").Parse(`
- package main
- import "io"
- import "os"
- import "testing"
- import p {{printf "%q" .Pkg.Pkg.Path}}
- {{if .Go18}}
- type deps struct{}
- func (deps) ImportPath() string { return "" }
- func (deps) MatchString(pat, str string) (bool, error) { return true, nil }
- func (deps) StartCPUProfile(io.Writer) error { return nil }
- func (deps) StartTestLog(io.Writer) {}
- func (deps) StopCPUProfile() {}
- func (deps) StopTestLog() error { return nil }
- func (deps) WriteHeapProfile(io.Writer) error { return nil }
- func (deps) WriteProfileTo(string, io.Writer, int) error { return nil }
- var match deps
- {{else}}
- func match(_, _ string) (bool, error) { return true, nil }
- {{end}}
- func main() {
- tests := []testing.InternalTest{
- {{range .Tests}}
- { {{printf "%q" .Name}}, p.{{.Name}} },
- {{end}}
- }
- benchmarks := []testing.InternalBenchmark{
- {{range .Benchmarks}}
- { {{printf "%q" .Name}}, p.{{.Name}} },
- {{end}}
- }
- examples := []testing.InternalExample{
- {{range .Examples}}
- {Name: {{printf "%q" .Name}}, F: p.{{.Name}}},
- {{end}}
- }
- m := testing.MainStart(match, tests, benchmarks, examples)
- {{with .Main}}
- p.{{.Name}}(m)
- {{else}}
- os.Exit(m.Run())
- {{end}}
- }
- `))
- var examplesOnlyTmpl = template.Must(template.New("examples").Parse(`
- package main
- import p {{printf "%q" .Pkg.Pkg.Path}}
- func main() {
- {{range .Examples}}
- p.{{.Name}}()
- {{end}}
- }
- `))
|