Skip to content

Commit 592f7a9

Browse files
committed
Fix the @Retry documentation
1 parent 5a5a20b commit 592f7a9

File tree

3 files changed

+121
-71
lines changed

3 files changed

+121
-71
lines changed

docs/extensions.adoc

Lines changed: 11 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -351,63 +351,27 @@ With the flag `applyGlobalTimeoutToFixtures` you can control if the global timeo
351351
The `@Retry` extensions can be used for flaky integration tests, where remote systems can fail sometimes.
352352
By default it retries an iteration `3` times with `0` delay if either an `Exception` or `AssertionError` has been thrown, all this is configurable.
353353
In addition, an optional `condition` closure can be used to determine if a feature should be retried.
354-
It also provides special support for data driven features, offering to either retry all iterations or just the failing ones.
354+
In its standard mode it only retries the feature method execution, but this can be changed using `mode` to
355+
also run setup and cleanup on retries. Even in this mode, the retry is only triggered if the feature method is failing
356+
in the expected way. If the setup or cleanup is failing, the test fails immediately.
355357

356358
[source,groovy]
357359
----
358-
class FlakyIntegrationSpec extends Specification {
359-
@Retry
360-
def retry3Times() { ... }
361-
362-
@Retry(count = 5)
363-
def retry5Times() { ... }
364-
365-
@Retry(exceptions=[IOException])
366-
def onlyRetryIOException() { ... }
367-
368-
@Retry(condition = { failure.message.contains('foo') })
369-
def onlyRetryIfConditionOnFailureHolds() { ... }
370-
371-
@Retry(condition = { instance.field != null })
372-
def onlyRetryIfConditionOnInstanceHolds() { ... }
373-
374-
@Retry
375-
def retryFailingIterations() {
376-
...
377-
where:
378-
data << sql.select()
379-
}
380-
381-
@Retry(mode = Retry.Mode.FEATURE)
382-
def retryWholeFeature() {
383-
...
384-
where:
385-
data << sql.select()
386-
}
387-
388-
@Retry(delay = 1000)
389-
def retryAfter1000MsDelay() { ... }
390-
}
360+
include::{sourcedir}/extension/RetryDocSpec.groovy[tag=example-common]
361+
include::{sourcedir}/extension/RetryDocSpec.groovy[tag=example-a]
391362
----
392363

393364
Retries can also be applied to spec classes which has the same effect as applying it to each feature method that isn't
394-
already annotated with {@code Retry}.
365+
already annotated with `@Retry`.
395366

396367
[source,groovy]
397368
----
398-
@Retry
399-
class FlakyIntegrationSpec extends Specification {
400-
def "will be retried with config from class"() {
401-
...
402-
}
403-
@Retry(count = 5)
404-
def "will be retried using its own config"() {
405-
...
406-
}
407-
}
369+
include::{sourcedir}/extension/RetryDocSpec.groovy[tag=example-b1]
370+
include::{sourcedir}/extension/RetryDocSpec.groovy[tag=example-common]
371+
include::{sourcedir}/extension/RetryDocSpec.groovy[tag=example-b2]
408372
----
409373

410-
A {@code @Retry} annotation that is declared on a spec class is applied to all features in all subclasses as well,
374+
A `@Retry` annotation that is declared on a spec class is applied to all features in all subclasses as well,
411375
unless a subclass declares its own annotation. If so, the retries defined in the subclass are applied to all feature
412376
methods declared in the subclass as well as inherited ones.
413377

@@ -416,25 +380,7 @@ Running `BarIntegrationSpec` will execute `inherited` and `bar` with two retries
416380

417381
[source,groovy]
418382
----
419-
@Retry(count = 1)
420-
abstract class AbstractIntegrationSpec extends Specification {
421-
def inherited() {
422-
...
423-
}
424-
}
425-
426-
class FooIntegrationSpec extends AbstractIntegrationSpec {
427-
def foo() {
428-
...
429-
}
430-
}
431-
432-
@Retry(count = 2)
433-
class BarIntegrationSpec extends AbstractIntegrationSpec {
434-
def bar() {
435-
...
436-
}
437-
}
383+
include::{sourcedir}/extension/RetryDocSpec.groovy[tag=example-c]
438384
----
439385

440386
Check https://github.com/spockframework/spock/blob/master/spock-specs/src/test/groovy/org/spockframework/smoke/extension/RetryFeatureExtensionSpec.groovy[RetryFeatureExtensionSpec] for more examples.

spock-core/src/main/java/spock/lang/Retry.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727

