Skip to content

Commit 5f4d8c3

Browse files
Vampireleonard84
andauthored
Fix @verify and @VerifyAll method compilation (#2249)
Fixes #2150 --------- Co-authored-by: Leonard Brünings <[email protected]>
1 parent 158e2e4 commit 5f4d8c3

File tree

57 files changed

+891
-198
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+891
-198
lines changed

docs/release_notes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ will now require that the spec is annotated with <<parallel_execution.adoc#isola
7171

7272
=== Misc
7373

74+
* Fix handling of `@Verify` and `@VerifyAll` spockIssue:2150[]
7475
* Fix handling of condition method calls spockPull:2162[]
7576
* Fix vararg handling in SpyStatic spockIssue:2161[]
7677
* Fix incompatibility with JUnit 6 in OSGi environment spockIssue:2231[]

docs/spock_primer.adoc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,13 +577,17 @@ false ...
577577

578578
Not very helpful. Fortunately, we can do better:
579579

580+
[#at-verify-helper-method]
581+
=== Using `@Verify` helper methods to assert expectations
582+
580583
[source,groovy,indent=0]
581584
----
582585
include::{sourcedir}/primer/VerifyMethodsDocSpec.groovy[tag=verify-helper-method]
583586
----
584587

585-
When factoring out conditions into a helper method, you need to ensure the method has return type `void`.
586-
Otherwise, Spock might interpret the return value as a failing condition, which is not what we want.
588+
When factoring out conditions into a `@Verify` annotated helper method, you need to ensure the method has a return type of `void`, or uses a dynamic return type (`def` or no declared type).
589+
After compilation, it will be `void` in both cases, and any other return type causes a compilation error, as Spock might interpret the return value as a failing condition otherwise.
590+
You can use `@Verify` for instance methods or static methods of a Specification class, or in separate utility classes.
587591

588592
As expected, the improved helper method tells us exactly what's wrong:
589593

@@ -684,6 +688,8 @@ Alternatively, `verifyAll` conditions can be extracted into a helper method anno
684688
include::{sourcedir}/primer/VerifyMethodsDocSpec.groovy[tag=verify-all-helper-method]
685689
----
686690

691+
It has the same return type constraints as the <<at-verify-helper-method,@Verify>> methods.
692+
687693
== Using `verifyEach` to assert on each element of an `Iterable`
688694

689695
There are several ways to do assertions on `Iterable` or `Collection`.

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class AbstractDeepBlockRewriter extends StatementReplacingVisitorSupport
2929
protected BinaryExpression currBinaryExpr;
3030
protected MethodCallExpression currMethodCallExpr;
3131
protected ClosureExpression currClosure;
32-
protected ISpecialMethodCall currSpecialMethodCall = NoSpecialMethodCall.INSTANCE;
32+
protected ISpecialMethodCall currSpecialMethodCall;
3333
protected Collection<Statement> pastSpecialMethodCallStats = new HashSet<>();
3434

3535
// following fields are filled in by subclasses
@@ -41,8 +41,13 @@ public class AbstractDeepBlockRewriter extends StatementReplacingVisitorSupport
4141
protected final List<Statement> thenBlockInteractionStats = new ArrayList<>();
4242

4343
public AbstractDeepBlockRewriter(Block block, AstNodeCache nodeCache) {
44+
this(block, nodeCache, NoSpecialMethodCall.INSTANCE);
45+
}
46+
47+
public AbstractDeepBlockRewriter(Block block, AstNodeCache nodeCache, ISpecialMethodCall currSpecialMethodCall) {
4448
this.block = block;
4549
this.nodeCache = nodeCache;
50+
this.currSpecialMethodCall = currSpecialMethodCall;
4651
}
4752

4853
protected void conditionFound() {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public void visitWhenBlock(WhenBlock block) throws Exception {}
4343
@Override
4444
public void visitThenBlock(ThenBlock block) throws Exception {}
4545
@Override
46+
public void visitVerifyBlock(VerifyBlock block) throws Exception {}
47+
@Override
4648
public void visitCleanupBlock(CleanupBlock block) throws Exception {}
4749
@Override
4850
public void visitWhereBlock(WhereBlock block) throws Exception {}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import spock.lang.Specification;
2626

2727
import org.codehaus.groovy.ast.*;
28+
import spock.lang.Verify;
29+
import spock.lang.VerifyAll;
2830

2931
import java.util.Set;
3032

@@ -46,6 +48,7 @@ public class AstNodeCache {
4648
public final ClassNode DataVariableMultiplication = ClassHelper.makeWithoutCaching(DataVariableMultiplication.class);
4749
public final ClassNode DataVariableMultiplicationFactor = ClassHelper.makeWithoutCaching(DataVariableMultiplicationFactor.class);
4850
public final ClassNode BlockInfo = ClassHelper.makeWithoutCaching(BlockInfo.class);
51+
public final ClassNode Void = ClassHelper.makeWithoutCaching(void.class);
4952

5053
public final MethodNode Specification_GetSpecificationContext =
5154
Specification.getDeclaredMethods(Identifiers.GET_SPECIFICATION_CONTEXT).get(0);

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.codehaus.groovy.ast.stmt.Statement;
2323
import org.codehaus.groovy.syntax.Types;
2424
import org.spockframework.compiler.model.*;
25-
import org.spockframework.util.Identifiers;
2625
import org.spockframework.util.Nullable;
2726

2827
import java.util.List;
@@ -43,13 +42,17 @@
4342
* @author Peter Niederwieser
4443
*/
4544
public class DeepBlockRewriter extends AbstractDeepBlockRewriter {
46-
private final ISpecRewriteResources resources;
45+
private final IRewriteResources resources;
4746
private boolean insideInteraction = false;
4847
private int interactionClosureDepth = 0;
4948
private int closureDepth = 0;
5049

51-
public DeepBlockRewriter(ISpecRewriteResources resources) {
52-
super(resources.getCurrentBlock(), resources.getAstNodeCache());
50+
public DeepBlockRewriter(IRewriteResources resources) {
51+
this(resources, NoSpecialMethodCall.INSTANCE);
52+
}
53+
54+
public DeepBlockRewriter(IRewriteResources resources, ISpecialMethodCall currSpecialMethodCall) {
55+
super(resources.getCurrentBlock(), resources.getAstNodeCache(), currSpecialMethodCall);
5356
this.resources = resources;
5457
}
5558

@@ -72,7 +75,7 @@ private String getValueRecorderSuffix() {
7275
}
7376

7477
private String getErrorCollectorSuffix() {
75-
return groupConditionFound ? String.valueOf(closureDepth) : "";
78+
return (groupConditionFound && (closureDepth > 0)) ? String.valueOf(closureDepth) : "";
7679
}
7780

7881
@Override
@@ -187,6 +190,11 @@ private boolean handleInteraction(InteractionRewriter rewriter, ExpressionStatem
187190
return true;
188191
}
189192

193+
if (block instanceof VerifyBlock) {
194+
resources.getErrorReporter().error(stat, "Interactions are not allowed in '@Verify' and '@VerifyAll' methods.");
195+
return true;
196+
}
197+
190198
replaceVisitedStatementWith(interaction);
191199
interactionFound = true;
192200
return true;

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,27 @@
1717
package org.spockframework.compiler;
1818

1919
import org.codehaus.groovy.ast.ASTNode;
20-
import org.spockframework.compiler.condition.IConditionErrorRecorders;
20+
import org.codehaus.groovy.ast.expr.Expression;
21+
import org.codehaus.groovy.ast.expr.MethodCallExpression;
22+
import org.codehaus.groovy.ast.expr.VariableExpression;
2123

22-
import java.util.List;
24+
import org.spockframework.compiler.condition.IConditionErrorRecorders;
25+
import org.spockframework.compiler.model.Block;
26+
import org.spockframework.compiler.model.Method;
2327

2428
/**
29+
*
2530
* @author Peter Niederwieser
2631
*/
2732
public interface IRewriteResources {
33+
Method getCurrentMethod();
34+
35+
Block getCurrentBlock();
36+
37+
VariableExpression captureOldValue(Expression oldValue);
38+
39+
MethodCallExpression getMockInvocationMatcher();
40+
2841
AstNodeCache getAstNodeCache();
2942

3043
String getSourceText(ASTNode node);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
* @author Peter Niederwieser
3737
*/
3838
public class InteractionRewriter {
39-
private final ISpecRewriteResources resources;
39+
private final IRewriteResources resources;
4040
private final ClosureExpression activeWithOrMockClosure;
4141

4242
// information about the interaction; filled in by parse()
@@ -52,7 +52,7 @@ public class InteractionRewriter {
5252
// "new InteractionBuilder(..).setCount(..).setTarget(..).setMethod(..).addArg(..).addResult(..).build()"
5353
private Expression builderExpr;
5454

55-
public InteractionRewriter(ISpecRewriteResources resources, @Nullable ClosureExpression activeWithOrMockClosure) {
55+
public InteractionRewriter(IRewriteResources resources, @Nullable ClosureExpression activeWithOrMockClosure) {
5656
this.resources = resources;
5757
this.activeWithOrMockClosure = activeWithOrMockClosure;
5858
}

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
import org.codehaus.groovy.ast.*;
2525
import org.codehaus.groovy.ast.expr.ConstantExpression;
2626
import org.codehaus.groovy.ast.stmt.Statement;
27+
import spock.lang.Verify;
28+
import spock.lang.VerifyAll;
2729

30+
import static org.spockframework.compiler.AstUtil.hasAnnotation;
2831
import static org.spockframework.util.Identifiers.*;
2932

3033
/**
@@ -34,13 +37,15 @@
3437
* @author Peter Niederwieser
3538
*/
3639
public class SpecParser implements GroovyClassVisitor {
40+
private final AstNodeCache nodeCache;
3741
private final ErrorReporter errorReporter;
3842

3943
private Spec spec;
4044
private int fieldCount = 0;
4145
private int featureMethodCount = 0;
4246

43-
public SpecParser(ErrorReporter errorReporter) {
47+
public SpecParser(AstNodeCache nodeCache, ErrorReporter errorReporter) {
48+
this.nodeCache = nodeCache;
4449
this.errorReporter = errorReporter;
4550
}
4651

@@ -65,7 +70,7 @@ public void visitField(FieldNode gField) {
6570
if (gField.isStatic()) return;
6671

6772
Field field = new Field(spec, gField, fieldCount++);
68-
field.setShared(AstUtil.hasAnnotation(gField, Shared.class));
73+
field.setShared(hasAnnotation(gField, Shared.class));
6974
field.setOwner(owner);
7075
spec.getFields().add(field);
7176
}
@@ -90,6 +95,8 @@ public void visitMethod(MethodNode method) {
9095
buildFixtureMethod(method);
9196
else if (isFeatureMethod(method))
9297
buildFeatureMethod(method);
98+
else if (isVerifyMethod(method))
99+
buildVerifyMethod(method);
93100
else buildHelperMethod(method);
94101
}
95102

@@ -176,6 +183,29 @@ private void buildFeatureMethod(MethodNode method) {
176183
spec.getMethods().add(feature);
177184
}
178185

186+
private boolean isVerifyMethod(MethodNode method) {
187+
boolean hasVerifyAnnotation = hasAnnotation(method, Verify.class);
188+
boolean hasVerifyAllAnnotation = hasAnnotation(method, VerifyAll.class);
189+
if (hasVerifyAnnotation && hasVerifyAllAnnotation) {
190+
errorReporter.error(method, "Method '%s' cannot be annotated with both @Verify and @VerifyAll.", method.getName());
191+
return false;
192+
}
193+
return hasVerifyAnnotation || hasVerifyAllAnnotation;
194+
}
195+
196+
private void buildVerifyMethod(MethodNode method) {
197+
VerifyMethod.verifyReturnType(method, errorReporter);
198+
method.setReturnType(nodeCache.Void);
199+
200+
Method verify;
201+
if (hasAnnotation(method, Verify.class)) {
202+
verify = new VerifyMethod(spec, method);
203+
} else {
204+
verify = new VerifyAllMethod(spec, method);
205+
}
206+
spec.getMethods().add(verify);
207+
}
208+
179209
private void buildHelperMethod(MethodNode method) {
180210
Method helper = new HelperMethod(spec, method);
181211
spec.getMethods().add(helper);

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.spockframework.compiler.model.*;
2929
import org.spockframework.runtime.GroovyRuntimeUtil;
3030
import org.spockframework.runtime.SpockException;
31+
import org.spockframework.util.Identifiers;
3132
import org.spockframework.util.InternalIdentifiers;
3233
import org.spockframework.util.ObjectUtil;
3334
import org.spockframework.util.ReflectionUtil;
@@ -45,7 +46,7 @@
4546
* @author Peter Niederwieser
4647
*/
4748
// IDEA: mock controller / leaveScope calls should only be inserted when necessary (increases robustness)
48-
public class SpecRewriter extends AbstractSpecVisitor implements ISpecRewriteResources {
49+
public class SpecRewriter extends AbstractSpecVisitor implements IRewriteResources {
4950
// https://issues.apache.org/jira/browse/GROOVY-10403
5051
// needed for groovy-4 compatibility and only available since groovy-4
5152
private static final java.lang.reflect.Method GET_PLAIN_NODE_REFERENCE = ReflectionUtil.getMethodBySignature(ClassNode.class, "getPlainNodeReference", boolean.class);
@@ -412,12 +413,16 @@ public void visitMethodAgain(Method method) {
412413
method.getStatements().add(createMockControllerCall(nodeCache.MockController_LeaveScope));
413414
}
414415

416+
if (method instanceof VerifyAllMethod) {
417+
if (methodHasCondition) {
418+
errorRecorders.defineErrorCollector(method.getStatements());
419+
}
420+
} else if (methodHasDeepNonGroupedCondition) {
421+
errorRecorders.defineErrorRethrower(method.getStatements());
422+
}
415423
if (methodHasCondition) {
416424
errorRecorders.defineValueRecorder(method.getStatements());
417425
}
418-
if (methodHasDeepNonGroupedCondition) {
419-
errorRecorders.defineErrorRethrower(method.getStatements());
420-
}
421426
}
422427

423428

@@ -474,13 +479,24 @@ private MethodCallExpression createBlockListenerCall(Block block, MethodNode blo
474479
public void visitAnyBlock(Block block) {
475480
this.block = block;
476481
if (block instanceof ThenBlock) return;
482+
if (block instanceof VerifyBlock) return;
477483

478484
DeepBlockRewriter deep = new DeepBlockRewriter(this);
479485
deep.visit(block);
480486
methodHasCondition |= deep.isConditionFound();
481487
methodHasDeepNonGroupedCondition |= deep.isDeepNonGroupedConditionFound();
482488
}
483489

490+
@Override
491+
public void visitVerifyBlock(VerifyBlock block) {
492+
DeepBlockRewriter deep = new DeepBlockRewriter(this, new SpecialMethodCall(
493+
method instanceof VerifyAllMethod ? Identifiers.VERIFY_ALL : Identifiers.WITH,
494+
null, null, null, null, null, false, nodeCache));
495+
deep.visit(block);
496+
methodHasCondition |= deep.isConditionFound();
497+
methodHasDeepNonGroupedCondition |= deep.isDeepNonGroupedConditionFound();
498+
}
499+
484500
@Override
485501
public void visitThenBlock(ThenBlock block) {
486502
if (block.isFirstInChain()) thenBlockChainHasExceptionCondition = false;
@@ -651,11 +667,6 @@ private CatchStatement createHandleSuppressedThrowableStatement(VariableExpressi
651667

652668
// IRewriteResources members
653669

654-
@Override
655-
public Spec getCurrentSpec() {
656-
return spec;
657-
}
658-
659670
@Override
660671
public Method getCurrentMethod() {
661672
return method;

0 commit comments

Comments
 (0)