go.tools/ssa: synthesize main functions for test packages.

Package.CreateTestMainFunction() creates a function called
main and adds it to the package.  This function calls
testing.Main in the Go library with the appropriate arguments:
slices of test, benchmark and example functions from the
package.

Tested by running the interpreter on the following tests:
- unicode/script_test.go
- unicode/digit_test.go
- hash/crc32/crc32_test.go
- path/path_test.go

It's also covered indirectly via the pointer analysis.

R=crawshaw, gri
CC=golang-dev
https://golang.org/cl/12814046
This commit is contained in:
Alan Donovan 2013-08-19 15:00:25 -04:00
parent 3b53279d8f
commit 50bd0e3288
2 changed files with 182 additions and 3 deletions

View File

@ -22,7 +22,7 @@ import (
//
// TODO(adonovan): integrate into the $GOROOT/test driver scripts,
// golden file checking, etc.
var gorootTests = []string{
var gorootTestTests = []string{
"235.go",
"alias1.go",
"chancap.go",
@ -139,6 +139,17 @@ var testdataTests = []string{
"mrvchain.go",
}
// These are files in $GOROOT/src/pkg/.
// These tests exercise the "testing" package.
var gorootSrcPkgTests = []string{
"unicode/script_test.go",
"unicode/digit_test.go",
"hash/crc32/crc32.go hash/crc32/crc32_generic.go hash/crc32/crc32_test.go",
"path/path.go path/path_test.go",
// TODO(adonovan): figure out the package loading error here:
// "strings.go strings/search.go strings/search_test.go",
}
func run(t *testing.T, dir, input string) bool {
fmt.Printf("Input: %s\n", input)
@ -182,8 +193,11 @@ func run(t *testing.T, dir, input string) bool {
}
prog.BuildAll()
mainPkg := prog.Package(info.Pkg)
mainPkg.CreateTestMainFunction() // (no-op if main already exists)
hint = fmt.Sprintf("To trace execution, run:\n%% go run src/code.google.com/p/go.tools/ssa/ssadump.go -build=C -run --interp=T %s\n", input)
if exitCode := interp.Interpret(prog.Package(info.Pkg), 0, inputs[0], []string{}); exitCode != 0 {
if exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}); exitCode != 0 {
t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode)
return false
}
@ -210,11 +224,17 @@ func TestInterp(t *testing.T) {
}
if !testing.Short() {
for _, input := range gorootTests {
for _, input := range gorootTestTests {
if !run(t, filepath.Join(build.Default.GOROOT, "test")+slash, input) {
failures = append(failures, input)
}
}
for _, input := range gorootSrcPkgTests {
if !run(t, filepath.Join(build.Default.GOROOT, "src/pkg")+slash, input) {
failures = append(failures, input)
}
}
}
if failures != nil {

159
ssa/testmain.go Normal file
View File

@ -0,0 +1,159 @@
package ssa
// CreateTestMainFunction synthesizes main functions for tests.
// It is closely coupled to src/cmd/go/test.go and src/pkg/testing.
import (
"go/token"
"strings"
"unicode"
"unicode/utf8"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
)
// CreateTestMainFunction adds to pkg (a test package) a synthetic
// main function similar to the one that would be created by the 'go
// test' tool. The synthetic function is returned.
//
// If the package already has a member named "main", the package
// remains unchanged; the result is that member if it's a function,
// nil if not.
//
func (pkg *Package) CreateTestMainFunction() *Function {
if main := pkg.Members["main"]; main != nil {
main, _ := main.(*Function)
return main
}
fn := &Function{
name: "main",
Signature: new(types.Signature),
Synthetic: "test main function",
Prog: pkg.Prog,
Pkg: pkg,
}
testingPkg := pkg.Prog.PackagesByPath["testing"]
if testingPkg == nil {
// If it doesn't import "testing", it can't be a test.
// 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)
// }
matcher := &Function{
name: "matcher",
Signature: testingMainParams.At(0).Type().(*types.Signature),
Synthetic: "test matcher predicate",
Enclosing: fn,
Pkg: fn.Pkg,
Prog: fn.Prog,
}
fn.AnonFuncs = append(fn.AnonFuncs, matcher)
matcher.startBody()
matcher.emit(&Ret{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}})
matcher.finishBody()
fn.startBody()
var c Call
c.Call.Value = testingMain
c.Call.Args = []Value{
matcher,
testMainSlice(fn, "Test", testingMainParams.At(1).Type()),
testMainSlice(fn, "Benchmark", testingMainParams.At(2).Type()),
testMainSlice(fn, "Example", testingMainParams.At(3).Type()),
}
// Emit: testing.Main(nil, tests, benchmarks, examples)
emitTailCall(fn, &c)
fn.finishBody()
pkg.Members["main"] = fn
return fn
}
// testMainSlice emits to fn code to construct a slice of type slice
// (one of []testing.Internal{Test,Benchmark,Example}) for all
// functions in this package 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, prefix string, slice types.Type) Value {
tElem := slice.(*types.Slice).Elem()
tFunc := tElem.Underlying().(*types.Struct).Field(1).Type()
var testfuncs []*Function
for name, mem := range fn.Pkg.Members {
if fn, ok := mem.(*Function); ok && isTest(name, prefix) && types.IsIdentical(fn.Signature, tFunc) {
testfuncs = append(testfuncs, fn)
}
}
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
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}