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 92c6990cb7ed..500f55818923 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 @@ -91,7 +91,7 @@ public final class Constants { * @see #DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME * @see org.junit.jupiter.api.extension.ExecutionCondition */ - public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN; + public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = ClassNamePatternFilterUtils.ALL_PATTERN; /** * Property name used to set the default display name generator class name: {@value} 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 f2e24ba494dc..9f05ce558b20 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 @@ -46,6 +46,16 @@ public CachingJupiterConfiguration(JupiterConfiguration delegate) { this.delegate = delegate; } + @Override + public Optional getExtensionAutodetectionIncludePattern() { + return delegate.getExtensionAutodetectionIncludePattern(); + } + + @Override + public Optional getExtensionAutodetectionExcludePattern() { + return delegate.getExtensionAutodetectionExcludePattern(); + } + @Override public Optional getRawConfigurationParameter(String key) { return delegate.getRawConfigurationParameter(key); 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 b72b0362e638..03f90542a714 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 @@ -73,6 +73,16 @@ public DefaultJupiterConfiguration(ConfigurationParameters configurationParamete "ConfigurationParameters must not be null"); } + @Override + public Optional getExtensionAutodetectionIncludePattern() { + return configurationParameters.get(EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME); + } + + @Override + public Optional getExtensionAutodetectionExcludePattern() { + return configurationParameters.get(EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME); + } + @Override public Optional getRawConfigurationParameter(String key) { return configurationParameters.get(key); 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 b342054c5cbf..a90ece7beedb 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 @@ -36,6 +36,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; @@ -48,6 +50,10 @@ 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; + Optional getExtensionAutodetectionIncludePattern(); + + Optional getExtensionAutodetectionExcludePattern(); + Optional getRawConfigurationParameter(String key); Optional getRawConfigurationParameter(String key, Function transformer); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java index 676598c7abe0..6c715b275287 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java @@ -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; @@ -37,6 +38,7 @@ import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.commons.util.Preconditions; /** @@ -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()) { @@ -93,9 +95,25 @@ public static MutableExtensionRegistry createRegistryWithDefaultExtensions(Jupit return extensionRegistry; } - private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry) { - ServiceLoader.load(Extension.class, ClassLoaderUtils.getDefaultClassLoader())// - .forEach(extensionRegistry::registerAutoDetectedExtension); + private static void registerAutoDetectedExtensions(MutableExtensionRegistry registry, JupiterConfiguration config) { + Predicate filter = createExtensionFilterByPatterns( + config.getExtensionAutodetectionIncludePattern().orElse("*"), + config.getExtensionAutodetectionExcludePattern().orElse("")); + + ServiceLoader.load(Extension.class, ClassLoaderUtils.getDefaultClassLoader()).forEach(extension -> { + if (filter.test(extension.getClass().getName())) { + registry.registerAutoDetectedExtension(extension); + logger.trace( + () -> String.format("Auto-detected and registered extension [%s]", extension.getClass().getName())); + return; + } + logger.trace(() -> String.format("Skipping auto-detected extension [%s]", extension.getClass().getName())); + }); + } + + private static Predicate createExtensionFilterByPatterns(String include, String exclude) { + return ClassNamePatternFilterUtils.includeMatchingClassNames(include).and( + ClassNamePatternFilterUtils.excludeMatchingClassNames(exclude)); } /** diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java index 6c713d8c6cb4..13b0c9b6c2dc 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java @@ -41,7 +41,7 @@ private ClassNamePatternFilterUtils() { /* no-op */ } - public static final String DEACTIVATE_ALL_PATTERN = "*"; + public static final String ALL_PATTERN = "*"; /** * Create a {@link Predicate} that can be used to exclude (i.e., filter out) @@ -51,7 +51,7 @@ private ClassNamePatternFilterUtils() { * @param patterns a comma-separated list of patterns */ public static Predicate excludeMatchingClasses(String patterns) { - return excludeMatchingClasses(patterns, object -> object.getClass().getName()); + return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.EXCLUDE); } /** @@ -61,26 +61,58 @@ public static Predicate excludeMatchingClasses(String patterns) { * @param patterns a comma-separated list of patterns */ public static Predicate excludeMatchingClassNames(String patterns) { - return excludeMatchingClasses(patterns, Function.identity()); + return matchingClasses(patterns, Function.identity(), FilterType.EXCLUDE); } - private static Predicate excludeMatchingClasses(String patterns, Function classNameGetter) { + /** + * Create a {@link Predicate} that can be used to include (i.e., filter in) + * objects of type {@code T} whose fully qualified class names match any of + * the supplied patterns. + * + * @param patterns a comma-separated list of patterns + */ + public static Predicate includeMatchingClasses(String patterns) { + return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.INCLUDE); + } + + /** + * Create a {@link Predicate} that can be used to include (i.e., filter in) + * fully qualified class names matching any of the supplied patterns. + * + * @param patterns a comma-separated list of patterns + */ + public static Predicate includeMatchingClassNames(String patterns) { + return matchingClasses(patterns, Function.identity(), FilterType.INCLUDE); + } + + private enum FilterType { + INCLUDE, EXCLUDE + } + + private static Predicate matchingClasses(String patterns, Function classNameProvider, + FilterType type) { // @formatter:off return Optional.ofNullable(patterns) .filter(StringUtils::isNotBlank) .map(String::trim) - .map(trimmedPatterns -> createPredicateFromPatterns(trimmedPatterns, classNameGetter)) - .orElse(object -> true); + .map(trimmedPatterns -> createPredicateFromPatterns(trimmedPatterns, classNameProvider, type)) + .orElse(type == FilterType.EXCLUDE ? object -> true : object -> false); // @formatter:on } - private static Predicate createPredicateFromPatterns(String patterns, - Function classNameProvider) { - if (DEACTIVATE_ALL_PATTERN.equals(patterns)) { - return object -> false; + private static Predicate createPredicateFromPatterns(String patterns, Function classNameProvider, + FilterType mode) { + if (ALL_PATTERN.equals(patterns)) { + return object -> mode == FilterType.INCLUDE; } List patternList = convertToRegularExpressions(patterns); - return object -> patternList.stream().noneMatch(it -> it.matcher(classNameProvider.apply(object)).matches()); + return object -> { + String className = classNameProvider.apply(object); + boolean isMatchingAnyPattern = patternList.stream().anyMatch( + pattern -> pattern.matcher(className).matches()); + + return (mode == FilterType.INCLUDE) == isMatchingAnyPattern; + }; } private static List convertToRegularExpressions(String patterns) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java index 5cb30bf8e69d..3a7aa079aed1 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java @@ -146,7 +146,7 @@ public class LauncherConstants { * @see #DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME * @see org.junit.platform.launcher.TestExecutionListener */ - public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN; + public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.ALL_PATTERN; /** * Property name used to enable support for diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java index 1d68086db38b..a76f67a31026 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java @@ -169,4 +169,155 @@ void alwaysExcludedClassName(String pattern) { .isEmpty(); } + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "AExecutionConditionClass, BExecutionConditionClass" + }) + //@formatter:on + @ParameterizedTest + void neverIncludedConditions(String pattern) { + List executionConditions = List.of(new AExecutionConditionClass(), + new BExecutionConditionClass()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AExecutionConditionClass, *BExecutionConditionClass", + "*ExecutionConditionClass" + }) + //@formatter:on + @ParameterizedTest + void alwaysIncludedConditions(String pattern) { + List executionConditions = List.of(new AExecutionConditionClass(), + new BExecutionConditionClass()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .hasSize(2); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "ATestExecutionListenerClass, BTestExecutionListenerClass" + }) + //@formatter:on + @ParameterizedTest + void neverIncludedListeners(String pattern) { + List executionConditions = List.of(new ATestExecutionListenerClass(), + new BTestExecutionListenerClass()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*ATestExecutionListenerClass, *BTestExecutionListenerClass", + "*TestExecutionListenerClass" + }) + //@formatter:on + @ParameterizedTest + void alwaysIncludedListeners(String pattern) { + List executionConditions = List.of(new ATestExecutionListenerClass(), + new BTestExecutionListenerClass()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .hasSize(2); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "AVanillaEmpty, BVanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void neverIncludedClass(String pattern) { + var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AVanillaEmpty, *BVanillaEmpty", + "*VanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void alwaysIncludedClass(String pattern) { + var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .hasSize(2); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "AVanillaEmpty, BVanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void neverIncludedClassName(String pattern) { + var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", + "org.junit.platform.commons.util.classes.BVanillaEmpty"); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // + .isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AVanillaEmpty, *BVanillaEmpty", + "*VanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void alwaysIncludedClassName(String pattern) { + var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", + "org.junit.platform.commons.util.classes.BVanillaEmpty"); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // + .hasSize(2); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AVanillaEmpty, *BVanillaEmpty", + "*VanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void includeAndExcludeSame(String pattern) { + var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", + "org.junit.platform.commons.util.classes.BVanillaEmpty"); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // + .hasSize(2); + } + }