2828
/**
29-
* Retries the given feature if an exception occurs during execution.
29+
* Retries the given feature if an exception occurs during execution of the feature method.
3030
*
3131
* <p>Retries can be applied to feature methods and spec classes. Applying it to
3232
* a spec class has the same effect as applying it to each feature method that
@@ -48,7 +48,7 @@
4848
/**
4949
* Configures which types of Exceptions should be retried.
5050
*
51-
* Subclasses are included if their parent class is listed.
51+
* <p>Subclasses are included if their parent class is listed.
5252
*
5353
* @return array of Exception classes to retry.
5454
*/
@@ -58,12 +58,12 @@
5858
* Condition that is evaluated to decide whether the feature should be
5959
* retried.
6060
*
61-
* The configured closure is called with a delegate of type
61+
* <p>The configured closure is called with a delegate of type
6262
* {@link org.spockframework.runtime.extension.builtin.RetryConditionContext}
6363
* which provides access to the current exception and {@code Specification}
6464
* instance.
6565
*
66-
* The feature is retried if the exception class passes the type check and the
66+
* <p>The feature is retried if the exception class passes the type check and the
6767
* specified condition holds true. If no condition is specified, only the type
6868
* check is performed.
6969
*
@@ -94,12 +94,14 @@
9494

9595
enum Mode {
9696
/**
97-
* Retry the iterations individually.
97+
* Retry only the feature method execution, setup and cleanup are not running on retries.
9898
*/
9999
ITERATION,
100100

101101
/**
102-
* Retry the the feature together with the setup and cleanup methods.
102+
* Retry the iteration together with setup and cleanup.
103+
* Even in this mode, the retry is only triggered if the feature method is failing
104+
* in the expected way. If the setup or cleanup is failing, the test fails immediately.
103105
*/
104106
SETUP_FEATURE_CLEANUP
105107
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.spockframework.docs.extension
2+
3+
import groovy.sql.Sql
4+
import spock.lang.Retry
5+
import spock.lang.Shared
6+
import spock.lang.Specification
7+
8+
abstract
9+
// tag::example-common[]
10+
class FlakyIntegrationSpec extends Specification {
11+
// end::example-common[]
12+
@Shared
13+
def sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
14+
}
15+
16+
class FlakyIntegrationSpecA extends FlakyIntegrationSpec {
17+
// tag::example-a[]
18+
@Retry
19+
def "retry 3 times"() {
20+
expect: true
21+
}
22+
23+
@Retry(count = 5)
24+
def "retry 5 times"() {
25+
expect: true
26+
}
27+
28+
@Retry(exceptions = [IOException])
29+
def "only retry on IOException"() {
30+
expect: true
31+
}
32+
33+
@Retry(condition = { failure.message.contains('foo') })
34+
def "only retry if condition on failure holds"() {
35+
expect: true
36+
}
37+
38+
@Retry(condition = { instance.field != null })
39+
def "only retry if condition on instance holds"() {
40+
expect: true
41+
}
42+
43+
@Retry
44+
def "retry failing feature methods"() {
45+
expect: true
46+
47+
where:
48+
data << sql.execute('')
49+
}
50+
51+
@Retry(mode = Retry.Mode.SETUP_FEATURE_CLEANUP)
52+
def "retry with setup and cleanup"() {
53+
expect: true
54+
55+
where:
56+
data << sql.execute('')
57+
}
58+
59+
@Retry(delay = 1000)
60+
def "retry after 1000 ms delay"() {
61+
expect: true
62+
}
63+
}
64+
// end::example-a[]
65+
66+
// tag::example-b1[]
67+
@Retry
68+
// end::example-b1[]
69+
class FlakyIntegrationSpecB extends FlakyIntegrationSpec {
70+
// tag::example-b2[]
71+
def "will be retried with config from class"() {
72+
expect: true
73+
}
74+
75+
@Retry(count = 5)
76+
def "will be retried using its own config"() {
77+
expect: true
78+
}
79+
}
80+
// end::example-b2[]
81+
82+
// tag::example-c[]
83+
@Retry(count = 1)
84+
abstract class AbstractIntegrationSpec extends Specification {
85+
def inherited() {
86+
expect: true
87+
}
88+
}
89+
90+
class FooIntegrationSpec extends AbstractIntegrationSpec {
91+
def foo() {
92+
expect: true
93+
}
94+
}
95+
96+
@Retry(count = 2)
97+
class BarIntegrationSpec extends AbstractIntegrationSpec {
98+
def bar() {
99+
expect: true
100+
}
101+
}
102+
// end::example-c[]

0 commit comments

Comments
 (0)