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:
parent
3b53279d8f
commit
50bd0e3288
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
Loading…
Reference in New Issue