Skip to content

Commit 1b2ff10

Browse files
committed
testing: implement Cleanup method
Fixes golang#32111 Change-Id: I7078947889d1e126d9679fb28f27b3fa6ce133ef Reviewed-on: https://go-review.googlesource.com/c/go/+/201359 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent fb29e22 commit 1b2ff10

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

doc/go1.14.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,16 @@ <h2 id="library">Core library</h2>
248248

249249
</dl><!-- runtime -->
250250

251+
<dl id="testing"><dt><a href="/pkg/testing/">testing</a></dt>
252+
<dd>
253+
<p><!-- CL 201359 -->
254+
The testing package now supports cleanup functions, called after
255+
a test or benchmark has finished, by calling
256+
<a href="/pkg/testing#T.Cleanup"><code>T.Cleanup</code></a> or
257+
<a href="/pkg/testing#B.Cleanup"><code>B.Cleanup</code></a> respectively.
258+
</p>
259+
</dl><!-- testing -->
260+
251261
<h3 id="minor_library_changes">Minor changes to the library</h3>
252262

253263
<p>

src/testing/sub_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package testing
77
import (
88
"bytes"
99
"fmt"
10+
"reflect"
1011
"regexp"
1112
"runtime"
1213
"strings"
@@ -790,3 +791,67 @@ func TestBenchmark(t *T) {
790791
t.Errorf("want >5ms; got %v", time.Duration(res.NsPerOp()))
791792
}
792793
}
794+
795+
func TestCleanup(t *T) {
796+
var cleanups []int
797+
t.Run("test", func(t *T) {
798+
t.Cleanup(func() { cleanups = append(cleanups, 1) })
799+
t.Cleanup(func() { cleanups = append(cleanups, 2) })
800+
})
801+
if got, want := cleanups, []int{2, 1}; !reflect.DeepEqual(got, want) {
802+
t.Errorf("unexpected cleanup record; got %v want %v", got, want)
803+
}
804+
}
805+
806+
func TestConcurrentCleanup(t *T) {
807+
cleanups := 0
808+
t.Run("test", func(t *T) {
809+
done := make(chan struct{})
810+
for i := 0; i < 2; i++ {
811+
i := i
812+
go func() {
813+
t.Cleanup(func() {
814+
cleanups |= 1 << i
815+
})
816+
done <- struct{}{}
817+
}()
818+
}
819+
<-done
820+
<-done
821+
})
822+
if cleanups != 1|2 {
823+
t.Errorf("unexpected cleanup; got %d want 3", cleanups)
824+
}
825+
}
826+
827+
func TestCleanupCalledEvenAfterGoexit(t *T) {
828+
cleanups := 0
829+
t.Run("test", func(t *T) {
830+
t.Cleanup(func() {
831+
cleanups++
832+
})
833+
t.Cleanup(func() {
834+
runtime.Goexit()
835+
})
836+
})
837+
if cleanups != 1 {
838+
t.Errorf("unexpected cleanup count; got %d want 1", cleanups)
839+
}
840+
}
841+
842+
func TestRunCleanup(t *T) {
843+
outerCleanup := 0
844+
innerCleanup := 0
845+
t.Run("test", func(t *T) {
846+
t.Cleanup(func() { outerCleanup++ })
847+
t.Run("x", func(t *T) {
848+
t.Cleanup(func() { innerCleanup++ })
849+
})
850+
})
851+
if innerCleanup != 1 {
852+
t.Errorf("unexpected inner cleanup count; got %d want 1", innerCleanup)
853+
}
854+
if outerCleanup != 1 {
855+
t.Errorf("unexpected outer cleanup count; got %d want 0", outerCleanup)
856+
}
857+
}

src/testing/testing.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ type common struct {
344344
skipped bool // Test of benchmark has been skipped.
345345
done bool // Test is finished and all subtests have completed.
346346
helpers map[string]struct{} // functions to be skipped when writing file/line info
347+
cleanup func() // optional function to be called at the end of the test
347348

348349
chatty bool // A copy of the chatty flag.
349350
finished bool // Test function has completed.
@@ -543,21 +544,22 @@ func fmtDuration(d time.Duration) string {
543544

544545
// TB is the interface common to T and B.
545546
type TB interface {
547+
Cleanup(func())
546548
Error(args ...interface{})
547549
Errorf(format string, args ...interface{})
548550
Fail()
549551
FailNow()
550552
Failed() bool
551553
Fatal(args ...interface{})
552554
Fatalf(format string, args ...interface{})
555+
Helper()
553556
Log(args ...interface{})
554557
Logf(format string, args ...interface{})
555558
Name() string
556559
Skip(args ...interface{})
557560
SkipNow()
558561
Skipf(format string, args ...interface{})
559562
Skipped() bool
560-
Helper()
561563

562564
// A private method to prevent users implementing the
563565
// interface and so future additions to it will not
@@ -774,6 +776,32 @@ func (c *common) Helper() {
774776
c.helpers[callerName(1)] = struct{}{}
775777
}
776778

779+
// Cleanup registers a function to be called when the test finishes.
780+
// Cleanup functions will be called in last added, first called
781+
// order.
782+
func (c *common) Cleanup(f func()) {
783+
c.mu.Lock()
784+
defer c.mu.Unlock()
785+
oldCleanup := c.cleanup
786+
c.cleanup = func() {
787+
if oldCleanup != nil {
788+
defer oldCleanup()
789+
}
790+
f()
791+
}
792+
}
793+
794+
// runCleanup is called at the end of the test.
795+
func (c *common) runCleanup() {
796+
c.mu.Lock()
797+
cleanup := c.cleanup
798+
c.cleanup = nil
799+
c.mu.Unlock()
800+
if cleanup != nil {
801+
cleanup()
802+
}
803+
}
804+
777805
// callerName gives the function name (qualified with a package path)
778806
// for the caller after skip frames (where 0 means the current function).
779807
func callerName(skip int) string {
@@ -919,6 +947,7 @@ func tRunner(t *T, fn func(t *T)) {
919947
}
920948
t.signal <- signal
921949
}()
950+
defer t.runCleanup()
922951

923952
t.start = time.Now()
924953
t.raceErrors = -race.Errors()

0 commit comments

Comments
 (0)