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); + } +}