Skip to content

Commit 34517fe

Browse files
committed
Enable implicit non-top-level conditions (#1160)
1 parent b2e8c39 commit 34517fe

File tree

8 files changed

+65
-10
lines changed

8 files changed

+65
-10
lines changed

docs/release_notes.adoc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,25 @@ _This is a summary of the highlights of the milestone releases_
3737
3838
=== Breaking Changes
3939
40+
* It is no longer true that only top-level expression statements are treated as implicit conditions.
41+
Just like within `with { ... }`, `verifyAll { ... }`, `@ConditionBlock` methods, and other places,
42+
each expression statement is now considered an implicit condition, except for statements in unknown
43+
closures.
44+
+
45+
This enables the usage of `if` and similar while also being able to still safely use `.every { ... }`,
46+
but without the regular confusion why a test is not failing if the condition is inside an `if`.
47+
You can even have interactions in those nested places now.
48+
+
49+
As now expression statements are considered implicit conditions that previously were not,
50+
this can lead to compilation errors in existing code-bases, because such nested statements were
51+
previously simply ignored by the Spock compiler and now also must be valid conditions.
52+
+
53+
If those compile errors are not accidentally written assignments that should have been conditions
54+
or similar, you can for example use the <<spock_primer.adoc#opt-out-of-condition-handling,`!!` prefix operator>>
55+
to declare an expression statement explicitly as not being an implicit condition.
56+
+
57+
spockPull:2250[]
58+
4059
* _This affects users of the `@Snapshot` extension, only if you were using the snapshotter in parent specification classes._ +
4160
`@Snapshot` used to look up snapshots in directories named after the class containing feature methods. Now, the snapshots will be loaded from directories named after the bottom class in the specification hierarchy. The motivation of the change is to allow users to define features in base specification classes, but overwrite expected snapshots per child specification.
4261
spockPull:2112[]
@@ -69,6 +88,27 @@ will now require that the spec is annotated with <<parallel_execution.adoc#isola
6988
* Spock supports Groovy 5.0 spockIssue:2196[]
7089
* Spock is also tested to run correctly on Java 25 LTS spockPull:2212[]
7190

91+
=== Breaking Changes
92+
93+
* It is no longer true that only top-level expression statements are treated as implicit conditions.
94+
Just like within `with { ... }`, `verifyAll { ... }`, `@ConditionBlock` methods, and other places,
95+
each expression statement is now considered an implicit condition, except for statements in unknown
96+
closures.
97+
+
98+
This enables the usage of `if` and similar while also being able to still safely use `.every { ... }`,
99+
but without the regular confusion why a test is not failing if the condition is inside an `if`.
100+
You can even have interactions in those nested places now.
101+
+
102+
As now expression statements are considered implicit conditions that previously were not,
103+
this can lead to compilation errors in existing code-bases, because such nested statements were
104+
previously simply ignored by the Spock compiler and now also must be valid conditions.
105+
+
106+
If those compile errors are not accidentally written assignments that should have been conditions
107+
or similar, you can for example use the <<spock_primer.adoc#opt-out-of-condition-handling,`!!` prefix operator>>
108+
to declare an expression statement explicitly as not being an implicit condition.
109+
+
110+
spockPull:2250[]
111+
72112
=== Misc
73113

74114
* Fix handling of condition method calls spockPull:2162[]

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
@@ -146,7 +146,7 @@ public final void visitClosureExpression(ClosureExpression expr) {
146146
groupConditionFound = false;
147147
ISpecialMethodCall oldSpecialMethodCall = currSpecialMethodCall;
148148
if (!currSpecialMethodCall.isMatch(expr)) {
149-
currSpecialMethodCall = NoSpecialMethodCall.INSTANCE; // unrelated closure terminates currSpecialMethodCall scope
149+
currSpecialMethodCall = NoSpecialMethodCall.CLOSURE_INSTANCE; // unrelated closure terminates currSpecialMethodCall scope
150150
}
151151
try {
152152
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
@@ -193,7 +193,7 @@ private boolean handleInteraction(InteractionRewriter rewriter, ExpressionStatem
193193
}
194194

195195
private boolean handleImplicitCondition(ExpressionStatement stat) {
196-
if (!(stat == currTopLevelStat && isThenOrExpectOrFilterBlock()
196+
if (!(((currSpecialMethodCall == NoSpecialMethodCall.INSTANCE) && isThenOrExpectOrFilterBlock())
197197
|| currSpecialMethodCall.isConditionMethodCall()
198198
|| currSpecialMethodCall.isConditionBlock()
199199
|| 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
@@ -56,6 +56,19 @@ class ConditionEvaluation extends EmbeddedSpecification {
5656
7
5757
}
5858

59+
@FailsWith(ConditionNotSatisfiedError)
60+
def "failing non-top-level condition"() {
61+
expect:
62+
if (true) {
63+
2 * 3 == 7
64+
}
65+
}
66+
67+
def "failing non-top-level non-condition"() {
68+
expect:
69+
[1, 2, 3].any { it == 2 }
70+
}
71+
5972
def "MethodCallExpression"() {
6073
expect:
6174
[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)