Commit f80d8fbc authored by Miki Tebeka's avatar Miki Tebeka Committed by Rob Pike

testing: Add support for running tests in parallel (t.Parallel API).

See discussion at https://groups.google.com/d/topic/golang-dev/RAKiqi44GEU/discussion

R=golang-dev, bradfitz, dvyukov, rogpeppe, r, r, borman
CC=golang-dev
https://golang.org/cl/5071044
parent cd80d04c
...@@ -28,6 +28,7 @@ var usageMessage = `Usage of %s: ...@@ -28,6 +28,7 @@ var usageMessage = `Usage of %s:
-cpuprofile="": passes -test.cpuprofile to test -cpuprofile="": passes -test.cpuprofile to test
-memprofile="": passes -test.memprofile to test -memprofile="": passes -test.memprofile to test
-memprofilerate=0: passes -test.memprofilerate to test -memprofilerate=0: passes -test.memprofilerate to test
-parallel=0: passes -test.parallel to test
-run="": passes -test.run to test -run="": passes -test.run to test
-short=false: passes -test.short to test -short=false: passes -test.short to test
-timeout=0: passes -test.timeout to test -timeout=0: passes -test.timeout to test
...@@ -63,6 +64,7 @@ var flagDefn = []*flagSpec{ ...@@ -63,6 +64,7 @@ var flagDefn = []*flagSpec{
&flagSpec{name: "cpuprofile", passToTest: true}, &flagSpec{name: "cpuprofile", passToTest: true},
&flagSpec{name: "memprofile", passToTest: true}, &flagSpec{name: "memprofile", passToTest: true},
&flagSpec{name: "memprofilerate", passToTest: true}, &flagSpec{name: "memprofilerate", passToTest: true},
&flagSpec{name: "parallel", passToTest: true},
&flagSpec{name: "run", passToTest: true}, &flagSpec{name: "run", passToTest: true},
&flagSpec{name: "short", isBool: true, passToTest: true}, &flagSpec{name: "short", isBool: true, passToTest: true},
&flagSpec{name: "timeout", passToTest: true}, &flagSpec{name: "timeout", passToTest: true},
......
...@@ -44,8 +44,8 @@ import ( ...@@ -44,8 +44,8 @@ import (
"os" "os"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
"strings"
"strconv" "strconv"
"strings"
"time" "time"
) )
...@@ -65,6 +65,7 @@ var ( ...@@ -65,6 +65,7 @@ var (
cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution") cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution")
timeout = flag.Int64("test.timeout", 0, "if > 0, sets time limit for tests in seconds") timeout = flag.Int64("test.timeout", 0, "if > 0, sets time limit for tests in seconds")
cpuListStr = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test") cpuListStr = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test")
parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "maximum test parallelism")
cpuList []int cpuList []int
) )
...@@ -92,9 +93,12 @@ func tabify(s string) string { ...@@ -92,9 +93,12 @@ func tabify(s string) string {
// T is a type passed to Test functions to manage test state and support formatted test logs. // T is a type passed to Test functions to manage test state and support formatted test logs.
// Logs are accumulated during execution and dumped to standard error when done. // Logs are accumulated during execution and dumped to standard error when done.
type T struct { type T struct {
errors string name string // Name of test.
failed bool errors string // Error string from test.
ch chan *T failed bool // Test has failed.
ch chan *T // Output for serial tests.
startParallel chan bool // Parallel tests will wait on this.
ns int64 // Duration of test in nanoseconds.
} }
// Fail marks the Test function as having failed but continues execution. // Fail marks the Test function as having failed but continues execution.
...@@ -145,6 +149,13 @@ func (t *T) Fatalf(format string, args ...interface{}) { ...@@ -145,6 +149,13 @@ func (t *T) Fatalf(format string, args ...interface{}) {
t.FailNow() t.FailNow()
} }
// Parallel signals that this test is to be run in parallel with (and only with)
// other parallel tests in this CPU group.
func (t *T) Parallel() {
t.ch <- nil // Release main testing loop
<-t.startParallel // Wait for serial tests to finish
}
// An internal type but exported because it is cross-package; part of the implementation // An internal type but exported because it is cross-package; part of the implementation
// of gotest. // of gotest.
type InternalTest struct { type InternalTest struct {
...@@ -153,7 +164,9 @@ type InternalTest struct { ...@@ -153,7 +164,9 @@ type InternalTest struct {
} }
func tRunner(t *T, test *InternalTest) { func tRunner(t *T, test *InternalTest) {
t.ns = time.Nanoseconds()
test.F(t) test.F(t)
t.ns = time.Nanoseconds() - t.ns
t.ch <- t t.ch <- t
} }
...@@ -171,50 +184,73 @@ func Main(matchString func(pat, str string) (bool, os.Error), tests []InternalTe ...@@ -171,50 +184,73 @@ func Main(matchString func(pat, str string) (bool, os.Error), tests []InternalTe
after() after()
} }
func report(t *T) {
tstr := fmt.Sprintf("(%.2f seconds)", float64(t.ns)/1e9)
format := "--- %s: %s %s\n%s"
if t.failed {
fmt.Fprintf(os.Stderr, format, "FAIL", t.name, tstr, t.errors)
} else if *chatty {
fmt.Fprintf(os.Stderr, format, "PASS", t.name, tstr, t.errors)
}
}
func RunTests(matchString func(pat, str string) (bool, os.Error), tests []InternalTest) { func RunTests(matchString func(pat, str string) (bool, os.Error), tests []InternalTest) {
ok := true
if len(tests) == 0 { if len(tests) == 0 {
println("testing: warning: no tests to run") fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
return
} }
for i := 0; i < len(tests); i++ {
matched, err := matchString(*match, tests[i].Name) ok := true
if err != nil { ch := make(chan *T)
println("invalid regexp for -test.run:", err.String())
os.Exit(1) for _, procs := range cpuList {
} runtime.GOMAXPROCS(procs)
if !matched {
continue numParallel := 0
} startParallel := make(chan bool)
for _, procs := range cpuList {
runtime.GOMAXPROCS(procs) for i := 0; i < len(tests); i++ {
matched, err := matchString(*match, tests[i].Name)
if err != nil {
println("invalid regexp for -test.run:", err.String())
os.Exit(1)
}
if !matched {
continue
}
testName := tests[i].Name testName := tests[i].Name
if procs != 1 { if procs != 1 {
testName = fmt.Sprintf("%s-%d", tests[i].Name, procs) testName = fmt.Sprintf("%s-%d", tests[i].Name, procs)
} }
t := &T{ch: ch, name: testName, startParallel: startParallel}
if *chatty { if *chatty {
println("=== RUN ", testName) println("=== RUN", t.name)
} }
ns := -time.Nanoseconds()
t := new(T)
t.ch = make(chan *T)
go tRunner(t, &tests[i]) go tRunner(t, &tests[i])
<-t.ch out := <-t.ch
ns += time.Nanoseconds() if out == nil { // Parallel run.
tstr := fmt.Sprintf("(%.2f seconds)", float64(ns)/1e9) numParallel++
if p := runtime.GOMAXPROCS(-1); t.failed == false && p != procs { continue
t.failed = true
t.errors = fmt.Sprintf("%s left GOMAXPROCS set to %d\n", testName, p)
} }
if t.failed { report(t)
println("--- FAIL:", testName, tstr) ok = ok && !out.failed
print(t.errors) }
ok = false
} else if *chatty { running := 0
println("--- PASS:", testName, tstr) for numParallel+running > 0 {
print(t.errors) if running < *parallel && numParallel > 0 {
startParallel <- true
running++
numParallel--
continue
} }
t := <-ch
report(t)
ok = ok && !t.failed
running--
} }
} }
if !ok { if !ok {
println("FAIL") println("FAIL")
os.Exit(1) os.Exit(1)
......
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