Skip to content

Commit e4c4265

Browse files
committed
Add EnforceDefaultTimeoutsWhenUsingContexts()
Resolves #781
1 parent 7cabed6 commit e4c4265

7 files changed

+59
-9
lines changed

docs/index.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ You can also configure the context in this way:
280280
Eventually(ACTUAL).WithTimeout(TIMEOUT).WithPolling(POLLING_INTERVAL).WithContext(ctx).Should(MATCHER)
281281
```
282282

283-
When no explicit timeout is provided, `Eventually` will use the default timeout. However if no explicit timeout is provided _and_ a context is provided, `Eventually` will not apply a timeout but will instead keep trying until the context is cancelled. If both a context and a timeout are provided, `Eventually` will keep trying until either the context is cancelled or time runs out, whichever comes first.
283+
When no explicit timeout is provided, `Eventually` will use the default timeout. If both a context and a timeout are provided, `Eventually` will keep trying until either the context is cancelled or time runs out, whichever comes first. However if no explicit timeout is provided _and_ a context is provided, `Eventually` will not apply a timeout but will instead keep trying until the context is cancelled. This behavior is intentional in order to allow a single `context` to control the duration of a collection of `Eventually` assertions. To opt out of this behavior you can call the global `EnforceDefaultTimeoutsWhenUsingContexts()` configuration to force `Eventually` to apply a default timeout even when a context is provided.
284284

285285
You can also ensure a number of consecutive pass before continuing with `MustPassRepeatedly`:
286286

@@ -588,6 +588,8 @@ SetDefaultConsistentlyPollingInterval(t time.Duration)
588588

589589
You can also adjust these global timeouts by setting the `GOMEGA_DEFAULT_EVENTUALLY_TIMEOUT`, `GOMEGA_DEFAULT_EVENTUALLY_POLLING_INTERVAL`, `GOMEGA_DEFAULT_CONSISTENTLY_DURATION`, and `GOMEGA_DEFAULT_CONSISTENTLY_POLLING_INTERVAL` environment variables to a parseable duration string. The environment variables have a lower precedence than `SetDefault...()`.
590590

591+
As discussed [above](#category-2-making-eventually-assertions-on-functions) `Eventually`s that are passed a `context` object without an explicit timeout will only stop polling when the context is cancelled. If you would like to enforce the default timeout when a context is provided you can call `EnforceDefaultTimeoutsWhenUsingContexts()` (to go back to the default behavior call `DoNotEnforceDefaultTimeoutsWhenUsingContexts()`). You can also set the `GOMEGA_ENFORCE_DEFAULT_TIMEOUTS_WHEN_USING_CONTEXTS` environment variable to enforce the default timeout when a context is provided.
592+
591593
## Making Assertions in Helper Functions
592594

593595
While writing [custom matchers](#adding-your-own-matchers) is an expressive way to make assertions against your code, it is often more convenient to write one-off helper functions like so:

gomega_dsl.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,19 @@ you an also use Eventually().WithContext(ctx) to pass in the context. Passed-in
319319
Eventually(client.FetchCount).WithContext(ctx).WithArguments("/users").Should(BeNumerically(">=", 17))
320320
}, SpecTimeout(time.Second))
321321
322-
Either way the context passd to Eventually is also passed to the underlying function. Now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit.
322+
Either way the context pasesd to Eventually is also passed to the underlying function. Now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit.
323+
324+
By default, when a context is passed to Eventually *without* an explicit timeout, Gomega will rely solely on the context's cancellation to determine when to stop polling. If you want to specify a timeout in addition to the context you can do so using the .WithTimeout() method. For example:
325+
326+
Eventually(client.FetchCount).WithContext(ctx).WithTimeout(10*time.Second).Should(BeNumerically(">=", 17))
327+
328+
now either the context cacnellation or the timeout will cause Eventually to stop polling.
329+
330+
If, instead, you would like to opt out of this behavior and have Gomega's default timeouts govern Eventuallys that take a context you can call:
331+
332+
EnforceDefaultTimeoutsWhenUsingContexts()
333+
334+
in the DSL (or on a Gomega instance). Now all calls to Eventually that take a context will fail if eitehr the context is cancelled or the default timeout elapses.
323335
324336
**Category 3: Making assertions _in_ the function passed into Eventually**
325337

internal/async_assertion.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ func (assertion *AsyncAssertion) afterTimeout() <-chan time.Time {
335335
if assertion.asyncType == AsyncAssertionTypeConsistently {
336336
return time.After(assertion.g.DurationBundle.ConsistentlyDuration)
337337
} else {
338-
if assertion.ctx == nil {
338+
if assertion.ctx == nil || assertion.g.DurationBundle.EnforceDefaultTimeoutsWhenUsingContexts {
339339
return time.After(assertion.g.DurationBundle.EventuallyTimeout)
340340
} else {
341341
return nil

internal/async_assertion_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,26 @@ var _ = Describe("Asynchronous Assertions", func() {
346346
Ω(ig.FailureMessage).Should(ContainSubstring("Context was cancelled after"))
347347
})
348348

349+
It("uses the default timeout if the user explicitly opts into EnforceDefaultTimeoutsWhenUsingContexts()", func() {
350+
ig.G.SetDefaultEventuallyTimeout(time.Millisecond * 100)
351+
ig.G.SetDefaultEventuallyPollingInterval(time.Millisecond * 10)
352+
ig.G.EnforceDefaultTimeoutsWhenUsingContexts()
353+
t := time.Now()
354+
ctx, cancel := context.WithCancel(context.Background())
355+
iterations := 0
356+
ig.G.Eventually(func() string {
357+
iterations += 1
358+
if time.Since(t) > time.Millisecond*1000 {
359+
cancel()
360+
}
361+
return "A"
362+
}).WithContext(ctx).Should(Equal("B"))
363+
Ω(time.Since(t)).Should(BeNumerically("~", time.Millisecond*100, time.Millisecond*50))
364+
Ω(iterations).Should(BeNumerically("~", 100/10, 2))
365+
Ω(ig.FailureMessage).Should(ContainSubstring("Timed out after"))
366+
Ω(ctx.Err()).Should(BeNil())
367+
})
368+
349369
It("uses the explicit timeout when it is provided", func() {
350370
t := time.Now()
351371
ctx, cancel := context.WithCancel(context.Background())

internal/duration_bundle.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import (
88
)
99

1010
type DurationBundle struct {
11-
EventuallyTimeout time.Duration
12-
EventuallyPollingInterval time.Duration
13-
ConsistentlyDuration time.Duration
14-
ConsistentlyPollingInterval time.Duration
11+
EventuallyTimeout time.Duration
12+
EventuallyPollingInterval time.Duration
13+
ConsistentlyDuration time.Duration
14+
ConsistentlyPollingInterval time.Duration
15+
EnforceDefaultTimeoutsWhenUsingContexts bool
1516
}
1617

1718
const (
@@ -20,15 +21,19 @@ const (
2021

2122
ConsistentlyDurationEnvVarName = "GOMEGA_DEFAULT_CONSISTENTLY_DURATION"
2223
ConsistentlyPollingIntervalEnvVarName = "GOMEGA_DEFAULT_CONSISTENTLY_POLLING_INTERVAL"
24+
25+
EnforceDefaultTimeoutsWhenUsingContextsEnvVarName = "GOMEGA_ENFORCE_DEFAULT_TIMEOUTS_WHEN_USING_CONTEXTS"
2326
)
2427

2528
func FetchDefaultDurationBundle() DurationBundle {
29+
_, EnforceDefaultTimeoutsWhenUsingContexts := os.LookupEnv(EnforceDefaultTimeoutsWhenUsingContextsEnvVarName)
2630
return DurationBundle{
2731
EventuallyTimeout: durationFromEnv(EventuallyTimeoutEnvVarName, time.Second),
2832
EventuallyPollingInterval: durationFromEnv(EventuallyPollingIntervalEnvVarName, 10*time.Millisecond),
2933

30-
ConsistentlyDuration: durationFromEnv(ConsistentlyDurationEnvVarName, 100*time.Millisecond),
31-
ConsistentlyPollingInterval: durationFromEnv(ConsistentlyPollingIntervalEnvVarName, 10*time.Millisecond),
34+
ConsistentlyDuration: durationFromEnv(ConsistentlyDurationEnvVarName, 100*time.Millisecond),
35+
ConsistentlyPollingInterval: durationFromEnv(ConsistentlyPollingIntervalEnvVarName, 10*time.Millisecond),
36+
EnforceDefaultTimeoutsWhenUsingContexts: EnforceDefaultTimeoutsWhenUsingContexts,
3237
}
3338
}
3439

internal/duration_bundle_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var _ = Describe("DurationBundle and Duration Support", func() {
4343
Ω(bundle.EventuallyPollingInterval).Should(Equal(10 * time.Millisecond))
4444
Ω(bundle.ConsistentlyDuration).Should(Equal(100 * time.Millisecond))
4545
Ω(bundle.ConsistentlyPollingInterval).Should(Equal(10 * time.Millisecond))
46+
Ω(bundle.EnforceDefaultTimeoutsWhenUsingContexts).Should(BeFalse())
4647
})
4748
})
4849

@@ -52,6 +53,7 @@ var _ = Describe("DurationBundle and Duration Support", func() {
5253
os.Setenv(internal.EventuallyPollingIntervalEnvVarName, "2s")
5354
os.Setenv(internal.ConsistentlyDurationEnvVarName, "1h")
5455
os.Setenv(internal.ConsistentlyPollingIntervalEnvVarName, "3ms")
56+
os.Setenv(internal.EnforceDefaultTimeoutsWhenUsingContextsEnvVarName, "")
5557
})
5658

5759
It("returns an appropriate bundle", func() {
@@ -60,6 +62,7 @@ var _ = Describe("DurationBundle and Duration Support", func() {
6062
Ω(bundle.EventuallyPollingInterval).Should(Equal(2 * time.Second))
6163
Ω(bundle.ConsistentlyDuration).Should(Equal(time.Hour))
6264
Ω(bundle.ConsistentlyPollingInterval).Should(Equal(3 * time.Millisecond))
65+
Ω(bundle.EnforceDefaultTimeoutsWhenUsingContexts).Should(BeTrue())
6366
})
6467
})
6568

internal/gomega.go

+8
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,11 @@ func (g *Gomega) SetDefaultConsistentlyDuration(t time.Duration) {
127127
func (g *Gomega) SetDefaultConsistentlyPollingInterval(t time.Duration) {
128128
g.DurationBundle.ConsistentlyPollingInterval = t
129129
}
130+
131+
func (g *Gomega) EnforceDefaultTimeoutsWhenUsingContexts() {
132+
g.DurationBundle.EnforceDefaultTimeoutsWhenUsingContexts = true
133+
}
134+
135+
func (g *Gomega) DisableDefaultTimeoutsWhenUsingContext() {
136+
g.DurationBundle.EnforceDefaultTimeoutsWhenUsingContexts = false
137+
}

0 commit comments

Comments
 (0)