From e477e83cce272a84aa86e06c2962bddd58e4f6f4 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Oct 2024 12:10:29 +0200 Subject: [PATCH] Introduce configuration parameter for default extension context scope --- .../TestInstantiationAwareExtension.java | 22 ++++++++++- .../org/junit/jupiter/engine/Constants.java | 11 ++++++ .../config/CachingJupiterConfiguration.java | 29 +++++++++------ .../config/DefaultJupiterConfiguration.java | 10 +++++ .../engine/config/JupiterConfiguration.java | 6 ++- .../descriptor/ClassBasedTestDescriptor.java | 2 +- .../execution/ExtensionContextSupplier.java | 6 ++- .../InterceptingExecutableInvokerTests.java | 5 +-- .../extension/TestInstanceFactoryTests.java | 37 ++++++++++++++++++- 9 files changed, 106 insertions(+), 22 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java index 48c327705413..5308f65cb516 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java @@ -106,10 +106,16 @@ enum ExtensionContextScope { /** * The extension should receive an {@link ExtensionContext} scoped to - * the test class. + * in the default scope. + * + *

The default scope is determined by the configuration parameter + * {@link #DEFAULT_SCOPE_PROPERTY_NAME}. If not specified, extensions + * will receive an {@link ExtensionContext} scoped to the test class. * * @deprecated This behavior will be removed from future versions of * JUnit and {@link #TEST_METHOD} will become the default. + * + * @see #DEFAULT_SCOPE_PROPERTY_NAME */ @API(status = DEPRECATED, since = "5.12") // @Deprecated @@ -120,7 +126,19 @@ enum ExtensionContextScope { * the test method, unless the * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. */ - TEST_METHOD + TEST_METHOD; + + /** + * Property name used to set the default extension context scope: {@value} + * + *

Supported Values

+ * + *

Supported values include names of enum constants defined in this + * class, ignoring case. + * + * @see #DEFAULT + */ + public static final String DEFAULT_SCOPE_PROPERTY_NAME = "junit.jupiter.extensions.testInstantiation.extensionContextScope.default"; } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java index 1b0636f18d10..2f64dd866cd2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java @@ -33,6 +33,7 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.config.JupiterConfiguration; @@ -369,6 +370,16 @@ public final class Constants { @API(status = EXPERIMENTAL, since = "5.10") public static final String DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME = TempDir.DEFAULT_FACTORY_PROPERTY_NAME; + /** + * Property name used to set the default extension context scope for + * extensions that participate in test instantiation: {@value} + * + * @since 5.12 + * @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension + */ + @API(status = EXPERIMENTAL, since = "5.12") + public static final String DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; + private Constants() { /* no-op */ } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java index 1280c4b12a11..3082830d2146 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -58,71 +59,77 @@ public Optional getRawConfigurationParameter(String key, Function delegate.isParallelExecutionEnabled()); + __ -> delegate.isParallelExecutionEnabled()); } @Override public boolean isExtensionAutoDetectionEnabled() { return (boolean) cache.computeIfAbsent(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME, - key -> delegate.isExtensionAutoDetectionEnabled()); + __ -> delegate.isExtensionAutoDetectionEnabled()); } @Override public ExecutionMode getDefaultExecutionMode() { return (ExecutionMode) cache.computeIfAbsent(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, - key -> delegate.getDefaultExecutionMode()); + __ -> delegate.getDefaultExecutionMode()); } @Override public ExecutionMode getDefaultClassesExecutionMode() { return (ExecutionMode) cache.computeIfAbsent(DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, - key -> delegate.getDefaultClassesExecutionMode()); + __ -> delegate.getDefaultClassesExecutionMode()); } @Override public TestInstance.Lifecycle getDefaultTestInstanceLifecycle() { return (TestInstance.Lifecycle) cache.computeIfAbsent(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, - key -> delegate.getDefaultTestInstanceLifecycle()); + __ -> delegate.getDefaultTestInstanceLifecycle()); } @SuppressWarnings("unchecked") @Override public Predicate getExecutionConditionFilter() { return (Predicate) cache.computeIfAbsent(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, - key -> delegate.getExecutionConditionFilter()); + __ -> delegate.getExecutionConditionFilter()); } @Override public DisplayNameGenerator getDefaultDisplayNameGenerator() { return (DisplayNameGenerator) cache.computeIfAbsent(DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME, - key -> delegate.getDefaultDisplayNameGenerator()); + __ -> delegate.getDefaultDisplayNameGenerator()); } @SuppressWarnings("unchecked") @Override public Optional getDefaultTestMethodOrderer() { return (Optional) cache.computeIfAbsent(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, - key -> delegate.getDefaultTestMethodOrderer()); + __ -> delegate.getDefaultTestMethodOrderer()); } @SuppressWarnings("unchecked") @Override public Optional getDefaultTestClassOrderer() { return (Optional) cache.computeIfAbsent(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, - key -> delegate.getDefaultTestClassOrderer()); + __ -> delegate.getDefaultTestClassOrderer()); } @Override public CleanupMode getDefaultTempDirCleanupMode() { return (CleanupMode) cache.computeIfAbsent(DEFAULT_CLEANUP_MODE_PROPERTY_NAME, - key -> delegate.getDefaultTempDirCleanupMode()); + __ -> delegate.getDefaultTempDirCleanupMode()); } @SuppressWarnings("unchecked") @Override public Supplier getDefaultTempDirFactorySupplier() { return (Supplier) cache.computeIfAbsent(DEFAULT_FACTORY_PROPERTY_NAME, - key -> delegate.getDefaultTempDirFactorySupplier()); + __ -> delegate.getDefaultTempDirFactorySupplier()); } + @Override + public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() { + return (ExtensionContextScope) cache.computeIfAbsent( + DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, + __ -> delegate.getDefaultTestInstantiationExtensionContextScope()); + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 83ef7592534f..1057ab563b1f 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -62,6 +63,9 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration { private static final InstantiatingConfigurationParameterConverter tempDirFactoryConverter = // new InstantiatingConfigurationParameterConverter<>(TempDirFactory.class, "temp dir factory"); + private static final EnumConfigurationParameterConverter extensionContextScopeConverter = // + new EnumConfigurationParameterConverter<>(ExtensionContextScope.class, "extension context scope"); + private final ConfigurationParameters configurationParameters; public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) { @@ -141,4 +145,10 @@ public Supplier getDefaultTempDirFactorySupplier() { return () -> supplier.get().orElse(TempDirFactory.Standard.INSTANCE); } + @SuppressWarnings("deprecation") + @Override + public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() { + return extensionContextScopeConverter.get(configurationParameters, + DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, ExtensionContextScope.DEFAULT); + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java index 7a90072363d2..c695e7e4b10b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.Execution; @@ -42,7 +43,8 @@ public interface JupiterConfiguration { String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME; - String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME; + String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME;; + String DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; Optional getRawConfigurationParameter(String key); @@ -70,4 +72,6 @@ public interface JupiterConfiguration { Supplier getDefaultTempDirFactorySupplier(); + ExtensionContextScope getDefaultTestInstantiationExtensionContextScope(); + } 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 69ad2ee520d0..ee53adcb2500 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 @@ -288,7 +288,7 @@ private TestInstances instantiateAndPostProcessTestInstance(JupiterEngineExecuti JupiterEngineExecutionContext context) { ExtensionContextSupplier extensionContext = ExtensionContextSupplier.create(context.getExtensionContext(), - ourExtensionContext); + ourExtensionContext, configuration); TestInstances instances = instantiateTestClass(parentExecutionContext, extensionContext, registry, context); context.getThrowableCollector().execute(() -> { invokeTestInstancePostProcessors(instances.getInnermostInstance(), registry, extensionContext); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionContextSupplier.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionContextSupplier.java index 9b911c86a626..84c172765b93 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionContextSupplier.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionContextSupplier.java @@ -16,6 +16,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension; +import org.junit.jupiter.engine.config.JupiterConfiguration; /** * Container of two instances of {@link ExtensionContext} to simplify the legacy for @@ -29,8 +30,9 @@ public interface ExtensionContextSupplier { static ExtensionContextSupplier create(ExtensionContext currentExtensionContext, - ExtensionContext legacyExtensionContext) { - if (currentExtensionContext == legacyExtensionContext) { + ExtensionContext legacyExtensionContext, JupiterConfiguration configuration) { + if (currentExtensionContext == legacyExtensionContext + || configuration.getDefaultTestInstantiationExtensionContextScope() == TEST_METHOD) { return __ -> currentExtensionContext; } return new ScopeBasedExtensionContextSupplier(currentExtensionContext, legacyExtensionContext); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java index aea972a34978..6de1cd4935eb 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java @@ -31,9 +31,8 @@ void invokeMethod() { @Override T invokeConstructor(Constructor constructor, Object outerInstance) { - ExtensionContextSupplier context = ExtensionContextSupplier.create(extensionContext, extensionContext); - return newInvoker().invoke(constructor, Optional.ofNullable(outerInstance), context, extensionRegistry, - passthroughInterceptor()); + return newInvoker().invoke(constructor, Optional.ofNullable(outerInstance), __ -> extensionContext, + extensionRegistry, passthroughInterceptor()); } private InterceptingExecutableInvoker newInvoker() { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java index 15a280b5683c..1549260f0007 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java @@ -15,6 +15,8 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; @@ -44,6 +46,7 @@ import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.jupiter.api.extension.TestInstantiationException; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.Constants; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.testkit.engine.EngineExecutionResults; @@ -429,6 +432,36 @@ void instanceFactoryWithLegacyContext() { // @formatter:on } + @Test + void instanceFactoryWithLegacyContextAndChangedDefaultScope() { + var executionResults = executeTests(request() // + .selectors(selectClass(LegacyContextTestCase.class)) // + .configurationParameter( + Constants.DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, + TEST_METHOD.name())); + + assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "outerTest", + "close LegacyContextTestCase", + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "LegacyInstanceFactory instantiated: InnerTestCase", + "innerTest1", + "close InnerTestCase", + "close LegacyContextTestCase", + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "LegacyInstanceFactory instantiated: InnerTestCase", + "innerTest2", + "close InnerTestCase", + "close LegacyContextTestCase" + ); + // @formatter:on + } + // ------------------------------------------------------------------------- @SuppressWarnings("JUnitMalformedDeclaration") @@ -797,8 +830,8 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte } } - private static boolean instantiated(Class factoryClass, Class testClass) { - return callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName()); + private static void instantiated(Class factoryClass, Class testClass) { + callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName()); } }