mirror of
https://github.com/golang/go.git
synced 2025-05-06 16:13:04 +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.
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
123
ssa/testmain.go
123
ssa/testmain.go
@ -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)
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user