diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc index 90112bdc7455..4b3b26d38b76 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -55,6 +55,8 @@ JUnit repository on GitHub. * Output written to `System.out` and `System.err` from non-test threads is now attributed to the most recent test or container that was started or has written output. * Introduced contracts for Kotlin-specific assertion methods. +* New public interface `ClasspathScanner` allowing third parties to provide a custom + implementation for scanning the classpath for classes and resources. [[release-notes-5.12.0-M1-junit-jupiter]] diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java index 5990d37bb869..8f031faf22e0 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java @@ -1,5 +1,5 @@ /** - * Maintained functional interfaces and support classes. + * Functional interfaces and support classes. */ package org.junit.platform.commons.function; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathResource.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java similarity index 63% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathResource.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java index af1944cc20e4..31e1a9eb9972 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathResource.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java @@ -8,22 +8,33 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support; + +import static org.apiguardian.api.API.Status.INTERNAL; import java.net.URI; import java.util.Objects; -import org.junit.platform.commons.support.Resource; +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; /** + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * * @since 1.11 */ -class ClasspathResource implements Resource { +@API(status = INTERNAL, since = "1.12") +public class DefaultResource implements Resource { private final String name; private final URI uri; - ClasspathResource(String name, URI uri) { + public DefaultResource(String name, URI uri) { this.name = Preconditions.notNull(name, "name must not be null"); this.uri = Preconditions.notNull(uri, "uri must not be null"); } @@ -44,7 +55,7 @@ public boolean equals(Object o) { return true; if (o == null || getClass() != o.getClass()) return false; - ClasspathResource that = (ClasspathResource) o; + DefaultResource that = (DefaultResource) o; return name.equals(that.name) && uri.equals(that.uri); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java index e51977179941..18807d6a4e90 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java @@ -1,5 +1,5 @@ /** - * Maintained conversion APIs provided by the JUnit Platform. + * Conversion APIs provided by the JUnit Platform. */ package org.junit.platform.commons.support.conversion; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java index 6e9c460116f4..fae0c2a81547 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java @@ -1,5 +1,5 @@ /** - * Maintained common support APIs provided by the JUnit Platform. + * Common support APIs provided by the JUnit Platform. * *

The purpose of this package is to provide {@code TestEngine} and * {@code Extension} authors convenient access to a subset of internal utility diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClassFilter.java similarity index 59% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClassFilter.java index 8b0728213055..4e222c89f683 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClassFilter.java @@ -8,30 +8,28 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; -import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.function.Predicate; import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; /** * Class-related predicate used by reflection utilities. * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * * @since 1.1 */ -@API(status = INTERNAL, since = "1.1") -public class ClassFilter implements Predicate> { +@API(status = EXPERIMENTAL, since = "1.12") +public class ClassFilter { /** * Create a {@link ClassFilter} instance that accepts all names but filters classes. + * + * @param classPredicate the class type predicate; never {@code null} + * @return an instance of {@code ClassFilter}; never {@code null} */ public static ClassFilter of(Predicate> classPredicate) { return of(name -> true, classPredicate); @@ -39,6 +37,10 @@ public static ClassFilter of(Predicate> classPredicate) { /** * Create a {@link ClassFilter} instance that filters by names and classes. + * + * @param namePredicate the class name predicate; never {@code null} + * @param classPredicate the class type predicate; never {@code null} + * @return an instance of {@code ClassFilter}; never {@code null} */ public static ClassFilter of(Predicate namePredicate, Predicate> classPredicate) { return new ClassFilter(namePredicate, classPredicate); @@ -53,26 +55,25 @@ private ClassFilter(Predicate namePredicate, Predicate> classPr } /** - * Test name using the stored name predicate. + * Test the given name using the stored name predicate. + * + * @param name the name to test; never {@code null} + * @return {@code true} if the input name matches the predicate, otherwise + * {@code false} */ public boolean match(String name) { return namePredicate.test(name); } /** - * Test class using the stored class predicate. + * Test the given class using the stored class predicate. + * + * @param type the type to test; never {@code null} + * @return {@code true} if the input type matches the predicate, otherwise + * {@code false} */ public boolean match(Class type) { return classPredicate.test(type); } - /** - * @implNote This implementation combines all tests stored in the predicates - * of this instance. Any new predicate must be added to this test method as - * well. - */ - @Override - public boolean test(Class type) { - return match(type.getName()) && match(type); - } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFileVisitor.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java similarity index 97% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFileVisitor.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java index 7f2ad8195085..e29a6c11cac2 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFileVisitor.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static java.nio.file.FileVisitResult.CONTINUE; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFilters.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java similarity index 95% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFilters.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java index 7ad6cd3b0682..ea461d7d6abe 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFilters.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import java.nio.file.Path; import java.util.function.Predicate; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathScanner.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathScanner.java new file mode 100644 index 000000000000..ed1daabf2c69 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathScanner.java @@ -0,0 +1,96 @@ +/* + * 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.support.scanning; + +import static org.apiguardian.api.API.Status; + +import java.net.URI; +import java.util.List; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.support.Resource; + +/** + * {@code ClasspathScanner} allows to scan the classpath for classes and + * resources. + * + *

An implementation of this interface can be registered via the + * {@link java.util.ServiceLoader ServiceLoader} mechanism. + * + * @since 1.12 + */ +@API(status = Status.EXPERIMENTAL, since = "1.12") +public interface ClasspathScanner { + + /** + * Find all {@linkplain Class classes} in the supplied classpath {@code root} + * that match the specified {@code classFilter} filter. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param basePackageName the name of the base package in which to start + * scanning; must not be {@code null} and must be valid in terms of Java + * syntax + * @param classFilter the class type filter; never {@code null} + * @return a list of all such classes found; never {@code null} + * but potentially empty + */ + List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter); + + /** + * Find all {@linkplain Class classes} in the supplied classpath {@code root} + * that match the specified {@code classFilter} filter. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param root the URI for the classpath root in which to scan; never + * {@code null} + * @param classFilter the class type filter; never {@code null} + * @return a list of all such classes found; never {@code null} + * but potentially empty + */ + List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter); + + /** + * Find all {@linkplain Resource resources} in the supplied classpath {@code root} + * that match the specified {@code resourceFilter} predicate. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param basePackageName the name of the base package in which to start + * scanning; must not be {@code null} and must be valid in terms of Java + * syntax + * @param resourceFilter the resource type filter; never {@code null} + * @return a list of all such resources found; never {@code null} + * but potentially empty + */ + List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter); + + /** + * Find all {@linkplain Resource resources} in the supplied classpath {@code root} + * that match the specified {@code resourceFilter} predicate. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param root the URI for the classpath root in which to scan; never + * {@code null} + * @param resourceFilter the resource type filter; never {@code null} + * @return a list of all such resources found; never {@code null} + * but potentially empty + */ + List scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter); + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java similarity index 98% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java index c1da5bacd819..d299a3a7a495 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static java.util.Collections.emptyMap; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java similarity index 89% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java index 19bec125b93c..0d0c04eeee0a 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java @@ -8,13 +8,14 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; -import static org.junit.platform.commons.util.ClasspathFilters.CLASS_FILE_SUFFIX; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.support.scanning.ClasspathFilters.CLASS_FILE_SUFFIX; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import java.io.IOException; @@ -35,11 +36,16 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.support.DefaultResource; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.util.PackageUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.UnrecoverableExceptions; /** *

DISCLAIMER

@@ -50,9 +56,10 @@ * * @since 1.0 */ -class ClasspathScanner { +@API(status = INTERNAL, since = "1.12") +public class DefaultClasspathScanner implements ClasspathScanner { - private static final Logger logger = LoggerFactory.getLogger(ClasspathScanner.class); + private static final Logger logger = LoggerFactory.getLogger(DefaultClasspathScanner.class); private static final char CLASSPATH_RESOURCE_PATH_SEPARATOR = '/'; private static final String CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING = String.valueOf( @@ -69,14 +76,15 @@ class ClasspathScanner { private final BiFunction>> loadClass; - ClasspathScanner(Supplier classLoaderSupplier, + public DefaultClasspathScanner(Supplier classLoaderSupplier, BiFunction>> loadClass) { this.classLoaderSupplier = classLoaderSupplier; this.loadClass = loadClass; } - List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter) { + @Override + public List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter) { Preconditions.condition( PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); @@ -87,14 +95,16 @@ List> scanForClassesInPackage(String basePackageName, ClassFilter class return findClassesForUris(roots, basePackageName, classFilter); } - List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter) { + @Override + public List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter) { Preconditions.notNull(root, "root must not be null"); Preconditions.notNull(classFilter, "classFilter must not be null"); return findClassesForUri(root, PackageUtils.DEFAULT_PACKAGE_NAME, classFilter); } - List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter) { + @Override + public List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter) { Preconditions.condition( PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); @@ -105,7 +115,8 @@ List scanForResourcesInPackage(String basePackageName, Predicate scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter) { + @Override + public List scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter) { Preconditions.notNull(root, "root must not be null"); Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); @@ -188,8 +199,7 @@ private void processClassFileSafely(Path baseDir, String basePackageName, ClassF // @formatter:off loadClass.apply(fullyQualifiedClassName, getClassLoader()) .toOptional() - // Always use ".filter(classFilter)" to include future predicates. - .filter(classFilter) + .filter(classFilter::match) .ifPresent(classConsumer); // @formatter:on } @@ -208,7 +218,7 @@ private void processResourceFileSafely(Path baseDir, String basePackageName, Pre try { String fullyQualifiedResourceName = determineFullyQualifiedResourceName(baseDir, basePackageName, resourceFile); - Resource resource = new ClasspathResource(fullyQualifiedResourceName, resourceFile.toUri()); + Resource resource = new DefaultResource(fullyQualifiedResourceName, resourceFile.toUri()); if (resourceFilter.test(resource)) { resourceConsumer.accept(resource); } @@ -309,7 +319,7 @@ private List getRootUrisForPackageNameOnClassPathAndModulePath(String baseP Set uriSet = new LinkedHashSet<>(getRootUrisForPackage(basePackageName)); if (!basePackageName.isEmpty() && !basePackageName.endsWith(PACKAGE_SEPARATOR_STRING)) { getRootUrisForPackage(basePackageName + PACKAGE_SEPARATOR_STRING).stream() // - .map(ClasspathScanner::removeTrailingClasspathResourcePathSeparator) // + .map(DefaultClasspathScanner::removeTrailingClasspathResourcePathSeparator) // .forEach(uriSet::add); } return new ArrayList<>(uriSet); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java new file mode 100644 index 000000000000..769733a65aef --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java @@ -0,0 +1,5 @@ +/** + * Classpath scanning APIs provided by the JUnit Platform. + */ + +package org.junit.platform.commons.support.scanning; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java new file mode 100644 index 000000000000..6a2240815605 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java @@ -0,0 +1,47 @@ +/* + * 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 static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; + +import java.util.List; +import java.util.ServiceLoader; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.scanning.ClasspathScanner; +import org.junit.platform.commons.support.scanning.DefaultClasspathScanner; + +/** + * @since 1.12 + */ +class ClasspathScannerLoader { + + static ClasspathScanner getInstance() { + ServiceLoader serviceLoader = ServiceLoader.load(ClasspathScanner.class, + ClassLoaderUtils.getDefaultClassLoader()); + + List classpathScanners = stream(serviceLoader.spliterator(), false).collect(toList()); + + if (classpathScanners.size() == 1) { + return classpathScanners.get(0); + } + + if (classpathScanners.size() > 1) { + throw new JUnitException(String.format( + "There should not be more than one ClasspathScanner implementation present on the classpath but there were %d: %s", + classpathScanners.size(), classpathScanners)); + } + + return new DefaultClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); + } + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java index d24b977d71eb..54da1d85af11 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java @@ -23,6 +23,7 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.support.scanning.ClassFilter; /** * Collection of utilities for working with {@code java.lang.Module} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java index 6b23a5324bac..e83f42af02f4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java @@ -40,7 +40,7 @@ private PackageUtils() { /* no-op */ } - static final String DEFAULT_PACKAGE_NAME = ""; + public static final String DEFAULT_PACKAGE_NAME = ""; /** * Get the package attribute for the supplied {@code type} using the diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 2dafd46d5344..3f8a23d2857d 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -62,7 +62,10 @@ import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.support.DefaultResource; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.support.scanning.ClassFilter; +import org.junit.platform.commons.support.scanning.ClasspathScanner; /** * Collection of utilities for working with the Java reflection APIs. @@ -148,8 +151,7 @@ public enum HierarchyTraversalMode { private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - private static final ClasspathScanner classpathScanner = new ClasspathScanner( - ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); + private static final ClasspathScanner classpathScanner = ClasspathScannerLoader.getInstance(); /** * Cache for equivalent methods on an interface implemented by the declaring class. @@ -935,7 +937,7 @@ public static Try> tryToGetResources(String classpathResourceName, List resources = Collections.list(classLoader.getResources(canonicalClasspathResourceName)); return resources.stream().map(url -> { try { - return new ClasspathResource(canonicalClasspathResourceName, url.toURI()); + return new DefaultResource(canonicalClasspathResourceName, url.toURI()); } catch (URISyntaxException e) { throw ExceptionUtils.throwAsUncheckedException(e); diff --git a/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java b/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java index f3a6bd1a5f33..fe83af684518 100644 --- a/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java +++ b/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java @@ -37,7 +37,9 @@ import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.support.DefaultResource; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.support.scanning.ClassFilter; /** * Collection of utilities for working with {@code java.lang.Module} @@ -225,8 +227,7 @@ List> scan(ModuleReference reference) { .filter(name -> !name.equals("module-info")) .filter(classFilter::match) .map(this::loadClassUnchecked) - // Always use ".filter(classFilter)" to include future predicates. - .filter(classFilter) + .filter(classFilter::match) .collect(Collectors.toList()); // @formatter:on } @@ -298,7 +299,7 @@ List scan(ModuleReference reference) { private Resource loadResourceUnchecked(String binaryName) { try { URI uri = classLoader.getResource(binaryName).toURI(); - return new ClasspathResource(binaryName, uri); + return new DefaultResource(binaryName, uri); } catch (URISyntaxException e) { throw new JUnitException("Failed to load resource with name '" + binaryName + "'.", e); diff --git a/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java b/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java index 774684198f9f..fb3fdba07a68 100644 --- a/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java +++ b/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java @@ -37,6 +37,7 @@ org.junit.vintage.engine; exports org.junit.platform.commons.support; exports org.junit.platform.commons.support.conversion; + exports org.junit.platform.commons.support.scanning; exports org.junit.platform.commons.util to org.junit.jupiter.api, org.junit.jupiter.engine, @@ -52,4 +53,5 @@ org.junit.platform.suite.engine, org.junit.platform.testkit, org.junit.vintage.engine; + uses org.junit.platform.commons.support.scanning.ClasspathScanner; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java index e3d2b0392cb2..12361024de15 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java @@ -18,7 +18,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.discovery.ClassNameFilter; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java index e1bbc8834e78..8f160a2152f1 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java @@ -18,7 +18,7 @@ import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.UniqueId.Segment; @@ -43,7 +43,10 @@ class ClassSelectorResolver implements SelectorResolver { @Override public Resolution resolve(ClassSelector selector, Context context) { - return resolveTestClass(selector.getJavaClass(), context); + if (classFilter.match(selector.getClassName())) { + return resolveTestClassThatPassedNameFilter(selector.getJavaClass(), context); + } + return unresolved(); } @Override @@ -51,15 +54,17 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { Segment lastSegment = selector.getUniqueId().getLastSegment(); if (SEGMENT_TYPE_RUNNER.equals(lastSegment.getType())) { String testClassName = lastSegment.getValue(); - Class testClass = ReflectionSupport.tryToLoadClass(testClassName)// - .getOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); - return resolveTestClass(testClass, context); + if (classFilter.match(testClassName)) { + Class testClass = ReflectionSupport.tryToLoadClass(testClassName)// + .getOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); + return resolveTestClassThatPassedNameFilter(testClass, context); + } } return unresolved(); } - private Resolution resolveTestClass(Class testClass, Context context) { - if (!classFilter.test(testClass)) { + private Resolution resolveTestClassThatPassedNameFilter(Class testClass, Context context) { + if (!classFilter.match(testClass)) { return unresolved(); } Runner runner = RUNNER_BUILDER.safeRunnerForClass(testClass); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java index a8868df3abd4..ce67578616e6 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java @@ -13,7 +13,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/CloseablePathTests.java similarity index 90% rename from platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java rename to platform-tests/src/test/java/org/junit/platform/commons/support/scanning/CloseablePathTests.java index 512eefeb41ec..ecac16c34c01 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/CloseablePathTests.java @@ -8,12 +8,11 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; -import static org.junit.platform.commons.util.CloseablePath.JAR_URI_SCHEME; +import static org.junit.platform.commons.support.scanning.CloseablePath.JAR_URI_SCHEME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.only; @@ -32,7 +31,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.CloseablePath.FileSystemProvider; +import org.junit.platform.commons.support.scanning.CloseablePath.FileSystemProvider; +import org.junit.platform.commons.test.ConcurrencyTestingUtils; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; class CloseablePathTests { @@ -92,7 +92,8 @@ void createsAndClosesJarFileSystemOnceWhenCalledConcurrently() throws Exception when(fileSystemProvider.newFileSystem(any())) // .thenAnswer(invocation -> FileSystems.newFileSystem((URI) invocation.getArgument(0), Map.of())); - paths = executeConcurrently(numThreads, () -> CloseablePath.create(uri, fileSystemProvider)); + paths = ConcurrencyTestingUtils.executeConcurrently(numThreads, + () -> CloseablePath.create(uri, fileSystemProvider)); verify(fileSystemProvider, only()).newFileSystem(jarUri); // Close all but the first path diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java similarity index 90% rename from platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java rename to platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java index 7cd2f31456b4..1bcd92ce8b11 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; @@ -50,14 +50,16 @@ import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.commons.util.ReflectionUtils; /** - * Unit tests for {@link ClasspathScanner}. + * Unit tests for {@link DefaultClasspathScanner}. * * @since 1.0 */ @TrackLogRecords -class ClasspathScannerTests { +class DefaultClasspathScannerTests { private static final ClassFilter allClasses = ClassFilter.of(type -> true); private static final Predicate allResources = type -> true; @@ -67,8 +69,8 @@ class ClasspathScannerTests { private final BiFunction>> trackingClassLoader = (name, classLoader) -> ReflectionUtils.tryToLoadClass(name, classLoader).ifSuccess(loadedClasses::add); - private final ClasspathScanner classpathScanner = new ClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, - trackingClassLoader); + private final DefaultClasspathScanner classpathScanner = new DefaultClasspathScanner( + ClassLoaderUtils::getDefaultClassLoader, trackingClassLoader); @Test void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccursWithNullDetailedMessage( @@ -152,7 +154,7 @@ private void assertResourcesScannedWhenExceptionIsThrown(Predicate fil private void assertDebugMessageLogged(LogRecordListener listener, String regex) { // @formatter:off - assertThat(listener.stream(ClasspathScanner.class, Level.FINE) + assertThat(listener.stream(DefaultClasspathScanner.class, Level.FINE) .map(LogRecord::getMessage) .filter(m -> m.matches(regex)) ).hasSize(1); @@ -187,7 +189,7 @@ private void scanForClassesInClasspathRootWithinJarFile(String resourceName) thr var jarfile = getClass().getResource(resourceName); try (var classLoader = new URLClassLoader(new URL[] { jarfile }, null)) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var classes = classpathScanner.scanForClassesInClasspathRoot(jarfile.toURI(), allClasses); assertThat(classes).extracting(Class::getName) // @@ -211,7 +213,7 @@ private void scanForResourcesInClasspathRootWithinJarFile(String resourceName) t var jarfile = getClass().getResource(resourceName); try (var classLoader = new URLClassLoader(new URL[] { jarfile }, null)) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInClasspathRoot(jarfile.toURI(), allResources); assertThat(resources).extracting(Resource::getName) // @@ -228,7 +230,7 @@ void scanForResourcesInShadowedClassPathRoot() throws Exception { var shadowedJarFile = getClass().getResource("/jartest-shadowed.jar"); try (var classLoader = new URLClassLoader(new URL[] { jarFile, shadowedJarFile }, null)) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInClasspathRoot(shadowedJarFile.toURI(), allResources); assertThat(resources).extracting(Resource::getName).containsExactlyInAnyOrder( @@ -238,7 +240,7 @@ void scanForResourcesInShadowedClassPathRoot() throws Exception { "META-INF/MANIFEST.MF"); assertThat(resources).extracting(Resource::getUri) // - .map(ClasspathScannerTests::jarFileAndEntry) // + .map(DefaultClasspathScannerTests::jarFileAndEntry) // .containsExactlyInAnyOrder( // This resource only exists in the shadowed jar file "jartest-shadowed.jar!/org/junit/platform/jartest/included/unique.resource", @@ -256,13 +258,13 @@ void scanForResourcesInPackageWithDuplicateResources() throws Exception { var shadowedJarFile = getClass().getResource("/jartest-shadowed.jar"); try (var classLoader = new URLClassLoader(new URL[] { jarFile, shadowedJarFile }, null)) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInPackage("org.junit.platform.jartest.included", allResources); assertThat(resources).extracting(Resource::getUri) // - .map(ClasspathScannerTests::jarFileAndEntry) // + .map(DefaultClasspathScannerTests::jarFileAndEntry) // .containsExactlyInAnyOrder( // This resource only exists in the shadowed jar file "jartest-shadowed.jar!/org/junit/platform/jartest/included/unique.resource", @@ -329,7 +331,8 @@ private void checkModules2500(ModuleFinder finder) { var parent = ClassLoader.getPlatformClassLoader(); var layer = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(boot), parent).layer(); - var classpathScanner = new ClasspathScanner(() -> layer.findLoader(root), ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> layer.findLoader(root), + ReflectionUtils::tryToLoadClass); { var classes = classpathScanner.scanForClassesInPackage("foo", allClasses); var classNames = classes.stream().map(Class::getName).collect(Collectors.toList()); @@ -348,7 +351,7 @@ void findAllClassesInPackageWithinJarFileConcurrently() throws Exception { var jarUri = URI.create("jar:" + jarFile); try (var classLoader = new URLClassLoader(new URL[] { jarFile })) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var results = executeConcurrently(10, () -> classpathScanner.scanForClassesInPackage("org.junit.platform.jartest.included", allClasses)); @@ -369,7 +372,7 @@ void findAllResourcesInPackageWithinJarFileConcurrently() throws Exception { var jarUri = URI.create("jar:" + jarFile); try (var classLoader = new URLClassLoader(new URL[] { jarFile })) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var results = executeConcurrently(10, () -> classpathScanner.scanForResourcesInPackage("org.junit.platform.jartest.included", allResources)); @@ -410,9 +413,9 @@ void scanForResourcesInDefaultPackage() { @Test void scanForClassesInPackageWithFilter() { - var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class); + var thisClassOnly = ClassFilter.of(clazz -> clazz == DefaultClasspathScannerTests.class); var classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", thisClassOnly); - assertSame(ClasspathScannerTests.class, classes.get(0)); + assertSame(DefaultClasspathScannerTests.class, classes.get(0)); } @Test @@ -471,34 +474,34 @@ void scanForClassesInPackageForNullClassFilter() { @Test void scanForClassesInPackageWhenIOExceptionOccurs() { - var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); + var scanner = new DefaultClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); var classes = scanner.scanForClassesInPackage("org.junit.platform.commons", allClasses); assertThat(classes).isEmpty(); } @Test void scanForResourcesInPackageWhenIOExceptionOccurs() { - var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); + var scanner = new DefaultClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); var classes = scanner.scanForResourcesInPackage("org.junit.platform.commons", allResources); assertThat(classes).isEmpty(); } @Test void scanForClassesInPackageOnlyLoadsClassesThatAreIncludedByTheClassNameFilter() { - Predicate classNameFilter = name -> ClasspathScannerTests.class.getName().equals(name); + Predicate classNameFilter = name -> DefaultClasspathScannerTests.class.getName().equals(name); var classFilter = ClassFilter.of(classNameFilter, type -> true); classpathScanner.scanForClassesInPackage("org.junit.platform.commons", classFilter); - assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class); + assertThat(loadedClasses).containsExactly(DefaultClasspathScannerTests.class); } @Test void findAllClassesInClasspathRoot() throws Exception { - var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class); + var thisClassOnly = ClassFilter.of(clazz -> clazz == DefaultClasspathScannerTests.class); var root = getTestClasspathRoot(); var classes = classpathScanner.scanForClassesInClasspathRoot(root, thisClassOnly); - assertSame(ClasspathScannerTests.class, classes.get(0)); + assertSame(DefaultClasspathScannerTests.class, classes.get(0)); } @Test @@ -543,7 +546,7 @@ void findAllClassesInClasspathRootWithFilter() throws Exception { var classes = classpathScanner.scanForClassesInClasspathRoot(root, allClasses); assertThat(classes).hasSizeGreaterThanOrEqualTo(20); - assertTrue(classes.contains(ClasspathScannerTests.class)); + assertTrue(classes.contains(DefaultClasspathScannerTests.class)); } @Test @@ -566,16 +569,17 @@ void findAllClassesInClasspathRootForNullClassFilter() { @Test void onlyLoadsClassesInClasspathRootThatAreIncludedByTheClassNameFilter() throws Exception { - var classFilter = ClassFilter.of(name -> ClasspathScannerTests.class.getName().equals(name), type -> true); + var classFilter = ClassFilter.of(name -> DefaultClasspathScannerTests.class.getName().equals(name), + type -> true); var root = getTestClasspathRoot(); classpathScanner.scanForClassesInClasspathRoot(root, classFilter); - assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class); + assertThat(loadedClasses).containsExactly(DefaultClasspathScannerTests.class); } private static URI uriOf(String name) { - var resource = ClasspathScannerTests.class.getResource(name); + var resource = DefaultClasspathScannerTests.class.getResource(name); try { return requireNonNull(resource).toURI(); } diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt index cb3eb72ae947..11e66a7ca44f 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt @@ -4,9 +4,11 @@ exports org.junit.platform.commons.annotation exports org.junit.platform.commons.function exports org.junit.platform.commons.support exports org.junit.platform.commons.support.conversion +exports org.junit.platform.commons.support.scanning requires java.base mandated requires java.logging requires java.management requires org.apiguardian.api static transitive +uses org.junit.platform.commons.support.scanning.ClasspathScanner qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.commons org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine diff --git a/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java b/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java index d4d961dcc251..c74a8e97c853 100644 --- a/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java +++ b/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.commons.util.ModuleUtils; /**