 6a979fb40d
			
		
	
	
		6a979fb40d
		
			
		
	
	
	
	
		
			
			* update staticcheck Don't fork staticcheck: use the upstream version. Remove unneeded SSA loading. * Cache go/analysis facts Don't load unneeded packages for go/analysis. Repeated run of go/analysis linters now 10x faster (2s vs 20s on this repo) than before.
		
			
				
	
	
		
			272 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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}}
 | |
| }
 | |
| `))
 |