Skip to content

Commit

Permalink
Enable specific global extension in JUnit 5
Browse files Browse the repository at this point in the history
Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
  • Loading branch information
YongGoose committed Nov 10, 2024
1 parent be4edac commit a2ef07f
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ public CachingJupiterConfiguration(JupiterConfiguration delegate) {
this.delegate = delegate;
}

@Override
public Optional<String> getExtensionAutodetectionIncludePattern() {
return delegate.getExtensionAutodetectionIncludePattern();
}

@Override
public Optional<String> getExtensionAutodetectionExcludePattern() {
return delegate.getExtensionAutodetectionExcludePattern();
}

@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 @@ -73,6 +73,16 @@ public DefaultJupiterConfiguration(ConfigurationParameters configurationParamete
"ConfigurationParameters must not be null");
}

@Override
public Optional<String> getExtensionAutodetectionIncludePattern() {
return configurationParameters.get(EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME);
}

@Override
public 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 @@ -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;
Expand All @@ -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<String> getExtensionAutodetectionIncludePattern();

Optional<String> getExtensionAutodetectionExcludePattern();

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 @@ -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;

/**
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,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<String> 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<String> createExtensionFilterByPatterns(String include, String exclude) {
return ClassNamePatternFilterUtils.includeMatchingClassNames(include).and(
ClassNamePatternFilterUtils.excludeMatchingClassNames(exclude));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -51,7 +51,7 @@ private ClassNamePatternFilterUtils() {
* @param patterns a comma-separated list of patterns
*/
public static <T> Predicate<T> excludeMatchingClasses(String patterns) {
return excludeMatchingClasses(patterns, object -> object.getClass().getName());
return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.EXCLUDE);
}

/**
Expand All @@ -61,26 +61,58 @@ public static <T> Predicate<T> excludeMatchingClasses(String patterns) {
* @param patterns a comma-separated list of patterns
*/
public static Predicate<String> excludeMatchingClassNames(String patterns) {
return excludeMatchingClasses(patterns, Function.identity());
return matchingClasses(patterns, Function.identity(), FilterType.EXCLUDE);
}

private static <T> Predicate<T> excludeMatchingClasses(String patterns, Function<T, String> 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 <T> Predicate<T> 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<String> includeMatchingClassNames(String patterns) {
return matchingClasses(patterns, Function.identity(), FilterType.INCLUDE);
}

private enum FilterType {
INCLUDE, EXCLUDE
}

private static <T> Predicate<T> matchingClasses(String patterns, Function<T, String> 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 <T> Predicate<T> createPredicateFromPatterns(String patterns,
Function<T, String> classNameProvider) {
if (DEACTIVATE_ALL_PATTERN.equals(patterns)) {
return object -> false;
private static <T> Predicate<T> createPredicateFromPatterns(String patterns, Function<T, String> classNameProvider,
FilterType mode) {
if (ALL_PATTERN.equals(patterns)) {
return object -> mode == FilterType.INCLUDE;
}
List<Pattern> 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<Pattern> convertToRegularExpressions(String patterns) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<? extends ExecutionCondition> 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<? extends ExecutionCondition> 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<? extends TestExecutionListener> 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<? extends TestExecutionListener> 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);
}

}

0 comments on commit a2ef07f

Please sign in to comment.