summaryrefslogtreecommitdiff
path: root/src/pkg/testing
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2012-01-30 15:38:19 +0100
committerOndřej Surý <ondrej@sury.org>2012-01-30 15:38:19 +0100
commit4cecda6c347bd6902b960c6a35a967add7070b0d (patch)
treea462e224ff41ec9f3eb1a0b6e815806f9e8804ad /src/pkg/testing
parent6c7ca6e4d4e26e4c8cbe0d183966011b3b088a0a (diff)
downloadgolang-4cecda6c347bd6902b960c6a35a967add7070b0d.tar.gz
Imported Upstream version 2012.01.27upstream-weekly/2012.01.27
Diffstat (limited to 'src/pkg/testing')
-rw-r--r--src/pkg/testing/Makefile3
-rw-r--r--src/pkg/testing/benchmark.go121
-rw-r--r--src/pkg/testing/example.go74
-rw-r--r--src/pkg/testing/iotest/logger.go5
-rw-r--r--src/pkg/testing/iotest/reader.go12
-rw-r--r--src/pkg/testing/iotest/writer.go7
-rw-r--r--src/pkg/testing/quick/quick.go19
-rw-r--r--src/pkg/testing/quick/quick_test.go5
-rw-r--r--src/pkg/testing/script/script.go13
-rw-r--r--src/pkg/testing/testing.go288
10 files changed, 403 insertions, 144 deletions
diff --git a/src/pkg/testing/Makefile b/src/pkg/testing/Makefile
index 9e8bd1756..a0c1232e3 100644
--- a/src/pkg/testing/Makefile
+++ b/src/pkg/testing/Makefile
@@ -6,7 +6,8 @@ include ../../Make.inc
TARG=testing
GOFILES=\
- benchmark.go\
+ benchmark.go\
+ example.go\
testing.go\
include ../../Make.pkg
diff --git a/src/pkg/testing/benchmark.go b/src/pkg/testing/benchmark.go
index fd0bd8665..0bf567b7c 100644
--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -25,19 +25,21 @@ type InternalBenchmark struct {
// B is a type passed to Benchmark functions to manage benchmark
// timing and to specify the number of iterations to run.
type B struct {
+ common
N int
benchmark InternalBenchmark
- ns int64
bytes int64
- start int64
+ timerOn bool
+ result BenchmarkResult
}
// StartTimer starts timing a test. This function is called automatically
// before a benchmark starts, but it can also used to resume timing after
// a call to StopTimer.
func (b *B) StartTimer() {
- if b.start == 0 {
- b.start = time.Nanoseconds()
+ if !b.timerOn {
+ b.start = time.Now()
+ b.timerOn = true
}
}
@@ -45,19 +47,19 @@ func (b *B) StartTimer() {
// while performing complex initialization that you don't
// want to measure.
func (b *B) StopTimer() {
- if b.start > 0 {
- b.ns += time.Nanoseconds() - b.start
+ if b.timerOn {
+ b.duration += time.Now().Sub(b.start)
+ b.timerOn = false
}
- b.start = 0
}
// ResetTimer sets the elapsed benchmark time to zero.
// It does not affect whether the timer is running.
func (b *B) ResetTimer() {
- if b.start > 0 {
- b.start = time.Nanoseconds()
+ if b.timerOn {
+ b.start = time.Now()
}
- b.ns = 0
+ b.duration = 0
}
// SetBytes records the number of bytes processed in a single operation.
@@ -68,7 +70,7 @@ func (b *B) nsPerOp() int64 {
if b.N <= 0 {
return 0
}
- return b.ns / int64(b.N)
+ return b.duration.Nanoseconds() / int64(b.N)
}
// runN runs a single benchmark for the specified number of iterations.
@@ -125,23 +127,38 @@ func roundUp(n int) int {
return 10 * base
}
-// run times the benchmark function. It gradually increases the number
+// run times the benchmark function in a separate goroutine.
+func (b *B) run() BenchmarkResult {
+ go b.launch()
+ <-b.signal
+ return b.result
+}
+
+// launch launches the benchmark function. It gradually increases the number
// of benchmark iterations until the benchmark runs for a second in order
// to get a reasonable measurement. It prints timing information in this form
// testing.BenchmarkHello 100000 19 ns/op
-func (b *B) run() BenchmarkResult {
+// launch is run by the fun function as a separate goroutine.
+func (b *B) launch() {
// Run the benchmark for a single iteration in case it's expensive.
n := 1
+
+ // Signal that we're done whether we return normally
+ // or by FailNow's runtime.Goexit.
+ defer func() {
+ b.signal <- b
+ }()
+
b.runN(n)
// Run the benchmark for at least the specified amount of time.
- time := int64(*benchTime * 1e9)
- for b.ns < time && n < 1e9 {
+ d := time.Duration(*benchTime * float64(time.Second))
+ for !b.failed && b.duration < d && n < 1e9 {
last := n
// Predict iterations/sec.
if b.nsPerOp() == 0 {
n = 1e9
} else {
- n = int(time / b.nsPerOp())
+ n = int(d.Nanoseconds() / b.nsPerOp())
}
// Run more iterations than we think we'll need for a second (1.5x).
// Don't grow too fast in case we had timing errors previously.
@@ -151,28 +168,28 @@ func (b *B) run() BenchmarkResult {
n = roundUp(n)
b.runN(n)
}
- return BenchmarkResult{b.N, b.ns, b.bytes}
+ b.result = BenchmarkResult{b.N, b.duration, b.bytes}
}
// The results of a benchmark run.
type BenchmarkResult struct {
- N int // The number of iterations.
- Ns int64 // The total time taken.
- Bytes int64 // Bytes processed in one iteration.
+ N int // The number of iterations.
+ T time.Duration // The total time taken.
+ Bytes int64 // Bytes processed in one iteration.
}
func (r BenchmarkResult) NsPerOp() int64 {
if r.N <= 0 {
return 0
}
- return r.Ns / int64(r.N)
+ return r.T.Nanoseconds() / int64(r.N)
}
func (r BenchmarkResult) mbPerSec() float64 {
- if r.Bytes <= 0 || r.Ns <= 0 || r.N <= 0 {
+ if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 {
return 0
}
- return float64(r.Bytes) * float64(r.N) / float64(r.Ns) * 1e3
+ return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
}
func (r BenchmarkResult) String() string {
@@ -187,9 +204,9 @@ func (r BenchmarkResult) String() string {
// The format specifiers here make sure that
// the ones digits line up for all three possible formats.
if nsop < 10 {
- ns = fmt.Sprintf("%13.2f ns/op", float64(r.Ns)/float64(r.N))
+ ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
} else {
- ns = fmt.Sprintf("%12.1f ns/op", float64(r.Ns)/float64(r.N))
+ ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
}
}
return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb)
@@ -197,7 +214,7 @@ func (r BenchmarkResult) String() string {
// An internal function but exported because it is cross-package; part of the implementation
// of gotest.
-func RunBenchmarks(matchString func(pat, str string) (bool, os.Error), benchmarks []InternalBenchmark) {
+func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) {
// If no flag was specified, don't run benchmarks.
if len(*matchBenchmarks) == 0 {
return
@@ -205,7 +222,7 @@ func RunBenchmarks(matchString func(pat, str string) (bool, os.Error), benchmark
for _, Benchmark := range benchmarks {
matched, err := matchString(*matchBenchmarks, Benchmark.Name)
if err != nil {
- println("invalid regexp for -test.bench:", err.String())
+ fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err)
os.Exit(1)
}
if !matched {
@@ -213,16 +230,51 @@ func RunBenchmarks(matchString func(pat, str string) (bool, os.Error), benchmark
}
for _, procs := range cpuList {
runtime.GOMAXPROCS(procs)
- b := &B{benchmark: Benchmark}
+ b := &B{
+ common: common{
+ signal: make(chan interface{}),
+ },
+ benchmark: Benchmark,
+ }
benchName := Benchmark.Name
if procs != 1 {
benchName = fmt.Sprintf("%s-%d", Benchmark.Name, procs)
}
- print(fmt.Sprintf("%s\t", benchName))
+ fmt.Printf("%s\t", benchName)
r := b.run()
- print(fmt.Sprintf("%v\n", r))
+ if b.failed {
+ // The output could be very long here, but probably isn't.
+ // We print it all, regardless, because we don't want to trim the reason
+ // the benchmark failed.
+ fmt.Printf("--- FAIL: %s\n%s", benchName, b.output)
+ continue
+ }
+ fmt.Printf("%v\n", r)
+ // Unlike with tests, we ignore the -chatty flag and always print output for
+ // benchmarks since the output generation time will skew the results.
+ if len(b.output) > 0 {
+ b.trimOutput()
+ fmt.Printf("--- BENCH: %s\n%s", benchName, b.output)
+ }
if p := runtime.GOMAXPROCS(-1); p != procs {
- print(fmt.Sprintf("%s left GOMAXPROCS set to %d\n", benchName, p))
+ fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
+ }
+ }
+ }
+}
+
+// trimOutput shortens the output from a benchmark, which can be very long.
+func (b *B) trimOutput() {
+ // The output is likely to appear multiple times because the benchmark
+ // is run multiple times, but at least it will be seen. This is not a big deal
+ // because benchmarks rarely print, but just in case, we trim it if it's too long.
+ const maxNewlines = 10
+ for nlCount, j := 0, 0; j < len(b.output); j++ {
+ if b.output[j] == '\n' {
+ nlCount++
+ if nlCount >= maxNewlines {
+ b.output = append(b.output[:j], "\n\t... [output truncated]\n"...)
+ break
}
}
}
@@ -231,6 +283,11 @@ func RunBenchmarks(matchString func(pat, str string) (bool, os.Error), benchmark
// Benchmark benchmarks a single function. Useful for creating
// custom benchmarks that do not use gotest.
func Benchmark(f func(b *B)) BenchmarkResult {
- b := &B{benchmark: InternalBenchmark{"", f}}
+ b := &B{
+ common: common{
+ signal: make(chan interface{}),
+ },
+ benchmark: InternalBenchmark{"", f},
+ }
return b.run()
}
diff --git a/src/pkg/testing/example.go b/src/pkg/testing/example.go
new file mode 100644
index 000000000..7f8ff2d05
--- /dev/null
+++ b/src/pkg/testing/example.go
@@ -0,0 +1,74 @@
+// Copyright 2009 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
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "time"
+)
+
+type InternalExample struct {
+ Name string
+ F func()
+ Output string
+}
+
+func RunExamples(examples []InternalExample) (ok bool) {
+ ok = true
+
+ var eg InternalExample
+
+ stdout, stderr := os.Stdout, os.Stderr
+
+ for _, eg = range examples {
+ if *chatty {
+ fmt.Printf("=== RUN: %s\n", eg.Name)
+ }
+
+ // capture stdout and stderr
+ r, w, err := os.Pipe()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ os.Stdout, os.Stderr = w, w
+ outC := make(chan string)
+ go func() {
+ buf := new(bytes.Buffer)
+ _, err := io.Copy(buf, r)
+ if err != nil {
+ fmt.Fprintf(stderr, "testing: copying pipe: %v\n", err)
+ os.Exit(1)
+ }
+ outC <- buf.String()
+ }()
+
+ // run example
+ t0 := time.Now()
+ eg.F()
+ dt := time.Now().Sub(t0)
+
+ // close pipe, restore stdout/stderr, get output
+ w.Close()
+ os.Stdout, os.Stderr = stdout, stderr
+ out := <-outC
+
+ // report any errors
+ tstr := fmt.Sprintf("(%.2f seconds)", dt.Seconds())
+ if g, e := strings.TrimSpace(out), strings.TrimSpace(eg.Output); g != e {
+ fmt.Printf("--- FAIL: %s %s\ngot:\n%s\nwant:\n%s\n",
+ eg.Name, tstr, g, e)
+ ok = false
+ } else if *chatty {
+ fmt.Printf("--- PASS: %s %s\n", eg.Name, tstr)
+ }
+ }
+
+ return
+}
diff --git a/src/pkg/testing/iotest/logger.go b/src/pkg/testing/iotest/logger.go
index c3bf5df3c..1475d9b0c 100644
--- a/src/pkg/testing/iotest/logger.go
+++ b/src/pkg/testing/iotest/logger.go
@@ -7,7 +7,6 @@ package iotest
import (
"io"
"log"
- "os"
)
type writeLogger struct {
@@ -15,7 +14,7 @@ type writeLogger struct {
w io.Writer
}
-func (l *writeLogger) Write(p []byte) (n int, err os.Error) {
+func (l *writeLogger) Write(p []byte) (n int, err error) {
n, err = l.w.Write(p)
if err != nil {
log.Printf("%s %x: %v", l.prefix, p[0:n], err)
@@ -37,7 +36,7 @@ type readLogger struct {
r io.Reader
}
-func (l *readLogger) Read(p []byte) (n int, err os.Error) {
+func (l *readLogger) Read(p []byte) (n int, err error) {
n, err = l.r.Read(p)
if err != nil {
log.Printf("%s %x: %v", l.prefix, p[0:n], err)
diff --git a/src/pkg/testing/iotest/reader.go b/src/pkg/testing/iotest/reader.go
index dcf5565e0..ab8dc31a1 100644
--- a/src/pkg/testing/iotest/reader.go
+++ b/src/pkg/testing/iotest/reader.go
@@ -6,8 +6,8 @@
package iotest
import (
+ "errors"
"io"
- "os"
)
// OneByteReader returns a Reader that implements
@@ -18,7 +18,7 @@ type oneByteReader struct {
r io.Reader
}
-func (r *oneByteReader) Read(p []byte) (int, os.Error) {
+func (r *oneByteReader) Read(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
@@ -33,7 +33,7 @@ type halfReader struct {
r io.Reader
}
-func (r *halfReader) Read(p []byte) (int, os.Error) {
+func (r *halfReader) Read(p []byte) (int, error) {
return r.r.Read(p[0 : (len(p)+1)/2])
}
@@ -48,7 +48,7 @@ type dataErrReader struct {
data []byte
}
-func (r *dataErrReader) Read(p []byte) (n int, err os.Error) {
+func (r *dataErrReader) Read(p []byte) (n int, err error) {
// loop because first call needs two reads:
// one to get data and a second to look for an error.
for {
@@ -66,7 +66,7 @@ func (r *dataErrReader) Read(p []byte) (n int, err os.Error) {
return
}
-var ErrTimeout = os.NewError("timeout")
+var ErrTimeout = errors.New("timeout")
// TimeoutReader returns ErrTimeout on the second read
// with no data. Subsequent calls to read succeed.
@@ -77,7 +77,7 @@ type timeoutReader struct {
count int
}
-func (r *timeoutReader) Read(p []byte) (int, os.Error) {
+func (r *timeoutReader) Read(p []byte) (int, error) {
r.count++
if r.count == 2 {
return 0, ErrTimeout
diff --git a/src/pkg/testing/iotest/writer.go b/src/pkg/testing/iotest/writer.go
index 71f504ce2..af61ab858 100644
--- a/src/pkg/testing/iotest/writer.go
+++ b/src/pkg/testing/iotest/writer.go
@@ -4,10 +4,7 @@
package iotest
-import (
- "io"
- "os"
-)
+import "io"
// TruncateWriter returns a Writer that writes to w
// but stops silently after n bytes.
@@ -20,7 +17,7 @@ type truncateWriter struct {
n int64
}
-func (t *truncateWriter) Write(p []byte) (n int, err os.Error) {
+func (t *truncateWriter) Write(p []byte) (n int, err error) {
if t.n <= 0 {
return len(p), nil
}
diff --git a/src/pkg/testing/quick/quick.go b/src/pkg/testing/quick/quick.go
index 756a60e13..f94c541f2 100644
--- a/src/pkg/testing/quick/quick.go
+++ b/src/pkg/testing/quick/quick.go
@@ -9,8 +9,7 @@ import (
"flag"
"fmt"
"math"
- "os"
- "rand"
+ "math/rand"
"reflect"
"strings"
)
@@ -123,9 +122,9 @@ func Value(t reflect.Type, rand *rand.Rand) (value reflect.Value, ok bool) {
return s, true
case reflect.String:
numChars := rand.Intn(complexSize)
- codePoints := make([]int, numChars)
+ codePoints := make([]rune, numChars)
for i := 0; i < numChars; i++ {
- codePoints[i] = rand.Intn(0x10ffff)
+ codePoints[i] = rune(rand.Intn(0x10ffff))
}
return reflect.ValueOf(string(codePoints)), true
case reflect.Struct:
@@ -191,7 +190,7 @@ func (c *Config) getMaxCount() (maxCount int) {
// used, independent of the functions being tested.
type SetupError string
-func (s SetupError) String() string { return string(s) }
+func (s SetupError) Error() string { return string(s) }
// A CheckError is the result of Check finding an error.
type CheckError struct {
@@ -199,7 +198,7 @@ type CheckError struct {
In []interface{}
}
-func (s *CheckError) String() string {
+func (s *CheckError) Error() string {
return fmt.Sprintf("#%d: failed on input %s", s.Count, toString(s.In))
}
@@ -210,7 +209,7 @@ type CheckEqualError struct {
Out2 []interface{}
}
-func (s *CheckEqualError) String() string {
+func (s *CheckEqualError) Error() string {
return fmt.Sprintf("#%d: failed on input %s. Output 1: %s. Output 2: %s", s.Count, toString(s.In), toString(s.Out1), toString(s.Out2))
}
@@ -229,7 +228,7 @@ func (s *CheckEqualError) String() string {
// t.Error(err)
// }
// }
-func Check(function interface{}, config *Config) (err os.Error) {
+func Check(function interface{}, config *Config) (err error) {
if config == nil {
config = &defaultConfig
}
@@ -272,7 +271,7 @@ func Check(function interface{}, config *Config) (err os.Error) {
// It calls f and g repeatedly with arbitrary values for each argument.
// If f and g return different answers, CheckEqual returns a *CheckEqualError
// describing the input and the outputs.
-func CheckEqual(f, g interface{}, config *Config) (err os.Error) {
+func CheckEqual(f, g interface{}, config *Config) (err error) {
if config == nil {
config = &defaultConfig
}
@@ -317,7 +316,7 @@ func CheckEqual(f, g interface{}, config *Config) (err os.Error) {
// arbitraryValues writes Values to args such that args contains Values
// suitable for calling f.
-func arbitraryValues(args []reflect.Value, f reflect.Type, config *Config, rand *rand.Rand) (err os.Error) {
+func arbitraryValues(args []reflect.Value, f reflect.Type, config *Config, rand *rand.Rand) (err error) {
if config.Values != nil {
config.Values(args, rand)
return
diff --git a/src/pkg/testing/quick/quick_test.go b/src/pkg/testing/quick/quick_test.go
index f2618c3c2..a6cf0dc39 100644
--- a/src/pkg/testing/quick/quick_test.go
+++ b/src/pkg/testing/quick/quick_test.go
@@ -5,10 +5,9 @@
package quick
import (
- "rand"
+ "math/rand"
"reflect"
"testing"
- "os"
)
func fBool(a bool) bool { return a }
@@ -63,7 +62,7 @@ func fIntptr(a *int) *int {
return &b
}
-func reportError(property string, err os.Error, t *testing.T) {
+func reportError(property string, err error, t *testing.T) {
if err != nil {
t.Errorf("%s: %s", property, err)
}
diff --git a/src/pkg/testing/script/script.go b/src/pkg/testing/script/script.go
index afb286f5b..d8f8093af 100644
--- a/src/pkg/testing/script/script.go
+++ b/src/pkg/testing/script/script.go
@@ -7,8 +7,7 @@ package script
import (
"fmt"
- "os"
- "rand"
+ "math/rand"
"reflect"
"strings"
)
@@ -171,7 +170,7 @@ type ReceivedUnexpected struct {
ready []*Event
}
-func (r ReceivedUnexpected) String() string {
+func (r ReceivedUnexpected) Error() string {
names := make([]string, len(r.ready))
for i, v := range r.ready {
names[i] = v.name
@@ -183,7 +182,7 @@ func (r ReceivedUnexpected) String() string {
// Events.
type SetupError string
-func (s SetupError) String() string { return string(s) }
+func (s SetupError) Error() string { return string(s) }
func NewEvent(name string, predecessors []*Event, action action) *Event {
e := &Event{name, false, predecessors, action}
@@ -223,7 +222,7 @@ func NewEvent(name string, predecessors []*Event, action action) *Event {
// the other. At each receive step, all the receive channels are considered,
// thus Perform may see a value from a channel that is not in the current ready
// set and fail.
-func Perform(seed int64, events []*Event) (err os.Error) {
+func Perform(seed int64, events []*Event) (err error) {
r := rand.New(rand.NewSource(seed))
channels, err := getChannels(events)
@@ -269,7 +268,7 @@ Outer:
}
// getChannels returns all the channels listed in any receive events.
-func getChannels(events []*Event) ([]interface{}, os.Error) {
+func getChannels(events []*Event) ([]interface{}, error) {
channels := make([]interface{}, len(events))
j := 0
@@ -326,7 +325,7 @@ type channelRecv struct {
}
// readyEvents returns the subset of events that are ready.
-func readyEvents(events []*Event) ([]*Event, os.Error) {
+func readyEvents(events []*Event) ([]*Event, error) {
ready := make([]*Event, len(events))
j := 0
diff --git a/src/pkg/testing/testing.go b/src/pkg/testing/testing.go
index ec4a45371..f1acb97e1 100644
--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// Package testing provides support for automated testing of Go packages.
-// It is intended to be used in concert with the ``gotest'' utility, which automates
+// It is intended to be used in concert with the ``go test'' command, which automates
// execution of any function of the form
// func TestXxx(*testing.T)
// where Xxx can be any alphanumeric string (but the first letter must not be in
@@ -21,10 +21,11 @@
// fmt.Sprintf("hello")
// }
// }
+//
// The benchmark package will vary b.N until the benchmark function lasts
// long enough to be timed reliably. The output
-// testing.BenchmarkHello 500000 4076 ns/op
-// means that the loop ran 500000 times at a speed of 4076 ns per loop.
+// testing.BenchmarkHello 10000000 282 ns/op
+// means that the loop ran 10000000 times at a speed of 282 ns per loop.
//
// If a benchmark needs some expensive setup before running, the timer
// may be stopped:
@@ -36,6 +37,33 @@
// big.Len()
// }
// }
+//
+// The package also runs and verifies example code. Example functions
+// include an introductory comment that is compared with the standard output
+// of the function when the tests are run, as in this example of an example:
+//
+// // hello
+// func ExampleHello() {
+// fmt.Println("hello")
+// }
+//
+// Example functions without comments are compiled but not executed.
+//
+// The naming convention to declare examples for a function F, a type T and
+// method M on type T are:
+//
+// func ExampleF() { ... }
+// func ExampleT() { ... }
+// func ExampleT_M() { ... }
+//
+// Multiple example functions for a type/function/method may be provided by
+// appending a distinct suffix to the name. The suffix must start with a
+// lower-case letter.
+//
+// func ExampleF_suffix() { ... }
+// func ExampleT_suffix() { ... }
+// func ExampleT_M_suffix() { ... }
+//
package testing
import (
@@ -44,8 +72,8 @@ import (
"os"
"runtime"
"runtime/pprof"
- "strings"
"strconv"
+ "strings"
"time"
)
@@ -63,19 +91,48 @@ var (
memProfile = flag.String("test.memprofile", "", "write a memory profile to the named file after execution")
memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate")
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.Duration("test.timeout", 0, "if positive, sets an aggregate time limit for all tests")
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
)
+// common holds the elements common between T and B and
+// captures common methods such as Errorf.
+type common struct {
+ output []byte // Output generated by test or benchmark.
+ failed bool // Test or benchmark has failed.
+ start time.Time // Time test or benchmark started
+ duration time.Duration
+ self interface{} // To be sent on signal channel when done.
+ signal chan interface{} // Output for serial tests.
+}
+
// Short reports whether the -test.short flag is set.
func Short() bool {
return *short
}
-// Insert final newline if needed and tabs after internal newlines.
-func tabify(s string) string {
+// decorate inserts the final newline if needed and indentation tabs for formatting.
+// If addFileLine is true, it also prefixes the string with the file and line of the call site.
+func decorate(s string, addFileLine bool) string {
+ if addFileLine {
+ _, file, line, ok := runtime.Caller(3) // decorate + log + public function.
+ if ok {
+ // Truncate file name at last file name separator.
+ if index := strings.LastIndex(file, "/"); index >= 0 {
+ file = file[index+1:]
+ } else if index = strings.LastIndex(file, "\\"); index >= 0 {
+ file = file[index+1:]
+ }
+ } else {
+ file = "???"
+ line = 1
+ }
+ s = fmt.Sprintf("%s:%d: %s", file, line, s)
+ }
+ s = "\t" + s // Every line is indented at least one tab.
n := len(s)
if n > 0 && s[n-1] != '\n' {
s += "\n"
@@ -83,7 +140,8 @@ func tabify(s string) string {
}
for i := 0; i < n-1; i++ { // -1 to avoid final newline
if s[i] == '\n' {
- return s[0:i+1] + "\t" + tabify(s[i+1:n])
+ // Second and subsequent lines are indented an extra tab.
+ return s[0:i+1] + "\t" + decorate(s[i+1:n], false)
}
}
return s
@@ -92,57 +150,86 @@ func tabify(s string) string {
// 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.
type T struct {
- errors string
- failed bool
- ch chan *T
+ common
+ name string // Name of test.
+ startParallel chan bool // Parallel tests will wait on this.
}
-// Fail marks the Test function as having failed but continues execution.
-func (t *T) Fail() { t.failed = true }
+// Fail marks the function as having failed but continues execution.
+func (c *common) Fail() { c.failed = true }
-// Failed returns whether the Test function has failed.
-func (t *T) Failed() bool { return t.failed }
+// Failed returns whether the function has failed.
+func (c *common) Failed() bool { return c.failed }
-// FailNow marks the Test function as having failed and stops its execution.
+// FailNow marks the function as having failed and stops its execution.
// Execution will continue at the next Test.
-func (t *T) FailNow() {
- t.Fail()
- t.ch <- t
+func (c *common) FailNow() {
+ c.Fail()
+
+ // Calling runtime.Goexit will exit the goroutine, which
+ // will run the deferred functions in this goroutine,
+ // which will eventually run the deferred lines in tRunner,
+ // which will signal to the test loop that this test is done.
+ //
+ // A previous version of this code said:
+ //
+ // c.duration = ...
+ // c.signal <- c.self
+ // runtime.Goexit()
+ //
+ // This previous version duplicated code (those lines are in
+ // tRunner no matter what), but worse the goroutine teardown
+ // implicit in runtime.Goexit was not guaranteed to complete
+ // before the test exited. If a test deferred an important cleanup
+ // function (like removing temporary files), there was no guarantee
+ // it would run on a test failure. Because we send on c.signal during
+ // a top-of-stack deferred function now, we know that the send
+ // only happens after any other stacked defers have completed.
runtime.Goexit()
}
-// Log formats its arguments using default formatting, analogous to Print(),
+// log generates the output. It's always at the same stack depth.
+func (c *common) log(s string) {
+ c.output = append(c.output, decorate(s, true)...)
+}
+
+// Log formats its arguments using default formatting, analogous to Println(),
// and records the text in the error log.
-func (t *T) Log(args ...interface{}) { t.errors += "\t" + tabify(fmt.Sprintln(args...)) }
+func (c *common) Log(args ...interface{}) { c.log(fmt.Sprintln(args...)) }
// Logf formats its arguments according to the format, analogous to Printf(),
// and records the text in the error log.
-func (t *T) Logf(format string, args ...interface{}) {
- t.errors += "\t" + tabify(fmt.Sprintf(format, args...))
-}
+func (c *common) Logf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) }
// Error is equivalent to Log() followed by Fail().
-func (t *T) Error(args ...interface{}) {
- t.Log(args...)
- t.Fail()
+func (c *common) Error(args ...interface{}) {
+ c.log(fmt.Sprintln(args...))
+ c.Fail()
}
// Errorf is equivalent to Logf() followed by Fail().
-func (t *T) Errorf(format string, args ...interface{}) {
- t.Logf(format, args...)
- t.Fail()
+func (c *common) Errorf(format string, args ...interface{}) {
+ c.log(fmt.Sprintf(format, args...))
+ c.Fail()
}
// Fatal is equivalent to Log() followed by FailNow().
-func (t *T) Fatal(args ...interface{}) {
- t.Log(args...)
- t.FailNow()
+func (c *common) Fatal(args ...interface{}) {
+ c.log(fmt.Sprintln(args...))
+ c.FailNow()
}
// Fatalf is equivalent to Logf() followed by FailNow().
-func (t *T) Fatalf(format string, args ...interface{}) {
- t.Logf(format, args...)
- t.FailNow()
+func (c *common) Fatalf(format string, args ...interface{}) {
+ c.log(fmt.Sprintf(format, args...))
+ c.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.signal <- (*T)(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
@@ -153,73 +240,120 @@ type InternalTest struct {
}
func tRunner(t *T, test *InternalTest) {
+ t.start = time.Now()
+
+ // When this goroutine is done, either because test.F(t)
+ // returned normally or because a test failure triggered
+ // a call to runtime.Goexit, record the duration and send
+ // a signal saying that the test is done.
+ defer func() {
+ t.duration = time.Now().Sub(t.start)
+ t.signal <- t
+ }()
+
test.F(t)
- t.ch <- t
}
// An internal function but exported because it is cross-package; part of the implementation
// of gotest.
-func Main(matchString func(pat, str string) (bool, os.Error), tests []InternalTest, benchmarks []InternalBenchmark) {
+func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) {
flag.Parse()
parseCpuList()
before()
startAlarm()
- RunTests(matchString, tests)
+ testOk := RunTests(matchString, tests)
+ exampleOk := RunExamples(examples)
+ if !testOk || !exampleOk {
+ fmt.Println("FAIL")
+ os.Exit(1)
+ }
+ fmt.Println("PASS")
stopAlarm()
RunBenchmarks(matchString, benchmarks)
after()
}
-func RunTests(matchString func(pat, str string) (bool, os.Error), tests []InternalTest) {
- ok := true
+func (t *T) report() {
+ tstr := fmt.Sprintf("(%.2f seconds)", t.duration.Seconds())
+ format := "--- %s: %s %s\n%s"
+ if t.failed {
+ fmt.Printf(format, "FAIL", t.name, tstr, t.output)
+ } else if *chatty {
+ fmt.Printf(format, "PASS", t.name, tstr, t.output)
+ }
+}
+
+func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) {
+ ok = true
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)
- if err != nil {
- println("invalid regexp for -test.run:", err.String())
- os.Exit(1)
- }
- if !matched {
- continue
- }
- for _, procs := range cpuList {
- runtime.GOMAXPROCS(procs)
+ for _, procs := range cpuList {
+ runtime.GOMAXPROCS(procs)
+ // We build a new channel tree for each run of the loop.
+ // collector merges in one channel all the upstream signals from parallel tests.
+ // If all tests pump to the same channel, a bug can occur where a test
+ // kicks off a goroutine that Fails, yet the test still delivers a completion signal,
+ // which skews the counting.
+ var collector = make(chan interface{})
+
+ numParallel := 0
+ startParallel := make(chan bool)
+
+ for i := 0; i < len(tests); i++ {
+ matched, err := matchString(*match, tests[i].Name)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err)
+ os.Exit(1)
+ }
+ if !matched {
+ continue
+ }
testName := tests[i].Name
if procs != 1 {
testName = fmt.Sprintf("%s-%d", tests[i].Name, procs)
}
+ t := &T{
+ common: common{
+ signal: make(chan interface{}),
+ },
+ name: testName,
+ startParallel: startParallel,
+ }
+ t.self = t
if *chatty {
- println("=== RUN ", testName)
+ fmt.Printf("=== RUN %s\n", t.name)
}
- ns := -time.Nanoseconds()
- t := new(T)
- t.ch = make(chan *T)
go tRunner(t, &tests[i])
- <-t.ch
- ns += time.Nanoseconds()
- tstr := fmt.Sprintf("(%.2f seconds)", float64(ns)/1e9)
- if p := runtime.GOMAXPROCS(-1); t.failed == false && p != procs {
- t.failed = true
- t.errors = fmt.Sprintf("%s left GOMAXPROCS set to %d\n", testName, p)
+ out := (<-t.signal).(*T)
+ if out == nil { // Parallel run.
+ go func() {
+ collector <- <-t.signal
+ }()
+ numParallel++
+ continue
}
- if t.failed {
- println("--- FAIL:", testName, tstr)
- print(t.errors)
- ok = false
- } else if *chatty {
- println("--- PASS:", testName, tstr)
- print(t.errors)
+ t.report()
+ ok = ok && !out.failed
+ }
+
+ running := 0
+ for numParallel+running > 0 {
+ if running < *parallel && numParallel > 0 {
+ startParallel <- true
+ running++
+ numParallel--
+ continue
}
+ t := (<-collector).(*T)
+ t.report()
+ ok = ok && !t.failed
+ running--
}
}
- if !ok {
- println("FAIL")
- os.Exit(1)
- }
- println("PASS")
+ return
}
// before runs before all testing.
@@ -266,7 +400,7 @@ var timer *time.Timer
// startAlarm starts an alarm if requested.
func startAlarm() {
if *timeout > 0 {
- timer = time.AfterFunc(*timeout*1e9, alarm)
+ timer = time.AfterFunc(*timeout, alarm)
}
}
@@ -289,7 +423,7 @@ func parseCpuList() {
for _, val := range strings.Split(*cpuListStr, ",") {
cpu, err := strconv.Atoi(val)
if err != nil || cpu <= 0 {
- println("invalid value for -test.cpu")
+ fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu", val)
os.Exit(1)
}
cpuList = append(cpuList, cpu)