mirror of
https://github.com/golang/go.git
synced 2025-05-06 08:03:03 +00:00
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:
parent
87ced824bd
commit
8636f40baf
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
123
ssa/testmain.go
123
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):])
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user