github.com/jd-ly/tools@v0.5.7/go/ssa/testmain.go (about)

     1  // Copyright 2013 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  package ssa
     6  
     7  // CreateTestMainPackage synthesizes a main package that runs all the
     8  // tests of the supplied packages.
     9  // It is closely coupled to $GOROOT/src/cmd/go/test.go and $GOROOT/src/testing.
    10  //
    11  // TODO(adonovan): throws this all away now that x/tools/go/packages
    12  // provides access to the actual synthetic test main files.
    13  
    14  import (
    15  	"bytes"
    16  	"fmt"
    17  	"go/ast"
    18  	"go/parser"
    19  	"go/types"
    20  	"log"
    21  	"os"
    22  	"strings"
    23  	"text/template"
    24  )
    25  
    26  // FindTests returns the Test, Benchmark, and Example functions
    27  // (as defined by "go test") defined in the specified package,
    28  // and its TestMain function, if any.
    29  //
    30  // Deprecated: Use github.com/jd-ly/tools/go/packages to access synthetic
    31  // testmain packages.
    32  func FindTests(pkg *Package) (tests, benchmarks, examples []*Function, main *Function) {
    33  	prog := pkg.Prog
    34  
    35  	// The first two of these may be nil: if the program doesn't import "testing",
    36  	// it can't contain any tests, but it may yet contain Examples.
    37  	var testSig *types.Signature                              // func(*testing.T)
    38  	var benchmarkSig *types.Signature                         // func(*testing.B)
    39  	var exampleSig = types.NewSignature(nil, nil, nil, false) // func()
    40  
    41  	// Obtain the types from the parameters of testing.MainStart.
    42  	if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
    43  		mainStart := testingPkg.Func("MainStart")
    44  		params := mainStart.Signature.Params()
    45  		testSig = funcField(params.At(1).Type())
    46  		benchmarkSig = funcField(params.At(2).Type())
    47  
    48  		// Does the package define this function?
    49  		//   func TestMain(*testing.M)
    50  		if f := pkg.Func("TestMain"); f != nil {
    51  			sig := f.Type().(*types.Signature)
    52  			starM := mainStart.Signature.Results().At(0).Type() // *testing.M
    53  			if sig.Results().Len() == 0 &&
    54  				sig.Params().Len() == 1 &&
    55  				types.Identical(sig.Params().At(0).Type(), starM) {
    56  				main = f
    57  			}
    58  		}
    59  	}
    60  
    61  	// TODO(adonovan): use a stable order, e.g. lexical.
    62  	for _, mem := range pkg.Members {
    63  		if f, ok := mem.(*Function); ok &&
    64  			ast.IsExported(f.Name()) &&
    65  			strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") {
    66  
    67  			switch {
    68  			case testSig != nil && isTestSig(f, "Test", testSig):
    69  				tests = append(tests, f)
    70  			case benchmarkSig != nil && isTestSig(f, "Benchmark", benchmarkSig):
    71  				benchmarks = append(benchmarks, f)
    72  			case isTestSig(f, "Example", exampleSig):
    73  				examples = append(examples, f)
    74  			default:
    75  				continue
    76  			}
    77  		}
    78  	}
    79  	return
    80  }
    81  
    82  // Like isTest, but checks the signature too.
    83  func isTestSig(f *Function, prefix string, sig *types.Signature) bool {
    84  	return isTest(f.Name(), prefix) && types.Identical(f.Signature, sig)
    85  }
    86  
    87  // Given the type of one of the three slice parameters of testing.Main,
    88  // returns the function type.
    89  func funcField(slice types.Type) *types.Signature {
    90  	return slice.(*types.Slice).Elem().Underlying().(*types.Struct).Field(1).Type().(*types.Signature)
    91  }
    92  
    93  // isTest tells whether name looks like a test (or benchmark, according to prefix).
    94  // It is a Test (say) if there is a character after Test that is not a lower-case letter.
    95  // We don't want TesticularCancer.
    96  // Plundered from $GOROOT/src/cmd/go/test.go
    97  func isTest(name, prefix string) bool {
    98  	if !strings.HasPrefix(name, prefix) {
    99  		return false
   100  	}
   101  	if len(name) == len(prefix) { // "Test" is ok
   102  		return true
   103  	}
   104  	return ast.IsExported(name[len(prefix):])
   105  }
   106  
   107  // CreateTestMainPackage creates and returns a synthetic "testmain"
   108  // package for the specified package if it defines tests, benchmarks or
   109  // executable examples, or nil otherwise.  The new package is named
   110  // "main" and provides a function named "main" that runs the tests,
   111  // similar to the one that would be created by the 'go test' tool.
   112  //
   113  // Subsequent calls to prog.AllPackages include the new package.
   114  // The package pkg must belong to the program prog.
   115  //
   116  // Deprecated: Use github.com/jd-ly/tools/go/packages to access synthetic
   117  // testmain packages.
   118  func (prog *Program) CreateTestMainPackage(pkg *Package) *Package {
   119  	if pkg.Prog != prog {
   120  		log.Fatal("Package does not belong to Program")
   121  	}
   122  
   123  	// Template data
   124  	var data struct {
   125  		Pkg                         *Package
   126  		Tests, Benchmarks, Examples []*Function
   127  		Main                        *Function
   128  		Go18                        bool
   129  	}
   130  	data.Pkg = pkg
   131  
   132  	// Enumerate tests.
   133  	data.Tests, data.Benchmarks, data.Examples, data.Main = FindTests(pkg)
   134  	if data.Main == nil &&
   135  		data.Tests == nil && data.Benchmarks == nil && data.Examples == nil {
   136  		return nil
   137  	}
   138  
   139  	// Synthesize source for testmain package.
   140  	path := pkg.Pkg.Path() + "$testmain"
   141  	tmpl := testmainTmpl
   142  	if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
   143  		// In Go 1.8, testing.MainStart's first argument is an interface, not a func.
   144  		data.Go18 = types.IsInterface(testingPkg.Func("MainStart").Signature.Params().At(0).Type())
   145  	} else {
   146  		// The program does not import "testing", but FindTests
   147  		// returned non-nil, which must mean there were Examples
   148  		// but no Test, Benchmark, or TestMain functions.
   149  
   150  		// We'll simply call them from testmain.main; this will
   151  		// ensure they don't panic, but will not check any
   152  		// "Output:" comments.
   153  		// (We should not execute an Example that has no
   154  		// "Output:" comment, but it's impossible to tell here.)
   155  		tmpl = examplesOnlyTmpl
   156  	}
   157  	var buf bytes.Buffer
   158  	if err := tmpl.Execute(&buf, data); err != nil {
   159  		log.Fatalf("internal error expanding template for %s: %v", path, err)
   160  	}
   161  	if false { // debugging
   162  		fmt.Fprintln(os.Stderr, buf.String())
   163  	}
   164  
   165  	// Parse and type-check the testmain package.
   166  	f, err := parser.ParseFile(prog.Fset, path+".go", &buf, parser.Mode(0))
   167  	if err != nil {
   168  		log.Fatalf("internal error parsing %s: %v", path, err)
   169  	}
   170  	conf := types.Config{
   171  		DisableUnusedImportCheck: true,
   172  		Importer:                 importer{pkg},
   173  	}
   174  	files := []*ast.File{f}
   175  	info := &types.Info{
   176  		Types:      make(map[ast.Expr]types.TypeAndValue),
   177  		Defs:       make(map[*ast.Ident]types.Object),
   178  		Uses:       make(map[*ast.Ident]types.Object),
   179  		Implicits:  make(map[ast.Node]types.Object),
   180  		Scopes:     make(map[ast.Node]*types.Scope),
   181  		Selections: make(map[*ast.SelectorExpr]*types.Selection),
   182  	}
   183  	testmainPkg, err := conf.Check(path, prog.Fset, files, info)
   184  	if err != nil {
   185  		log.Fatalf("internal error type-checking %s: %v", path, err)
   186  	}
   187  
   188  	// Create and build SSA code.
   189  	testmain := prog.CreatePackage(testmainPkg, files, info, false)
   190  	testmain.SetDebugMode(false)
   191  	testmain.Build()
   192  	testmain.Func("main").Synthetic = "test main function"
   193  	testmain.Func("init").Synthetic = "package initializer"
   194  	return testmain
   195  }
   196  
   197  // An implementation of types.Importer for an already loaded SSA program.
   198  type importer struct {
   199  	pkg *Package // package under test; may be non-importable
   200  }
   201  
   202  func (imp importer) Import(path string) (*types.Package, error) {
   203  	if p := imp.pkg.Prog.ImportedPackage(path); p != nil {
   204  		return p.Pkg, nil
   205  	}
   206  	if path == imp.pkg.Pkg.Path() {
   207  		return imp.pkg.Pkg, nil
   208  	}
   209  	return nil, fmt.Errorf("not found") // can't happen
   210  }
   211  
   212  var testmainTmpl = template.Must(template.New("testmain").Parse(`
   213  package main
   214  
   215  import "io"
   216  import "os"
   217  import "testing"
   218  import p {{printf "%q" .Pkg.Pkg.Path}}
   219  
   220  {{if .Go18}}
   221  type deps struct{}
   222  
   223  func (deps) ImportPath() string { return "" }
   224  func (deps) MatchString(pat, str string) (bool, error) { return true, nil }
   225  func (deps) SetPanicOnExit0(bool) {}
   226  func (deps) StartCPUProfile(io.Writer) error { return nil }
   227  func (deps) StartTestLog(io.Writer) {}
   228  func (deps) StopCPUProfile() {}
   229  func (deps) StopTestLog() error { return nil }
   230  func (deps) WriteHeapProfile(io.Writer) error { return nil }
   231  func (deps) WriteProfileTo(string, io.Writer, int) error { return nil }
   232  
   233  var match deps
   234  {{else}}
   235  func match(_, _ string) (bool, error) { return true, nil }
   236  {{end}}
   237  
   238  func main() {
   239  	tests := []testing.InternalTest{
   240  {{range .Tests}}
   241  		{ {{printf "%q" .Name}}, p.{{.Name}} },
   242  {{end}}
   243  	}
   244  	benchmarks := []testing.InternalBenchmark{
   245  {{range .Benchmarks}}
   246  		{ {{printf "%q" .Name}}, p.{{.Name}} },
   247  {{end}}
   248  	}
   249  	examples := []testing.InternalExample{
   250  {{range .Examples}}
   251  		{Name: {{printf "%q" .Name}}, F: p.{{.Name}}},
   252  {{end}}
   253  	}
   254  	m := testing.MainStart(match, tests, benchmarks, examples)
   255  {{with .Main}}
   256  	p.{{.Name}}(m)
   257  {{else}}
   258  	os.Exit(m.Run())
   259  {{end}}
   260  }
   261  
   262  `))
   263  
   264  var examplesOnlyTmpl = template.Must(template.New("examples").Parse(`
   265  package main
   266  
   267  import p {{printf "%q" .Pkg.Pkg.Path}}
   268  
   269  func main() {
   270  {{range .Examples}}
   271  	p.{{.Name}}()
   272  {{end}}
   273  }
   274  `))