diff --git a/cmd/ssadump/main.go b/cmd/ssadump/main.go index ea8f4ddf95..3a135b023d 100644 --- a/cmd/ssadump/main.go +++ b/cmd/ssadump/main.go @@ -40,13 +40,18 @@ T [T]race execution of the program. Best for single-threaded programs! `) const usage = `SSA builder and interpreter. -Usage: ssadump [ ...] [ ...] [ ...] - ssadump [ ...] [ ...] +Usage: ssadump [ ...] ... 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) } diff --git a/oracle/oracle.go b/oracle/oracle.go index 6477f18c9e..26f8442b97 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -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 { diff --git a/pointer/pointer_test.go b/pointer/pointer_test.go index aa161c748f..2ac988a5bf 100644 --- a/pointer/pointer_test.go +++ b/pointer/pointer_test.go @@ -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 diff --git a/ssa/builder.go b/ssa/builder.go index 980e88d5d6..a11246afad 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -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() diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go index 2a27851f75..1fb90bb23a 100644 --- a/ssa/interp/interp_test.go +++ b/ssa/interp/interp_test.go @@ -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 diff --git a/ssa/testmain.go b/ssa/testmain.go index 0941790d42..39828cb177 100644 --- a/ssa/testmain.go +++ b/ssa/testmain.go @@ -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):]) }