Commit 182d1316 authored by Russ Cox's avatar Russ Cox

cmd/go, testing: add TestMain support

Fixes #8202.

LGTM=r, bradfitz
R=r, josharian, bradfitz
CC=golang-codereviews
https://golang.org/cl/148770043
parent 0c47bd1e
...@@ -32,6 +32,7 @@ sync/atomic: add Value (CL 136710045) ...@@ -32,6 +32,7 @@ sync/atomic: add Value (CL 136710045)
syscall: Setuid, Setgid are disabled on linux platforms. On linux those syscalls operate on the calling thread, not the whole process. This does not match the semantics of other platforms, nor the expectations of the caller, so the operations have been disabled until issue 1435 is resolved (CL 106170043) syscall: Setuid, Setgid are disabled on linux platforms. On linux those syscalls operate on the calling thread, not the whole process. This does not match the semantics of other platforms, nor the expectations of the caller, so the operations have been disabled until issue 1435 is resolved (CL 106170043)
syscall: now frozen (CL 129820043) syscall: now frozen (CL 129820043)
testing: add Coverage (CL 98150043) testing: add Coverage (CL 98150043)
testing: add TestMain support (CL 148770043)
text/scanner: add IsIdentRune field of Scanner. (CL 108030044) text/scanner: add IsIdentRune field of Scanner. (CL 108030044)
time: use the micro symbol (µ (U+00B5)) to print microsecond duration (CL 105030046) time: use the micro symbol (µ (U+00B5)) to print microsecond duration (CL 105030046)
encoding/asn1: optional elements with a default value will now only be omitted if they have that value (CL 86960045). encoding/asn1: optional elements with a default value will now only be omitted if they have that value (CL 86960045).
......
...@@ -6,6 +6,7 @@ package main ...@@ -6,6 +6,7 @@ package main
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
...@@ -291,6 +292,7 @@ var testMainDeps = map[string]bool{ ...@@ -291,6 +292,7 @@ var testMainDeps = map[string]bool{
// Dependencies for testmain. // Dependencies for testmain.
"testing": true, "testing": true,
"regexp": true, "regexp": true,
"os": true,
} }
func runTest(cmd *Command, args []string) { func runTest(cmd *Command, args []string) {
...@@ -687,7 +689,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, ...@@ -687,7 +689,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
omitDWARF: !testC && !testNeedBinary, omitDWARF: !testC && !testNeedBinary,
} }
// The generated main also imports testing and regexp. // The generated main also imports testing, regexp, and os.
stk.push("testmain") stk.push("testmain")
for dep := range testMainDeps { for dep := range testMainDeps {
if dep == ptest.ImportPath { if dep == ptest.ImportPath {
...@@ -1057,6 +1059,31 @@ func (b *builder) notest(a *action) error { ...@@ -1057,6 +1059,31 @@ func (b *builder) notest(a *action) error {
return nil return nil
} }
// isTestMain tells whether fn is a TestMain(m *testing.Main) function.
func isTestMain(fn *ast.FuncDecl) bool {
if fn.Name.String() != "TestMain" ||
fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) > 1 {
return false
}
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
if !ok {
return false
}
// We can't easily check that the type is *testing.M
// because we don't know how testing has been imported,
// but at least check that it's *M or *something.M.
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" {
return true
}
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" {
return true
}
return false
}
// isTest tells whether name looks like a test (or benchmark, according to prefix). // isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter. // It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer. // We don't want TesticularCancer.
...@@ -1113,6 +1140,7 @@ type testFuncs struct { ...@@ -1113,6 +1140,7 @@ type testFuncs struct {
Tests []testFunc Tests []testFunc
Benchmarks []testFunc Benchmarks []testFunc
Examples []testFunc Examples []testFunc
TestMain *testFunc
Package *Package Package *Package
ImportTest bool ImportTest bool
NeedTest bool NeedTest bool
...@@ -1168,6 +1196,12 @@ func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error { ...@@ -1168,6 +1196,12 @@ func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
} }
name := n.Name.String() name := n.Name.String()
switch { switch {
case isTestMain(n):
if t.TestMain != nil {
return errors.New("multiple definitions of TestMain")
}
t.TestMain = &testFunc{pkg, name, ""}
*doImport, *seen = true, true
case isTest(name, "Test"): case isTest(name, "Test"):
t.Tests = append(t.Tests, testFunc{pkg, name, ""}) t.Tests = append(t.Tests, testFunc{pkg, name, ""})
*doImport, *seen = true, true *doImport, *seen = true, true
...@@ -1200,6 +1234,9 @@ var testmainTmpl = template.Must(template.New("main").Parse(` ...@@ -1200,6 +1234,9 @@ var testmainTmpl = template.Must(template.New("main").Parse(`
package main package main
import ( import (
{{if not .TestMain}}
"os"
{{end}}
"regexp" "regexp"
"testing" "testing"
...@@ -1294,7 +1331,12 @@ func main() { ...@@ -1294,7 +1331,12 @@ func main() {
CoveredPackages: {{printf "%q" .Covered}}, CoveredPackages: {{printf "%q" .Covered}},
}) })
{{end}} {{end}}
testing.Main(matchString, tests, benchmarks, examples) m := testing.MainStart(matchString, tests, benchmarks, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
{{else}}
os.Exit(m.Run())
{{end}}
} }
`)) `))
...@@ -117,6 +117,26 @@ ...@@ -117,6 +117,26 @@
// The entire test file is presented as the example when it contains a single // The entire test file is presented as the example when it contains a single
// example function, at least one other function, type, variable, or constant // example function, at least one other function, type, variable, or constant
// declaration, and no test or benchmark functions. // declaration, and no test or benchmark functions.
//
// Main
//
// It is sometimes necessary for a test program to do extra setup or teardown
// before or after testing. It is also sometimes necessary for a test to control
// which code runs on the main thread. To support these and other cases,
// if a test file contains a function:
//
// func TestMain(m *testing.M)
//
// then the generated test will call TestMain(m) instead of running the tests
// directly. TestMain runs in the main goroutine and can do whatever setup
// and teardown is necessary around a call to m.Run. It should then call
// os.Exit with the result of m.Run.
//
// The minimal implementation of TestMain is:
//
// func TestMain(m *testing.M) { os.Exit(m.Run()) }
//
// In effect, that is the implementation used when no TestMain is explicitly defined.
package testing package testing
import ( import (
...@@ -431,23 +451,49 @@ func tRunner(t *T, test *InternalTest) { ...@@ -431,23 +451,49 @@ func tRunner(t *T, test *InternalTest) {
// An internal function but exported because it is cross-package; part of the implementation // An internal function but exported because it is cross-package; part of the implementation
// of the "go test" command. // of the "go test" command.
func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) { func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) {
os.Exit(MainStart(matchString, tests, benchmarks, examples).Run())
}
// M is a type passed to a TestMain function to run the actual tests.
type M struct {
matchString func(pat, str string) (bool, error)
tests []InternalTest
benchmarks []InternalBenchmark
examples []InternalExample
}
// MainStart is meant for use by tests generated by 'go test'.
// It is not meant to be called directly and is not subject to the Go 1 compatibility document.
// It may change signature from release to release.
func MainStart(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) *M {
return &M{
matchString: matchString,
tests: tests,
benchmarks: benchmarks,
examples: examples,
}
}
// Run runs the tests. It returns an exit code to pass to os.Exit.
func (m *M) Run() int {
flag.Parse() flag.Parse()
parseCpuList() parseCpuList()
before() before()
startAlarm() startAlarm()
haveExamples = len(examples) > 0 haveExamples = len(m.examples) > 0
testOk := RunTests(matchString, tests) testOk := RunTests(m.matchString, m.tests)
exampleOk := RunExamples(matchString, examples) exampleOk := RunExamples(m.matchString, m.examples)
stopAlarm() stopAlarm()
if !testOk || !exampleOk { if !testOk || !exampleOk {
fmt.Println("FAIL") fmt.Println("FAIL")
after() after()
os.Exit(1) return 1
} }
fmt.Println("PASS") fmt.Println("PASS")
RunBenchmarks(matchString, benchmarks) RunBenchmarks(m.matchString, m.benchmarks)
after() after()
return 0
} }
func (t *T) report() { func (t *T) report() {
......
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing_test
import (
"os"
"testing"
)
// This is exactly what a test would do without a TestMain.
// It's here only so that there is at least one package in the
// standard library with a TestMain, so that code is executed.
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment