Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce configuration parameter for default extension context scope #4064

Merged
merged 2 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,16 @@ enum ExtensionContextScope {

/**
* The extension should receive an {@link ExtensionContext} scoped to
* the test class.
* in the <em>default</em> scope.
*
* <p>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
Expand All @@ -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}
*
* <h4>Supported Values</h4>
*
* <p>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";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,71 +59,77 @@ public <T> Optional<T> getRawConfigurationParameter(String key, Function<String,
@Override
public boolean isParallelExecutionEnabled() {
return (boolean) cache.computeIfAbsent(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME,
key -> 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<ExecutionCondition> getExecutionConditionFilter() {
return (Predicate<ExecutionCondition>) 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<MethodOrderer> getDefaultTestMethodOrderer() {
return (Optional<MethodOrderer>) cache.computeIfAbsent(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME,
key -> delegate.getDefaultTestMethodOrderer());
__ -> delegate.getDefaultTestMethodOrderer());
}

@SuppressWarnings("unchecked")
@Override
public Optional<ClassOrderer> getDefaultTestClassOrderer() {
return (Optional<ClassOrderer>) 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<TempDirFactory> getDefaultTempDirFactorySupplier() {
return (Supplier<TempDirFactory>) 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,6 +63,9 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration {
private static final InstantiatingConfigurationParameterConverter<TempDirFactory> tempDirFactoryConverter = //
new InstantiatingConfigurationParameterConverter<>(TempDirFactory.class, "temp dir factory");

private static final EnumConfigurationParameterConverter<ExtensionContextScope> extensionContextScopeConverter = //
new EnumConfigurationParameterConverter<>(ExtensionContextScope.class, "extension context scope");

private final ConfigurationParameters configurationParameters;

public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) {
Expand Down Expand Up @@ -141,4 +145,10 @@ public Supplier<TempDirFactory> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> getRawConfigurationParameter(String key);

Expand Down Expand Up @@ -70,4 +72,6 @@ public interface JupiterConfiguration {

Supplier<TempDirFactory> getDefaultTempDirFactorySupplier();

ExtensionContextScope getDefaultTestInstantiationExtensionContextScope();

}
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ private TestInstances instantiateAndPostProcessTestInstance(JupiterEngineExecuti
ClassExtensionContext ourExtensionContext, ExtensionRegistry registry,
JupiterEngineExecutionContext context) {

ExtensionContextSupplier extensionContext = new ExtensionContextSupplier(context.getExtensionContext(),
ourExtensionContext);
ExtensionContextSupplier extensionContext = ExtensionContextSupplier.create(context.getExtensionContext(),
ourExtensionContext, configuration);
TestInstances instances = instantiateTestClass(parentExecutionContext, extensionContext, registry, context);
context.getThrowableCollector().execute(() -> {
invokeTestInstancePostProcessors(instances.getInnermostInstance(), registry, extensionContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,28 +25,39 @@
* @since 5.12
* @see TestInstantiationAwareExtension
*/
@FunctionalInterface
@API(status = INTERNAL, since = "5.12")
public final class ExtensionContextSupplier {
public interface ExtensionContextSupplier {

private final ExtensionContext currentExtensionContext;
private final ExtensionContext legacyExtensionContext;

public ExtensionContextSupplier(ExtensionContext currentExtensionContext, ExtensionContext legacyExtensionContext) {
this.currentExtensionContext = currentExtensionContext;
this.legacyExtensionContext = legacyExtensionContext;
static ExtensionContextSupplier create(ExtensionContext currentExtensionContext,
ExtensionContext legacyExtensionContext, JupiterConfiguration configuration) {
if (currentExtensionContext == legacyExtensionContext
|| configuration.getDefaultTestInstantiationExtensionContextScope() == TEST_METHOD) {
return __ -> currentExtensionContext;
}
return new ScopeBasedExtensionContextSupplier(currentExtensionContext, legacyExtensionContext);
}

public ExtensionContext get(TestInstantiationAwareExtension extension) {
if (currentExtensionContext == legacyExtensionContext || isTestScoped(extension)) {
return currentExtensionContext;
ExtensionContext get(TestInstantiationAwareExtension extension);

class ScopeBasedExtensionContextSupplier implements ExtensionContextSupplier {

private final ExtensionContext currentExtensionContext;
private final ExtensionContext legacyExtensionContext;

private ScopeBasedExtensionContextSupplier(ExtensionContext currentExtensionContext,
ExtensionContext legacyExtensionContext) {
this.currentExtensionContext = currentExtensionContext;
this.legacyExtensionContext = legacyExtensionContext;
}
else {
return legacyExtensionContext;

public ExtensionContext get(TestInstantiationAwareExtension extension) {
return isTestScoped(extension) ? currentExtensionContext : legacyExtensionContext;
}
}

private boolean isTestScoped(TestInstantiationAwareExtension extension) {
ExtensionContext rootContext = currentExtensionContext.getRoot();
return extension.getTestInstantiationExtensionContextScope(rootContext) == TEST_METHOD;
private boolean isTestScoped(TestInstantiationAwareExtension extension) {
ExtensionContext rootContext = legacyExtensionContext.getRoot();
return extension.getTestInstantiationExtensionContextScope(rootContext) == TEST_METHOD;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ public static Object[] resolveParameters(Method method, Optional<Object> target,
*/
public static Object[] resolveParameters(Executable executable, Optional<Object> target,
Optional<Object> outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) {
ExtensionContextSupplier context = new ExtensionContextSupplier(extensionContext, extensionContext);
return resolveParameters(executable, target, outerInstance, context, extensionRegistry);
return resolveParameters(executable, target, outerInstance, __ -> extensionContext, extensionRegistry);
}

public static Object[] resolveParameters(Executable executable, Optional<Object> target,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ void invokeMethod() {

@Override
<T> T invokeConstructor(Constructor<T> constructor, Object outerInstance) {
ExtensionContextSupplier context = new ExtensionContextSupplier(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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -797,8 +830,8 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte
}
}

private static boolean instantiated(Class<? extends TestInstanceFactory> factoryClass, Class<?> testClass) {
return callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName());
private static void instantiated(Class<? extends TestInstanceFactory> factoryClass, Class<?> testClass) {
callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName());
}

}