Skip to content

Commit 869542a

Browse files
authored
Make IStore available via ISpecificationContext... (#2064)
And change `IBlockListener` to receive the current `Specification` instance instead of just the `IterationInfo`. This gives access to the `ISpecificationContext` as well as potential fields. For example, this could be used to trigger screenshots in a `Geb` spec.
1 parent 2499dbd commit 869542a

File tree

38 files changed

+398
-327
lines changed

38 files changed

+398
-327
lines changed

docs/extensions.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,9 @@ The `org.spockframework.runtime.IRunListener` can be registered via `SpecInfo.ad
13991399
The `org.spockframework.runtime.extension.IBlockListener` can be registered on a feature via, `FeatureInfo.addBlockListener(IBlockListener)` and will receive notifications about the progress of the feature.
14001400

14011401
It will be called once when entering a block (`blockEntered`) and once when exiting a block (`blockExited`).
1402+
Both methods receive the `BlockInfo` of the block that is entered or exited.
1403+
They also receive the current `Specification` instance which gives access to the `ISpecificationContext` to get the current `IterationInfo` or retrieve an `IStore`.
1404+
While this gives extensive access to the current state of the test run, it should be used responsibly as it can lead to surprising results if abused.
14021405

14031406
When an exception is thrown in a block, the `blockExited` will not be called for that block.
14041407
The failed block will be part of the `ErrorContext` in `ErrorInfo` that is passed to `IRunListener.error(ErrorInfo)`.

docs/release_notes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ include::include.adoc[]
2121
** Built-in extensions have been updated to use this new interface where applicable.
2222
* Add best-effort error reporting for interactions on final methods when using the `byte-buddy` mock maker spockIssue:2039[]
2323
* Add support for `@FailsWith` to assert an exception message spockIssue:2039[]
24+
* Add support for accessing the `IStore` via `ISpecificationContext` spockPull:2064[]
2425
* Improve `@Timeout` extension will now use virtual threads if available spockPull:1986[]
2526
* Improve mock argument matching, types constraints or arguments in interactions can now handle primitive types like `_ as int` spockIssue:1974[]
2627
* Improve `verifyEach` to accept an optional second index parameter for the assertion block closure spockPull:2043[]

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,9 @@ private void addBlockListeners(Block block) {
422422
if (!blockType.isSupportingBlockListeners()) return;
423423

424424
// SpockRuntime.callBlockEntered(getSpecificationContext(), blockMetadataIndex)
425-
MethodCallExpression blockEnteredCall = createBlockListenerCall(block, blockType, nodeCache.SpockRuntime_CallBlockEntered);
425+
MethodCallExpression blockEnteredCall = createBlockListenerCall(block, nodeCache.SpockRuntime_CallBlockEntered);
426426
// SpockRuntime.callBlockExited(getSpecificationContext(), blockMetadataIndex)
427-
MethodCallExpression blockExitedCall = createBlockListenerCall(block, blockType, nodeCache.SpockRuntime_CallBlockExited);
427+
MethodCallExpression blockExitedCall = createBlockListenerCall(block, nodeCache.SpockRuntime_CallBlockExited);
428428

429429
block.getAst().add(0, new ExpressionStatement(blockEnteredCall));
430430
if (blockType == BlockParseInfo.CLEANUP) {
@@ -455,13 +455,13 @@ private IfStatement ifThrowableIsNotNull(Statement statement) {
455455
);
456456
}
457457

458-
private MethodCallExpression createBlockListenerCall(Block block, BlockParseInfo blockType, MethodNode blockListenerMethod) {
458+
private MethodCallExpression createBlockListenerCall(Block block, MethodNode blockListenerMethod) {
459459
if (block.getBlockMetaDataIndex() < 0) throw new SpockException("Block metadata index not set: " + block);
460460
return createDirectMethodCall(
461461
new ClassExpression(nodeCache.SpockRuntime),
462462
blockListenerMethod,
463463
new ArgumentListExpression(
464-
getSpecificationContext(),
464+
VariableExpression.THIS_EXPRESSION,
465465
new ConstantExpression(block.getBlockMetaDataIndex(), true)
466466
));
467467
}

spock-core/src/main/java/org/spockframework/lang/ISpecificationContext.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package org.spockframework.lang;
1616

1717
import org.spockframework.mock.IThreadAwareMockController;
18+
import org.spockframework.runtime.extension.IStoreProvider;
1819
import org.spockframework.runtime.model.BlockInfo;
1920
import org.spockframework.runtime.model.FeatureInfo;
2021
import org.spockframework.runtime.model.SpecInfo;
@@ -24,7 +25,7 @@
2425
import org.spockframework.util.Nullable;
2526

2627
@Beta
27-
public interface ISpecificationContext {
28+
public interface ISpecificationContext extends IStoreProvider {
2829
@Nullable
2930
SpecInfo getCurrentSpec();
3031

spock-core/src/main/java/org/spockframework/runtime/PlatformSpecRunner.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ SpockExecutionContext createSpecInstance(SpockExecutionContext context, boolean
8787

8888
context = context.withChildStoreProvider().withCurrentInstance(instance);
8989
getSpecificationContext(context).setCurrentSpec(context.getSpec());
90+
getSpecificationContext(context).pushStoreProvider(context.getStoreProvider());
9091
if (shared) {
9192
context = context.withSharedInstance(instance);
9293
}
@@ -188,13 +189,15 @@ public void runFeature(SpockExecutionContext context, Runnable feature) {
188189
throw new InternalSpockError("Invalid state, feature is executed although it should have been skipped");
189190
}
190191
getSpecificationContext(context).setCurrentFeature(currentFeature);
192+
getSpecificationContext(context).pushStoreProvider(context.getStoreProvider());
191193

192194
supervisor.beforeFeature(currentFeature);
193195
invoke(context, this, createMethodInfoForDoRunFeature(context, feature));
194196
supervisor.afterFeature(currentFeature);
195197

196198
runCloseContextStoreProvider(context, MethodKind.CLEANUP);
197199
getSpecificationContext(context).setCurrentFeature(null);
200+
getSpecificationContext(context).popStoreProvider();
198201
}
199202

200203
private MethodInfo createMethodInfoForDoRunFeature(SpockExecutionContext context, Runnable feature) {
@@ -216,13 +219,15 @@ void runIteration(SpockExecutionContext context, IterationInfo iterationInfo, Ru
216219

217220
context = context.withCurrentIteration(iterationInfo);
218221
getSpecificationContext(context).setCurrentIteration(iterationInfo);
222+
getSpecificationContext(context).pushStoreProvider(context.getStoreProvider());
219223

220224
supervisor.beforeIteration(iterationInfo);
221225
invoke(context, this, createMethodInfoForDoRunIteration(context, runnable));
222226
supervisor.afterIteration(iterationInfo);
223227
runCloseContextStoreProvider(context, MethodKind.CLEANUP);
224228

225229
getSpecificationContext(context).setCurrentIteration(null); // TODO check if we really need to null here
230+
getSpecificationContext(context).popStoreProvider();
226231
}
227232

228233
IterationInfo createIterationInfo(SpockExecutionContext context, int iterationIndex, Object[] args, int estimatedNumIterations) {

spock-core/src/main/java/org/spockframework/runtime/SpecificationContext.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44
import org.spockframework.mock.IMockController;
55
import org.spockframework.mock.IThreadAwareMockController;
66
import org.spockframework.mock.runtime.MockController;
7+
import org.spockframework.runtime.extension.IStore;
8+
import org.spockframework.runtime.extension.IStoreProvider;
79
import org.spockframework.runtime.model.*;
810
import org.spockframework.util.Nullable;
911
import spock.lang.Specification;
1012

13+
import java.util.ArrayDeque;
14+
import java.util.Deque;
15+
1116
public class SpecificationContext implements ISpecificationContext {
1217
private volatile SpecInfo currentSpec;
1318
private volatile FeatureInfo currentFeature;
@@ -18,6 +23,7 @@ public class SpecificationContext implements ISpecificationContext {
1823
private volatile Specification sharedInstance;
1924

2025
private volatile Throwable thrownException;
26+
private final Deque<IStoreProvider> storeProvider = new ArrayDeque<>(3); // spec, feature, iteration
2127

2228
private final MockController mockController = new MockController();
2329

@@ -107,4 +113,16 @@ public IThreadAwareMockController getThreadAwareMockController() {
107113
return mockController;
108114
}
109115

116+
@Override
117+
public IStore getStore(IStore.Namespace namespace) {
118+
return storeProvider.getLast().getStore(namespace);
119+
}
120+
121+
public void pushStoreProvider(IStoreProvider storeProvider) {
122+
this.storeProvider.push(storeProvider);
123+
}
124+
125+
public void popStoreProvider() {
126+
this.storeProvider.pop();
127+
}
110128
}

spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import org.hamcrest.Matcher;
2525
import org.hamcrest.collection.IsIterableContainingInAnyOrder;
2626
import org.opentest4j.MultipleFailuresError;
27+
import spock.lang.Specification;
28+
29+
import org.spockframework.lang.ISpecificationContext;
2730
import org.spockframework.runtime.extension.IBlockListener;
2831
import org.spockframework.runtime.model.BlockInfo;
2932
import org.spockframework.runtime.model.ExpressionInfo;
@@ -231,11 +234,12 @@ public static Object[] despreadList(Object[] args, Object[] spreads, int[] posit
231234

232235
public static final String CALL_BLOCK_ENTERED = "callBlockEntered";
233236

234-
public static void callBlockEntered(SpecificationContext context, int blockInfoIndex) {
237+
public static void callBlockEntered(Specification specification, int blockInfoIndex) {
238+
SpecificationContext context = (SpecificationContext) specification.getSpecificationContext();
235239
IterationInfo currentIteration = context.getCurrentIteration();
236240
BlockInfo blockInfo = context.getCurrentFeature().getBlocks().get(blockInfoIndex);
237241
context.setCurrentBlock(blockInfo);
238-
notifyBlockListener(currentIteration, blockListener -> blockListener.blockEntered(currentIteration, blockInfo));
242+
notifyBlockListener(currentIteration, blockListener -> blockListener.blockEntered(specification, blockInfo));
239243
}
240244

241245
private static void notifyBlockListener(IterationInfo currentIteration, Consumer<IBlockListener> consumer) {
@@ -246,10 +250,11 @@ private static void notifyBlockListener(IterationInfo currentIteration, Consumer
246250

247251
public static final String CALL_BLOCK_EXITED = "callBlockExited";
248252

249-
public static void callBlockExited(SpecificationContext context, int blockInfoIndex) {
253+
public static void callBlockExited(Specification specification, int blockInfoIndex) {
254+
SpecificationContext context = (SpecificationContext) specification.getSpecificationContext();
250255
IterationInfo currentIteration = context.getCurrentIteration();
251256
BlockInfo blockInfo = context.getCurrentFeature().getBlocks().get(blockInfoIndex);
252-
notifyBlockListener(currentIteration, blockListener -> blockListener.blockExited(currentIteration, blockInfo));
257+
notifyBlockListener(currentIteration, blockListener -> blockListener.blockExited(specification, blockInfo));
253258
context.setCurrentBlock(null);
254259
}
255260

spock-core/src/main/java/org/spockframework/runtime/StoreProvider.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
1919
import org.spockframework.runtime.extension.IStore;
20+
import org.spockframework.runtime.extension.IStoreProvider;
2021
import org.spockframework.util.Nullable;
2122

2223
import java.util.Objects;
@@ -25,7 +26,7 @@
2526
* @author Leonard Brünings
2627
* @since 2.4
2728
*/
28-
public class StoreProvider implements AutoCloseable {
29+
public class StoreProvider implements AutoCloseable, IStoreProvider {
2930
private static final NamespacedHierarchicalStore.CloseAction<IStore.Namespace> CLOSE_ACTION = (IStore.Namespace namespace, Object key, Object value) -> {
3031
if (value instanceof AutoCloseable) {
3132
((AutoCloseable) value).close();
@@ -36,7 +37,7 @@ public class StoreProvider implements AutoCloseable {
3637
@Nullable
3738
private final StoreProvider parent;
3839

39-
private StoreProvider(NamespacedHierarchicalStore<IStore.Namespace> backend, StoreProvider parent) {
40+
private StoreProvider(NamespacedHierarchicalStore<IStore.Namespace> backend, @Nullable StoreProvider parent) {
4041
this.backend = Objects.requireNonNull(backend);
4142
this.parent = parent;
4243
}
@@ -49,6 +50,7 @@ public StoreProvider createChildStoreProvider() {
4950
return new StoreProvider(newBackendStore(backend), this);
5051
}
5152

53+
@Override
5254
public NamespacedExtensionStore getStore(IStore.Namespace namespace) {
5355
return new NamespacedExtensionStore(backend,
5456
() -> parent == null ? null : parent.getStore(namespace),

spock-core/src/main/java/org/spockframework/runtime/extension/IBlockListener.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package org.spockframework.runtime.extension;
1616

17+
import spock.lang.Specification;
18+
1719
import org.spockframework.runtime.model.BlockInfo;
1820
import org.spockframework.runtime.model.ErrorInfo;
1921
import org.spockframework.runtime.model.IterationInfo;
@@ -44,7 +46,7 @@ public interface IBlockListener {
4446
/**
4547
* Called when a block is entered.
4648
*/
47-
default void blockEntered(IterationInfo iterationInfo, BlockInfo blockInfo) {}
49+
default <S extends Specification> void blockEntered(S specificationInstance, BlockInfo blockInfo) {}
4850

4951
/**
5052
* Called when a block is exited.
@@ -53,5 +55,5 @@ default void blockEntered(IterationInfo iterationInfo, BlockInfo blockInfo) {}
5355
* The block that was active will be available in the {@link org.spockframework.runtime.model.IErrorContext}
5456
* and can be observed via {@link org.spockframework.runtime.IRunListener#error(ErrorInfo)}.
5557
*/
56-
default void blockExited(IterationInfo iterationInfo, BlockInfo blockInfo) {}
58+
default <S extends Specification> void blockExited(S specificationInstance, BlockInfo blockInfo) {}
5759
}

spock-core/src/main/java/org/spockframework/runtime/extension/IMethodInvocation.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
/**
2323
* @author Peter Niederwieser
2424
*/
25-
public interface IMethodInvocation {
25+
public interface IMethodInvocation extends IStoreProvider {
2626
/**
2727
* Returns the specification which this method invocation belongs to.
2828
*
@@ -98,22 +98,6 @@ public interface IMethodInvocation {
9898
*/
9999
Object[] getArguments();
100100

101-
/**
102-
* Get the {@link IStore} for the supplied {@linkplain IStore.Namespace namespace}.
103-
*
104-
* <p>A store is bound to its context lifecycle. When a
105-
* context lifecycle ends it closes its associated store. All stored values
106-
* that are instances of {@link AutoCloseable} are
107-
* notified by invoking their {@code close()} methods.
108-
*
109-
* @param namespace the {@code Namespace} to get the store for; never {@code null}
110-
* @return the store in which to put and get objects for other invocations
111-
* working in the same namespace; never {@code null}
112-
* @since 2.4
113-
*/
114-
@Beta
115-
IStore getStore(IStore.Namespace namespace);
116-
117101
/**
118102
* Sets the arguments for this method invocation.
119103
*

0 commit comments

Comments
 (0)