mirror of
https://github.com/golang/go
synced 2024-11-20 08:14:41 -07:00
testing: fix defer race
In a test that does func TestFoo(t *testing.T) { defer cleanup() t.Fatal("oops") } it can be important that cleanup run as the test fails. The old code did this in Fatal: t.signal <- t runtime.Goexit() The runtime.Goexit would run the deferred cleanup but the send on t.signal would cause the main test loop to move on and possibly even exit the program before the runtime.Goexit got a chance to run. This CL changes tRunner (the top stack frame of a test goroutine) to send on t.signal as part of a function deferred by the top stack frame. This delays the send on t.signal until after runtime.Goexit has run functions deferred by the test itself. For the above TestFoo, this CL guarantees that cleanup will run before the test binary exits. This is particularly important when cleanup is doing externally visible work, like removing temporary files or unmounting file systems. R=golang-dev, r CC=golang-dev https://golang.org/cl/5532078
This commit is contained in:
parent
725f084b11
commit
4953b87296
@ -142,6 +142,13 @@ func (b *B) run() BenchmarkResult {
|
||||
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.
|
||||
d := time.Duration(*benchTime * float64(time.Second))
|
||||
@ -162,7 +169,6 @@ func (b *B) launch() {
|
||||
b.runN(n)
|
||||
}
|
||||
b.result = BenchmarkResult{b.N, b.duration, b.bytes}
|
||||
b.signal <- b
|
||||
}
|
||||
|
||||
// The results of a benchmark run.
|
||||
|
@ -136,9 +136,27 @@ func (c *common) Failed() bool { return c.failed }
|
||||
// FailNow marks the function as having failed and stops its execution.
|
||||
// Execution will continue at the next Test.
|
||||
func (c *common) FailNow() {
|
||||
c.duration = time.Now().Sub(c.start)
|
||||
c.Fail()
|
||||
c.signal <- c.self
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
@ -195,9 +213,17 @@ 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.duration = time.Now().Sub(t.start)
|
||||
t.signal <- t
|
||||
}
|
||||
|
||||
// An internal function but exported because it is cross-package; part of the implementation
|
||||
|
Loading…
Reference in New Issue
Block a user