From d909371992c34cdf94ff7d7ed467bcb8c5339732 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 --- .../org/junit/jupiter/engine/Constants.java | 11 ++++++ .../config/CachingJupiterConfiguration.java | 29 +++++++++------ .../config/DefaultJupiterConfiguration.java | 11 ++++++ .../engine/config/JupiterConfiguration.java | 7 +++- .../descriptor/ClassBasedTestDescriptor.java | 2 +- .../execution/ExtensionContextSupplier.java | 8 +++- .../InterceptingExecutableInvokerTests.java | 5 +-- .../extension/TestInstanceFactoryTests.java | 37 ++++++++++++++++++- 8 files changed, 90 insertions(+), 20 deletions(-) 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..29b10fb8e078 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 @@ -36,6 +36,7 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy; @@ -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 class instance construction: {@value} + * + * @since 5.12 + * @see org.junit.jupiter.api.extension.TestClassInstanceConstructionParticipatingExtension + */ + @API(status = EXPERIMENTAL, since = "5.12") + public static final String DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextSupplier.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..b5503ac8db29 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.TestClassInstanceConstructionParticipatingExtension.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 getDefaultTestClassInstanceConstructionExtensionContextScope() { + return (ExtensionContextScope) cache.computeIfAbsent( + DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, + __ -> delegate.getDefaultTestClassInstanceConstructionExtensionContextScope()); + } } 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..ae1ef6778a9c 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.TestClassInstanceConstructionParticipatingExtension.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,11 @@ public Supplier getDefaultTempDirFactorySupplier() { return () -> supplier.get().orElse(TempDirFactory.Standard.INSTANCE); } + @SuppressWarnings("deprecation") + @Override + public ExtensionContextScope getDefaultTestClassInstanceConstructionExtensionContextScope() { + return extensionContextScopeConverter.get(configurationParameters, + DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_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..978d038201d5 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,10 +23,12 @@ 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.TestClassInstanceConstructionParticipatingExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.execution.ExtensionContextSupplier; /** * @since 5.4 @@ -42,7 +44,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_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextSupplier.DEFAULT_SCOPE_PROPERTY_NAME; Optional getRawConfigurationParameter(String key); @@ -70,4 +73,6 @@ public interface JupiterConfiguration { Supplier getDefaultTempDirFactorySupplier(); + ExtensionContextScope getDefaultTestClassInstanceConstructionExtensionContextScope(); + } 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 28c63d7a7815..ca653e46057d 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.TestClassInstanceConstructionParticipatingExtension; +import org.junit.jupiter.engine.config.JupiterConfiguration; /** * Container of two instances of {@link ExtensionContext} to simplify the legacy for @@ -28,9 +29,12 @@ @API(status = INTERNAL, since = "5.12") public interface ExtensionContextSupplier { + String DEFAULT_SCOPE_PROPERTY_NAME = "junit.jupiter.extensions.testClassInstanceConstruction.extensionContextScope.default"; + static ExtensionContextSupplier create(ExtensionContext currentExtensionContext, - ExtensionContext legacyExtensionContext) { - if (currentExtensionContext == legacyExtensionContext) { + ExtensionContext legacyExtensionContext, JupiterConfiguration configuration) { + if (currentExtensionContext == legacyExtensionContext + || configuration.getDefaultTestClassInstanceConstructionExtensionContextScope() == 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 1306ab73a351..b3b0ec64ceea 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.TestClassInstanceConstructionParticipatingExtension.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 + } + // ------------------------------------------------------------------------- @ExtendWith({ FooInstanceFactory.class, BarInstanceFactory.class }) @@ -784,8 +817,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()); } }