Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cfb6845
Enable specific global extension in JUnit 5
YongGoose Nov 12, 2024
07a6ae3
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/ex…
YongGoose Nov 14, 2024
4a896f1
Add Java 9+ variant for optimized ServiceLoader filtering
YongGoose Nov 14, 2024
7c58f66
Merge branch 'main' into feature/enable-specific-global-extensions-in…
YongGoose Nov 16, 2024
09dca93
Remove final keyword
YongGoose Nov 18, 2024
355e113
Merge branch 'main' into feature/enable-specific-global-extensions-in…
YongGoose Nov 18, 2024
2568ffe
Avoid instantiating deactivated TestExecutionListeners
marcphilipp Nov 19, 2024
d5a3511
Pass ServiceLoader to Utils to avoid failing its caller check
marcphilipp Nov 19, 2024
a599008
Merge pull request #1 from junit-team/marc/avoid-deactivated-test-exe…
YongGoose Nov 19, 2024
8b2f41e
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/ex…
YongGoose Nov 19, 2024
eb32053
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/ex…
YongGoose Nov 19, 2024
1597992
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/ex…
YongGoose Nov 19, 2024
eb1b5b3
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/ex…
YongGoose Nov 19, 2024
f86c1d6
Add constants and javadoc for supported syntax
YongGoose Nov 19, 2024
02c91b5
move code to JupiterConfiguration
YongGoose Nov 19, 2024
3c95ac9
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/co…
YongGoose Nov 27, 2024
624760b
Rename method
YongGoose Nov 27, 2024
6c5d6c3
Add javadoc
YongGoose Nov 27, 2024
13645f0
Update user-guide
YongGoose Nov 27, 2024
9daa88a
Merge branch 'main' into feature/enable-specific-global-extensions-in…
YongGoose Nov 29, 2024
f870747
Log excluded classes once
YongGoose Nov 30, 2024
671e387
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/ex…
YongGoose Dec 5, 2024
87ac022
Merge branch 'main' into feature/enable-specific-global-extensions-in…
YongGoose Dec 5, 2024
a190de6
Apply spotless
YongGoose Dec 5, 2024
13c1b19
Update user-guide
YongGoose Dec 5, 2024
6e5b161
Document new configuration parameters
marcphilipp Dec 6, 2024
ec13b6d
Add integration test
YongGoose Dec 14, 2024
ff30825
Merge remote-tracking branch 'origin/feature/enable-specific-global-e…
YongGoose Dec 14, 2024
4b3c670
Avoid 2nd loop and loading all extensions
marcphilipp Dec 16, 2024
6279713
Polish tests
marcphilipp Dec 16, 2024
93f0e2f
Improve readability
marcphilipp Dec 16, 2024
f92b14d
Return "constant" predicates for default case
marcphilipp Dec 16, 2024
fbefdc9
Remove orphaned anchor
marcphilipp Dec 16, 2024
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 @@ -49,6 +49,76 @@
@API(status = STABLE, since = "5.0")
public final class Constants {

/**
* Property name used to include patterns for auto-detecting extensions: {@value}
*
* <h4>Pattern Matching Syntax</h4>
*
* <p>If the property value consists solely of an asterisk ({@code *}), all
* extensions will be included. Otherwise, the property value will be treated
* as a comma-separated list of patterns where each individual pattern will be
* matched against the fully qualified class name (<em>FQCN</em>) of each extension.
* Any dot ({@code .}) in a pattern will match against a dot ({@code .})
* or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match
* against one or more characters in a FQCN. All other characters in a pattern
* will be matched one-to-one against a FQCN.
*
* <h4>Examples</h4>
*
* <ul>
* <li>{@code *}: includes all extensions.
* <li>{@code org.junit.*}: includes every extension under the {@code org.junit}
* base package and any of its subpackages.
* <li>{@code *.MyExtension}: includes every extension whose simple class name is
* exactly {@code MyExtension}.
* <li>{@code *System*}: includes every extension whose FQCN contains
* {@code System}.
* <li>{@code *System*, *Dev*}: includes every extension whose FQCN contains
* {@code System} or {@code Dev}.
* <li>{@code org.example.MyExtension, org.example.TheirExtension}: includes
* extensions whose FQCN is exactly {@code org.example.MyExtension} or
* {@code org.example.TheirExtension}.
* </ul>
*
* @see JupiterConfiguration#EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME
*/
public static final String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME;

/**
* Property name used to exclude patterns for auto-detecting extensions: {@value}
*
* <h4>Pattern Matching Syntax</h4>
*
* <p>If the property value consists solely of an asterisk ({@code *}), all
* extensions will be excluded. Otherwise, the property value will be treated
* as a comma-separated list of patterns where each individual pattern will be
* matched against the fully qualified class name (<em>FQCN</em>) of each extension.
* Any dot ({@code .}) in a pattern will match against a dot ({@code .})
* or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match
* against one or more characters in a FQCN. All other characters in a pattern
* will be matched one-to-one against a FQCN.
*
* <h4>Examples</h4>
*
* <ul>
* <li>{@code *}: excludes all extensions.
* <li>{@code org.junit.*}: excludes every extension under the {@code org.junit}
* base package and any of its subpackages.
* <li>{@code *.MyExtension}: excludes every extension whose simple class name is
* exactly {@code MyExtension}.
* <li>{@code *System*}: excludes every extension whose FQCN contains
* {@code System}.
* <li>{@code *System*, *Dev*}: excludes every extension whose FQCN contains
* {@code System} or {@code Dev}.
* <li>{@code org.example.MyExtension, org.example.TheirExtension}: excludes
* extensions whose FQCN is exactly {@code org.example.MyExtension} or
* {@code org.example.TheirExtension}.
* </ul>
*
* @see JupiterConfiguration#EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME
*/
public static final String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME;

/**
* Property name used to provide patterns for deactivating conditions: {@value}
*
Expand Down Expand Up @@ -93,6 +163,15 @@ public final class Constants {
*/
public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = ClassNamePatternFilterUtils.ALL_PATTERN;

/**
* A blank pattern used for class name filtering: {@value}
*
* <p>This constant is used to represent an empty or blank pattern in class name filtering operations.
*
* @see ClassNamePatternFilterUtils#BLANK
*/
public static final String BLANK = ClassNamePatternFilterUtils.BLANK;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to me to introduce a constant for an empty string here. Let's keep it internal and remove it from here, ok?

/**
* Property name used to set the default display name generator class name: {@value}
*
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.Extension;
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDirFactory;
Expand All @@ -46,6 +47,11 @@ public CachingJupiterConfiguration(JupiterConfiguration delegate) {
this.delegate = delegate;
}

@Override
public Predicate<Class<? extends Extension>> createExtensionFilterByPatterns() {
return delegate.createExtensionFilterByPatterns();
}

@Override
public Optional<String> getRawConfigurationParameter(String key) {
return delegate.getRawConfigurationParameter(key);
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.Lifecycle;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDirFactory;
Expand Down Expand Up @@ -73,6 +74,23 @@ public DefaultJupiterConfiguration(ConfigurationParameters configurationParamete
"ConfigurationParameters must not be null");
}

@Override
public Predicate<Class<? extends Extension>> createExtensionFilterByPatterns() {
Predicate<String> predicate = ClassNamePatternFilterUtils.includeMatchingClassNames(
getExtensionAutodetectionIncludePattern().orElse(ClassNamePatternFilterUtils.ALL_PATTERN)) //
.and(ClassNamePatternFilterUtils.excludeMatchingClassNames(
getExtensionAutodetectionExcludePattern().orElse(ClassNamePatternFilterUtils.BLANK)));
return clazz -> predicate.test(clazz.getName());
}

private Optional<String> getExtensionAutodetectionIncludePattern() {
return configurationParameters.get(EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME);
}

private Optional<String> getExtensionAutodetectionExcludePattern() {
return configurationParameters.get(EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME);
}

@Override
public Optional<String> getRawConfigurationParameter(String key) {
return configurationParameters.get(key);
Expand Down
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.Extension;
import org.junit.jupiter.api.extension.PreInterruptCallback;
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
import org.junit.jupiter.api.io.CleanupMode;
Expand All @@ -36,6 +37,8 @@
@API(status = INTERNAL, since = "5.4")
public interface JupiterConfiguration {

String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.include";
String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude";
String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate";
String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled";
String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME;
Expand All @@ -48,6 +51,8 @@ public interface JupiterConfiguration {
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;

Predicate<Class<? extends Extension>> createExtensionFilterByPatterns();

Optional<String> getRawConfigurationParameter(String key);

<T> Optional<T> getRawConfigurationParameter(String key, Function<String, T> transformer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.apiguardian.api.API;
Expand All @@ -38,6 +39,7 @@
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.commons.util.ClassLoaderUtils;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ServiceLoaderUtils;

/**
* Default, mutable implementation of {@link ExtensionRegistry}.
Expand Down Expand Up @@ -83,7 +85,7 @@ public static MutableExtensionRegistry createRegistryWithDefaultExtensions(Jupit
extensionRegistry.registerDefaultExtension(new TempDirectory(configuration));

if (configuration.isExtensionAutoDetectionEnabled()) {
registerAutoDetectedExtensions(extensionRegistry);
registerAutoDetectedExtensions(extensionRegistry, configuration);
}

if (configuration.isThreadDumpOnTimeoutEnabled()) {
Expand All @@ -93,8 +95,13 @@ public static MutableExtensionRegistry createRegistryWithDefaultExtensions(Jupit
return extensionRegistry;
}

private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry) {
ServiceLoader.load(Extension.class, ClassLoaderUtils.getDefaultClassLoader())//
private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry,
JupiterConfiguration configuration) {
Predicate<Class<? extends Extension>> filter = configuration.createExtensionFilterByPatterns();

ServiceLoader<Extension> serviceLoader = ServiceLoader.load(Extension.class,
ClassLoaderUtils.getDefaultClassLoader());
ServiceLoaderUtils.filter(serviceLoader, filter) //
.forEach(extensionRegistry::registerAutoDetectedExtension);
}

Expand Down
1 change: 1 addition & 0 deletions junit-platform-commons/junit-platform-commons.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ tasks.jar {
tasks.codeCoverageClassesJar {
exclude("org/junit/platform/commons/util/ModuleUtils.class")
exclude("org/junit/platform/commons/util/PackageNameUtils.class")
exclude("org/junit/platform/commons/util/ServiceLoaderUtils.class")
}

eclipse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ private ClassNamePatternFilterUtils() {

public static final String ALL_PATTERN = "*";

public static final String BLANK = "";

/**
* Create a {@link Predicate} that can be used to exclude (i.e., filter out)
* objects of type {@code T} whose fully qualified class names match any of
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2015-2024 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.commons.util;

import java.util.ServiceLoader;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apiguardian.api.API;

/**
* Collection of utilities for working with {@link ServiceLoader}.
*
* <h2>DISCLAIMER</h2>
*
* <p>These utilities are intended solely for usage within the JUnit framework
* itself. <strong>Any usage by external parties is not supported.</strong>
* Use at your own risk!
*
* @since 5.11
*/
@API(status = API.Status.INTERNAL, since = "5.11")
public class ServiceLoaderUtils {

private ServiceLoaderUtils() {
/* no-op */
}

/**
* Filters the supplied service loader using the supplied predicate.
*
* @param <T> the type of the service
* @param serviceLoader the service loader to be filtered
* @param providerPredicate the predicate to filter the loaded services
* @return a stream of loaded services that match the predicate
*/
public static <T> Stream<T> filter(ServiceLoader<T> serviceLoader,
Predicate<? super Class<? extends T>> providerPredicate) {
return StreamSupport.stream(serviceLoader.spliterator(), false).filter(it -> {
@SuppressWarnings("unchecked")
Class<? extends T> type = (Class<? extends T>) it.getClass();
return providerPredicate.test(type);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2015-2024 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.commons.util;

import java.util.ServiceLoader;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;

/**
* Collection of utilities for working with {@link ServiceLoader}.
*
* <h2>DISCLAIMER</h2>
*
* <p>These utilities are intended solely for usage within the JUnit framework
* itself. <strong>Any usage by external parties is not supported.</strong>
* Use at your own risk!
*
* @since 5.11
*/
@API(status = Status.INTERNAL, since = "5.11")
public class ServiceLoaderUtils {

private ServiceLoaderUtils() {
/* no-op */
}

/**
* Filters the supplied service loader using the supplied predicate.
*
* @param <T> the type of the service
* @param serviceLoader the service loader to be filtered
* @param providerPredicate the predicate to filter the loaded services
* @return a stream of loaded services that match the predicate
*/
public static <T> Stream<T> filter(ServiceLoader<T> serviceLoader,
Predicate<? super Class<? extends T>> providerPredicate) {
// @formatter:off
return serviceLoader
.stream()
.filter(provider -> providerPredicate.test(provider.type()))
.map(ServiceLoader.Provider::get);
// @formatter:on
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.function.Predicate;

import org.apiguardian.api.API;
import org.junit.platform.commons.PreconditionViolationException;
Expand Down Expand Up @@ -198,15 +197,12 @@ private static void registerTestExecutionListeners(LauncherConfig config, Launch
config.getAdditionalTestExecutionListeners().forEach(launcher::registerTestExecutionListeners);
}

private static Stream<TestExecutionListener> loadAndFilterTestExecutionListeners(
private static Iterable<TestExecutionListener> loadAndFilterTestExecutionListeners(
ConfigurationParameters configurationParameters) {
Iterable<TestExecutionListener> listeners = ServiceLoaderRegistry.load(TestExecutionListener.class);
String deactivatedListenersPattern = configurationParameters.get(
DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME).orElse(null);
// @formatter:off
return StreamSupport.stream(listeners.spliterator(), false)
.filter(ClassNamePatternFilterUtils.excludeMatchingClasses(deactivatedListenersPattern));
// @formatter:on
Predicate<String> classNameFilter = configurationParameters.get(DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME) //
.map(ClassNamePatternFilterUtils::excludeMatchingClassNames) //
.orElse(__ -> true);
return ServiceLoaderRegistry.load(TestExecutionListener.class, classNameFilter);
}

}
Loading