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