Skip to content

Commit 1290fa7

Browse files
committed
Make @PendingFeature repeatable (#1030)
1 parent 3370650 commit 1290fa7

File tree

5 files changed

+172
-3
lines changed

5 files changed

+172
-3
lines changed

docs/extensions.adoc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,18 @@ Groovy has the `groovy.transform.NotYetImplemented` annotation which is similar
167167
* if at least one iteration of a data-driven test fails it will be reported as skipped
168168
* if every iteration of a data-driven test passes it will be reported as error
169169

170-
[source,groovy]
170+
[source,groovy,indent=0]
171+
----
172+
include::{sourcedir}/extension/PendingFeatureDocSpec.groovy[tag=example-a]
173+
----
174+
175+
It is also supported to have multiple `@PendingFeature` annotations or a mixture of `@PendingFeature` and
176+
`@PendingFeatureIf`, for example to ignore certain exceptions only under certain conditions or show different
177+
reasons for different exceptions.
178+
179+
[source,groovy,indent=0]
171180
----
172-
@PendingFeature
173-
def "not implemented yet"() { ... }
181+
include::{sourcedir}/extension/PendingFeatureDocSpec.groovy[tag=example-b]
174182
----
175183

176184
=== PendingFeatureIf

docs/release_notes.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ include::include.adoc[]
1414

1515
- `@ConfineMetaClassChanges` is now repeatable
1616

17+
- `@PendingFeature` is now repeatable
18+
1719

1820
== 2.0-M3 (2020-06-11)
1921

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
@Retention(RetentionPolicy.RUNTIME)
3636
@Target({ElementType.METHOD})
3737
@ExtensionAnnotation(PendingFeatureExtension.class)
38+
@Repeatable(PendingFeature.Container.class)
3839
public @interface PendingFeature {
3940
/**
4041
* Configures which types of Exceptions are expected in the pending feature.
@@ -51,4 +52,14 @@
5152
* @return reason why this feature is pending
5253
*/
5354
String reason() default "";
55+
56+
/**
57+
* @since 2.0
58+
*/
59+
@Beta
60+
@Retention(RetentionPolicy.RUNTIME)
61+
@Target({ElementType.METHOD})
62+
@interface Container {
63+
PendingFeature[] value();
64+
}
5465
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.spockframework.docs.extension
2+
3+
import org.spockframework.runtime.extension.builtin.PreconditionContext
4+
import spock.lang.PendingFeature
5+
import spock.lang.PendingFeatureIf
6+
import spock.lang.Specification
7+
8+
class PendingFeatureDocSpec extends Specification {
9+
// tag::example-a[]
10+
@PendingFeature
11+
def "not implemented yet"() {
12+
// end::example-a[]
13+
expect:
14+
false
15+
}
16+
17+
// tag::example-b[]
18+
@PendingFeature(
19+
exceptions = UnsupportedOperationException,
20+
reason = 'operation not yet supported')
21+
@PendingFeature(
22+
exceptions = IllegalArgumentException,
23+
reason = 'arguments are broken')
24+
@PendingFeatureIf(
25+
value = { os.windows },
26+
reason = 'Does not yet work on Windows')
27+
def "I have various problems in certain situations"() {
28+
// end::example-b[]
29+
expect:
30+
throw new IllegalArgumentException()
31+
}
32+
}

spock-specs/src/test/groovy/org/spockframework/smoke/extension/PendingFeatureExtensionSpec.groovy

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,29 @@ def bar() {
3939
}
4040
}
4141

42+
def "@PendingFeature marks failing feature as skipped even if applied twice"() {
43+
when:
44+
def result = runner.runSpecBody """
45+
@PendingFeature
46+
@PendingFeature
47+
def bar() {
48+
expect: false
49+
}
50+
"""
51+
52+
then:
53+
verifyAll(result) {
54+
testsStartedCount == 1
55+
testsSucceededCount == 0
56+
testsFailedCount == 0
57+
testsSkippedCount == 0
58+
testsAbortedCount == 1
59+
testEvents().aborted().assertEventsMatchExactly(
60+
abortedWithReason(message('Feature not yet implemented correctly.'))
61+
)
62+
}
63+
}
64+
4265
def "@PendingFeature includes reason in exception message"() {
4366
when:
4467
def result = runner.runSpecBody """
@@ -61,6 +84,52 @@ def bar() {
6184
}
6285
}
6386

87+
def "@PendingFeature includes last reason in exception message if applied twice"() {
88+
when:
89+
def result = runner.runSpecBody """
90+
@PendingFeature(reason='42')
91+
@PendingFeature(reason='4711')
92+
def bar() {
93+
expect: false
94+
}
95+
"""
96+
97+
then:
98+
verifyAll(result) {
99+
testsStartedCount == 1
100+
testsSucceededCount == 0
101+
testsFailedCount == 0
102+
testsSkippedCount == 0
103+
testsAbortedCount == 1
104+
testEvents().aborted().assertEventsMatchExactly(
105+
abortedWithReason(message('Feature not yet implemented correctly. Reason: 4711'))
106+
)
107+
}
108+
}
109+
110+
def "@PendingFeature includes reason of matching annotation in exception message if applied twice"() {
111+
when:
112+
def result = runner.runSpecBody """
113+
@PendingFeature(exceptions=IllegalArgumentException, reason='42')
114+
@PendingFeature(exceptions=IllegalAccessException, reason='4711')
115+
def bar() {
116+
expect: throw new IllegalArgumentException()
117+
}
118+
"""
119+
120+
then:
121+
verifyAll(result) {
122+
testsStartedCount == 1
123+
testsSucceededCount == 0
124+
testsFailedCount == 0
125+
testsSkippedCount == 0
126+
testsAbortedCount == 1
127+
testEvents().aborted().assertEventsMatchExactly(
128+
abortedWithReason(message('Feature not yet implemented correctly. Reason: 42'))
129+
)
130+
}
131+
}
132+
64133
def "@PendingFeature marks feature that fails with exception as skipped"() {
65134
when:
66135
def result = runner.runSpecBody """
@@ -107,6 +176,30 @@ def bar() {
107176
}
108177
}
109178

179+
def "@PendingFeature rethrows non handled exceptions even if applied twice"() {
180+
when:
181+
def result = runner.runSpecBody """
182+
@PendingFeature(exceptions=IndexOutOfBoundsException)
183+
@PendingFeature(exceptions=IllegalAccessException)
184+
def bar() {
185+
expect:
186+
throw new IllegalArgumentException()
187+
}
188+
"""
189+
190+
then:
191+
verifyAll(result) {
192+
testsStartedCount == 1
193+
testsSucceededCount == 0
194+
testsFailedCount == 1
195+
testsSkippedCount == 0
196+
testsAbortedCount == 0
197+
testEvents().failed().assertEventsMatchExactly(
198+
finishedWithFailure(instanceOf(IllegalArgumentException))
199+
)
200+
}
201+
}
202+
110203
def "@PendingFeature marks passing feature as failed"() {
111204
when:
112205
def result = runner.runSpecBody """
@@ -129,6 +222,29 @@ def bar() {
129222
}
130223
}
131224

225+
def "@PendingFeature marks passing feature as failed even if applied twice"() {
226+
when:
227+
def result = runner.runSpecBody """
228+
@PendingFeature
229+
@PendingFeature
230+
def bar() {
231+
expect: true
232+
}
233+
"""
234+
235+
then:
236+
verifyAll(result) {
237+
testsStartedCount == 1
238+
testsSucceededCount == 0
239+
testsFailedCount == 1
240+
testsSkippedCount == 0
241+
testsAbortedCount == 0
242+
testEvents().failed().assertEventsMatchExactly(
243+
finishedWithFailure(message('Feature is marked with @PendingFeature but passes unexpectedly'))
244+
)
245+
}
246+
}
247+
132248
def "@PendingFeature ignores aborted feature"() {
133249
when:
134250
def result = runner.runSpecBody """

0 commit comments

Comments
 (0)