From cadc2255feedfce8b7d695aedf3bbdb13591a5ad Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 13 Nov 2013 16:05:13 -0500 Subject: [PATCH] go.tools/ssa: don't synthesize an empty "testmain" package. Also, only examine functions defined in *_test.go files. Added tests for empty and nonempty behaviour of CreateTestMainPackage. (Required some surgery to interp_test.) R=gri, gri CC=golang-dev https://golang.org/cl/25570044 --- ssa/interp/interp_test.go | 142 ++++++++++++++++++++++++++-------- ssa/interp/testdata/a_test.go | 17 ++++ ssa/interp/testdata/b_test.go | 11 +++ ssa/testmain.go | 24 ++++-- 4 files changed, 152 insertions(+), 42 deletions(-) create mode 100644 ssa/interp/testdata/a_test.go create mode 100644 ssa/interp/testdata/b_test.go diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go index 0e8ebea1..5446482e 100644 --- a/ssa/interp/interp_test.go +++ b/ssa/interp/interp_test.go @@ -155,7 +155,9 @@ var gorootSrcPkgTests = []string{ // "strings.go strings/search.go strings/search_test.go", } -func run(t *testing.T, dir, input string) bool { +type successPredicate func(exitcode int, output string) error + +func run(t *testing.T, dir, input string, success successPredicate) bool { fmt.Printf("Input: %s\n", input) start := time.Now() @@ -189,7 +191,7 @@ func run(t *testing.T, dir, input string) bool { }() hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input) - mainInfo := imp.CreatePackage("main", files...) + mainInfo := imp.CreatePackage(files[0].Name.Name, files...) if _, err := imp.LoadPackage("runtime"); err != nil { t.Errorf("LoadPackage(runtime) failed: %s", err) @@ -204,21 +206,27 @@ func run(t *testing.T, dir, input string) bool { mainPkg := prog.Package(mainInfo.Pkg) if mainPkg.Func("main") == nil { - mainPkg = prog.CreateTestMainPackage(mainPkg) + testmainPkg := prog.CreateTestMainPackage(mainPkg) + if testmainPkg == nil { + t.Errorf("CreateTestMainPackage(%s) returned nil", mainPkg) + return false + } + if testmainPkg.Func("main") == nil { + t.Errorf("synthetic testmain package has no main") + return false + } + mainPkg = testmainPkg } var out bytes.Buffer interp.CapturedOutput = &out hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=C -run --interp=T %s\n", input) - if exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}); exitCode != 0 { - t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode) - return false - } + exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}) - // $GOROOT/tests are considered a failure if they print "BUG". - if strings.Contains(out.String(), "BUG") { - t.Errorf("interp.Interpret(%s) exited zero but output contained 'BUG'", inputs) + // The definition of success varies with each file. + if err := success(exitCode, out.String()); err != nil { + t.Errorf("interp.Interpret(%s) failed: %s", inputs, err) return false } @@ -233,30 +241,7 @@ func run(t *testing.T, dir, input string) bool { const slash = string(os.PathSeparator) -// TestInterp runs the interpreter on a selection of small Go programs. -func TestInterp(t *testing.T) { - var failures []string - - for _, input := range testdataTests { - if !run(t, "testdata"+slash, input) { - failures = append(failures, input) - } - } - - if !testing.Short() { - for _, input := range gorootTestTests { - if !run(t, filepath.Join(build.Default.GOROOT, "test")+slash, input) { - failures = append(failures, input) - } - } - - for _, input := range gorootSrcPkgTests { - if !run(t, filepath.Join(build.Default.GOROOT, "src/pkg")+slash, input) { - failures = append(failures, input) - } - } - } - +func printFailures(failures []string) { if failures != nil { fmt.Println("The following tests failed:") for _, f := range failures { @@ -264,3 +249,92 @@ func TestInterp(t *testing.T) { } } } + +// The "normal" success predicate. +func exitsZero(exitcode int, _ string) error { + if exitcode != 0 { + return fmt.Errorf("exit code was %d", exitcode) + } + return nil +} + +// TestTestdataFiles runs the interpreter on testdata/*.go. +func TestTestdataFiles(t *testing.T) { + var failures []string + for _, input := range testdataTests { + if !run(t, "testdata"+slash, input, exitsZero) { + failures = append(failures, input) + } + } + printFailures(failures) +} + +// TestGorootTest runs the interpreter on $GOROOT/test/*.go. +func TestGorootTest(t *testing.T) { + if testing.Short() { + return // too slow (~30s) + } + + var failures []string + + // $GOROOT/tests are also considered a failure if they print "BUG". + success := func(exitcode int, output string) error { + if exitcode != 0 { + return fmt.Errorf("exit code was %d", exitcode) + } + if strings.Contains(output, "BUG") { + return fmt.Errorf("exited zero but output contained 'BUG'") + } + return nil + } + for _, input := range gorootTestTests { + if !run(t, filepath.Join(build.Default.GOROOT, "test")+slash, input, success) { + failures = append(failures, input) + } + } + for _, input := range gorootSrcPkgTests { + if !run(t, filepath.Join(build.Default.GOROOT, "src/pkg")+slash, input, success) { + failures = append(failures, input) + } + } + printFailures(failures) +} + +// TestTestmainPackage runs the interpreter on a synthetic "testmain" package. +func TestTestmainPackage(t *testing.T) { + success := func(exitcode int, output string) error { + if exitcode == 0 { + return fmt.Errorf("unexpected success") + } + if !strings.Contains(output, "FAIL: TestFoo") { + return fmt.Errorf("missing failure log for TestFoo") + } + if !strings.Contains(output, "FAIL: TestBar") { + return fmt.Errorf("missing failure log for TestBar") + } + // TODO(adonovan): test benchmarks too + return nil + } + run(t, "testdata"+slash, "a_test.go", success) +} + +// CreateTestMainPackage should return nil if there were no tests. +func TestNullTestmainPackage(t *testing.T) { + imp := importer.New(&importer.Config{Build: build.Default}) + files, err := importer.ParseFiles(imp.Fset, ".", "testdata/b_test.go") + if err != nil { + t.Fatalf("ParseFiles failed: %s", err) + } + mainInfo := imp.CreatePackage("b", files...) + prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) + if err := prog.CreatePackages(imp); err != nil { + t.Fatalf("CreatePackages failed: %s", err) + } + mainPkg := prog.Package(mainInfo.Pkg) + if mainPkg.Func("main") != nil { + t.Fatalf("unexpected main function") + } + if prog.CreateTestMainPackage(mainPkg) != nil { + t.Fatalf("CreateTestMainPackage returned non-nil") + } +} diff --git a/ssa/interp/testdata/a_test.go b/ssa/interp/testdata/a_test.go new file mode 100644 index 00000000..844ec5cd --- /dev/null +++ b/ssa/interp/testdata/a_test.go @@ -0,0 +1,17 @@ +package a + +import "testing" + +func TestFoo(t *testing.T) { + t.Error("foo") +} + +func TestBar(t *testing.T) { + t.Error("bar") +} + +func BenchmarkWiz(b *testing.B) { + b.Error("wiz") +} + +// Don't test Examples since that testing package needs pipe(2) for that. diff --git a/ssa/interp/testdata/b_test.go b/ssa/interp/testdata/b_test.go new file mode 100644 index 00000000..4a30e96a --- /dev/null +++ b/ssa/interp/testdata/b_test.go @@ -0,0 +1,11 @@ +package b + +import "testing" + +func NotATest(t *testing.T) { + t.Error("foo") +} + +func NotABenchmark(b *testing.B) { + b.Error("wiz") +} diff --git a/ssa/testmain.go b/ssa/testmain.go index 39828cb1..081c1b02 100644 --- a/ssa/testmain.go +++ b/ssa/testmain.go @@ -42,7 +42,7 @@ func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package { Prog: prog, } init.startBody() - var expfuncs []*Function // all exported functions of pkgs, unordered + var expfuncs []*Function // all exported functions of *_test.go in pkgs, unordered for _, pkg := range pkgs { // Initialize package to test. var v Call @@ -52,7 +52,9 @@ func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package { // Enumerate its possible tests/benchmarks. for _, mem := range pkg.Members { - if f, ok := mem.(*Function); ok && ast.IsExported(f.Name()) { + if f, ok := mem.(*Function); ok && + ast.IsExported(f.Name()) && + strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") { expfuncs = append(expfuncs, f) } } @@ -107,12 +109,18 @@ func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package { main.startBody() var c Call c.Call.Value = testingMain - c.Call.Args = []Value{ - matcher, - testMainSlice(main, expfuncs, "Test", testingMainParams.At(1).Type()), - testMainSlice(main, expfuncs, "Benchmark", testingMainParams.At(2).Type()), - testMainSlice(main, expfuncs, "Example", testingMainParams.At(3).Type()), + + tests := testMainSlice(main, expfuncs, "Test", testingMainParams.At(1).Type()) + benchmarks := testMainSlice(main, expfuncs, "Benchmark", testingMainParams.At(2).Type()) + examples := testMainSlice(main, expfuncs, "Example", testingMainParams.At(3).Type()) + _, noTests := tests.(*Const) // i.e. nil slice + _, noBenchmarks := benchmarks.(*Const) + _, noExamples := examples.(*Const) + if noTests && noBenchmarks && noExamples { + return nil } + + c.Call.Args = []Value{matcher, tests, benchmarks, examples} // Emit: testing.Main(nil, tests, benchmarks, examples) emitTailCall(main, &c) main.finishBody() @@ -122,6 +130,7 @@ func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package { if prog.mode&LogPackages != 0 { testmain.DumpTo(os.Stderr) } + if prog.mode&SanityCheckFunctions != 0 { sanityCheckPackage(testmain) } @@ -141,7 +150,6 @@ func testMainSlice(fn *Function, expfuncs []*Function, prefix string, slice type var testfuncs []*Function 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) }