Skip to content

Commit

Permalink
testcase.T#Done now allows you to be notified about when a test has c…
Browse files Browse the repository at this point in the history
…ompleted

This makes it easy to be notified about the end of a test when you are working with goroutines and channels.
  • Loading branch information
adamluzsi committed Jun 4, 2024
1 parent 85b2109 commit 2f7606f
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 11 deletions.
34 changes: 23 additions & 11 deletions T.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package testcase

import (
"math/rand"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -35,9 +34,11 @@ func newT(tb testing.TB, spec *Spec) *T {
Random: random.New(rand.NewSource(spec.getTestSeed(tb))),
It: assert.MakeIt(tb),

spec: spec,
spec: spec,
tags: spec.getTagSet(),

vars: newVariables(),
tags: spec.getTagSet(),
done: make(chan struct{}),
teardown: &teardown.Teardown{CallerOffset: 1},
}
}
Expand All @@ -63,16 +64,14 @@ type T struct {
// but mark test failed on a failed assertion.
assert.It

spec *Spec
spec *Spec
tags map[string]struct{}

vars *variables
tags map[string]struct{}
done chan struct{}
teardown *teardown.Teardown

depsInit sync.Once
deps map[string]struct{}

// TODO: protect it against concurrency
timerPaused bool
timerPaused bool // TODO: protect it against concurrency

cache struct {
contexts []*Spec
Expand Down Expand Up @@ -116,6 +115,9 @@ func (t *T) setUp() func() {
t.TB.Helper()
t.vars.reset()

done := make(chan struct{})
t.done = done

contexts := t.contexts()
for _, c := range contexts {
t.vars.merge(c.vars)
Expand All @@ -133,7 +135,10 @@ func (t *T) setUp() func() {
}
}

return t.teardown.Finish
return func() {
t.teardown.Finish()
close(done)
}
}

func (t *T) HasTag(tag string) bool {
Expand Down Expand Up @@ -271,3 +276,10 @@ func (t *T) LogPretty(vs ...any) {
}
t.Log(args...)
}

// Done function notifies the end of the test.
// If a test involves goroutines, listening to the done channel from the test
// can notify them about the test's end, preventing goroutine leaks.
func (t *T) Done() <-chan struct{} {
return t.done
}
52 changes: 52 additions & 0 deletions T_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"math/rand"
"os"
"sync/atomic"
"testing"
"time"

Expand Down Expand Up @@ -599,3 +600,54 @@ func TestT_LogPretty(t *testing.T) {
assert.Contain(t, dtb.Logs.String(), "[]int{\n\t1,\n\t2,\n\t4,\n}")
assert.Contain(t, dtb.Logs.String(), "testcase_test.X{\n\tFoo: \"hello\",\n}")
}

func ExampleT_Done() {
s := testcase.NewSpec(nil)

s.Test("", func(t *testcase.T) {
go func() {
select {
// case do something for the test
case <-t.Done():
return // test is over, time to garbage collect
}
}()
})
}

func TestT_Done(t *testing.T) {
s := testcase.NewSpec(t)

var isdone = func(t *testcase.T) bool {
select {
case <-t.Done():
return true
default:
return false
}
}

var done int32
s.Test("", func(t *testcase.T) {
assert.False(t, isdone(t))
go func() {
<-t.Done() // after the test is done
atomic.AddInt32(&done, 1)
}()
t.Cleanup(func() {
assert.False(t, isdone(t),
"during cleanup the done should be not ready")

t.Cleanup(func() {
assert.False(t, isdone(t),
"during a cleanup of cleanup, done should not be ready")
})
})
})

s.Finish()

assert.Eventually(t, time.Second, func(t assert.It) {
assert.Equal(t, atomic.LoadInt32(&done), 1)
})
}

0 comments on commit 2f7606f

Please sign in to comment.