Merge pull request #56 from rogpeppe-contrib/010-defer-vs-cleanup
integrate Defer with Cleanup
Francesco Banconi authored 4 years ago
GitHub committed 4 years ago
0 | // +build go1.14 | |
1 | ||
2 | package quicktest_test | |
3 | ||
4 | import ( | |
5 | "testing" | |
6 | ||
7 | qt "github.com/frankban/quicktest" | |
8 | ) | |
9 | ||
10 | // This file defines tests that are only valid since the Cleanup | |
11 | // method was added in Go 1.14. | |
12 | ||
13 | func TestCCleanup(t *testing.T) { | |
14 | c := qt.New(t) | |
15 | cleanups := 0 | |
16 | c.Run("defer", func(c *qt.C) { | |
17 | c.Cleanup(func() { | |
18 | cleanups++ | |
19 | }) | |
20 | }) | |
21 | c.Assert(cleanups, qt.Equals, 1) | |
22 | } | |
23 | ||
24 | func TestCDeferWithoutDone(t *testing.T) { | |
25 | c := qt.New(t) | |
26 | tc := &testingTWithCleanup{ | |
27 | TB: t, | |
28 | cleanup: func() {}, | |
29 | } | |
30 | c1 := qt.New(tc) | |
31 | c1.Defer(func() {}) | |
32 | c1.Defer(func() {}) | |
33 | c.Assert(tc.cleanup, qt.PanicMatches, `Done not called after Defer`) | |
34 | } | |
35 | ||
36 | func TestCDeferVsCleanupOrder(t *testing.T) { | |
37 | c := qt.New(t) | |
38 | var defers []int | |
39 | testDefer(c, func(c *qt.C) { | |
40 | c.Defer(func() { | |
41 | defers = append(defers, 0) | |
42 | }) | |
43 | c.Cleanup(func() { | |
44 | defers = append(defers, 1) | |
45 | }) | |
46 | c.Defer(func() { | |
47 | defers = append(defers, 2) | |
48 | }) | |
49 | c.Cleanup(func() { | |
50 | defers = append(defers, 3) | |
51 | }) | |
52 | }) | |
53 | c.Assert(defers, qt.DeepEquals, []int{3, 2, 1, 0}) | |
54 | } | |
55 | ||
56 | type testingTWithCleanup struct { | |
57 | testing.TB | |
58 | cleanup func() | |
59 | } | |
60 | ||
61 | func (t *testingTWithCleanup) Cleanup(f func()) { | |
62 | oldCleanup := t.cleanup | |
63 | t.cleanup = func() { | |
64 | defer oldCleanup() | |
65 | f() | |
66 | } | |
67 | } |
0 | // +build !go1.14 | |
1 | ||
2 | package quicktest_test | |
3 | ||
4 | import ( | |
5 | "testing" | |
6 | ||
7 | qt "github.com/frankban/quicktest" | |
8 | ) | |
9 | ||
10 | func TestCDeferCalledEvenAfterDeferPanic(t *testing.T) { | |
11 | // This test doesn't test anything useful under go 1.14 and | |
12 | // later when Cleanup is built in. | |
13 | c := qt.New(t) | |
14 | deferred1 := 0 | |
15 | deferred2 := 0 | |
16 | c.Defer(func() { | |
17 | deferred1++ | |
18 | }) | |
19 | c.Defer(func() { | |
20 | panic("scream and shout") | |
21 | }) | |
22 | c.Defer(func() { | |
23 | deferred2++ | |
24 | }) | |
25 | c.Defer(func() { | |
26 | panic("run in circles") | |
27 | }) | |
28 | func() { | |
29 | defer func() { | |
30 | c.Check(recover(), qt.Equals, "scream and shout") | |
31 | }() | |
32 | c.Done() | |
33 | }() | |
34 | c.Assert(deferred1, qt.Equals, 1) | |
35 | c.Assert(deferred2, qt.Equals, 1) | |
36 | // Check that calling Done again doesn't panic. | |
37 | c.Done() | |
38 | c.Assert(deferred1, qt.Equals, 1) | |
39 | c.Assert(deferred2, qt.Equals, 1) | |
40 | } |
13 | 13 | func TestPatchSetInt(t *testing.T) { |
14 | 14 | c := qt.New(t) |
15 | 15 | i := 99 |
16 | c.Patch(&i, 88) | |
17 | c.Assert(i, qt.Equals, 88) | |
18 | c.Done() | |
16 | testDefer(c, func(c *qt.C) { | |
17 | c.Patch(&i, 88) | |
18 | c.Assert(i, qt.Equals, 88) | |
19 | }) | |
19 | 20 | c.Assert(i, qt.Equals, 99) |
20 | 21 | } |
21 | 22 | |
24 | 25 | oldErr := errors.New("foo") |
25 | 26 | newErr := errors.New("bar") |
26 | 27 | err := oldErr |
27 | c.Patch(&err, newErr) | |
28 | c.Assert(err, qt.Equals, newErr) | |
29 | c.Done() | |
28 | testDefer(c, func(c *qt.C) { | |
29 | c.Patch(&err, newErr) | |
30 | c.Assert(err, qt.Equals, newErr) | |
31 | }) | |
30 | 32 | c.Assert(err, qt.Equals, oldErr) |
31 | 33 | } |
32 | 34 | |
34 | 36 | c := qt.New(t) |
35 | 37 | oldErr := errors.New("foo") |
36 | 38 | err := oldErr |
37 | c.Patch(&err, nil) | |
38 | c.Assert(err, qt.Equals, nil) | |
39 | c.Done() | |
39 | testDefer(c, func(c *qt.C) { | |
40 | c.Patch(&err, nil) | |
41 | c.Assert(err, qt.Equals, nil) | |
42 | }) | |
40 | 43 | c.Assert(err, qt.Equals, oldErr) |
41 | 44 | } |
42 | 45 | |
44 | 47 | c := qt.New(t) |
45 | 48 | oldMap := map[string]int{"foo": 1234} |
46 | 49 | m := oldMap |
47 | c.Patch(&m, nil) | |
48 | c.Assert(m, qt.IsNil) | |
49 | c.Done() | |
50 | testDefer(c, func(c *qt.C) { | |
51 | c.Patch(&m, nil) | |
52 | c.Assert(m, qt.IsNil) | |
53 | }) | |
50 | 54 | c.Assert(m, qt.DeepEquals, oldMap) |
51 | 55 | } |
52 | 56 | |
61 | 65 | c := qt.New(t) |
62 | 66 | const envName = "SOME_VAR" |
63 | 67 | os.Setenv(envName, "initial") |
64 | c.Setenv(envName, "new value") | |
65 | c.Check(os.Getenv(envName), qt.Equals, "new value") | |
66 | c.Done() | |
68 | testDefer(c, func(c *qt.C) { | |
69 | c.Setenv(envName, "new value") | |
70 | c.Check(os.Getenv(envName), qt.Equals, "new value") | |
71 | }) | |
67 | 72 | c.Check(os.Getenv(envName), qt.Equals, "initial") |
68 | 73 | } |
69 | 74 | |
70 | 75 | func TestMkdir(t *testing.T) { |
71 | 76 | c := qt.New(t) |
72 | dir := c.Mkdir() | |
73 | c.Assert(c, qt.Not(qt.Equals), "") | |
74 | info, err := os.Stat(dir) | |
75 | c.Assert(err, qt.Equals, nil) | |
76 | c.Assert(info.IsDir(), qt.Equals, true) | |
77 | f, err := os.Create(filepath.Join(dir, "hello")) | |
78 | c.Assert(err, qt.Equals, nil) | |
79 | f.Close() | |
80 | c.Done() | |
81 | _, err = os.Stat(dir) | |
77 | var dir string | |
78 | testDefer(c, func(c *qt.C) { | |
79 | dir = c.Mkdir() | |
80 | c.Assert(dir, qt.Not(qt.Equals), "") | |
81 | info, err := os.Stat(dir) | |
82 | c.Assert(err, qt.Equals, nil) | |
83 | c.Assert(info.IsDir(), qt.Equals, true) | |
84 | f, err := os.Create(filepath.Join(dir, "hello")) | |
85 | c.Assert(err, qt.Equals, nil) | |
86 | f.Close() | |
87 | }) | |
88 | _, err := os.Stat(dir) | |
82 | 89 | c.Assert(err, qt.Not(qt.IsNil)) |
83 | 90 | } |
57 | 57 | type C struct { |
58 | 58 | testing.TB |
59 | 59 | |
60 | mu sync.Mutex | |
61 | deferred func() | |
62 | format formatFunc | |
60 | mu sync.Mutex | |
61 | doneNeeded bool | |
62 | deferred func() | |
63 | format formatFunc | |
64 | } | |
65 | ||
66 | // cleaner is implemented by testing.TB on Go 1.14 and later. | |
67 | type cleaner interface { | |
68 | Cleanup(func()) | |
63 | 69 | } |
64 | 70 | |
65 | 71 | // Defer registers a function to be called when c.Done is |
66 | 72 | // called. Deferred functions will be called in last added, first called |
67 | // order. | |
73 | // order. If c.Done is not called by the end of the test, the test | |
74 | // may panic. Note that if Cleanup is called, there is no | |
75 | // need to call Done. | |
68 | 76 | func (c *C) Defer(f func()) { |
69 | 77 | c.mu.Lock() |
70 | 78 | defer c.mu.Unlock() |
79 | if cleaner, ok := c.TB.(cleaner); ok { | |
80 | // Use TB.Cleanup when available, but add a check | |
81 | // that Done has been called so that we don't run | |
82 | // into unexpected Go version incompatibilities. | |
83 | if c.doneNeeded { | |
84 | // We've already installed the wrapper func that checks for Done | |
85 | // so we can avoid doing it again. | |
86 | cleaner.Cleanup(f) | |
87 | return | |
88 | } | |
89 | c.doneNeeded = true | |
90 | cleaner.Cleanup(func() { | |
91 | c.mu.Lock() | |
92 | doneNeeded := c.doneNeeded | |
93 | c.mu.Unlock() | |
94 | if doneNeeded { | |
95 | panic("Done not called after Defer") | |
96 | } | |
97 | f() | |
98 | }) | |
99 | return | |
100 | } | |
101 | ||
71 | 102 | oldDeferred := c.deferred |
72 | 103 | c.deferred = func() { |
73 | 104 | if oldDeferred != nil { |
87 | 118 | c.mu.Lock() |
88 | 119 | deferred := c.deferred |
89 | 120 | c.deferred = nil |
121 | c.doneNeeded = false | |
90 | 122 | c.mu.Unlock() |
91 | 123 | |
92 | 124 | if deferred != nil { |
5 | 5 | "bytes" |
6 | 6 | "errors" |
7 | 7 | "fmt" |
8 | "runtime" | |
9 | 8 | "strings" |
10 | 9 | "testing" |
11 | 10 | |
510 | 509 | func TestCDefer(t *testing.T) { |
511 | 510 | c := qt.New(t) |
512 | 511 | var defers []int |
513 | c.Defer(func() { defers = append(defers, 1) }) | |
514 | c.Defer(func() { defers = append(defers, 2) }) | |
515 | c.Done() | |
512 | testDefer(c, func(c *qt.C) { | |
513 | c.Defer(func() { defers = append(defers, 1) }) | |
514 | c.Defer(func() { defers = append(defers, 2) }) | |
515 | // Calling Done twice should not do anything more. | |
516 | c.Done() | |
517 | }) | |
516 | 518 | c.Assert(defers, qt.DeepEquals, []int{2, 1}) |
517 | // Calling Done again should not do anything. | |
518 | c.Done() | |
519 | c.Assert(defers, qt.DeepEquals, []int{2, 1}) | |
520 | } | |
521 | ||
522 | func TestCDeferCalledEvenAfterDeferPanic(t *testing.T) { | |
523 | c := qt.New(t) | |
524 | deferred1 := 0 | |
525 | deferred2 := 0 | |
526 | c.Defer(func() { | |
527 | deferred1++ | |
528 | }) | |
529 | c.Defer(func() { | |
530 | panic("scream and shout") | |
531 | }) | |
532 | c.Defer(func() { | |
533 | deferred2++ | |
534 | }) | |
535 | c.Defer(func() { | |
536 | panic("run in circles") | |
537 | }) | |
538 | func() { | |
539 | defer func() { | |
540 | c.Check(recover(), qt.Equals, "scream and shout") | |
541 | }() | |
542 | c.Done() | |
543 | }() | |
544 | c.Assert(deferred1, qt.Equals, 1) | |
545 | c.Assert(deferred2, qt.Equals, 1) | |
546 | c.Done() | |
547 | c.Assert(deferred1, qt.Equals, 1) | |
548 | c.Assert(deferred2, qt.Equals, 1) | |
549 | 519 | } |
550 | 520 | |
551 | 521 | func TestCDeferCalledEvenAfterGoexit(t *testing.T) { |
554 | 524 | // called in that case. |
555 | 525 | c := qt.New(t) |
556 | 526 | defers := 0 |
557 | c.Defer(func() { | |
558 | defers++ | |
559 | }) | |
560 | c.Defer(func() { | |
561 | runtime.Goexit() | |
562 | }) | |
563 | done := make(chan struct{}) | |
564 | go func() { | |
565 | defer close(done) | |
566 | c.Done() | |
567 | select {} | |
568 | }() | |
569 | <-done | |
527 | testDefer(c, func(c *qt.C) { | |
528 | c.Defer(func() { | |
529 | defers++ | |
530 | }) | |
531 | c.Defer(func() { | |
532 | c.SkipNow() | |
533 | }) | |
534 | }) | |
570 | 535 | c.Assert(defers, qt.Equals, 1) |
571 | c.Done() | |
536 | } | |
537 | ||
538 | func TestCRunDefer(t *testing.T) { | |
539 | c := qt.New(t) | |
540 | defers := 0 | |
541 | testDefer(c, func(c *qt.C) { | |
542 | c.Run("x", func(c *qt.C) { | |
543 | c.Defer(func() { defers++ }) | |
544 | }) | |
545 | }) | |
572 | 546 | c.Assert(defers, qt.Equals, 1) |
573 | } | |
574 | ||
575 | func TestCRunDefer(t *testing.T) { | |
576 | c := qt.New(&testingT{}) | |
577 | outerDefer := 0 | |
578 | innerDefer := 0 | |
579 | c.Defer(func() { outerDefer++ }) | |
580 | c.Run("x", func(c *qt.C) { | |
581 | c.Defer(func() { innerDefer++ }) | |
582 | }) | |
583 | c.Assert(innerDefer, qt.Equals, 1) | |
584 | c.Assert(outerDefer, qt.Equals, 0) | |
585 | 547 | } |
586 | 548 | |
587 | 549 | type customT struct { |
755 | 717 | func (c *testingChecker) ArgNames() []string { |
756 | 718 | return c.argNames |
757 | 719 | } |
720 | ||
721 | func testDefer(c *qt.C, f func(c *qt.C)) { | |
722 | c.Run("defer", func(c *qt.C) { | |
723 | defer c.Done() | |
724 | f(c) | |
725 | }) | |
726 | } |
19 | 19 | const N = 100 |
20 | 20 | |
21 | 21 | var x, y int32 |
22 | c := qt.New(dummyT{}) | |
23 | var wg sync.WaitGroup | |
24 | // start calls f in two goroutines, each | |
25 | // running it N times. | |
26 | // All the goroutines get started before we actually | |
27 | // start them running, so that the race detector | |
28 | // has a better chance of catching issues. | |
29 | gogogo := make(chan struct{}) | |
30 | start := func(f func()) { | |
31 | repeat := func() { | |
32 | defer wg.Done() | |
33 | <-gogogo | |
34 | for i := 0; i < N; i++ { | |
35 | f() | |
22 | c := qt.New(dummyT{t}) | |
23 | testDefer(c, func(c *qt.C) { | |
24 | var wg sync.WaitGroup | |
25 | // start calls f in two goroutines, each | |
26 | // running it N times. | |
27 | // All the goroutines get started before we actually | |
28 | // start them running, so that the race detector | |
29 | // has a better chance of catching issues. | |
30 | gogogo := make(chan struct{}) | |
31 | start := func(f func()) { | |
32 | repeat := func() { | |
33 | defer wg.Done() | |
34 | <-gogogo | |
35 | for i := 0; i < N; i++ { | |
36 | f() | |
37 | } | |
36 | 38 | } |
39 | wg.Add(2) | |
40 | go repeat() | |
41 | go repeat() | |
37 | 42 | } |
38 | wg.Add(2) | |
39 | go repeat() | |
40 | go repeat() | |
41 | } | |
42 | start(func() { | |
43 | c.Defer(func() { | |
44 | atomic.AddInt32(&x, 1) | |
43 | start(func() { | |
44 | c.Defer(func() { | |
45 | atomic.AddInt32(&x, 1) | |
46 | }) | |
47 | c.Defer(func() { | |
48 | atomic.AddInt32(&y, 1) | |
49 | }) | |
45 | 50 | }) |
46 | c.Defer(func() { | |
47 | atomic.AddInt32(&y, 1) | |
51 | start(func() { | |
52 | c.Done() | |
48 | 53 | }) |
54 | start(func() { | |
55 | c.SetFormat(func(v interface{}) string { | |
56 | return "x" | |
57 | }) | |
58 | }) | |
59 | start(func() { | |
60 | // Do an assert to exercise the formatter. | |
61 | c.Check(true, qt.Equals, false) | |
62 | }) | |
63 | start(func() { | |
64 | c.Run("", func(c *qt.C) {}) | |
65 | }) | |
66 | close(gogogo) | |
67 | wg.Wait() | |
49 | 68 | }) |
50 | start(func() { | |
51 | c.Done() | |
52 | }) | |
53 | start(func() { | |
54 | c.SetFormat(func(v interface{}) string { | |
55 | return "x" | |
56 | }) | |
57 | }) | |
58 | start(func() { | |
59 | // Do an assert to exercise the formatter. | |
60 | c.Check(true, qt.Equals, false) | |
61 | }) | |
62 | start(func() { | |
63 | c.Run("", func(c *qt.C) {}) | |
64 | }) | |
65 | close(gogogo) | |
66 | wg.Wait() | |
67 | c.Done() | |
68 | ||
69 | 69 | // Check that all the defer functions ran OK. |
70 | 70 | if x != N*2 || y != N*2 { |
71 | 71 | t.Fatalf("unexpected x, y counts; got %d, %d; want %d, %d", x, y, N*2, N*2) |
72 | 72 | } |
73 | 73 | } |
74 | 74 | |
75 | // dummyT implements the testing.TB methods | |
76 | // required for TestConcurentMethods. | |
75 | // dummyT wraps a *testing.T value suitable | |
76 | // for TestConcurrentMethods so that calling Error | |
77 | // won't fail the test and that it implements | |
78 | // Run correctly. | |
77 | 79 | type dummyT struct { |
78 | testing.TB | |
80 | *testing.T | |
79 | 81 | } |
80 | 82 | |
81 | 83 | func (dummyT) Error(...interface{}) {} |
82 | 84 | |
83 | func (dummyT) Run(name string, f func(t *testing.T)) bool { | |
84 | return false | |
85 | func (t dummyT) Run(name string, f func(t dummyT)) bool { | |
86 | return t.T.Run(name, func(t *testing.T) { | |
87 | f(dummyT{t}) | |
88 | }) | |
85 | 89 | } |