Skip to content

Commit eeca931

Browse files
committed
Add Successfully() to StopTrying() to signal that Consistently can end early without failure
fixes #786
1 parent 3bdbc4e commit eeca931

File tree

4 files changed

+62
-3
lines changed

4 files changed

+62
-3
lines changed

docs/index.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,23 @@ calling `.Now()` will trigger a panic that will signal to `Eventually` that it s
539539

540540
You can also return `StopTrying()` errors and use `StopTrying().Now()` with `Consistently`.
541541

542-
Both `Eventually` and `Consistently` always treat the `StopTrying()` signal as a failure. The failure message will include the message passed in to `StopTrying()`.
542+
By default, both `Eventually` and `Consistently` treat the `StopTrying()` signal as a failure. The failure message will include the message passed in to `StopTrying()`. However, there are cases when you might want to short-circuit `Consistently` early without failing the test (e.g. you are using consistently to monitor the sideeffect of a goroutine and that goroutine has now ended. Once it ends there is no need to continue polling `Consistently`). In this case you can use `StopTrying(message).Successfully()` to signal that `Consistently` can end early without failing. For example:
543+
544+
```
545+
Consistently(func() bool {
546+
select{
547+
case err := <-done: //the process has ended
548+
if err != nil {
549+
return StopTrying("error occurred").Now()
550+
}
551+
StopTrying("success!).Successfully().Now()
552+
default:
553+
return GetCounts()
554+
}
555+
}).Should(BeNumerically("<", 10))
556+
```
557+
558+
note taht `StopTrying(message).Successfully()` is not intended for use with `Eventually`. `Eventually` *always* interprets `StopTrying` as a failure.
543559

544560
You can add additional information to this failure message in a few ways. You can wrap an error via `StopTrying(message).Wrap(wrappedErr)` - now the output will read `<message>: <wrappedErr.Error()>`.
545561

internal/async_assertion.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,15 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
496496
for _, err := range []error{actualErr, matcherErr} {
497497
if pollingSignalErr, ok := AsPollingSignalError(err); ok {
498498
if pollingSignalErr.IsStopTrying() {
499-
fail("Told to stop trying")
499+
if pollingSignalErr.IsSuccessful() {
500+
if assertion.asyncType == AsyncAssertionTypeEventually {
501+
fail("Told to stop trying (and ignoring call to Successfully(), as it is only relevant with Consistently)")
502+
} else {
503+
return true // early escape hatch for Consistently
504+
}
505+
} else {
506+
fail("Told to stop trying")
507+
}
500508
return false
501509
}
502510
if pollingSignalErr.IsTryAgainAfter() {

internal/async_assertion_test.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,19 @@ var _ = Describe("Asynchronous Assertions", func() {
12201220
Ω(ig.FailureMessage).Should(ContainSubstring("Told to stop trying after"))
12211221
Ω(ig.FailureMessage).Should(ContainSubstring("bam"))
12221222
})
1223+
1224+
It("fails, even if the match were to happen to succeed and the user uses Succeed", func() {
1225+
ig.G.Eventually(func() (int, error) {
1226+
i += 1
1227+
if i < 3 {
1228+
return i, nil
1229+
}
1230+
return i, StopTrying("bam").Successfully()
1231+
}).Should(Equal(3))
1232+
Ω(i).Should(Equal(3))
1233+
Ω(ig.FailureMessage).Should(ContainSubstring("Told to stop trying (and ignoring call to Successfully(), as it is only relevant with Consistently) after"))
1234+
Ω(ig.FailureMessage).Should(ContainSubstring("bam"))
1235+
})
12231236
})
12241237

12251238
Context("when returned as the sole actual", func() {
@@ -1278,7 +1291,7 @@ var _ = Describe("Asynchronous Assertions", func() {
12781291
})
12791292

12801293
Context("when used with consistently", func() {
1281-
It("always signifies a failure", func() {
1294+
It("signifies a failure", func() {
12821295
ig.G.Consistently(func() (int, error) {
12831296
i += 1
12841297
if i >= 3 {
@@ -1290,6 +1303,17 @@ var _ = Describe("Asynchronous Assertions", func() {
12901303
Ω(ig.FailureMessage).Should(ContainSubstring("Told to stop trying after"))
12911304
Ω(ig.FailureMessage).Should(ContainSubstring("bam"))
12921305
})
1306+
1307+
It("signifies success when called Successfully", func() {
1308+
Consistently(func() (int, error) {
1309+
i += 1
1310+
if i >= 3 {
1311+
return i, StopTrying("bam").Successfully()
1312+
}
1313+
return i, nil
1314+
}).Should(BeNumerically("<", 10))
1315+
Ω(i).Should(Equal(3))
1316+
})
12931317
})
12941318

12951319
Context("when StopTrying has attachments", func() {

internal/polling_signal_error.go

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type PollingSignalError interface {
1717
error
1818
Wrap(err error) PollingSignalError
1919
Attach(description string, obj any) PollingSignalError
20+
Successfully() PollingSignalError
2021
Now()
2122
}
2223

@@ -45,6 +46,7 @@ type PollingSignalErrorImpl struct {
4546
wrappedErr error
4647
pollingSignalErrorType PollingSignalErrorType
4748
duration time.Duration
49+
successful bool
4850
Attachments []PollingSignalErrorAttachment
4951
}
5052

@@ -73,6 +75,11 @@ func (s *PollingSignalErrorImpl) Unwrap() error {
7375
return s.wrappedErr
7476
}
7577

78+
func (s *PollingSignalErrorImpl) Successfully() PollingSignalError {
79+
s.successful = true
80+
return s
81+
}
82+
7683
func (s *PollingSignalErrorImpl) Now() {
7784
panic(s)
7885
}
@@ -81,6 +88,10 @@ func (s *PollingSignalErrorImpl) IsStopTrying() bool {
8188
return s.pollingSignalErrorType == PollingSignalErrorTypeStopTrying
8289
}
8390

91+
func (s *PollingSignalErrorImpl) IsSuccessful() bool {
92+
return s.successful
93+
}
94+
8495
func (s *PollingSignalErrorImpl) IsTryAgainAfter() bool {
8596
return s.pollingSignalErrorType == PollingSignalErrorTypeTryAgainAfter
8697
}

0 commit comments

Comments
 (0)