diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java
index 3e9e3dd03430..bb1953d82ac6 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java
@@ -11,6 +11,7 @@
package org.junit.jupiter.api.extension;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
+import static org.apiguardian.api.API.Status.INTERNAL;
import static org.apiguardian.api.API.Status.STABLE;
import java.lang.reflect.AnnotatedElement;
@@ -418,6 +419,29 @@ default void publishReportEntry(String value) {
*/
Store getStore(Namespace namespace);
+ /**
+ * Returns the store for session-level data.
+ *
+ *
This store is used to store data that is scoped to the session level.
+ * The data stored in this store will be available throughout the entire session.
+ *
+ * @return the store for session-level data
+ * @since 5.13
+ */
+ @API(status = EXPERIMENTAL, since = "5.13")
+ Store getSessionLevelStore(Namespace namespace);
+
+ /**
+ * Returns the store for request-level data.
+ *
+ *
This store is used to store data that is scoped to the request level.
+ * The data stored in this store will be available only for the duration of the current request.
+ *
+ * @return the store for request-level data
+ * @since 5.13
+ */
+ Store getRequestLevelStore(Namespace namespace);
+
/**
* Get the {@link ExecutionMode} associated with the current test or container.
*
@@ -744,6 +768,11 @@ public Namespace append(Object... parts) {
Collections.addAll(newParts, parts);
return new Namespace(newParts);
}
+
+ @API(status = INTERNAL, since = "5.13")
+ public Object[] getParts() {
+ return parts.toArray();
+ }
}
}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java
index 0de9bbb308ec..e560e962143e 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java
@@ -19,6 +19,7 @@
import org.junit.jupiter.engine.config.DefaultJupiterConfiguration;
import org.junit.jupiter.engine.config.JupiterConfiguration;
import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor;
+import org.junit.jupiter.engine.descriptor.LauncherStoreFacade;
import org.junit.jupiter.engine.discovery.DiscoverySelectorResolver;
import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
import org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory;
@@ -82,8 +83,8 @@ protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest
@Override
protected JupiterEngineExecutionContext createExecutionContext(ExecutionRequest request) {
- return new JupiterEngineExecutionContext(request.getEngineExecutionListener(),
- getJupiterConfiguration(request));
+ return new JupiterEngineExecutionContext(request.getEngineExecutionListener(), getJupiterConfiguration(request),
+ new LauncherStoreFacade(request.getRequestLevelStore()));
}
/**
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java
index bf7cb5d9a059..28f4cb622871 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java
@@ -33,7 +33,6 @@
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.engine.config.JupiterConfiguration;
import org.junit.jupiter.engine.execution.DefaultExecutableInvoker;
-import org.junit.jupiter.engine.execution.NamespaceAwareStore;
import org.junit.jupiter.engine.extension.ExtensionContextInternal;
import org.junit.jupiter.engine.extension.ExtensionRegistry;
import org.junit.platform.commons.JUnitException;
@@ -52,7 +51,8 @@
*/
abstract class AbstractExtensionContext implements ExtensionContextInternal, AutoCloseable {
- private static final NamespacedHierarchicalStore.CloseAction CLOSE_RESOURCES = (__, ___, value) -> {
+ private static final NamespacedHierarchicalStore.CloseAction CLOSE_RESOURCES = (
+ __, ___, value) -> {
if (value instanceof CloseableResource) {
((CloseableResource) value).close();
}
@@ -63,12 +63,14 @@ abstract class AbstractExtensionContext implements Ext
private final T testDescriptor;
private final Set tags;
private final JupiterConfiguration configuration;
- private final NamespacedHierarchicalStore valuesStore;
+ private final NamespacedHierarchicalStore valuesStore;
private final ExecutableInvoker executableInvoker;
private final ExtensionRegistry extensionRegistry;
+ private final LauncherStoreFacade launcherStoreFacade;
AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor,
- JupiterConfiguration configuration, ExtensionRegistry extensionRegistry) {
+ JupiterConfiguration configuration, ExtensionRegistry extensionRegistry,
+ LauncherStoreFacade launcherStoreFacade) {
Preconditions.notNull(testDescriptor, "TestDescriptor must not be null");
Preconditions.notNull(configuration, "JupiterConfiguration must not be null");
@@ -78,8 +80,9 @@ abstract class AbstractExtensionContext implements Ext
this.engineExecutionListener = engineExecutionListener;
this.testDescriptor = testDescriptor;
this.configuration = configuration;
- this.valuesStore = createStore(parent);
+ this.valuesStore = createStore(parent, launcherStoreFacade);
this.extensionRegistry = extensionRegistry;
+ this.launcherStoreFacade = launcherStoreFacade;
// @formatter:off
this.tags = testDescriptor.getTags().stream()
@@ -88,9 +91,13 @@ abstract class AbstractExtensionContext implements Ext
// @formatter:on
}
- private static NamespacedHierarchicalStore createStore(ExtensionContext parent) {
- NamespacedHierarchicalStore parentStore = null;
- if (parent != null) {
+ private static NamespacedHierarchicalStore createStore(
+ ExtensionContext parent, LauncherStoreFacade launcherStoreFacade) {
+ NamespacedHierarchicalStore parentStore;
+ if (parent == null) {
+ parentStore = launcherStoreFacade.getRequestLevelStore();
+ }
+ else {
parentStore = ((AbstractExtensionContext>) parent).valuesStore;
}
return new NamespacedHierarchicalStore<>(parentStore, CLOSE_RESOURCES);
@@ -188,8 +195,17 @@ protected T getTestDescriptor() {
@Override
public Store getStore(Namespace namespace) {
- Preconditions.notNull(namespace, "Namespace must not be null");
- return new NamespaceAwareStore(this.valuesStore, namespace);
+ return launcherStoreFacade.getStoreAdapter(this.valuesStore, namespace);
+ }
+
+ @Override
+ public Store getSessionLevelStore(Namespace namespace) {
+ return launcherStoreFacade.getSessionLevelStore(namespace);
+ }
+
+ @Override
+ public Store getRequestLevelStore(Namespace namespace) {
+ return launcherStoreFacade.getRequestLevelStore(namespace);
}
@Override
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
index ea4f217a24d5..32037c80b527 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
@@ -194,7 +194,7 @@ public final JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext
ThrowableCollector throwableCollector = createThrowableCollector();
ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(),
context.getExecutionListener(), this, this.lifecycle, context.getConfiguration(), registry,
- throwableCollector);
+ context.getLauncherStoreFacade(), throwableCollector);
// @formatter:off
return context.extend()
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java
index 546472781200..a816ed12423b 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java
@@ -36,9 +36,10 @@ final class ClassExtensionContext extends AbstractExtensionContext this.invocationContext.prepareInvocation(extensionContext));
return context.extend() //
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java
index cd3c292709e2..f4a3a79370d5 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java
@@ -26,8 +26,8 @@ class DynamicExtensionContext extends AbstractExtensionContext requestLevelStore;
+ private final NamespacedHierarchicalStore sessionLevelStore;
+
+ public LauncherStoreFacade(NamespacedHierarchicalStore requestLevelStore) {
+ this.requestLevelStore = requestLevelStore;
+ this.sessionLevelStore = requestLevelStore.getParent().orElseThrow(
+ () -> new JUnitException("Request-level store must have a parent"));
+ }
+
+ NamespacedHierarchicalStore getRequestLevelStore() {
+ return this.requestLevelStore;
+ }
+
+ ExtensionContext.Store getRequestLevelStore(ExtensionContext.Namespace namespace) {
+ return getStoreAdapter(this.requestLevelStore, namespace);
+ }
+
+ ExtensionContext.Store getSessionLevelStore(ExtensionContext.Namespace namespace) {
+ return getStoreAdapter(this.sessionLevelStore, namespace);
+ }
+
+ NamespaceAwareStore getStoreAdapter(NamespacedHierarchicalStore valuesStore,
+ ExtensionContext.Namespace namespace) {
+ Preconditions.notNull(namespace, "Namespace must not be null");
+ return new NamespaceAwareStore<>(valuesStore, convert(namespace));
+ }
+
+ private Namespace convert(ExtensionContext.Namespace namespace) {
+ return Namespace.create(namespace.getParts());
+ }
+}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java
index a6d983e7196b..9d3983dbfeb9 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java
@@ -34,9 +34,10 @@ final class MethodExtensionContext extends AbstractExtensionContext prepareExtensionContext(extensionContext));
+
// @formatter:off
JupiterEngineExecutionContext newContext = context.extend()
.withExtensionRegistry(registry)
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java
index ea6df34d7436..56e4c28ae68a 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java
@@ -31,9 +31,9 @@ final class TestTemplateExtensionContext extends AbstractExtensionContext implements Store {
- private final NamespacedHierarchicalStore valuesStore;
- private final Namespace namespace;
+ private final NamespacedHierarchicalStore valuesStore;
+ private final N namespace;
- public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, Namespace namespace) {
+ public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, N namespace) {
this.valuesStore = valuesStore;
this.namespace = namespace;
}
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java
index 447790814427..3bdacace4401 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java
@@ -94,4 +94,5 @@ default OutputDirectoryProvider getOutputDirectoryProvider() {
throw new JUnitException(
"OutputDirectoryProvider not available; probably due to unaligned versions of the junit-platform-engine and junit-platform-launcher jars on the classpath/module path.");
}
+
}
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java
index 3d320a0d1c74..6609f55bb906 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java
@@ -19,6 +19,8 @@
import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.reporting.OutputDirectoryProvider;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
/**
* Provides a single {@link TestEngine} access to the information necessary to
@@ -40,22 +42,25 @@ public class ExecutionRequest {
private final EngineExecutionListener engineExecutionListener;
private final ConfigurationParameters configurationParameters;
private final OutputDirectoryProvider outputDirectoryProvider;
+ private final NamespacedHierarchicalStore requestLevelStore;
@Deprecated
@API(status = DEPRECATED, since = "1.11")
public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener,
ConfigurationParameters configurationParameters) {
- this(rootTestDescriptor, engineExecutionListener, configurationParameters, null);
+ this(rootTestDescriptor, engineExecutionListener, configurationParameters, null, null);
}
private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener,
- ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider) {
+ ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider,
+ NamespacedHierarchicalStore requestLevelStore) {
this.rootTestDescriptor = Preconditions.notNull(rootTestDescriptor, "rootTestDescriptor must not be null");
this.engineExecutionListener = Preconditions.notNull(engineExecutionListener,
"engineExecutionListener must not be null");
this.configurationParameters = Preconditions.notNull(configurationParameters,
"configurationParameters must not be null");
this.outputDirectoryProvider = outputDirectoryProvider;
+ this.requestLevelStore = requestLevelStore;
}
/**
@@ -68,7 +73,7 @@ private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListe
* engine may use to influence test execution
* @return a new {@code ExecutionRequest}; never {@code null}
* @since 1.9
- * @deprecated Use {@link #create(TestDescriptor, EngineExecutionListener, ConfigurationParameters, OutputDirectoryProvider)}
+ * @deprecated without replacement
*/
@Deprecated
@API(status = DEPRECATED, since = "1.11")
@@ -88,16 +93,19 @@ public static ExecutionRequest create(TestDescriptor rootTestDescriptor,
* engine may use to influence test execution; never {@code null}
* @param outputDirectoryProvider {@link OutputDirectoryProvider} for
* writing reports and other output files; never {@code null}
+ * @param requestLevelStore {@link NamespacedHierarchicalStore} for storing
+ * request-scoped data; never {@code null}
* @return a new {@code ExecutionRequest}; never {@code null}
- * @since 1.12
+ * @since 1.13
*/
- @API(status = INTERNAL, since = "1.12")
+ @API(status = INTERNAL, since = "1.13")
public static ExecutionRequest create(TestDescriptor rootTestDescriptor,
EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters,
- OutputDirectoryProvider outputDirectoryProvider) {
+ OutputDirectoryProvider outputDirectoryProvider, NamespacedHierarchicalStore requestLevelStore) {
return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters,
- Preconditions.notNull(outputDirectoryProvider, "outputDirectoryProvider must not be null"));
+ Preconditions.notNull(outputDirectoryProvider, "outputDirectoryProvider must not be null"),
+ Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null"));
}
/**
@@ -138,8 +146,14 @@ public ConfigurationParameters getConfigurationParameters() {
*/
@API(status = EXPERIMENTAL, since = "1.12")
public OutputDirectoryProvider getOutputDirectoryProvider() {
- return Preconditions.notNull(outputDirectoryProvider,
+ return Preconditions.notNull(this.outputDirectoryProvider,
"No OutputDirectoryProvider was configured for this request");
}
+ @API(status = EXPERIMENTAL, since = "1.13")
+ public NamespacedHierarchicalStore getRequestLevelStore() {
+ return Preconditions.notNull(this.requestLevelStore,
+ "No NamespacedHierarchicalStore was configured for this request");
+ }
+
}
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/Namespace.java
new file mode 100644
index 000000000000..4b687c42c37b
--- /dev/null
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/Namespace.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.platform.engine.support.store;
+
+import static org.apiguardian.api.API.Status.EXPERIMENTAL;
+import static org.apiguardian.api.API.Status.STABLE;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apiguardian.api.API;
+import org.junit.platform.commons.util.Preconditions;
+
+@API(status = EXPERIMENTAL, since = "5.13")
+public class Namespace {
+
+ /**
+ * The default, global namespace which allows access to stored data from
+ * all extensions.
+ */
+ public static final Namespace GLOBAL = Namespace.create(new Object());
+
+ /**
+ * Create a namespace which restricts access to data to all extensions
+ * which use the same sequence of {@code parts} for creating a namespace.
+ *
+ * The order of the {@code parts} is significant.
+ *
+ *
Internally the {@code parts} are compared using {@link Object#equals(Object)}.
+ */
+ public static Namespace create(Object... parts) {
+ Preconditions.notEmpty(parts, "parts array must not be null or empty");
+ Preconditions.containsNoNullElements(parts, "individual parts must not be null");
+ return new Namespace(new ArrayList<>(Arrays.asList(parts)));
+ }
+
+ private final List parts;
+
+ private Namespace(List parts) {
+ this.parts = parts;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Namespace that = (Namespace) o;
+ return this.parts.equals(that.parts);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.parts.hashCode();
+ }
+
+ /**
+ * Create a new namespace by appending the supplied {@code parts} to the
+ * existing sequence of parts in this namespace.
+ *
+ * @return new namespace; never {@code null}
+ * @since 5.13
+ */
+ @API(status = STABLE, since = "5.13")
+ public Namespace append(Object... parts) {
+ Preconditions.notEmpty(parts, "parts array must not be null or empty");
+ Preconditions.containsNoNullElements(parts, "individual parts must not be null");
+ ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length);
+ newParts.addAll(this.parts);
+ Collections.addAll(newParts, parts);
+ return new Namespace(newParts);
+ }
+}
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java
index 9167dde42b13..bd27996973b1 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java
@@ -17,6 +17,7 @@
import java.util.Comparator;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -85,6 +86,19 @@ public NamespacedHierarchicalStore newChild() {
return new NamespacedHierarchicalStore<>(this, this.closeAction);
}
+ /**
+ * Returns the parent store of this {@code NamespacedHierarchicalStore}.
+ *
+ * If this store does not have a parent, an empty {@code Optional} is returned.
+ *
+ * @return an {@code Optional} containing the parent store, or an empty {@code Optional} if there is no parent
+ * @since 5.13
+ */
+ @API(status = EXPERIMENTAL, since = "5.13")
+ public Optional> getParent() {
+ return Optional.ofNullable(this.parentStore);
+ }
+
/**
* Determine if this store has been {@linkplain #close() closed}.
*
@@ -447,6 +461,15 @@ public Failure(Throwable throwable) {
@FunctionalInterface
public interface CloseAction {
+ @API(status = EXPERIMENTAL, since = "1.13")
+ static CloseAction closeAutoCloseables() {
+ return (__, ___, value) -> {
+ if (value instanceof AutoCloseable) {
+ ((AutoCloseable) value).close();
+ }
+ };
+ }
+
/**
* Close the supplied {@code value}.
*
diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java
index c78ed7761888..ecf9e5a47414 100644
--- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java
+++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java
@@ -13,6 +13,8 @@
import static org.apiguardian.api.API.Status.STABLE;
import org.apiguardian.api.API;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.core.LauncherFactory;
/**
@@ -47,4 +49,6 @@ public interface LauncherSession extends AutoCloseable {
@Override
void close();
+ NamespacedHierarchicalStore getStore();
+
}
diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java
index 6bd73c4b641e..70106c48fb72 100644
--- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java
+++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java
@@ -103,4 +103,5 @@ public LauncherDiscoveryListener getDiscoveryListener() {
public OutputDirectoryProvider getOutputDirectoryProvider() {
return this.outputDirectoryProvider;
}
+
}
diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java
index 5ebc4f9a638e..c71d3846e6f3 100644
--- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java
+++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java
@@ -11,6 +11,7 @@
package org.junit.platform.launcher.core;
import static java.util.Collections.unmodifiableCollection;
+import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables;
import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.DISCOVERY;
import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION;
@@ -18,6 +19,8 @@
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.TestEngine;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryListener;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
@@ -38,9 +41,9 @@
class DefaultLauncher implements Launcher {
private final LauncherListenerRegistry listenerRegistry = new LauncherListenerRegistry();
- private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator(
- listenerRegistry.testExecutionListeners);
+ private final EngineExecutionOrchestrator executionOrchestrator;
private final EngineDiscoveryOrchestrator discoveryOrchestrator;
+ private final NamespacedHierarchicalStore sessionLevelStore;
/**
* Construct a new {@code DefaultLauncher} with the supplied test engines.
@@ -50,7 +53,8 @@ class DefaultLauncher implements Launcher {
* @param postDiscoveryFilters the additional post discovery filters for
* discovery requests; never {@code null}
*/
- DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters) {
+ DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters,
+ NamespacedHierarchicalStore sessionLevelStore) {
Preconditions.condition(testEngines != null && testEngines.iterator().hasNext(),
() -> "Cannot create Launcher without at least one TestEngine; "
+ "consider adding an engine implementation JAR to the classpath");
@@ -59,6 +63,8 @@ class DefaultLauncher implements Launcher {
"PostDiscoveryFilter array must not contain null elements");
this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines,
unmodifiableCollection(postDiscoveryFilters), listenerRegistry.launcherDiscoveryListeners);
+ this.sessionLevelStore = sessionLevelStore;
+ this.executionOrchestrator = new EngineExecutionOrchestrator(listenerRegistry.testExecutionListeners);
}
@Override
@@ -100,7 +106,13 @@ private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryReque
}
private void execute(InternalTestPlan internalTestPlan, TestExecutionListener[] listeners) {
- executionOrchestrator.execute(internalTestPlan, listeners);
+ try (NamespacedHierarchicalStore requestLevelStore = createRequestLevelStore()) {
+ executionOrchestrator.execute(internalTestPlan, requestLevelStore, listeners);
+ }
+ }
+
+ private NamespacedHierarchicalStore createRequestLevelStore() {
+ return new NamespacedHierarchicalStore<>(sessionLevelStore, closeAutoCloseables());
}
}
diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java
index 018eb41cf8a5..37a205531802 100644
--- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java
+++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java
@@ -10,10 +10,15 @@
package org.junit.platform.launcher.core;
+import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables;
+
import java.util.List;
+import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.platform.commons.PreconditionViolationException;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryListener;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
@@ -40,21 +45,26 @@ public void close() {
}
};
+ private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null,
+ closeAutoCloseables());
private final LauncherInterceptor interceptor;
private final LauncherSessionListener listener;
private final DelegatingLauncher launcher;
- DefaultLauncherSession(List interceptors, Supplier listenerSupplier,
- Supplier launcherSupplier) {
+ DefaultLauncherSession(List interceptors, //
+ Supplier listenerSupplier, //
+ Function, Launcher> launcherFactory //
+ ) {
interceptor = composite(interceptors);
Launcher launcher;
if (interceptor == NOOP_INTERCEPTOR) {
this.listener = listenerSupplier.get();
- launcher = launcherSupplier.get();
+ launcher = launcherFactory.apply(this.store);
}
else {
this.listener = interceptor.intercept(listenerSupplier::get);
- launcher = new InterceptingLauncher(interceptor.intercept(launcherSupplier::get), interceptor);
+ launcher = new InterceptingLauncher(interceptor.intercept(() -> launcherFactory.apply(this.store)),
+ interceptor);
}
this.launcher = new DelegatingLauncher(launcher);
listener.launcherSessionOpened(this);
@@ -73,11 +83,17 @@ LauncherSessionListener getListener() {
public void close() {
if (launcher.delegate != ClosedLauncher.INSTANCE) {
launcher.delegate = ClosedLauncher.INSTANCE;
+ store.close();
listener.launcherSessionClosed(this);
interceptor.close();
}
}
+ @Override
+ public NamespacedHierarchicalStore getStore() {
+ return store;
+ }
+
private static class ClosedLauncher implements Launcher {
static final ClosedLauncher INSTANCE = new ClosedLauncher();
diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java
index 09c895a69684..c9c71e06eee0 100644
--- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java
+++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java
@@ -28,7 +28,6 @@
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.UnrecoverableExceptions;
-import org.junit.platform.engine.EngineExecutionListener;
import org.junit.platform.engine.Filter;
import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
@@ -86,11 +85,11 @@ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase
* filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and
* {@linkplain TestDescriptor#prune() prunes} the resulting test tree.
*
- * Note: The test descriptors in the discovery result can safely be used as
+ * Note: The test descriptors in the discovery result can safely be used as
* non-root descriptors. Engine-test descriptor entries are pruned from
* the returned result. As such execution by
- * {@link EngineExecutionOrchestrator#execute(LauncherDiscoveryResult, EngineExecutionListener)}
- * will not emit start or emit events for engines without tests.
+ * {@link EngineExecutionOrchestrator} will not emit start or emit events
+ * for engines without tests.
*/
public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, UniqueId parentId) {
LauncherDiscoveryResult result = discover(request, phase, parentId::appendEngine);
diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java
index aedf22950cc1..f9fb5dcbfec6 100644
--- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java
+++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java
@@ -29,6 +29,8 @@
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.reporting.OutputDirectoryProvider;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.TestPlan;
@@ -51,12 +53,14 @@ public EngineExecutionOrchestrator() {
this.listenerRegistry = listenerRegistry;
}
- void execute(InternalTestPlan internalTestPlan, TestExecutionListener... listeners) {
+ void execute(InternalTestPlan internalTestPlan, NamespacedHierarchicalStore requestLevelStore,
+ TestExecutionListener... listeners) {
ConfigurationParameters configurationParameters = internalTestPlan.getConfigurationParameters();
ListenerRegistry testExecutionListenerListeners = buildListenerRegistryForExecution(
listeners);
withInterceptedStreams(configurationParameters, testExecutionListenerListeners,
- testExecutionListener -> execute(internalTestPlan, EngineExecutionListener.NOOP, testExecutionListener));
+ testExecutionListener -> execute(internalTestPlan, EngineExecutionListener.NOOP, testExecutionListener,
+ requestLevelStore));
}
/**
@@ -68,17 +72,18 @@ void execute(InternalTestPlan internalTestPlan, TestExecutionListener... listene
*/
@API(status = INTERNAL, since = "1.9", consumers = { "org.junit.platform.suite.engine" })
public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener,
- TestExecutionListener testExecutionListener) {
+ TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore) {
Preconditions.notNull(discoveryResult, "discoveryResult must not be null");
Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null");
Preconditions.notNull(testExecutionListener, "testExecutionListener must not be null");
+ Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null");
InternalTestPlan internalTestPlan = InternalTestPlan.from(discoveryResult);
- execute(internalTestPlan, engineExecutionListener, testExecutionListener);
+ execute(internalTestPlan, engineExecutionListener, testExecutionListener, requestLevelStore);
}
private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener parentEngineExecutionListener,
- TestExecutionListener testExecutionListener) {
+ TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore) {
internalTestPlan.markStarted();
// Do not directly pass the internal test plan to test execution listeners.
@@ -92,7 +97,8 @@ private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener
}
else {
execute(discoveryResult,
- buildEngineExecutionListener(parentEngineExecutionListener, testExecutionListener, testPlan));
+ buildEngineExecutionListener(parentEngineExecutionListener, testExecutionListener, testPlan),
+ requestLevelStore);
}
testExecutionListener.testPlanExecutionFinished(testPlan);
}
@@ -152,7 +158,8 @@ private void withInterceptedStreams(ConfigurationParameters configurationParamet
* EngineExecutionListener listener} of execution events.
*/
@API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit" })
- public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener) {
+ public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener,
+ NamespacedHierarchicalStore requestLevelStore) {
Preconditions.notNull(discoveryResult, "discoveryResult must not be null");
Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null");
@@ -168,7 +175,7 @@ public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionList
}
else {
execute(engineDescriptor, listener, configurationParameters, testEngine,
- discoveryResult.getOutputDirectoryProvider());
+ discoveryResult.getOutputDirectoryProvider(), requestLevelStore);
}
}
}
@@ -193,13 +200,13 @@ private ListenerRegistry buildListenerRegistryForExecutio
private void execute(TestDescriptor engineDescriptor, EngineExecutionListener listener,
ConfigurationParameters configurationParameters, TestEngine testEngine,
- OutputDirectoryProvider outputDirectoryProvider) {
+ OutputDirectoryProvider outputDirectoryProvider, NamespacedHierarchicalStore requestLevelStore) {
OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener,
engineDescriptor);
try {
testEngine.execute(ExecutionRequest.create(engineDescriptor, delayingListener, configurationParameters,
- outputDirectoryProvider));
+ outputDirectoryProvider, requestLevelStore));
delayingListener.reportEngineOutcome();
}
catch (Throwable throwable) {
diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java
index c756f27351f0..b3d6ab2613a0 100644
--- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java
+++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java
@@ -26,6 +26,8 @@
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.TestEngine;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryListener;
import org.junit.platform.launcher.LauncherInterceptor;
@@ -96,7 +98,8 @@ public static LauncherSession openSession(LauncherConfig config) throws Precondi
Preconditions.notNull(config, "LauncherConfig must not be null");
LauncherConfigurationParameters configurationParameters = LauncherConfigurationParameters.builder().build();
return new DefaultLauncherSession(collectLauncherInterceptors(configurationParameters),
- () -> createLauncherSessionListener(config), () -> createDefaultLauncher(config, configurationParameters));
+ () -> createLauncherSessionListener(config),
+ sessionLevelStore -> createDefaultLauncher(config, configurationParameters, sessionLevelStore));
}
/**
@@ -125,17 +128,17 @@ public static Launcher create() throws PreconditionViolationException {
public static Launcher create(LauncherConfig config) throws PreconditionViolationException {
Preconditions.notNull(config, "LauncherConfig must not be null");
LauncherConfigurationParameters configurationParameters = LauncherConfigurationParameters.builder().build();
- return new SessionPerRequestLauncher(() -> createDefaultLauncher(config, configurationParameters),
+ return new SessionPerRequestLauncher(
+ sessionLevelStore -> createDefaultLauncher(config, configurationParameters, sessionLevelStore),
() -> createLauncherSessionListener(config), () -> collectLauncherInterceptors(configurationParameters));
}
private static DefaultLauncher createDefaultLauncher(LauncherConfig config,
- LauncherConfigurationParameters configurationParameters) {
+ LauncherConfigurationParameters configurationParameters,
+ NamespacedHierarchicalStore sessionLevelStore) {
Set engines = collectTestEngines(config);
List filters = collectPostDiscoveryFilters(config);
-
- DefaultLauncher launcher = new DefaultLauncher(engines, filters);
-
+ DefaultLauncher launcher = new DefaultLauncher(engines, filters, sessionLevelStore);
registerLauncherDiscoveryListeners(config, launcher);
registerTestExecutionListeners(config, launcher, configurationParameters);
diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java
index dffde014867a..cb12eafb7282 100644
--- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java
+++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java
@@ -11,8 +11,11 @@
package org.junit.platform.launcher.core;
import java.util.List;
+import java.util.function.Function;
import java.util.function.Supplier;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryListener;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
@@ -28,14 +31,14 @@
class SessionPerRequestLauncher implements Launcher {
private final LauncherListenerRegistry listenerRegistry = new LauncherListenerRegistry();
- private final Supplier launcherSupplier;
+ private final Function, Launcher> launcherFactory;
private final Supplier sessionListenerSupplier;
private final Supplier> interceptorFactory;
- SessionPerRequestLauncher(Supplier launcherSupplier,
+ SessionPerRequestLauncher(Function, Launcher> launcherFactory,
Supplier sessionListenerSupplier,
Supplier> interceptorFactory) {
- this.launcherSupplier = launcherSupplier;
+ this.launcherFactory = launcherFactory;
this.sessionListenerSupplier = sessionListenerSupplier;
this.interceptorFactory = interceptorFactory;
}
@@ -73,7 +76,7 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) {
private LauncherSession createSession() {
LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier,
- launcherSupplier);
+ this.launcherFactory);
Launcher launcher = session.getLauncher();
listenerRegistry.launcherDiscoveryListeners.getListeners().forEach(
launcher::registerLauncherDiscoveryListeners);
diff --git a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/NamespacedHierarchicalStoreProviders.java b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/NamespacedHierarchicalStoreProviders.java
new file mode 100644
index 000000000000..e41b8bbc7343
--- /dev/null
+++ b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/NamespacedHierarchicalStoreProviders.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.platform.launcher.core;
+
+import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables;
+
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
+
+public class NamespacedHierarchicalStoreProviders {
+ private static final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null);
+
+ public static NamespacedHierarchicalStore dummyNamespacedHierarchicalStore() {
+ return new NamespacedHierarchicalStore<>(store, closeAutoCloseables());
+ }
+
+ public static NamespacedHierarchicalStore dummyNamespacedHierarchicalStoreWithNoParent() {
+ return new NamespacedHierarchicalStore<>(null, closeAutoCloseables());
+ }
+}
diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java
index c3a62006e859..6e3bf311ccfb 100644
--- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java
+++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java
@@ -19,6 +19,8 @@
import org.junit.platform.engine.EngineExecutionListener;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator;
import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase;
@@ -58,9 +60,10 @@ LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, Uniq
}
TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult,
- EngineExecutionListener parentEngineExecutionListener) {
+ EngineExecutionListener parentEngineExecutionListener,
+ NamespacedHierarchicalStore requestLevelStore) {
SummaryGeneratingListener listener = new SummaryGeneratingListener();
- executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener);
+ executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener, requestLevelStore);
return listener.getSummary();
}
diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java
index bfcd2f3b541f..1c062e6cfe9b 100644
--- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java
+++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java
@@ -31,6 +31,8 @@
import org.junit.platform.engine.support.descriptor.ClassSource;
import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.core.LauncherDiscoveryResult;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
@@ -130,7 +132,8 @@ private static String getSuiteDisplayName(Class> testClass) {
// @formatter:on
}
- void execute(EngineExecutionListener parentEngineExecutionListener) {
+ void execute(EngineExecutionListener parentEngineExecutionListener,
+ NamespacedHierarchicalStore requestLevelStore) {
parentEngineExecutionListener.executionStarted(this);
ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector();
@@ -139,7 +142,8 @@ void execute(EngineExecutionListener parentEngineExecutionListener) {
executeBeforeSuiteMethods(beforeSuiteMethods, throwableCollector);
- TestExecutionSummary summary = executeTests(parentEngineExecutionListener, throwableCollector);
+ TestExecutionSummary summary = executeTests(parentEngineExecutionListener, requestLevelStore,
+ throwableCollector);
executeAfterSuiteMethods(afterSuiteMethods, throwableCollector);
@@ -160,7 +164,7 @@ private void executeBeforeSuiteMethods(List beforeSuiteMethods, Throwabl
}
private TestExecutionSummary executeTests(EngineExecutionListener parentEngineExecutionListener,
- ThrowableCollector throwableCollector) {
+ NamespacedHierarchicalStore requestLevelStore, ThrowableCollector throwableCollector) {
if (throwableCollector.isNotEmpty()) {
return null;
}
@@ -170,7 +174,7 @@ private TestExecutionSummary executeTests(EngineExecutionListener parentEngineEx
// be pruned accordingly.
LauncherDiscoveryResult discoveryResult = this.launcherDiscoveryResult.withRetainedEngines(
getChildren()::contains);
- return launcher.execute(discoveryResult, parentEngineExecutionListener);
+ return launcher.execute(discoveryResult, parentEngineExecutionListener, requestLevelStore);
}
private void executeAfterSuiteMethods(List afterSuiteMethods, ThrowableCollector throwableCollector) {
diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java
index c0f754639c80..27c25aa686f8 100644
--- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java
+++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java
@@ -22,6 +22,8 @@
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
/**
* The JUnit Platform Suite {@link org.junit.platform.engine.TestEngine TestEngine}.
@@ -63,6 +65,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId
public void execute(ExecutionRequest request) {
SuiteEngineDescriptor suiteEngineDescriptor = (SuiteEngineDescriptor) request.getRootTestDescriptor();
EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener();
+ NamespacedHierarchicalStore requestLevelStore = request.getRequestLevelStore();
engineExecutionListener.executionStarted(suiteEngineDescriptor);
@@ -70,7 +73,7 @@ public void execute(ExecutionRequest request) {
suiteEngineDescriptor.getChildren()
.stream()
.map(SuiteTestDescriptor.class::cast)
- .forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener));
+ .forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener, requestLevelStore));
// @formatter:on
engineExecutionListener.executionFinished(suiteEngineDescriptor, TestExecutionResult.successful());
}
diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java
index 2813765250e0..08f726a6a9a3 100644
--- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java
+++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java
@@ -16,11 +16,13 @@
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.MAINTAINED;
import static org.apiguardian.api.API.Status.STABLE;
+import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables;
import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION;
import java.nio.file.Path;
import java.util.Map;
import java.util.ServiceLoader;
+import java.util.function.Consumer;
import java.util.stream.Stream;
import org.apiguardian.api.API;
@@ -38,6 +40,8 @@
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.reporting.OutputDirectoryProvider;
+import org.junit.platform.engine.support.store.Namespace;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator;
import org.junit.platform.launcher.core.EngineExecutionOrchestrator;
@@ -253,9 +257,11 @@ private static void executeDirectly(TestEngine testEngine, EngineDiscoveryReques
EngineExecutionListener listener) {
UniqueId engineUniqueId = UniqueId.forEngine(testEngine.getId());
TestDescriptor engineTestDescriptor = testEngine.discover(discoveryRequest, engineUniqueId);
- ExecutionRequest request = ExecutionRequest.create(engineTestDescriptor, listener,
- discoveryRequest.getConfigurationParameters(), discoveryRequest.getOutputDirectoryProvider());
- testEngine.execute(request);
+ withRequestLevelStore(store -> {
+ ExecutionRequest request = ExecutionRequest.create(engineTestDescriptor, listener,
+ discoveryRequest.getConfigurationParameters(), discoveryRequest.getOutputDirectoryProvider(), store);
+ testEngine.execute(request);
+ });
}
private static void executeUsingLauncherOrchestration(TestEngine testEngine,
@@ -264,7 +270,18 @@ private static void executeUsingLauncherOrchestration(TestEngine testEngine,
emptySet()).discover(discoveryRequest, EXECUTION);
TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine);
Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor");
- new EngineExecutionOrchestrator().execute(discoveryResult, listener);
+ withRequestLevelStore(store -> new EngineExecutionOrchestrator().execute(discoveryResult, listener, store));
+ }
+
+ private static void withRequestLevelStore(Consumer> action) {
+ try (NamespacedHierarchicalStore sessionLevelStore = newStore(null);
+ NamespacedHierarchicalStore requestLevelStore = newStore(sessionLevelStore)) {
+ action.accept(requestLevelStore);
+ }
+ }
+
+ private static NamespacedHierarchicalStore newStore(NamespacedHierarchicalStore parentStore) {
+ return new NamespacedHierarchicalStore<>(parentStore, closeAutoCloseables());
}
@SuppressWarnings("unchecked")
diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java
index 4d47f228f241..3d493c558835 100644
--- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java
+++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java
@@ -16,6 +16,7 @@
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
+import static org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore;
import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider;
import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.container;
@@ -924,8 +925,9 @@ private static void execute(Class> testClass, EngineExecutionListener listener
TestEngine testEngine = new VintageTestEngine();
var discoveryRequest = request(testClass);
var engineTestDescriptor = testEngine.discover(discoveryRequest, UniqueId.forEngine(testEngine.getId()));
- testEngine.execute(ExecutionRequest.create(engineTestDescriptor, listener,
- discoveryRequest.getConfigurationParameters(), dummyOutputDirectoryProvider()));
+ testEngine.execute(
+ ExecutionRequest.create(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters(),
+ dummyOutputDirectoryProvider(), dummyNamespacedHierarchicalStore()));
}
private static LauncherDiscoveryRequest request(Class> testClass) {
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/JupiterEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/JupiterEngineTests.java
new file mode 100644
index 000000000000..6779fa769c2c
--- /dev/null
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/JupiterEngineTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.jupiter.engine;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor;
+import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
+import org.junit.platform.commons.JUnitException;
+import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.engine.EngineExecutionListener;
+import org.junit.platform.engine.ExecutionRequest;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders;
+
+/**
+ * @since 5.13
+ */
+public class JupiterEngineTests {
+
+ private final TestDescriptor rootTestDescriptor = mock(JupiterEngineDescriptor.class);
+
+ private final ConfigurationParameters configurationParameters = mock();
+
+ private final EngineExecutionListener engineExecutionListener = mock();
+
+ private final ExecutionRequest executionRequest = mock();
+
+ private final JupiterTestEngine engine = new JupiterTestEngine();
+
+ @BeforeEach
+ void setUp() {
+ when(executionRequest.getEngineExecutionListener()).thenReturn(engineExecutionListener);
+ when(executionRequest.getConfigurationParameters()).thenReturn(configurationParameters);
+ when(executionRequest.getRootTestDescriptor()).thenReturn(rootTestDescriptor);
+ }
+
+ @Test
+ void createExecutionContextWithValidRequest() {
+ when(executionRequest.getRequestLevelStore()).thenReturn(
+ NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore());
+
+ JupiterEngineExecutionContext context = engine.createExecutionContext(executionRequest);
+ assertThat(context).isNotNull();
+ }
+
+ @Test
+ void createExecutionContextWithNoParentsRequestLevelStore() {
+ when(executionRequest.getRequestLevelStore()).thenReturn(
+ NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStoreWithNoParent());
+
+ assertThatThrownBy(() -> engine.createExecutionContext(executionRequest)).isInstanceOf(
+ JUnitException.class).hasMessageContaining("Request-level store must have a parent");
+ }
+
+}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
index 11b50f5dfd2b..1d604550038e 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
@@ -64,6 +64,7 @@
import org.junit.platform.engine.reporting.FileEntry;
import org.junit.platform.engine.reporting.ReportEntry;
import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector;
+import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.mockito.ArgumentCaptor;
/**
@@ -77,6 +78,8 @@ public class ExtensionContextTests {
private final JupiterConfiguration configuration = mock();
private final ExtensionRegistry extensionRegistry = mock();
+ private final LauncherStoreFacade launcherStoreFacade = new LauncherStoreFacade(
+ new NamespacedHierarchicalStore<>(new NamespacedHierarchicalStore<>(null)));
@BeforeEach
void setUp() {
@@ -91,7 +94,7 @@ void fromJupiterEngineDescriptor() {
var engineTestDescriptor = new JupiterEngineDescriptor(UniqueId.root("engine", "junit-jupiter"), configuration);
try (var engineContext = new JupiterEngineExtensionContext(null, engineTestDescriptor, configuration,
- extensionRegistry)) {
+ extensionRegistry, launcherStoreFacade)) {
// @formatter:off
assertAll("engineContext",
() -> assertThat(engineContext.getElement()).isEmpty(),
@@ -118,7 +121,7 @@ void fromClassTestDescriptor() {
var outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor);
var outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, PER_METHOD,
- configuration, extensionRegistry, null);
+ configuration, extensionRegistry, launcherStoreFacade, null);
// @formatter:off
assertAll("outerContext",
@@ -137,7 +140,7 @@ void fromClassTestDescriptor() {
// @formatter:on
var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, nestedClassDescriptor,
- PER_METHOD, configuration, extensionRegistry, null);
+ PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null);
assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext);
}
@@ -145,7 +148,7 @@ void fromClassTestDescriptor() {
void ExtensionContext_With_ExtensionRegistry_getExtensions() {
var classTestDescriptor = nestedClassDescriptor();
try (var ctx = new ClassExtensionContext(null, null, classTestDescriptor, PER_METHOD, configuration,
- extensionRegistry, null)) {
+ extensionRegistry, launcherStoreFacade, null)) {
Extension ext = mock();
when(extensionRegistry.getExtensions(Extension.class)).thenReturn(List.of(ext));
@@ -163,18 +166,18 @@ void tagsCanBeRetrievedInExtensionContext() {
outerClassDescriptor.addChild(methodTestDescriptor);
var outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, PER_METHOD,
- configuration, extensionRegistry, null);
+ configuration, extensionRegistry, launcherStoreFacade, null);
assertThat(outerExtensionContext.getTags()).containsExactly("outer-tag");
assertThat(outerExtensionContext.getRoot()).isSameAs(outerExtensionContext);
var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, nestedClassDescriptor,
- PER_METHOD, configuration, extensionRegistry, null);
+ PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null);
assertThat(nestedExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "nested-tag");
assertThat(nestedExtensionContext.getRoot()).isSameAs(outerExtensionContext);
var methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null, methodTestDescriptor,
- configuration, extensionRegistry, new OpenTest4JAwareThrowableCollector());
+ configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector());
methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClass()));
assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag");
assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext);
@@ -192,11 +195,11 @@ void fromMethodTestDescriptor() {
var testMethod = methodTestDescriptor.getTestMethod();
var engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor, configuration,
- extensionRegistry);
+ extensionRegistry, launcherStoreFacade);
var classExtensionContext = new ClassExtensionContext(engineExtensionContext, null, classTestDescriptor,
- PER_METHOD, configuration, extensionRegistry, null);
+ PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null);
var methodExtensionContext = new MethodExtensionContext(classExtensionContext, null, methodTestDescriptor,
- configuration, extensionRegistry, new OpenTest4JAwareThrowableCollector());
+ configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector());
methodExtensionContext.setTestInstances(DefaultTestInstances.of(testInstance));
// @formatter:off
@@ -222,7 +225,7 @@ void reportEntriesArePublishedToExecutionListener() {
var classTestDescriptor = outerClassDescriptor(null);
var engineExecutionListener = spy(EngineExecutionListener.class);
ExtensionContext extensionContext = new ClassExtensionContext(null, engineExecutionListener,
- classTestDescriptor, PER_METHOD, configuration, extensionRegistry, null);
+ classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null);
var map1 = Collections.singletonMap("key", "value");
var map2 = Collections.singletonMap("other key", "other value");
@@ -348,7 +351,7 @@ private ExtensionContext createExtensionContextForFilePublishing(Path tempDir,
when(configuration.getOutputDirectoryProvider()) //
.thenReturn(hierarchicalOutputDirectoryProvider(tempDir));
return new ClassExtensionContext(null, engineExecutionListener, classTestDescriptor, PER_METHOD, configuration,
- extensionRegistry, null);
+ extensionRegistry, launcherStoreFacade, null);
}
@Test
@@ -357,9 +360,9 @@ void usingStore() {
var methodTestDescriptor = methodDescriptor();
var classTestDescriptor = outerClassDescriptor(methodTestDescriptor);
ExtensionContext parentContext = new ClassExtensionContext(null, null, classTestDescriptor, PER_METHOD,
- configuration, extensionRegistry, null);
+ configuration, extensionRegistry, launcherStoreFacade, null);
var childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor, configuration,
- extensionRegistry, new OpenTest4JAwareThrowableCollector());
+ extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector());
childContext.setTestInstances(DefaultTestInstances.of(new OuterClass()));
var childStore = childContext.getStore(Namespace.GLOBAL);
@@ -406,18 +409,20 @@ void configurationParameter(Function>> extensionContextFactories() {
ExtensionRegistry extensionRegistry = mock();
+ LauncherStoreFacade launcherStoreFacade = mock();
var testClass = ExtensionContextTests.class;
return List.of( //
named("engine", (JupiterConfiguration configuration) -> {
var engineUniqueId = UniqueId.parse("[engine:junit-jupiter]");
var engineDescriptor = new JupiterEngineDescriptor(engineUniqueId, configuration);
- return new JupiterEngineExtensionContext(null, engineDescriptor, configuration, extensionRegistry);
+ return new JupiterEngineExtensionContext(null, engineDescriptor, configuration, extensionRegistry,
+ launcherStoreFacade);
}), //
named("class", (JupiterConfiguration configuration) -> {
var classUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]");
var classTestDescriptor = new ClassTestDescriptor(classUniqueId, testClass, configuration);
return new ClassExtensionContext(null, null, classTestDescriptor, PER_METHOD, configuration,
- extensionRegistry, null);
+ extensionRegistry, launcherStoreFacade, null);
}), //
named("method", (JupiterConfiguration configuration) -> {
var method = ReflectionSupport.findMethod(testClass, "extensionContextFactories").orElseThrow();
@@ -425,7 +430,7 @@ void configurationParameter(Function(null), Namespace.GLOBAL);
+ return new NamespaceAwareStore<>(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL);
}
}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java
index 2c62a186b9fa..1a648efc0ee8 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java
@@ -38,7 +38,7 @@ class ExtensionContextStoreTests {
private final NamespacedHierarchicalStore parentStore = new NamespacedHierarchicalStore<>(null);
private final NamespacedHierarchicalStore localStore = new NamespacedHierarchicalStore<>(parentStore);
- private final Store store = new NamespaceAwareStore(localStore, Namespace.GLOBAL);
+ private final Store store = new NamespaceAwareStore<>(localStore, Namespace.GLOBAL);
@Test
void getOrDefaultWithNoValuePresent() {
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java
index 80eeca958fbd..5e0b21658b32 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java
@@ -20,6 +20,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.engine.config.JupiterConfiguration;
+import org.junit.jupiter.engine.descriptor.LauncherStoreFacade;
import org.junit.jupiter.engine.extension.MutableExtensionRegistry;
import org.junit.platform.engine.EngineExecutionListener;
@@ -34,8 +35,10 @@ class JupiterEngineExecutionContextTests {
private final EngineExecutionListener engineExecutionListener = mock();
+ private final LauncherStoreFacade launcherStoreFacade = mock();
+
private final JupiterEngineExecutionContext originalContext = new JupiterEngineExecutionContext(
- engineExecutionListener, configuration);
+ engineExecutionListener, configuration, launcherStoreFacade);
@Test
void executionListenerIsHandedOnWhenContextIsExtended() {
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java
index 37cfcdd57f92..7d7c20fac53e 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java
@@ -96,7 +96,7 @@ class CloseablePathTests extends AbstractJupiterTestEngineTests {
@BeforeEach
void setUpExtensionContext() {
- var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL);
+ var store = new NamespaceAwareStore<>(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL);
when(extensionContext.getStore(any())).thenReturn(store);
}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java
index 750611a5c67a..90910b3f52b6 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java
@@ -71,7 +71,7 @@ void shouldThrowInvocationException() {
private static SeparateThreadTimeoutInvocation aSeparateThreadInvocation(Invocation invocation) {
var namespace = ExtensionContext.Namespace.create(SeparateThreadTimeoutInvocationTests.class);
- var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), namespace);
+ var store = new NamespaceAwareStore<>(new NamespacedHierarchicalStore<>(null), namespace);
var parameters = new TimeoutInvocationParameters<>(invocation,
new TimeoutDuration(PREEMPTIVE_TIMEOUT_MILLIS, MILLISECONDS), () -> "method()",
PreInterruptCallbackInvocation.NOOP);
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java
index 234a2c19e63e..8cec591f1e86 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java
@@ -41,7 +41,7 @@
class TimeoutInvocationFactoryTests {
@Spy
- private final Store store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null),
+ private final Store store = new NamespaceAwareStore<>(new NamespacedHierarchicalStore<>(null),
ExtensionContext.Namespace.create(TimeoutInvocationFactoryTests.class));
@Mock
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java
index 5550270a5300..067da08e84a7 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java
@@ -300,13 +300,23 @@ public void publishDirectory(String name, ThrowingConsumer action) {
@Override
public Store getStore(Namespace namespace) {
- var store = new NamespaceAwareStore(this.store, namespace);
+ var store = new NamespaceAwareStore<>(this.store, namespace);
method //
.map(it -> new ParameterizedTestContext(it, it.getAnnotation(ParameterizedTest.class))) //
.ifPresent(ctx -> store.put(DECLARATION_CONTEXT_KEY, ctx));
return store;
}
+ @Override
+ public Store getSessionLevelStore(Namespace namespace) {
+ return getStore(namespace);
+ }
+
+ @Override
+ public Store getRequestLevelStore(Namespace namespace) {
+ return getStore(namespace);
+ }
+
@Override
public ExecutionMode getExecutionMode() {
return ExecutionMode.SAME_THREAD;
diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java
index 09c83e273e60..85fb2c028746 100644
--- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java
+++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java
@@ -18,6 +18,7 @@
import static org.junit.platform.engine.TestExecutionResult.Status.FAILED;
import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL;
import static org.junit.platform.engine.TestExecutionResult.successful;
+import static org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore;
import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -80,7 +81,7 @@ void init() {
private HierarchicalTestExecutor createExecutor(
HierarchicalTestExecutorService executorService) {
var request = ExecutionRequest.create(root, listener, mock(ConfigurationParameters.class),
- dummyOutputDirectoryProvider());
+ dummyOutputDirectoryProvider(), dummyNamespacedHierarchicalStore());
return new HierarchicalTestExecutor<>(request, rootContext, executorService,
OpenTest4JAwareThrowableCollector::new);
}
diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespaceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespaceTests.java
new file mode 100644
index 000000000000..b2328f49851e
--- /dev/null
+++ b/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespaceTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.platform.engine.support.store;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class NamespaceTests {
+
+ @Test
+ void namespacesEqualForSamePartsSequence() {
+ Namespace ns1 = Namespace.create("part1", "part2");
+ Namespace ns2 = Namespace.create("part1", "part2");
+
+ assertEquals(ns1, ns2);
+ }
+
+ @Test
+ void orderOfNamespacePartsDoesMatter() {
+ Namespace ns1 = Namespace.create("part1", "part2");
+ Namespace ns2 = Namespace.create("part2", "part1");
+
+ assertNotEquals(ns1, ns2);
+ }
+}