Commit 1b2ff101 authored by Roger Peppe's avatar Roger Peppe Committed by roger peppe

testing: implement Cleanup method

Fixes #32111

Change-Id: I7078947889d1e126d9679fb28f27b3fa6ce133ef
Reviewed-on: https://go-review.googlesource.com/c/go/+/201359Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent fb29e225
...@@ -248,6 +248,16 @@ TODO ...@@ -248,6 +248,16 @@ TODO
</dl><!-- runtime --> </dl><!-- runtime -->
<dl id="testing"><dt><a href="/pkg/testing/">testing</a></dt>
<dd>
<p><!-- CL 201359 -->
The testing package now supports cleanup functions, called after
a test or benchmark has finished, by calling
<a href="/pkg/testing#T.Cleanup"><code>T.Cleanup</code></a> or
<a href="/pkg/testing#B.Cleanup"><code>B.Cleanup</code></a> respectively.
</p>
</dl><!-- testing -->
<h3 id="minor_library_changes">Minor changes to the library</h3> <h3 id="minor_library_changes">Minor changes to the library</h3>
<p> <p>
......
...@@ -7,6 +7,7 @@ package testing ...@@ -7,6 +7,7 @@ package testing
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"reflect"
"regexp" "regexp"
"runtime" "runtime"
"strings" "strings"
...@@ -790,3 +791,67 @@ func TestBenchmark(t *T) { ...@@ -790,3 +791,67 @@ func TestBenchmark(t *T) {
t.Errorf("want >5ms; got %v", time.Duration(res.NsPerOp())) t.Errorf("want >5ms; got %v", time.Duration(res.NsPerOp()))
} }
} }
func TestCleanup(t *T) {
var cleanups []int
t.Run("test", func(t *T) {
t.Cleanup(func() { cleanups = append(cleanups, 1) })
t.Cleanup(func() { cleanups = append(cleanups, 2) })
})
if got, want := cleanups, []int{2, 1}; !reflect.DeepEqual(got, want) {
t.Errorf("unexpected cleanup record; got %v want %v", got, want)
}
}
func TestConcurrentCleanup(t *T) {
cleanups := 0
t.Run("test", func(t *T) {
done := make(chan struct{})
for i := 0; i < 2; i++ {
i := i
go func() {
t.Cleanup(func() {
cleanups |= 1 << i
})
done <- struct{}{}
}()
}
<-done
<-done
})
if cleanups != 1|2 {
t.Errorf("unexpected cleanup; got %d want 3", cleanups)
}
}
func TestCleanupCalledEvenAfterGoexit(t *T) {
cleanups := 0
t.Run("test", func(t *T) {
t.Cleanup(func() {
cleanups++
})
t.Cleanup(func() {
runtime.Goexit()
})
})
if cleanups != 1 {
t.Errorf("unexpected cleanup count; got %d want 1", cleanups)
}
}
func TestRunCleanup(t *T) {
outerCleanup := 0
innerCleanup := 0
t.Run("test", func(t *T) {
t.Cleanup(func() { outerCleanup++ })
t.Run("x", func(t *T) {
t.Cleanup(func() { innerCleanup++ })
})
})
if innerCleanup != 1 {
t.Errorf("unexpected inner cleanup count; got %d want 1", innerCleanup)
}
if outerCleanup != 1 {
t.Errorf("unexpected outer cleanup count; got %d want 0", outerCleanup)
}
}
...@@ -344,6 +344,7 @@ type common struct { ...@@ -344,6 +344,7 @@ type common struct {
skipped bool // Test of benchmark has been skipped. skipped bool // Test of benchmark has been skipped.
done bool // Test is finished and all subtests have completed. done bool // Test is finished and all subtests have completed.
helpers map[string]struct{} // functions to be skipped when writing file/line info helpers map[string]struct{} // functions to be skipped when writing file/line info
cleanup func() // optional function to be called at the end of the test
chatty bool // A copy of the chatty flag. chatty bool // A copy of the chatty flag.
finished bool // Test function has completed. finished bool // Test function has completed.
...@@ -543,6 +544,7 @@ func fmtDuration(d time.Duration) string { ...@@ -543,6 +544,7 @@ func fmtDuration(d time.Duration) string {
// TB is the interface common to T and B. // TB is the interface common to T and B.
type TB interface { type TB interface {
Cleanup(func())
Error(args ...interface{}) Error(args ...interface{})
Errorf(format string, args ...interface{}) Errorf(format string, args ...interface{})
Fail() Fail()
...@@ -550,6 +552,7 @@ type TB interface { ...@@ -550,6 +552,7 @@ type TB interface {
Failed() bool Failed() bool
Fatal(args ...interface{}) Fatal(args ...interface{})
Fatalf(format string, args ...interface{}) Fatalf(format string, args ...interface{})
Helper()
Log(args ...interface{}) Log(args ...interface{})
Logf(format string, args ...interface{}) Logf(format string, args ...interface{})
Name() string Name() string
...@@ -557,7 +560,6 @@ type TB interface { ...@@ -557,7 +560,6 @@ type TB interface {
SkipNow() SkipNow()
Skipf(format string, args ...interface{}) Skipf(format string, args ...interface{})
Skipped() bool Skipped() bool
Helper()
// A private method to prevent users implementing the // A private method to prevent users implementing the
// interface and so future additions to it will not // interface and so future additions to it will not
...@@ -774,6 +776,32 @@ func (c *common) Helper() { ...@@ -774,6 +776,32 @@ func (c *common) Helper() {
c.helpers[callerName(1)] = struct{}{} c.helpers[callerName(1)] = struct{}{}
} }
// Cleanup registers a function to be called when the test finishes.
// Cleanup functions will be called in last added, first called
// order.
func (c *common) Cleanup(f func()) {
c.mu.Lock()
defer c.mu.Unlock()
oldCleanup := c.cleanup
c.cleanup = func() {
if oldCleanup != nil {
defer oldCleanup()
}
f()
}
}
// runCleanup is called at the end of the test.
func (c *common) runCleanup() {
c.mu.Lock()
cleanup := c.cleanup
c.cleanup = nil
c.mu.Unlock()
if cleanup != nil {
cleanup()
}
}
// callerName gives the function name (qualified with a package path) // callerName gives the function name (qualified with a package path)
// for the caller after skip frames (where 0 means the current function). // for the caller after skip frames (where 0 means the current function).
func callerName(skip int) string { func callerName(skip int) string {
...@@ -919,6 +947,7 @@ func tRunner(t *T, fn func(t *T)) { ...@@ -919,6 +947,7 @@ func tRunner(t *T, fn func(t *T)) {
} }
t.signal <- signal t.signal <- signal
}() }()
defer t.runCleanup()
t.start = time.Now() t.start = time.Now()
t.raceErrors = -race.Errors() t.raceErrors = -race.Errors()
......
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