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. const usage = `SSA builder and interpreter.
Usage: ssadump [<flag> ...] [<file.go> ...] [<arg> ...] Usage: ssadump [<flag> ...] <args> ...
ssadump [<flag> ...] <import/path> [<arg> ...]
Use -help flag to display options. Use -help flag to display options.
Examples: 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 -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") var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
@ -140,6 +145,7 @@ func main() {
if err := prog.CreatePackages(imp); err != nil { if err := prog.CreatePackages(imp); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if debugMode { if debugMode {
for _, pkg := range prog.AllPackages() { for _, pkg := range prog.AllPackages() {
pkg.SetDebugMode(true) pkg.SetDebugMode(true)
@ -147,18 +153,25 @@ func main() {
} }
prog.BuildAll() prog.BuildAll()
// Run the interpreter on the first package with a main function. // Run the interpreter.
if *runFlag { if *runFlag {
// If some package defines main, run that.
// Otherwise run all package's tests.
var main *ssa.Package var main *ssa.Package
var pkgs []*ssa.Package
for _, info := range infos { for _, info := range infos {
pkg := prog.Package(info.Pkg) pkg := prog.Package(info.Pkg)
if pkg.Func("main") != nil || pkg.CreateTestMainFunction() != nil { if pkg.Func("main") != nil {
main = pkg main = pkg
break break
} }
pkgs = append(pkgs, pkg)
}
if main == nil && pkgs != nil {
main = prog.CreateTestMainPackage(pkgs...)
} }
if main == nil { if main == nil {
log.Fatal("No main function") log.Fatal("No main package and no tests")
} }
interp.Interpret(main, interpMode, main.Object.Path(), args) 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 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 { for _, info := range initialPkgInfos {
initialPkg := o.prog.Package(info.Pkg) initialPkg := o.prog.Package(info.Pkg)
// Add package to the pointer analysis scope. // Add package to the pointer analysis scope.
if initialPkg.Func("main") == nil { if initialPkg.Func("main") != nil {
// TODO(adonovan): to simulate 'go test' more faithfully, we o.config.Mains = append(o.config.Mains, initialPkg)
// should build a single synthetic testmain package, } else {
// not synthetic main functions to many packages. testPkgs = append(testPkgs, initialPkg)
if initialPkg.CreateTestMainFunction() == nil {
return nil, fmt.Errorf("analysis scope has no main() entry points")
}
} }
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 { if needs&needSSADebug != 0 {

View File

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

View File

@ -10,8 +10,8 @@ package ssa
// (create.go), all packages are constructed and type-checked and // (create.go), all packages are constructed and type-checked and
// definitions of all package members are created, method-sets are // definitions of all package members are created, method-sets are
// computed, and wrapper methods are synthesized. The create phase // computed, and wrapper methods are synthesized. The create phase
// occurs sequentially (order is unimportant) as the client calls // proceeds in topological order over the import dependency graph,
// Program.CreatePackage. // initiated by client calls to Program.CreatePackage.
// //
// In the BUILD phase (builder.go), the builder traverses the AST of // In the BUILD phase (builder.go), the builder traverses the AST of
// each Go source function and generates SSA instructions for the // 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) { if !atomic.CompareAndSwapInt32(&p.started, 0, 1) {
return // already started return // already started
} }
if p.info == nil {
return // synthetic package, e.g. "testmain"
}
// Ensure we have runtime type info for all exported members. // Ensure we have runtime type info for all exported members.
// TODO(adonovan): ideally belongs in memberFromObject, but // TODO(adonovan): ideally belongs in memberFromObject, but
// that would require package creation in topological order. // that would require package creation in topological order.
@ -2328,11 +2330,6 @@ func (p *Package) Build() {
p.needMethodsOf(obj.Type()) p.needMethodsOf(obj.Type())
} }
} }
if p.info.Files == nil {
p.info = nil
return // nothing to do
}
if p.Prog.mode&LogSource != 0 { if p.Prog.mode&LogSource != 0 {
defer logStack("build %s", p)() defer logStack("build %s", p)()
} }
@ -2391,7 +2388,6 @@ func (p *Package) Build() {
// Finish up init(). // Finish up init().
emitJump(init, done) emitJump(init, done)
init.currentBlock = done init.currentBlock = done
init.emit(new(RunDefers))
init.emit(new(Return)) init.emit(new(Return))
init.finishBody() 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}) imp := importer.New(&importer.Config{Build: &build.Default})
// TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles. // 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...) files, err := importer.ParseFiles(imp.Fset, ".", inputs...)
if err != nil { if err != nil {
t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error()) 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() prog.BuildAll()
mainPkg := prog.Package(mainInfo.Pkg) 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 var out bytes.Buffer
interp.CapturedOutput = &out interp.CapturedOutput = &out

View File

@ -4,43 +4,68 @@
package ssa 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. // It is closely coupled to src/cmd/go/test.go and src/pkg/testing.
import ( import (
"go/ast"
"go/token" "go/token"
"os"
"strings" "strings"
"unicode"
"unicode/utf8"
"code.google.com/p/go.tools/go/exact" "code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
) )
// CreateTestMainFunction adds to pkg (a test package) a synthetic // CreateTestMainPackage creates and returns a synthetic "main"
// main function similar to the one that would be created by the 'go // package that runs all the tests of the supplied packages, similar
// test' tool. The synthetic function is returned. // to the one that would be created by the 'go test' tool.
// //
// If the package already has a member named "main", the package // It returns nil if the program contains no tests.
// remains unchanged; the result is that member if it's a function,
// nil if not.
// //
func (pkg *Package) CreateTestMainFunction() *Function { func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package {
if main := pkg.Members["main"]; main != nil { testmain := &Package{
main, _ := main.(*Function) Prog: prog,
return main Members: make(map[string]Member),
} values: make(map[types.Object]Value),
fn := &Function{ Object: types.NewPackage("testmain", "testmain", nil),
name: "main",
Signature: new(types.Signature),
Synthetic: "test main function",
Prog: pkg.Prog,
Pkg: pkg,
} }
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 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. // TODO(adonovan): but it might contain Examples.
// Support them (by just calling them directly). // Support them (by just calling them directly).
return nil return nil
@ -58,50 +83,67 @@ func (pkg *Package) CreateTestMainFunction() *Function {
// testing.Main(match, tests, benchmarks, examples) // testing.Main(match, tests, benchmarks, examples)
// } // }
main := &Function{
name: "main",
Signature: new(types.Signature),
Synthetic: "test main function",
Prog: prog,
Pkg: testmain,
}
matcher := &Function{ matcher := &Function{
name: "matcher", name: "matcher",
Signature: testingMainParams.At(0).Type().(*types.Signature), Signature: testingMainParams.At(0).Type().(*types.Signature),
Synthetic: "test matcher predicate", Synthetic: "test matcher predicate",
Enclosing: fn, Enclosing: main,
Pkg: fn.Pkg, Pkg: testmain,
Prog: fn.Prog, Prog: prog,
} }
fn.AnonFuncs = append(fn.AnonFuncs, matcher) main.AnonFuncs = append(main.AnonFuncs, matcher)
matcher.startBody() matcher.startBody()
matcher.emit(&Return{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}}) matcher.emit(&Return{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}})
matcher.finishBody() matcher.finishBody()
fn.startBody() main.startBody()
var c Call var c Call
c.Call.Value = testingMain c.Call.Value = testingMain
c.Call.Args = []Value{ c.Call.Args = []Value{
matcher, matcher,
testMainSlice(fn, "Test", testingMainParams.At(1).Type()), testMainSlice(main, expfuncs, "Test", testingMainParams.At(1).Type()),
testMainSlice(fn, "Benchmark", testingMainParams.At(2).Type()), testMainSlice(main, expfuncs, "Benchmark", testingMainParams.At(2).Type()),
testMainSlice(fn, "Example", testingMainParams.At(3).Type()), testMainSlice(main, expfuncs, "Example", testingMainParams.At(3).Type()),
} }
// Emit: testing.Main(nil, tests, benchmarks, examples) // Emit: testing.Main(nil, tests, benchmarks, examples)
emitTailCall(fn, &c) emitTailCall(main, &c)
fn.finishBody() main.finishBody()
pkg.Members["main"] = fn testmain.Members["main"] = main
return fn
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 // testMainSlice emits to fn code to construct a slice of type slice
// (one of []testing.Internal{Test,Benchmark,Example}) for all // (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. // "Test", "Benchmark" or "Example") and whose type is appropriate.
// It returns the slice value. // 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() tElem := slice.(*types.Slice).Elem()
tFunc := tElem.Underlying().(*types.Struct).Field(1).Type() tFunc := tElem.Underlying().(*types.Struct).Field(1).Type()
var testfuncs []*Function var testfuncs []*Function
for name, mem := range fn.Pkg.Members { for _, f := range expfuncs {
if fn, ok := mem.(*Function); ok && isTest(name, prefix) && types.IsIdentical(fn.Signature, tFunc) { // TODO(adonovan): only look at members defined in *_test.go files.
testfuncs = append(testfuncs, fn) if isTest(f.Name(), prefix) && types.IsIdentical(f.Signature, tFunc) {
testfuncs = append(testfuncs, f)
} }
} }
if testfuncs == nil { if testfuncs == nil {
@ -158,6 +200,5 @@ func isTest(name, prefix string) bool {
if len(name) == len(prefix) { // "Test" is ok if len(name) == len(prefix) { // "Test" is ok
return true return true
} }
rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) return ast.IsExported(name[len(prefix):])
return !unicode.IsLower(rune)
} }