blob: 51c8d4ed95c60293485717fa32658effc61542ef [file] [log] [blame]
// 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 src/cmd/go/test.go and src/pkg/testing.
import (
"go/ast"
"go/token"
"os"
"strings"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
)
// CreateTestMainPackage creates and returns a synthetic "main"
// package that runs all the tests of the supplied packages, similar
// to the one that would be created by the 'go test' tool.
//
// It returns nil if the program contains no tests.
//
func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package {
if len(pkgs) == 0 {
return nil
}
testmain := &Package{
Prog: prog,
Members: make(map[string]Member),
values: make(map[types.Object]Value),
Object: types.NewPackage("testmain", "testmain"),
}
// Build package's init function.
init := &Function{
name: "init",
Signature: new(types.Signature),
Synthetic: "package initializer",
Pkg: testmain,
Prog: prog,
}
init.startBody()
// TODO(adonovan): use lexical order.
var expfuncs []*Function // all exported functions of *_test.go in pkgs, unordered
for _, pkg := range pkgs {
if pkg.Prog != prog {
panic("wrong Program")
}
// Initialize package to test.
var v Call
v.Call.Value = pkg.init
v.setType(types.NewTuple())
init.emit(&v)
// Enumerate its possible tests/benchmarks.
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") {
expfuncs = append(expfuncs, f)
}
}
}
init.emit(new(Return))
init.finishBody()
testmain.init = init
testmain.Object.MarkComplete()
testmain.Members[init.name] = init
testingPkg := prog.ImportedPackage("testing")
if testingPkg == nil {
// If the program doesn't import "testing", it can't
// contain any tests.
// TODO(adonovan): but it might contain Examples.
// Support them (by just calling them directly).
return nil
}
testingMain := testingPkg.Func("Main")
testingMainParams := testingMain.Signature.Params()
// The generated code is as if compiled from this:
//
// func main() {
// match := func(_, _ string) (bool, error) { return true, nil }
// tests := []testing.InternalTest{{"TestFoo", TestFoo}, ...}
// benchmarks := []testing.InternalBenchmark{...}
// examples := []testing.InternalExample{...}
// testing.Main(match, tests, benchmarks, examples)
// }
main := &Function{
name: "main",
Signature: new(types.Signature),
Synthetic: "test main function",
Prog: prog,
Pkg: testmain,
}
matcher := &Function{
name: "matcher",
Signature: testingMainParams.At(0).Type().(*types.Signature),
Synthetic: "test matcher predicate",
Enclosing: main,
Pkg: testmain,
Prog: prog,
}
main.AnonFuncs = append(main.AnonFuncs, matcher)
matcher.startBody()
matcher.emit(&Return{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}})
matcher.finishBody()
main.startBody()
var c Call
c.Call.Value = testingMain
tests := testMainSlice(main, expfuncs, "Test", testingMainParams.At(1).Type())
benchmarks := testMainSlice(main, expfuncs, "Benchmark", testingMainParams.At(2).Type())
examples := testMainSlice(main, expfuncs, "Example", testingMainParams.At(3).Type())
_, noTests := tests.(*Const) // i.e. nil slice
_, noBenchmarks := benchmarks.(*Const)
_, noExamples := examples.(*Const)
if noTests && noBenchmarks && noExamples {
return nil
}
c.Call.Args = []Value{matcher, tests, benchmarks, examples}
// Emit: testing.Main(nil, tests, benchmarks, examples)
emitTailCall(main, &c)
main.finishBody()
testmain.Members["main"] = main
if prog.mode&LogPackages != 0 {
testmain.WriteTo(os.Stderr)
}
if prog.mode&SanityCheckFunctions != 0 {
sanityCheckPackage(testmain)
}
prog.packages[testmain.Object] = testmain
return testmain
}
// testMainSlice emits to fn code to construct a slice of type slice
// (one of []testing.Internal{Test,Benchmark,Example}) for all
// functions in expfuncs whose name starts with prefix (one of
// "Test", "Benchmark" or "Example") and whose type is appropriate.
// It returns the slice value.
//
func testMainSlice(fn *Function, expfuncs []*Function, prefix string, slice types.Type) Value {
tElem := slice.(*types.Slice).Elem()
tFunc := tElem.Underlying().(*types.Struct).Field(1).Type()
var testfuncs []*Function
for _, f := range expfuncs {
if isTest(f.Name(), prefix) && types.Identical(f.Signature, tFunc) {
testfuncs = append(testfuncs, f)
}
}
if testfuncs == nil {
return nilConst(slice)
}
tString := types.Typ[types.String]
tPtrString := types.NewPointer(tString)
tPtrElem := types.NewPointer(tElem)
tPtrFunc := types.NewPointer(tFunc)
// Emit: array = new [n]testing.InternalTest
tArray := types.NewArray(tElem, int64(len(testfuncs)))
array := emitNew(fn, tArray, token.NoPos)
array.Comment = "test main"
for i, testfunc := range testfuncs {
// Emit: pitem = &array[i]
ia := &IndexAddr{X: array, Index: intConst(int64(i))}
ia.setType(tPtrElem)
pitem := fn.emit(ia)
// Emit: pname = &pitem.Name
fa := &FieldAddr{X: pitem, Field: 0} // .Name
fa.setType(tPtrString)
pname := fn.emit(fa)
// Emit: *pname = "testfunc"
emitStore(fn, pname, NewConst(exact.MakeString(testfunc.Name()), tString))
// Emit: pfunc = &pitem.F
fa = &FieldAddr{X: pitem, Field: 1} // .F
fa.setType(tPtrFunc)
pfunc := fn.emit(fa)
// Emit: *pfunc = testfunc
emitStore(fn, pfunc, testfunc)
}
// Emit: slice array[:]
sl := &Slice{X: array}
sl.setType(slice)
return fn.emit(sl)
}
// Plundered from $GOROOT/src/cmd/go/test.go
// 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.
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):])
}