1
0
mirror of https://github.com/golang/go synced 2024-11-19 07:04:43 -07:00
go/ssa/testmain.go
Alan Donovan 8636f40baf go.tools/ssa: CreateTestMainPackage: synthesize test driver as a package ("testmain") not 'main' function.
This allows us to run/analyze multiple tests.
Also it causes the production code packages to be properly initialized.

Also:
- cmd/ssadump: improved usage message (add example;
  incorporate LoadInitialPackages usage; explain how -run
  finds main).
- pointer, oracle, ssa/interp: use CreateTestMainPackage.
- ssa/builder.go: remove 'rundefers' instruction from package init,
  which no longer uses 'defer'.

R=gri
CC=golang-dev
https://golang.org/cl/15920047
2013-10-23 18:07:53 -04:00

205 lines
5.8 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 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 {
testmain := &Package{
Prog: prog,
Members: make(map[string]Member),
values: make(map[types.Object]Value),
Object: types.NewPackage("testmain", "testmain", nil),
}
prog.packages[testmain.Object] = testmain
// Build package's init function.
init := &Function{
name: "init",
Signature: new(types.Signature),
Synthetic: "package initializer",
Pkg: testmain,
Prog: prog,
}
init.startBody()
var expfuncs []*Function // all exported functions of pkgs, unordered
for _, pkg := range pkgs {
// 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()) {
expfuncs = append(expfuncs, f)
}
}
}
init.emit(new(Return))
init.finishBody()
testmain.init = init
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
c.Call.Args = []Value{
matcher,
testMainSlice(main, expfuncs, "Test", testingMainParams.At(1).Type()),
testMainSlice(main, expfuncs, "Benchmark", testingMainParams.At(2).Type()),
testMainSlice(main, expfuncs, "Example", testingMainParams.At(3).Type()),
}
// Emit: testing.Main(nil, tests, benchmarks, examples)
emitTailCall(main, &c)
main.finishBody()
testmain.Members["main"] = main
if prog.mode&LogPackages != 0 {
testmain.DumpTo(os.Stderr)
}
if prog.mode&SanityCheckFunctions != 0 {
sanityCheckPackage(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 {
// TODO(adonovan): only look at members defined in *_test.go files.
if isTest(f.Name(), prefix) && types.IsIdentical(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):])
}