Skip to content

Commit 789cc32

Browse files
committed
Enable implicit non-top-level conditions (#1160)
1 parent 9c6342c commit 789cc32

8 files changed

Lines changed: 46 additions & 10 deletions

File tree

docs/release_notes.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,27 @@ include::include.adoc[]
66

77
== 2.5 (tbd)
88

9+
=== Breaking Changes
10+
11+
* It is no longer true that only top-level expression statements are treated as implicit conditions.
12+
Just like within `with { ... }`, `verifyAll { ... }`, `@ConditionBlock` methods, and other places,
13+
each expression statement is now considered an implicit condition, except for statements in unknown
14+
closures.
15+
+
16+
This enables the usage of `if` and similar while also being able to still safely use `.every { ... }`,
17+
but without the regular confusion why a test is not failing if the condition is inside an `if`.
18+
You can even have interactions in those nested places now.
19+
+
20+
As now expression statements are considered implicit conditions that previously were not,
21+
this can lead to compilation errors in existing code-bases, because such nested statements were
22+
previously simply ignored by the Spock compiler and now also must be valid conditions.
23+
+
24+
If those compile errors are not accidentally written assignments that should have been conditions
25+
or similar, you can for example use the <<spock_primer.adoc#opt-out-of-condition-handling,`!!` prefix operator>>
26+
to declare an expression statement explicitly as not being an implicit condition.
27+
+
28+
spockPull:2250[]
29+
930
=== Enhancements
1031

1132
* Improve `TooManyInvocationsError` now reports unsatisfied interactions with argument mismatch details, making it easier to diagnose why invocations didn't match expected interactions spockPull:2315[]

docs/spock_primer.adoc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,9 @@ digestible form. Nice, isn't it?
230230
===== Implicit and explicit conditions
231231

232232
Conditions are an essential ingredient of `then` blocks and `expect` blocks. Except for calls to `void` methods and
233-
expressions classified as interactions, all top-level expressions in these blocks are implicitly treated as conditions.
234-
To use conditions in other places, you need to designate them with Groovy's assert keyword:
233+
expressions classified as interactions, all expression statements in these blocks are implicitly treated as conditions
234+
except if they happen inside some unknown closure.
235+
To use conditions in other places, you need to designate them with Groovy's `assert` keyword:
235236

236237
[source,groovy]
237238
----

spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public final void visitClosureExpression(ClosureExpression expr) {
151151
groupConditionFound = false;
152152
ISpecialMethodCall oldSpecialMethodCall = currSpecialMethodCall;
153153
if (!currSpecialMethodCall.isMatch(expr)) {
154-
currSpecialMethodCall = NoSpecialMethodCall.INSTANCE; // unrelated closure terminates currSpecialMethodCall scope
154+
currSpecialMethodCall = NoSpecialMethodCall.CLOSURE_INSTANCE; // unrelated closure terminates currSpecialMethodCall scope
155155
}
156156
try {
157157
Statement code = expr.getCode();

spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ private boolean handleInteraction(InteractionRewriter rewriter, ExpressionStatem
201201
}
202202

203203
private boolean handleImplicitCondition(ExpressionStatement stat) {
204-
if (!(stat == currTopLevelStat && isThenOrExpectOrFilterBlock()
204+
if (!(((currSpecialMethodCall == NoSpecialMethodCall.INSTANCE) && isThenOrExpectOrFilterBlock())
205205
|| currSpecialMethodCall.isConditionMethodCall()
206206
|| currSpecialMethodCall.isConditionBlock()
207207
|| currSpecialMethodCall.isGroupConditionBlock()

spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
public class NoSpecialMethodCall implements ISpecialMethodCall {
2525
public static final ISpecialMethodCall INSTANCE = new NoSpecialMethodCall();
26+
public static final ISpecialMethodCall CLOSURE_INSTANCE = new NoSpecialMethodCall();
2627

2728
@Override
2829
public boolean isMethodName(String name) {

spock-junit4/src/test/groovy/org/spockframework/junit4/junit/JUnitFixtureMethods.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class JUnitFixtureMethods extends JUnitBaseSpec {
151151
then:
152152
def e = result.failures[exceptionPos].exception
153153
if (suppressed) {
154-
e = e.suppressed[0]
154+
!!(e = e.suppressed[0])
155155
}
156156
e instanceof RuntimeException
157157
e.message == name

spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ class ConditionEvaluation extends EmbeddedSpecification {
5959
7
6060
}
6161

62+
@FailsWith(ConditionNotSatisfiedError)
63+
def "failing non-top-level condition"() {
64+
expect:
65+
if (true) {
66+
2 * 3 == 7
67+
}
68+
}
69+
70+
def "failing non-top-level non-condition"() {
71+
expect:
72+
[1, 2, 3].any { it == 2 }
73+
}
74+
6275
def "MethodCallExpression"() {
6376
expect:
6477
[1, 2, 3].size() == 3

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ class TempDirExtensionSpec extends EmbeddedSpecification {
8888
def tempFile = aaabbb.resolve("tmp.txt")
8989
Files.createDirectories(aaabbb)
9090
Files.write(tempFile, "ewfwf".bytes)
91-
aaabbb.toFile().writable = false
92-
aaa.toFile().writable = false
93-
tempFile.toFile().writable = false
94-
previousIteration = iterationDir
91+
!!(aaabbb.toFile().writable = false)
92+
!!(aaa.toFile().writable = false)
93+
!!(tempFile.toFile().writable = false)
94+
!!(previousIteration = iterationDir)
9595
} else if (i == 1) {
96-
assert !previousIteration.exists()
96+
!previousIteration.exists()
9797
}
9898

9999
where:

0 commit comments

Comments
 (0)