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
This commit is contained in:
Alan Donovan 2013-10-23 18:07:53 -04:00
parent 87ced824bd
commit 8636f40baf
6 changed files with 128 additions and 68 deletions

View File

@ -40,13 +40,18 @@ T [T]race execution of the program. Best for single-threaded programs!
`)
const usage = `SSA builder and interpreter.
Usage: ssadump [<flag> ...] [<file.go> ...] [<arg> ...]
ssadump [<flag> ...] <import/path> [<arg> ...]
Usage: ssadump [<flag> ...] <args> ...
Use -help flag to display options.
Examples:
% ssadump -run -interp=T hello.go # interpret a program, with tracing
% ssadump -build=FPG hello.go # quickly dump SSA form of a single package
% ssadump -run -interp=T hello.go # interpret a program, with tracing
% ssadump -run unicode -- -test.v # interpret the unicode package's tests, verbosely
` + importer.InitialPackagesUsage +
`
When -run is specified, ssadump will find the first package that
defines a main function and run it in the interpreter.
If none is found, the tests of each package will be run instead.
`
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
@ -140,6 +145,7 @@ func main() {
if err := prog.CreatePackages(imp); err != nil {
log.Fatal(err)
}
if debugMode {
for _, pkg := range prog.AllPackages() {
pkg.SetDebugMode(true)
@ -147,18 +153,25 @@ func main() {
}
prog.BuildAll()
// Run the interpreter on the first package with a main function.
// Run the interpreter.
if *runFlag {
// If some package defines main, run that.
// Otherwise run all package's tests.
var main *ssa.Package
var pkgs []*ssa.Package
for _, info := range infos {
pkg := prog.Package(info.Pkg)
if pkg.Func("main") != nil || pkg.CreateTestMainFunction() != nil {
if pkg.Func("main") != nil {
main = pkg
break
}
pkgs = append(pkgs, pkg)
}
if main == nil && pkgs != nil {
main = prog.CreateTestMainPackage(pkgs...)
}
if main == nil {
log.Fatal("No main function")
log.Fatal("No main package and no tests")
}
interp.Interpret(main, interpMode, main.Object.Path(), args)
}

View File

@ -253,20 +253,27 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
return nil, err
}
// Initial packages (specified on command line)
// For each initial package (specified on the command line),
// if it has a main function, analyze that,
// otherwise analyze its tests, if any.
var testPkgs []*ssa.Package
for _, info := range initialPkgInfos {
initialPkg := o.prog.Package(info.Pkg)
// Add package to the pointer analysis scope.
if initialPkg.Func("main") == nil {
// TODO(adonovan): to simulate 'go test' more faithfully, we
// should build a single synthetic testmain package,
// not synthetic main functions to many packages.
if initialPkg.CreateTestMainFunction() == nil {
return nil, fmt.Errorf("analysis scope has no main() entry points")
}
if initialPkg.Func("main") != nil {
o.config.Mains = append(o.config.Mains, initialPkg)
} else {
testPkgs = append(testPkgs, initialPkg)
}
o.config.Mains = append(o.config.Mains, initialPkg)
}
if testPkgs != nil {
if p := o.prog.CreateTestMainPackage(testPkgs...); p != nil {
o.config.Mains = append(o.config.Mains, p)
}
}
if o.config.Mains == nil {
return nil, fmt.Errorf("analysis scope has no main and no tests")
}
if needs&needSSADebug != 0 {

View File

@ -185,8 +185,7 @@ func doOneInput(input, filename string) bool {
ptrmain := mainpkg // main package for the pointer analysis
if mainpkg.Func("main") == nil {
// No main function; assume it's a test.
mainpkg.CreateTestMainFunction()
// fmt.Printf("%s: synthesized testmain package for test.\n", imp.Fset.Position(f.Package))
ptrmain = prog.CreateTestMainPackage(mainpkg)
}
ok := true

View File

@ -10,8 +10,8 @@ package ssa
// (create.go), all packages are constructed and type-checked and
// definitions of all package members are created, method-sets are
// computed, and wrapper methods are synthesized. The create phase
// occurs sequentially (order is unimportant) as the client calls
// Program.CreatePackage.
// proceeds in topological order over the import dependency graph,
// initiated by client calls to Program.CreatePackage.
//
// In the BUILD phase (builder.go), the builder traverses the AST of
// each Go source function and generates SSA instructions for the
@ -2319,7 +2319,9 @@ func (p *Package) Build() {
if !atomic.CompareAndSwapInt32(&p.started, 0, 1) {
return // already started
}
if p.info == nil {
return // synthetic package, e.g. "testmain"
}
// Ensure we have runtime type info for all exported members.
// TODO(adonovan): ideally belongs in memberFromObject, but
// that would require package creation in topological order.
@ -2328,11 +2330,6 @@ func (p *Package) Build() {
p.needMethodsOf(obj.Type())
}
}
if p.info.Files == nil {
p.info = nil
return // nothing to do
}
if p.Prog.mode&LogSource != 0 {
defer logStack("build %s", p)()
}
@ -2391,7 +2388,6 @@ func (p *Package) Build() {
// Finish up init().
emitJump(init, done)
init.currentBlock = done
init.emit(new(RunDefers))
init.emit(new(Return))
init.finishBody()

View File

@ -169,6 +169,8 @@ func run(t *testing.T, dir, input string) bool {
imp := importer.New(&importer.Config{Build: &build.Default})
// TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles.
// Then add the following packages' tests, which pass:
// "flag", "unicode", "unicode/utf8", "testing", "log", "path".
files, err := importer.ParseFiles(imp.Fset, ".", inputs...)
if err != nil {
t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error())
@ -203,7 +205,9 @@ func run(t *testing.T, dir, input string) bool {
prog.BuildAll()
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.CreateTestMainFunction() // (no-op if main already exists)
if mainPkg.Func("main") == nil {
mainPkg = prog.CreateTestMainPackage(mainPkg)
}
var out bytes.Buffer
interp.CapturedOutput = &out

View File

@ -4,43 +4,68 @@
package ssa
// CreateTestMainFunction synthesizes main functions for tests.
// 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"
"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.
// 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.
//
// 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.
// It returns nil if the program contains no tests.
//
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,
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
testingPkg := pkg.Prog.ImportedPackage("testing")
// 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 it doesn't import "testing", it can't be a test.
// 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
@ -58,50 +83,67 @@ func (pkg *Package) CreateTestMainFunction() *Function {
// 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: fn,
Pkg: fn.Pkg,
Prog: fn.Prog,
Enclosing: main,
Pkg: testmain,
Prog: prog,
}
fn.AnonFuncs = append(fn.AnonFuncs, matcher)
main.AnonFuncs = append(main.AnonFuncs, matcher)
matcher.startBody()
matcher.emit(&Return{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}})
matcher.finishBody()
fn.startBody()
main.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()),
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(fn, &c)
fn.finishBody()
emitTailCall(main, &c)
main.finishBody()
pkg.Members["main"] = fn
return fn
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 this package whose name starts with prefix (one of
// 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, prefix string, slice types.Type) 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 name, mem := range fn.Pkg.Members {
if fn, ok := mem.(*Function); ok && isTest(name, prefix) && types.IsIdentical(fn.Signature, tFunc) {
testfuncs = append(testfuncs, fn)
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 {
@ -158,6 +200,5 @@ func isTest(name, prefix string) bool {
if len(name) == len(prefix) { // "Test" is ok
return true
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
return ast.IsExported(name[len(prefix):])
}