Skip to content

Commit

Permalink
fix: make EventuallyWithT concurrency safe
Browse files Browse the repository at this point in the history
  • Loading branch information
czeslavo committed Jul 31, 2023
1 parent 862e410 commit 3e9f56d
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 19 deletions.
37 changes: 18 additions & 19 deletions assert/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1870,23 +1870,18 @@ func (c *CollectT) Errorf(format string, args ...interface{}) {
}

// FailNow panics.
func (c *CollectT) FailNow() {
func (*CollectT) FailNow() {
panic("Assertion failed")
}

// Reset clears the collected errors.
func (c *CollectT) Reset() {
c.errors = nil
// Deprecated: That was a method for internal usage that should not have been published. Now just panics.
func (*CollectT) Reset() {
panic("Reset() is deprecated")
}

// Copy copies the collected errors to the supplied t.
func (c *CollectT) Copy(t TestingT) {
if tt, ok := t.(tHelper); ok {
tt.Helper()
}
for _, err := range c.errors {
t.Errorf("%v", err)
}
// Deprecated: That was a method for internal usage that should not have been published. Now just panics.
func (*CollectT) Copy(TestingT) {
panic("Copy() is deprecated")
}

// EventuallyWithT asserts that given condition will be met in waitFor time,
Expand All @@ -1912,8 +1907,8 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time
h.Helper()
}

collect := new(CollectT)
ch := make(chan bool, 1)
var lastTickErrs []error
ch := make(chan []error, 1)

timer := time.NewTimer(waitFor)
defer timer.Stop()
Expand All @@ -1924,19 +1919,23 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time
for tick := ticker.C; ; {
select {
case <-timer.C:
collect.Copy(t)
for _, err := range lastTickErrs {
t.Errorf("%v", err)
}
return Fail(t, "Condition never satisfied", msgAndArgs...)
case <-tick:
tick = nil
collect.Reset()
go func() {
collect := new(CollectT)
condition(collect)
ch <- len(collect.errors) == 0
ch <- collect.errors
}()
case v := <-ch:
if v {
case errs := <-ch:
if len(errs) == 0 {
return true
}
// Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached.
lastTickErrs = errs
tick = ticker.C
}
}
Expand Down
29 changes: 29 additions & 0 deletions assert/assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2786,6 +2786,35 @@ func TestEventuallyWithTTrue(t *testing.T) {
Len(t, mockT.errors, 0)
}

func TestEventuallyWithT_ConcurrencySafe(t *testing.T) {
mockT := new(testing.T)

condition := func(collect *CollectT) {
True(collect, false)
}

// To trigger race conditions, we run EventuallyWithT with a nanosecond tick.
False(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, time.Nanosecond))
}

func TestEventuallyWithT_ReturnsTheLatestFinishedConditionErrors(t *testing.T) {
var calledOnce bool
condition := func(collect *CollectT) {
if calledOnce {
// Sleep to ensure that the second condition runs longer than timeout.
time.Sleep(time.Second)
return
}

// The first condition will fail. We expect to get this error as a result.
True(collect, false)
calledOnce = true
}

mockT := new(testing.T)
False(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond))
}

func TestNeverFalse(t *testing.T) {
condition := func() bool {
return false
Expand Down

0 comments on commit 3e9f56d

Please sign in to comment.