Skip to content

Commit 7d9ef28

Browse files
committed
Enable implicit non-top-level conditions (#1160)
1 parent 9430fa6 commit 7d9ef28

10 files changed

Lines changed: 50 additions & 16 deletions

File tree

docs/release_notes.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ include::include.adoc[]
2222
=== Breaking Changes
2323

2424
* Mock/Stub checks on `Comparable<T>` with `T` being something other than `Object` now compare using the java identity hash code instead of always being equal spockIssue:2352[]
25+
* It is no longer true that only top-level expression statements are treated as implicit conditions.
26+
Just like within `with { ... }`, `verifyAll { ... }`, `@ConditionBlock` methods, and other places, each expression statement is now considered an implicit condition, except for statements in unknown closures.
27+
+
28+
This enables the usage of `if` and similar while also being able to still safely use `.every { ... }`, but without the regular confusion why a test is not failing if the condition is inside an `if`.
29+
You can even have interactions in those nested places now.
30+
+
31+
As now expression statements are considered implicit conditions that previously were not, this can lead to compilation errors in existing code-bases, because such nested statements were previously simply ignored by the Spock compiler and now also must be valid conditions.
32+
+
33+
If those compile errors are not accidentally written assignments that should have been conditions or similar, you can for example use the <<spock_primer.adoc#opt-out-of-condition-handling,`!!` prefix operator>> to declare an expression statement explicitly as not being an implicit condition.
34+
+
35+
spockPull:2250[]
2536

2637
== 2.4 (2025-12-11)
2738

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
@@ -200,7 +200,7 @@ private boolean handleInteraction(InteractionRewriter rewriter, ExpressionStatem
200200
}
201201

202202
private boolean handleImplicitCondition(ExpressionStatement stat) {
203-
if (!(stat == currTopLevelStat && isThenOrExpectOrFilterBlock()
203+
if (!(((currSpecialMethodCall == NoSpecialMethodCall.INSTANCE) && isThenOrExpectOrFilterBlock())
204204
|| currSpecialMethodCall.isConditionMethodCall()
205205
|| currSpecialMethodCall.isConditionBlock()
206206
|| 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:

spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/VerifyAllMethodsAstSpec/transforms_conditions_in_private_methods.groovy

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ public void $spock_feature_0_0() {
7070
catch (java.lang.Throwable $spock_condition_throwable) {
7171
org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, '[true, false].any { it }', 3, 5, null, $spock_condition_throwable)}
7272
if (true) {
73-
[true, false].any({
74-
it
75-
})
73+
try {
74+
org.spockframework.runtime.SpockRuntime.verifyMethodCondition($spock_errorCollector, $spock_valueRecorder.reset(), '[true, false].any { it }', 5, 9, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), [$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), true), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), false)]), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(3), 'any'), new java.lang.Object[]{$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(4), {
75+
it
76+
})}, $spock_valueRecorder.realizeNas(7, false), false, 6)
77+
}
78+
catch (java.lang.Throwable $spock_condition_throwable) {
79+
org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, '[true, false].any { it }', 5, 9, null, $spock_condition_throwable)}
7680
}
7781
this.with({
7882
org.spockframework.runtime.ValueRecorder $spock_valueRecorder1 = new org.spockframework.runtime.ValueRecorder()

spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/VerifyMethodsAstSpec/transforms_conditions_in_private_methods.groovy

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,13 @@ public void $spock_feature_0_0() {
6666
catch (java.lang.Throwable $spock_condition_throwable) {
6767
org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, '[true, false].any { it }', 3, 5, null, $spock_condition_throwable)}
6868
if (true) {
69-
[true, false].any({
70-
it
71-
})
69+
try {
70+
org.spockframework.runtime.SpockRuntime.verifyMethodCondition($spock_errorCollector, $spock_valueRecorder.reset(), '[true, false].any { it }', 5, 9, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), [$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), true), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), false)]), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(3), 'any'), new java.lang.Object[]{$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(4), {
71+
it
72+
})}, $spock_valueRecorder.realizeNas(7, false), false, 6)
73+
}
74+
catch (java.lang.Throwable $spock_condition_throwable) {
75+
org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, '[true, false].any { it }', 5, 9, null, $spock_condition_throwable)}
7276
}
7377
this.with({
7478
org.spockframework.runtime.ValueRecorder $spock_valueRecorder1 = new org.spockframework.runtime.ValueRecorder()

0 commit comments

Comments
 (0)