diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC3.adoc index fae30bcd5f32..56c397d92807 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC3.adoc @@ -32,12 +32,36 @@ JUnit repository on GitHub. - `LauncherDiscoveryRequestBuilder.outputDirectoryProvider(OutputDirectoryProvider)` - `TestPlan.getOutputDirectoryProvider()` - `EngineTestKit.Builder.outputDirectoryProvider(OutputDirectoryProvider)` +* Deprecate `org.junit.platform.commons.support.Resource` interface in favor of the new + `org.junit.platform.commons.io.Resource` one. +* Deprecate `Resource`-related methods in `ReflectionSupport` in favor of corresponding + methods in new `ResourceSupport` class: + - `findAllResourcesInClasspathRoot(URI, Predicate)` + - `findAllResourcesInModule(String, Predicate)` + - `findAllResourcesInPackage(String, Predicate)` + - `streamAllResourcesInClasspathRoot(URI, Predicate)` + - `streamAllResourcesInModule(String, Predicate)` + - `streamAllResourcesInPackage(String, Predicate)` + - `tryToGetResources(String)` + - `tryToGetResources(String, ClassLoader)` +* Deprecate `DiscoverySelectors.selectClasspathResource(Set)` method in favor of + `selectClasspathResourceByName(Set)`. +* Deprecate `ClasspathResourceSelector.getClasspathResources()` method in favor of + `getResources()`. +* Deprecate + `EngineDiscoveryRequestResolver.Builder.addResourceContainerSelectorResolver(Predicate)` + method in favor of `addResourceContainerSelectorResolver(ResourceFilter)`. +* Delete deprecated `Resource`-related methods in `ClasspathScanner` in favor of new + methods using `org.junit.platform.commons.io.Resource` and `ResourceFilter`: + - `scanForResourcesInPackage(String, Predicate)` + - `scanForResourcesInClasspathRoot(URI, Predicate)` [[release-notes-6.0.0-RC3-junit-platform-new-features-and-improvements]] ==== New Features and Improvements -* New `Resource.from(String, URI)` static factory method for creating an - `org.junit.platform.commons.support.Resource`. +* New classpath resource abstraction in `org.junit.platform.commons.io.Resource` with + support class for loading resources or finding them on the classpath via static utility + methods in the new `org.junit.platform.commons.support.ResourceSupport` class. [[release-notes-6.0.0-RC3-junit-jupiter]] diff --git a/junit-platform-commons/src/main/java/module-info.java b/junit-platform-commons/src/main/java/module-info.java index 3b83d2ffc150..919acfbd446d 100644 --- a/junit-platform-commons/src/main/java/module-info.java +++ b/junit-platform-commons/src/main/java/module-info.java @@ -27,6 +27,7 @@ exports org.junit.platform.commons; exports org.junit.platform.commons.annotation; exports org.junit.platform.commons.function; + exports org.junit.platform.commons.io; exports org.junit.platform.commons.logging to org.junit.jupiter.api, org.junit.jupiter.engine, diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/io/DefaultResource.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/io/DefaultResource.java new file mode 100644 index 000000000000..9f287250ab8d --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/io/DefaultResource.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2025 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.io; + +import java.net.URI; + +import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.annotation.Contract; + +/** + * Default implementation of {@link Resource}. + * + * @since 6.0 + */ +record DefaultResource(String name, URI uri) implements Resource { + + DefaultResource { + checkNotNull(name, "name"); + checkNotNull(uri, "uri"); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public URI getUri() { + return this.uri; + } + + // Cannot use Preconditions due to package cycle + @Contract("null, _ -> fail; !null, _ -> param1") + private static void checkNotNull(@Nullable T input, String title) { + if (input == null) { + throw new PreconditionViolationException(title + " must not be null"); + } + } + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/io/Resource.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/io/Resource.java new file mode 100644 index 000000000000..87d727101abf --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/io/Resource.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015-2025 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.io; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; + +import org.apiguardian.api.API; + +/** + * {@code Resource} represents a resource on the classpath. + * + *

WARNING: a {@code Resource} must provide correct + * {@link Object#equals(Object) equals} and {@link Object#hashCode() hashCode} + * implementations since a {@code Resource} may potentially be stored in a + * collection or map. + * + * @since 6.0 + * @see org.junit.platform.commons.support.ResourceSupport#findAllResourcesInClasspathRoot(URI, ResourceFilter) + * @see org.junit.platform.commons.support.ResourceSupport#findAllResourcesInPackage(String, ResourceFilter) + * @see org.junit.platform.commons.support.ResourceSupport#findAllResourcesInModule(String, ResourceFilter) + * @see org.junit.platform.commons.support.ResourceSupport#streamAllResourcesInClasspathRoot(URI, ResourceFilter) + * @see org.junit.platform.commons.support.ResourceSupport#streamAllResourcesInPackage(String, ResourceFilter) + * @see org.junit.platform.commons.support.ResourceSupport#streamAllResourcesInModule(String, ResourceFilter) + */ +@API(status = MAINTAINED, since = "6.0") +public interface Resource { + + /** + * Create a new {@link Resource} with the given name and URI. + * + * @param name the name of the resource; never {@code null} + * @param uri the URI of the resource; never {@code null} + * @return a new {@code Resource} + * @since 6.0 + */ + static Resource of(String name, URI uri) { + return new DefaultResource(name, uri); + } + + /** + * Get the name of this resource. + * + *

The resource name is a {@code /}-separated path. The path is relative + * to the classpath root in which the resource is located. + * + * @return the resource name; never {@code null} + */ + String getName(); + + /** + * Get the URI of this resource. + * + * @return the URI of the resource; never {@code null} + */ + URI getUri(); + + /** + * Get an {@link InputStream} for reading this resource. + * + *

The default implementation delegates to {@link java.net.URL#openStream()} + * for this resource's {@link #getUri() URI}. + * + * @return an input stream for this resource; never {@code null} + * @throws IOException if an I/O exception occurs + */ + default InputStream getInputStream() throws IOException { + return getUri().toURL().openStream(); + } + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/io/ResourceFilter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/io/ResourceFilter.java new file mode 100644 index 000000000000..9d182450be17 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/io/ResourceFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2025 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.io; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.function.Predicate; + +import org.apiguardian.api.API; + +/** + * Resource filter used by reflection and classpath scanning support. + * + * @since 6.0 + * @see Resource + */ +@API(status = MAINTAINED, since = "6.0") +public class ResourceFilter { + + /** + * Create a {@link ResourceFilter} instance from a predicate. + * + * @param resourcePredicate the resource predicate; never {@code null} + * @return an instance of {@code ResourceFilter}; never {@code null} + */ + public static ResourceFilter of(Predicate resourcePredicate) { + return new ResourceFilter(resourcePredicate); + } + + private final Predicate predicate; + + private ResourceFilter(Predicate predicate) { + this.predicate = predicate; + } + + public boolean match(Resource resource) { + return predicate.test(resource); + } + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/io/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/io/package-info.java new file mode 100644 index 000000000000..63f78b73f5b9 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/io/package-info.java @@ -0,0 +1,8 @@ +/** + * IO-related interfaces and support classes + */ + +@NullMarked +package org.junit.platform.commons.io; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java index de39e80d7ccf..cf8d3f9727ab 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java @@ -44,6 +44,7 @@ * @see ClassSupport * @see ModifierSupport * @see ReflectionSupport + * @see ResourceSupport */ @API(status = MAINTAINED, since = "1.0") public final class AnnotationSupport { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java index f7b7b1ff32f1..cb556c66fce6 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java @@ -31,6 +31,7 @@ * @see AnnotationSupport * @see ModifierSupport * @see ReflectionSupport + * @see ResourceSupport */ @API(status = MAINTAINED, since = "1.1") public final class ClassSupport { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java index c924e4a2249e..777e3de07292 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java @@ -20,6 +20,7 @@ * * @since 1.11 */ +@SuppressWarnings("removal") record DefaultResource(String name, URI uri) implements Resource { public DefaultResource { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java index 536cc908dc76..b1c555e24141 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java @@ -33,6 +33,7 @@ * @see AnnotationSupport * @see ClassSupport * @see ReflectionSupport + * @see ResourceSupport */ @API(status = MAINTAINED, since = "1.4") public final class ModifierSupport { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java index f7ff4130fdb6..19b051ba008c 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java @@ -10,21 +10,25 @@ package org.junit.platform.commons.support; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; @@ -42,6 +46,7 @@ * @see AnnotationSupport * @see ClassSupport * @see ModifierSupport + * @see ResourceSupport */ @API(status = MAINTAINED, since = "1.0") public final class ReflectionSupport { @@ -66,6 +71,7 @@ private ReflectionSupport() { * never {@code null} * @since 1.4 * @see #tryToLoadClass(String, ClassLoader) + * @see ResourceSupport#tryToGetResources(String) */ @API(status = MAINTAINED, since = "1.4") public static Try> tryToLoadClass(String name) { @@ -86,6 +92,7 @@ public static Try> tryToLoadClass(String name) { * never {@code null} * @since 1.10 * @see #tryToLoadClass(String) + * @see ResourceSupport#tryToGetResources(String, ClassLoader) */ @API(status = MAINTAINED, since = "1.13.3") public static Try> tryToLoadClass(String name, ClassLoader classLoader) { @@ -110,10 +117,14 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) * {@code null} * @since 1.12 * @see #tryToGetResources(String, ClassLoader) + * @deprecated Please use {@link ResourceSupport#tryToGetResources(String)} instead */ - @API(status = MAINTAINED, since = "1.13.3") + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") public static Try> tryToGetResources(String classpathResourceName) { - return ReflectionUtils.tryToGetResources(classpathResourceName); + return ResourceSupport.tryToGetResources(classpathResourceName) // + .andThenTry(ReflectionSupport::toSupportResourcesSet); } /** @@ -135,10 +146,14 @@ public static Try> tryToGetResources(String classpathResourceName) * {@code null} * @since 1.12 * @see #tryToGetResources(String) + * @deprecated Please use {@link ResourceSupport#tryToGetResources(String, ClassLoader)} instead */ - @API(status = MAINTAINED, since = "1.13.3") + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { - return ReflectionUtils.tryToGetResources(classpathResourceName, classLoader); + return ResourceSupport.tryToGetResources(classpathResourceName, classLoader) // + .andThenTry(ReflectionSupport::toSupportResourcesSet); } /** @@ -157,6 +172,7 @@ public static Try> tryToGetResources(String classpathResourceName, * but potentially empty * @see #findAllClassesInPackage(String, Predicate, Predicate) * @see #findAllClassesInModule(String, Predicate, Predicate) + * @see ResourceSupport#findAllResourcesInClasspathRoot(URI, ResourceFilter) */ public static List> findAllClassesInClasspathRoot(URI root, Predicate> classFilter, Predicate classNameFilter) { @@ -179,10 +195,14 @@ public static List> findAllClassesInClasspathRoot(URI root, Predicate findAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInClasspathRoot(root, resourceFilter); + return toSupportResourcesList( + ResourceSupport.findAllResourcesInClasspathRoot(root, toResourceFilter(resourceFilter))); } /** @@ -202,6 +222,7 @@ public static List findAllResourcesInClasspathRoot(URI root, Predicate * @since 1.10 * @see #streamAllClassesInPackage(String, Predicate, Predicate) * @see #streamAllClassesInModule(String, Predicate, Predicate) + * @see ResourceSupport#streamAllResourcesInClasspathRoot(URI, ResourceFilter) */ @API(status = MAINTAINED, since = "1.10") public static Stream> streamAllClassesInClasspathRoot(URI root, Predicate> classFilter, @@ -225,10 +246,14 @@ public static Stream> streamAllClassesInClasspathRoot(URI root, Predica * @since 1.11 * @see #streamAllResourcesInPackage(String, Predicate) * @see #streamAllResourcesInModule(String, Predicate) + * @deprecated Please use {@link ResourceSupport#streamAllResourcesInClasspathRoot(URI, ResourceFilter)} instead */ - @API(status = MAINTAINED, since = "1.13.3") + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") public static Stream streamAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInClasspathRoot(root, resourceFilter); + return toSupportResourcesStream( + ResourceSupport.streamAllResourcesInClasspathRoot(root, toResourceFilter(resourceFilter))); } /** @@ -248,6 +273,7 @@ public static Stream streamAllResourcesInClasspathRoot(URI root, Predi * but potentially empty * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #findAllClassesInModule(String, Predicate, Predicate) + * @see ResourceSupport#findAllResourcesInPackage(String, ResourceFilter) */ public static List> findAllClassesInPackage(String basePackageName, Predicate> classFilter, Predicate classNameFilter) { @@ -272,10 +298,14 @@ public static List> findAllClassesInPackage(String basePackageName, Pre * @since 1.11 * @see #findAllResourcesInClasspathRoot(URI, Predicate) * @see #findAllResourcesInModule(String, Predicate) + * @deprecated Please use {@link ResourceSupport#findAllResourcesInPackage(String, ResourceFilter)} instead */ - @API(status = MAINTAINED, since = "1.13.3") + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") public static List findAllResourcesInPackage(String basePackageName, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInPackage(basePackageName, resourceFilter); + return toSupportResourcesList( + ResourceSupport.findAllResourcesInPackage(basePackageName, toResourceFilter(resourceFilter))); } /** @@ -297,6 +327,7 @@ public static List findAllResourcesInPackage(String basePackageName, P * @since 1.10 * @see #streamAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #streamAllClassesInModule(String, Predicate, Predicate) + * @see ResourceSupport#streamAllResourcesInPackage(String, ResourceFilter) */ @API(status = MAINTAINED, since = "1.10") public static Stream> streamAllClassesInPackage(String basePackageName, Predicate> classFilter, @@ -322,12 +353,16 @@ public static Stream> streamAllClassesInPackage(String basePackageName, * @since 1.11 * @see #streamAllResourcesInClasspathRoot(URI, Predicate) * @see #streamAllResourcesInModule(String, Predicate) + * @deprecated Please use {@link ResourceSupport#streamAllResourcesInPackage(String, ResourceFilter)} instead */ - @API(status = MAINTAINED, since = "1.13.3") + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") public static Stream streamAllResourcesInPackage(String basePackageName, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInPackage(basePackageName, resourceFilter); + return toSupportResourcesStream( + ResourceSupport.streamAllResourcesInPackage(basePackageName, toResourceFilter(resourceFilter))); } /** @@ -347,6 +382,7 @@ public static Stream streamAllResourcesInPackage(String basePackageNam * @since 1.1.1 * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #findAllClassesInPackage(String, Predicate, Predicate) + * @see ResourceSupport#findAllResourcesInModule(String, ResourceFilter) */ @API(status = MAINTAINED, since = "1.1.1") public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, @@ -370,10 +406,14 @@ public static List> findAllClassesInModule(String moduleName, Predicate * @since 1.11 * @see #findAllResourcesInClasspathRoot(URI, Predicate) * @see #findAllResourcesInPackage(String, Predicate) + * @deprecated Please use {@link ResourceSupport#findAllResourcesInModule(String, ResourceFilter)} instead */ - @API(status = MAINTAINED, since = "1.13.3") + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") public static List findAllResourcesInModule(String moduleName, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInModule(moduleName, resourceFilter); + return toSupportResourcesList( + ResourceSupport.findAllResourcesInModule(moduleName, toResourceFilter(resourceFilter))); } /** @@ -416,10 +456,14 @@ public static Stream> streamAllClassesInModule(String moduleName, Predi * @since 1.11 * @see #streamAllResourcesInClasspathRoot(URI, Predicate) * @see #streamAllResourcesInPackage(String, Predicate) + * @deprecated Please use {@link ResourceSupport#streamAllResourcesInModule(String, ResourceFilter)} instead */ - @API(status = MAINTAINED, since = "1.13.3") + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") public static Stream streamAllResourcesInModule(String moduleName, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInModule(moduleName, resourceFilter); + return toSupportResourcesStream( + ResourceSupport.streamAllResourcesInModule(moduleName, toResourceFilter(resourceFilter))); } /** @@ -704,4 +748,25 @@ public static Field makeAccessible(Field field) { return ReflectionUtils.makeAccessible(Preconditions.notNull(field, "field must not be null")); } + @SuppressWarnings("removal") + private static ResourceFilter toResourceFilter(Predicate resourceFilter) { + Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); + return ResourceFilter.of(r -> resourceFilter.test(Resource.of(r))); + } + + @SuppressWarnings("removal") + static List toSupportResourcesList(List resources) { + return toSupportResourcesStream(resources.stream()).toList(); + } + + @SuppressWarnings("removal") + static Set toSupportResourcesSet(Set resources) { + return toSupportResourcesStream(resources.stream()).collect(Collectors.toCollection(LinkedHashSet::new)); + } + + @SuppressWarnings("removal") + static Stream toSupportResourcesStream(Stream resources) { + return resources.map(Resource::of); + } + } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java index 004d8481d9f7..733583125bad 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java @@ -10,14 +10,13 @@ package org.junit.platform.commons.support; -import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.apiguardian.api.API.Status.DEPRECATED; -import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.util.function.Predicate; import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; /** * {@code Resource} represents a resource on the classpath. @@ -34,51 +33,24 @@ * @see ReflectionSupport#streamAllResourcesInClasspathRoot(URI, Predicate) * @see ReflectionSupport#streamAllResourcesInPackage(String, Predicate) * @see ReflectionSupport#streamAllResourcesInModule(String, Predicate) + * @deprecated Please use {@link org.junit.platform.commons.io.Resource} instead. */ -@API(status = MAINTAINED, since = "1.13.3") -public interface Resource { +@SuppressWarnings("removal") +@API(status = DEPRECATED, since = "6.0") +@Deprecated(since = "6.0", forRemoval = true) +public interface Resource extends org.junit.platform.commons.io.Resource { /** - * Create a new {@link Resource} with the given name and URI. + * Create a new {@link Resource} from the supplied + * {@link org.junit.platform.commons.io.Resource}. * - * @param name the name of the resource; never {@code null} - * @param uri the URI of the resource; never {@code null} + * @param resource the resource to copy attributes from; never {@code null} * @return a new {@code Resource} * @since 6.0 */ - @API(status = MAINTAINED, since = "6.0") - static Resource of(String name, URI uri) { - return new DefaultResource(name, uri); - } - - /** - * Get the name of this resource. - * - *

The resource name is a {@code /}-separated path. The path is relative - * to the classpath root in which the resource is located. - * - * @return the resource name; never {@code null} - */ - String getName(); - - /** - * Get the URI of this resource. - * - * @return the URI of the resource; never {@code null} - */ - URI getUri(); - - /** - * Get an {@link InputStream} for reading this resource. - * - *

The default implementation delegates to {@link java.net.URL#openStream()} - * for this resource's {@link #getUri() URI}. - * - * @return an input stream for this resource; never {@code null} - * @throws IOException if an I/O exception occurs - */ - default InputStream getInputStream() throws IOException { - return getUri().toURL().openStream(); + static Resource of(org.junit.platform.commons.io.Resource resource) { + Preconditions.notNull(resource, "resource must not be null"); + return new DefaultResource(resource.getName(), resource.getUri()); } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ResourceSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ResourceSupport.java new file mode 100644 index 000000000000..eea5fb4654cc --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ResourceSupport.java @@ -0,0 +1,219 @@ +/* + * Copyright 2015-2025 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; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.net.URI; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.io.Resource; +import org.junit.platform.commons.io.ResourceFilter; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code ResourceSupport} provides static utility methods for common tasks + * dealing with resources; for example, scanning for resources on the class path + * or module path. + * + *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension + * authors are encouraged to use these supported methods in order to align with + * the behavior of the JUnit Platform. + * + * @since 6.0 + * @see AnnotationSupport + * @see ClassSupport + * @see ModifierSupport + * @see ReflectionSupport + */ +@API(status = MAINTAINED, since = "6.0") +public class ResourceSupport { + + /** + * Try to get the {@linkplain Resource resources} for the supplied classpath + * resource name. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + *

If the supplied classpath resource name is prefixed with a slash + * ({@code /}), the slash will be removed. + * + * @param classpathResourceName the name of the resource to load; never + * {@code null} or blank + * @return a successful {@code Try} containing the set of loaded resources + * (potentially empty) or a failed {@code Try} containing the exception in + * case a failure occurred while trying to list resources; never + * {@code null} + * @see #tryToGetResources(String, ClassLoader) + * @see ReflectionSupport#tryToLoadClass(String) + */ + public static Try> tryToGetResources(String classpathResourceName) { + return ReflectionUtils.tryToGetResources(classpathResourceName); + } + + /** + * Try to load the {@linkplain Resource resources} for the supplied classpath + * resource name, using the supplied {@link ClassLoader}. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + *

If the supplied classpath resource name is prefixed with a slash + * ({@code /}), the slash will be removed. + * + * @param classpathResourceName the name of the resource to load; never + * {@code null} or blank + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @return a successful {@code Try} containing the set of loaded resources + * (potentially empty) or a failed {@code Try} containing the exception in + * case a failure occurred while trying to list resources; never + * {@code null} + * @see #tryToGetResources(String) + * @see ReflectionSupport#tryToLoadClass(String, ClassLoader) + */ + public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { + return ReflectionUtils.tryToGetResources(classpathResourceName, classLoader); + } + + /** + * Find all {@linkplain Resource resources} in the supplied classpath {@code root} + * that match the specified {@code resourceFilter}. + * + *

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 an immutable list of all such resources found; never {@code null} + * but potentially empty + * @see #findAllResourcesInPackage(String, ResourceFilter) + * @see #findAllResourcesInModule(String, ResourceFilter) + * @see ReflectionSupport#findAllClassesInClasspathRoot(URI, Predicate, Predicate) + */ + public static List findAllResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { + return ReflectionUtils.findAllResourcesInClasspathRoot(root, resourceFilter); + } + + /** + * Find all {@linkplain Resource resources} in the supplied classpath {@code root} + * that match the specified {@code resourceFilter}. + * + *

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 stream of all such classes found; never {@code null} + * but potentially empty + * @see #streamAllResourcesInPackage(String, ResourceFilter) + * @see #streamAllResourcesInModule(String, ResourceFilter) + * @see ReflectionSupport#streamAllClassesInClasspathRoot(URI, Predicate, Predicate) + */ + public static Stream streamAllResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { + return ReflectionUtils.streamAllResourcesInClasspathRoot(root, resourceFilter); + } + + /** + * Find all {@linkplain Resource resources} in the supplied {@code basePackageName} + * that match the specified {@code resourceFilter}. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning within the supplied base package. The resulting list may include + * identically named resources from different classpath roots. + * + * @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 an immutable list of all such classes found; never {@code null} + * but potentially empty + * @see #findAllResourcesInClasspathRoot(URI, ResourceFilter) + * @see #findAllResourcesInModule(String, ResourceFilter) + * @see ReflectionSupport#findAllClassesInPackage(String, Predicate, Predicate) + */ + public static List findAllResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { + return ReflectionUtils.findAllResourcesInPackage(basePackageName, resourceFilter); + } + + /** + * Find all {@linkplain Resource resources} in the supplied {@code basePackageName} + * that match the specified {@code resourceFilter}. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning within the supplied base package. The resulting stream may + * include identically named resources from different classpath roots. + * + * @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 stream of all such resources found; never {@code null} + * but potentially empty + * @see #streamAllResourcesInClasspathRoot(URI, ResourceFilter) + * @see #streamAllResourcesInModule(String, ResourceFilter) + * @see ReflectionSupport#streamAllClassesInPackage(String, Predicate, Predicate) + */ + public static Stream streamAllResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { + return ReflectionUtils.streamAllResourcesInPackage(basePackageName, resourceFilter); + } + + /** + * Find all {@linkplain Resource resources} in the supplied {@code moduleName} + * that match the specified {@code resourceFilter}. + * + *

The module-path scanning algorithm searches recursively in all + * packages contained in the module. + * + * @param moduleName the name of the module to scan; never {@code null} or + * empty + * @param resourceFilter the resource type filter; never {@code null} + * @return an immutable list of all such resources found; never {@code null} + * but potentially empty + * @see #findAllResourcesInClasspathRoot(URI, ResourceFilter) + * @see #findAllResourcesInPackage(String, ResourceFilter) + * @see ReflectionSupport#findAllClassesInModule(String, Predicate, Predicate) + */ + public static List findAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) { + return ReflectionUtils.findAllResourcesInModule(moduleName, resourceFilter); + } + + /** + * Find all {@linkplain Resource resources} in the supplied {@code moduleName} + * that match the specified {@code resourceFilter}. + * + *

The module-path scanning algorithm searches recursively in all + * packages contained in the module. + * + * @param moduleName the name of the module to scan; never {@code null} or + * empty + * @param resourceFilter the resource type filter; never {@code null} + * @return a stream of all such resources found; never {@code null} + * but potentially empty + * @see #streamAllResourcesInClasspathRoot(URI, ResourceFilter) + * @see #streamAllResourcesInPackage(String, ResourceFilter) + * @see ReflectionSupport#streamAllClassesInModule(String, Predicate, Predicate) + */ + public static Stream streamAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) { + return ReflectionUtils.streamAllResourcesInModule(moduleName, resourceFilter); + } + + private ResourceSupport() { + } + +} 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 index e8590a3855e2..1a1982621caa 100644 --- 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 @@ -14,10 +14,10 @@ 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; +import org.junit.platform.commons.io.Resource; +import org.junit.platform.commons.io.ResourceFilter; /** * {@code ClasspathScanner} allows to scan the classpath for classes and @@ -75,8 +75,10 @@ public interface ClasspathScanner { * @param resourceFilter the resource type filter; never {@code null} * @return a list of all such resources found; never {@code null} * but potentially empty + * @since 6.0 */ - List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter); + @API(status = MAINTAINED, since = "6.0") + List scanForResourcesInPackage(String basePackageName, ResourceFilter resourceFilter); /** * Find all {@linkplain Resource resources} in the supplied classpath {@code root} @@ -90,7 +92,9 @@ public interface ClasspathScanner { * @param resourceFilter the resource type filter; never {@code null} * @return a list of all such resources found; never {@code null} * but potentially empty + * @since 6.0 */ - List scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter); + @API(status = MAINTAINED, since = "6.0") + List scanForResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/DefaultClasspathScanner.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/DefaultClasspathScanner.java index 2dc598a422de..e7a66df2a44d 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/DefaultClasspathScanner.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/DefaultClasspathScanner.java @@ -34,9 +34,10 @@ import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.io.Resource; +import org.junit.platform.commons.io.ResourceFilter; 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; import org.junit.platform.commons.support.scanning.ClasspathScanner; @@ -96,7 +97,7 @@ public List> scanForClassesInClasspathRoot(URI root, ClassFilter classF } @Override - public List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter) { + public List scanForResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { Preconditions.condition( PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); @@ -108,7 +109,7 @@ public List scanForResourcesInPackage(String basePackageName, Predicat } @Override - public List scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter) { + public List scanForResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { Preconditions.notNull(root, "root must not be null"); Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); @@ -142,7 +143,7 @@ private List> findClassesForUri(URI baseUri, String basePackageName, Cl * Recursively scan for resources in all the supplied source directories. */ private List findResourcesForUris(List baseUris, String basePackageName, - Predicate resourceFilter) { + ResourceFilter resourceFilter) { // @formatter:off return baseUris.stream() .map(baseUri -> findResourcesForUri(baseUri, basePackageName, resourceFilter)) @@ -152,8 +153,7 @@ private List findResourcesForUris(List baseUris, String basePacka // @formatter:on } - private List findResourcesForUri(URI baseUri, String basePackageName, - Predicate resourceFilter) { + private List findResourcesForUri(URI baseUri, String basePackageName, ResourceFilter resourceFilter) { List resources = new ArrayList<>(); // @formatter:off walkFilesForUri(baseUri, ClasspathFilters.resourceFiles(), @@ -205,13 +205,13 @@ private void processClassFileSafely(Path baseDir, String basePackageName, ClassF } } - private void processResourceFileSafely(Path baseDir, String basePackageName, Predicate resourceFilter, + private void processResourceFileSafely(Path baseDir, String basePackageName, ResourceFilter resourceFilter, Path resourceFile, Consumer resourceConsumer) { try { String fullyQualifiedResourceName = determineFullyQualifiedResourceName(baseDir, basePackageName, resourceFile); Resource resource = Resource.of(fullyQualifiedResourceName, resourceFile.toUri()); - if (resourceFilter.test(resource)) { + if (resourceFilter.match(resource)) { resourceConsumer.accept(resource); } // @formatter:on 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 76ae503ee733..a279ac135459 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 @@ -34,9 +34,10 @@ import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.io.Resource; +import org.junit.platform.commons.io.ResourceFilter; 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; /** @@ -119,7 +120,7 @@ public static List> findAllClassesInModule(String moduleName, ClassFilt * @since 1.11 */ @API(status = INTERNAL, since = "1.11") - public static List findAllResourcesInModule(String moduleName, Predicate filter) { + public static List findAllResourcesInModule(String moduleName, ResourceFilter filter) { Preconditions.notBlank(moduleName, "Module name must not be null or empty"); Preconditions.notNull(filter, "Resource filter must not be null"); @@ -177,8 +178,7 @@ private static List> scan(Set references, ClassFilter * Scan for classes using the supplied set of module references, class * filter, and loader. */ - private static List scan(Set references, Predicate filter, - ClassLoader loader) { + private static List scan(Set references, ResourceFilter filter, ClassLoader loader) { logger.debug(() -> "Scanning " + references.size() + " module references: " + references); ModuleReferenceResourceScanner scanner = new ModuleReferenceResourceScanner(filter, loader); List classes = new ArrayList<>(); @@ -258,10 +258,10 @@ private Class loadClassUnchecked(String binaryName) { */ static class ModuleReferenceResourceScanner { - private final Predicate resourceFilter; + private final ResourceFilter resourceFilter; private final ClassLoader classLoader; - ModuleReferenceResourceScanner(Predicate resourceFilter, ClassLoader classLoader) { + ModuleReferenceResourceScanner(ResourceFilter resourceFilter, ClassLoader classLoader) { this.resourceFilter = resourceFilter; this.classLoader = classLoader; } @@ -275,7 +275,7 @@ List scan(ModuleReference reference) { // @formatter:off return names.filter(name -> !name.endsWith(".class")) .map(this::loadResourceUnchecked) - .filter(resourceFilter) + .filter(resourceFilter::match) .toList(); // @formatter:on } 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 098bfb365701..1ae566fde234 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 @@ -58,9 +58,10 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.io.Resource; +import org.junit.platform.commons.io.ResourceFilter; 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; import org.junit.platform.commons.support.scanning.ClasspathScanner; @@ -772,7 +773,7 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) } /** - * @see org.junit.platform.commons.support.ReflectionSupport#tryToGetResources(String) + * @see org.junit.platform.commons.support.ResourceSupport#tryToGetResources(String) */ @API(status = INTERNAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName) { @@ -780,7 +781,7 @@ public static Try> tryToGetResources(String classpathResourceName) } /** - * @see org.junit.platform.commons.support.ReflectionSupport#tryToGetResources(String, ClassLoader) + * @see org.junit.platform.commons.support.ResourceSupport#tryToGetResources(String, ClassLoader) */ @API(status = INTERNAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { @@ -986,7 +987,7 @@ public static List> findAllClassesInClasspathRoot(URI root, ClassFilter /** * @since 1.11 */ - public static List findAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { + public static List findAllResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { return List.copyOf(classpathScanner.scanForResourcesInClasspathRoot(root, resourceFilter)); } @@ -1000,7 +1001,7 @@ public static Stream> streamAllClassesInClasspathRoot(URI root, ClassFi /** * @since 1.11 */ - public static Stream streamAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { + public static Stream streamAllResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { return findAllResourcesInClasspathRoot(root, resourceFilter).stream(); } @@ -1032,7 +1033,7 @@ public static List> findAllClassesInPackage(String basePackageName, Cla /** * @since 1.11 */ - public static List findAllResourcesInPackage(String basePackageName, Predicate resourceFilter) { + public static List findAllResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { return List.copyOf(classpathScanner.scanForResourcesInPackage(basePackageName, resourceFilter)); } @@ -1046,8 +1047,7 @@ public static Stream> streamAllClassesInPackage(String basePackageName, /** * @since 1.11 */ - public static Stream streamAllResourcesInPackage(String basePackageName, - Predicate resourceFilter) { + public static Stream streamAllResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { return findAllResourcesInPackage(basePackageName, resourceFilter).stream(); } @@ -1080,7 +1080,7 @@ public static List> findAllClassesInModule(String moduleName, ClassFilt /** * @since 1.11 */ - public static List findAllResourcesInModule(String moduleName, Predicate resourceFilter) { + public static List findAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) { return List.copyOf(ModuleUtils.findAllResourcesInModule(moduleName, resourceFilter)); } @@ -1094,7 +1094,7 @@ public static Stream> streamAllClassesInModule(String moduleName, Class /** * @since 1.11 */ - public static Stream streamAllResourcesInModule(String moduleName, Predicate resourceFilter) { + public static Stream streamAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) { return findAllResourcesInModule(moduleName, resourceFilter).stream(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java index 87c5e6b6bb9b..82b20ab563be 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java @@ -11,6 +11,8 @@ package org.junit.platform.engine.discovery; import static java.util.Collections.unmodifiableSet; +import static java.util.stream.Collectors.toCollection; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; @@ -24,9 +26,9 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; -import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.io.Resource; +import org.junit.platform.commons.support.ResourceSupport; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; @@ -60,7 +62,7 @@ public final class ClasspathResourceSelector implements DiscoverySelector { private final @Nullable FilePosition position; - private @Nullable Set classpathResources; + private @Nullable Set resources; ClasspathResourceSelector(String classpathResourceName, @Nullable FilePosition position) { boolean startsWithSlash = classpathResourceName.startsWith("/"); @@ -70,9 +72,9 @@ public final class ClasspathResourceSelector implements DiscoverySelector { this.position = position; } - ClasspathResourceSelector(Set classpathResources) { - this(classpathResources.iterator().next().getName(), null); - this.classpathResources = unmodifiableSet(new LinkedHashSet<>(classpathResources)); + ClasspathResourceSelector(Set resources) { + this(resources.iterator().next().getName(), null); + this.resources = unmodifiableSet(new LinkedHashSet<>(resources)); } /** @@ -98,21 +100,41 @@ public String getClasspathResourceName() { * resource cannot be loaded. * * @since 1.12 + * @deprecated Please use {{@link #getResources()}} instead. */ - @API(status = MAINTAINED, since = "1.13.3") - public Set getClasspathResources() { - if (this.classpathResources == null) { - Try> tryToGetResource = ReflectionUtils.tryToGetResources(this.classpathResourceName); - Set classpathResources = tryToGetResource.getOrThrow( // + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0") + @SuppressWarnings("removal") + public Set getClasspathResources() { + return getResources().stream() // + .map(org.junit.platform.commons.support.Resource::of) // + .collect(toCollection(LinkedHashSet::new)); + } + + /** + * Get the selected {@link Resource resources}. + * + *

If the {@link Resource resources} were not provided, but only their name, + * this method attempts to lazily load the {@link Resource resources} based on + * their name and throws a {@link PreconditionViolationException} if the + * resource cannot be loaded. + * + * @since 6.0 + */ + @API(status = MAINTAINED, since = "6.0") + public Set getResources() { + if (this.resources == null) { + Try> tryToGetResource = ResourceSupport.tryToGetResources(this.classpathResourceName); + Set classpathResources = tryToGetResource.getNonNullOrThrow( // cause -> new PreconditionViolationException( // "Could not load resource(s) with name: " + this.classpathResourceName, cause)); if (classpathResources.isEmpty()) { throw new PreconditionViolationException( "Could not find any resource(s) with name: " + this.classpathResourceName); } - this.classpathResources = unmodifiableSet(classpathResources); + this.resources = unmodifiableSet(classpathResources); } - return this.classpathResources; + return this.resources; } /** diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java index bf857aabb6b6..fb37bea95e3c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java @@ -10,6 +10,7 @@ package org.junit.platform.engine.discovery; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; @@ -30,8 +31,8 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.commons.support.Resource; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.DiscoverySelector; @@ -291,7 +292,7 @@ public static List selectClasspathRoots(Set classpa * @param classpathResourceName the name of the classpath resource; never * {@code null} or blank * @see #selectClasspathResource(String, FilePosition) - * @see #selectClasspathResource(Set) + * @see #selectClasspathResourceByName(Set) * @see ClasspathResourceSelector * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) @@ -321,7 +322,7 @@ public static ClasspathResourceSelector selectClasspathResource(String classpath * {@code null} or blank * @param position the position inside the classpath resource; may be {@code null} * @see #selectClasspathResource(String) - * @see #selectClasspathResource(Set) + * @see #selectClasspathResourceByName(Set) * @see ClasspathResourceSelector * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) @@ -362,9 +363,48 @@ public static ClasspathResourceSelector selectClasspathResource(String classpath * @see #selectClasspathResource(String) * @see ClasspathResourceSelector * @see ReflectionSupport#tryToGetResources(String) + * @deprecated Please use {@link #selectClasspathResourceByName(Set)} instead. */ - @API(status = MAINTAINED, since = "1.13.3") - public static ClasspathResourceSelector selectClasspathResource(Set classpathResources) { + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0") + @SuppressWarnings("removal") + public static ClasspathResourceSelector selectClasspathResource( + Set classpathResources) { + return selectClasspathResourceByName(classpathResources); + } + + /** + * Create a {@code ClasspathResourceSelector} for the supplied classpath + * resources. + * + *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not + * expected to modify the classpath, the supplied resource must be on the + * classpath of the + * {@linkplain Thread#getContextClassLoader() context class loader} of the + * {@linkplain Thread thread} that uses the resulting selector. + * + *

Note: Since Java 9, all resources are on the module path. Either in + * named or unnamed modules. These resources are also considered to be + * classpath resources. + * + *

The {@link Set} supplied to this method should have a reliable iteration + * order to support reliable discovery and execution order. It is therefore + * recommended that the set be a {@link java.util.SequencedSet} (on Java 21 + * or higher), {@link java.util.SortedSet}, {@link java.util.LinkedHashSet}, + * or similar. Note that {@link Set#of(Object[])} and related {@code Set.of()} + * methods do not guarantee a reliable iteration order. + * + * @param classpathResources a set of classpath resources; never + * {@code null} or empty. All resources must have the same name, may not + * be {@code null} or blank. + * @since 6.0 + * @see #selectClasspathResource(String, FilePosition) + * @see #selectClasspathResource(String) + * @see ClasspathResourceSelector + * @see org.junit.platform.commons.support.ResourceSupport#tryToGetResources(String) + */ + @API(status = MAINTAINED, since = "6.0") + public static ClasspathResourceSelector selectClasspathResourceByName(Set classpathResources) { Preconditions.notEmpty(classpathResources, "classpath resources must not be null or empty"); Preconditions.containsNoNullElements(classpathResources, "individual classpath resources must not be null"); List resourceNames = classpathResources.stream().map(Resource::getName).distinct().toList(); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java index e38072e58355..0fce9701ca86 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java @@ -11,6 +11,7 @@ package org.junit.platform.engine.support.discovery; import static java.util.stream.Collectors.toCollection; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; @@ -21,7 +22,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; -import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.EngineDiscoveryRequest; @@ -227,9 +228,33 @@ public Builder addClassContainerSelectorResolverWithContext( * {@code null} * @return this builder for method chaining * @since 1.12 + * @deprecated Please use {@link #addResourceContainerSelectorResolver(ResourceFilter)} instead. */ - @API(status = MAINTAINED, since = "1.13.3") - public Builder addResourceContainerSelectorResolver(Predicate resourceFilter) { + @API(status = DEPRECATED, since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") + public Builder addResourceContainerSelectorResolver( + Predicate resourceFilter) { + Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); + return addResourceContainerSelectorResolver( + ResourceFilter.of(r -> resourceFilter.test(org.junit.platform.commons.support.Resource.of(r)))); + } + + /** + * Add a predefined resolver that resolves {@link ClasspathRootSelector + * ClasspathRootSelectors}, {@link ModuleSelector ModuleSelectors}, and + * {@link PackageSelector PackageSelectors} into + * {@link ClasspathResourceSelector ClasspathResourceSelectors} by + * scanning for resources that match the supplied resource filter in the + * respective class containers to this builder. + * + * @param resourceFilter filter the resolved classes must match; never + * {@code null} + * @return this builder for method chaining + * @since 6.0 + */ + @API(status = MAINTAINED, since = "6.0") + public Builder addResourceContainerSelectorResolver(ResourceFilter resourceFilter) { Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); return addSelectorResolver( context -> new ResourceContainerSelectorResolver(resourceFilter, context.getPackageFilter())); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java index 34beef727045..a108bee73414 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java @@ -12,9 +12,9 @@ import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toSet; -import static org.junit.platform.commons.support.ReflectionSupport.findAllResourcesInClasspathRoot; -import static org.junit.platform.commons.support.ReflectionSupport.findAllResourcesInPackage; -import static org.junit.platform.commons.util.ReflectionUtils.findAllResourcesInModule; +import static org.junit.platform.commons.support.ResourceSupport.findAllResourcesInClasspathRoot; +import static org.junit.platform.commons.support.ResourceSupport.findAllResourcesInModule; +import static org.junit.platform.commons.support.ResourceSupport.findAllResourcesInPackage; import static org.junit.platform.engine.support.discovery.ResourceUtils.packageName; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.selectors; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; @@ -24,7 +24,8 @@ import java.util.Set; import java.util.function.Predicate; -import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.io.Resource; +import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; @@ -35,10 +36,10 @@ * @since 1.12 */ class ResourceContainerSelectorResolver implements SelectorResolver { - private final Predicate resourceFilter; + private final ResourceFilter resourceFilter; - ResourceContainerSelectorResolver(Predicate resourceFilter, Predicate packageFilter) { - this.resourceFilter = packageName(packageFilter).and(resourceFilter); + ResourceContainerSelectorResolver(ResourceFilter resourceFilter, Predicate packageFilter) { + this.resourceFilter = ResourceFilter.of(packageName(packageFilter).and(resourceFilter::match)); } @Override @@ -62,7 +63,7 @@ private Resolution resourceSelectors(List resources) { .values() // .stream() // .map(LinkedHashSet::new) // - .map(DiscoverySelectors::selectClasspathResource) // + .map(DiscoverySelectors::selectClasspathResourceByName) // .collect(toSet()); if (selectors.isEmpty()) { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java index 6a89461d3d1b..ce550caff7f0 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java @@ -12,8 +12,8 @@ import java.util.function.Predicate; +import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.commons.support.Resource; /** * Resource-related utilities to be used in conjunction with {@link ReflectionSupport}. diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java index 68fac91feb2e..3ee9ba666485 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java @@ -14,6 +14,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.commons.support.ReflectionSupport.toSupportResourcesList; +import static org.junit.platform.commons.support.ReflectionSupport.toSupportResourcesStream; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import static org.junit.platform.commons.util.ClassLoaderUtils.getDefaultClassLoader; @@ -30,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.util.ReflectionUtils; /** @@ -38,7 +41,8 @@ class ReflectionSupportTests { private static final Predicate> allTypes = type -> true; - private static final Predicate allResources = type -> true; + @SuppressWarnings("removal") + private static final Predicate allResources = __ -> true; private static final Predicate allNames = name -> true; private static final Predicate allMethods = name -> true; private static final Predicate allFields = name -> true; @@ -110,7 +114,7 @@ List findAllClassesInClasspathRootDelegates() throws Throwable { /** * @since 1.12 */ - @SuppressWarnings("DataFlowIssue") + @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void tryToGetResourcesPreconditions() { assertPreconditionViolationNotNullOrBlankFor("Resource name", () -> ReflectionSupport.tryToGetResources(null)); @@ -124,12 +128,16 @@ void tryToGetResourcesPreconditions() { /** * @since 1.12 */ + @SuppressWarnings("removal") @Test void tryToGetResources() { - assertEquals(ReflectionUtils.tryToGetResources("default-package.resource").toOptional(), + assertEquals( + ReflectionUtils.tryToGetResources("default-package.resource").toOptional().map( + ReflectionSupport::toSupportResourcesSet), ReflectionSupport.tryToGetResources("default-package.resource").toOptional()); assertEquals( - ReflectionUtils.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional(), // + ReflectionUtils.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional().map( + ReflectionSupport::toSupportResourcesSet), // ReflectionSupport.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional()); } @@ -148,6 +156,7 @@ void findAllClassesInClasspathRootPreconditions() { /** * @since 1.11 */ + @SuppressWarnings("removal") @TestFactory List findAllResourcesInClasspathRootDelegates() throws Throwable { List tests = new ArrayList<>(); @@ -156,8 +165,8 @@ List findAllResourcesInClasspathRootDelegates() throws Throwable { paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); for (var path : paths) { var root = path.toUri(); - tests.add(DynamicTest.dynamicTest(createDisplayName(root), - () -> assertThat(ReflectionUtils.findAllResourcesInClasspathRoot(root, allResources)) // + tests.add(DynamicTest.dynamicTest(createDisplayName(root), () -> assertThat(toSupportResourcesList( + ReflectionUtils.findAllResourcesInClasspathRoot(root, ResourceFilter.of(__ -> true)))) // .containsExactlyElementsOf( ReflectionSupport.findAllResourcesInClasspathRoot(root, allResources)))); } @@ -167,7 +176,7 @@ List findAllResourcesInClasspathRootDelegates() throws Throwable { /** * @since 1.11 */ - @SuppressWarnings("DataFlowIssue") + @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void findAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -180,6 +189,7 @@ void findAllResourcesInClasspathRootPreconditions() { /** * @since 1.11 */ + @SuppressWarnings("removal") @TestFactory List streamAllResourcesInClasspathRootDelegates() throws Throwable { List tests = new ArrayList<>(); @@ -188,8 +198,8 @@ List streamAllResourcesInClasspathRootDelegates() throws Throwable paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); for (var path : paths) { var root = path.toUri(); - tests.add(DynamicTest.dynamicTest(createDisplayName(root), - () -> assertThat(ReflectionUtils.streamAllResourcesInClasspathRoot(root, allResources)) // + tests.add(DynamicTest.dynamicTest(createDisplayName(root), () -> assertThat(toSupportResourcesStream( + ReflectionUtils.streamAllResourcesInClasspathRoot(root, ResourceFilter.of(__ -> true)))) // .containsExactlyElementsOf( ReflectionSupport.streamAllResourcesInClasspathRoot(root, allResources).toList()))); } @@ -199,7 +209,7 @@ List streamAllResourcesInClasspathRootDelegates() throws Throwable /** * @since 1.11 */ - @SuppressWarnings("DataFlowIssue") + @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void streamAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -230,18 +240,21 @@ void findAllClassesInPackagePreconditions() { /** * @since 1.11 */ + @SuppressWarnings("removal") @Test void findAllResourcesInPackageDelegates() { assertNotEquals(0, ReflectionSupport.findAllResourcesInPackage("org.junit", allResources).size()); - assertEquals(ReflectionUtils.findAllResourcesInPackage("org.junit", allResources), + assertEquals( + toSupportResourcesList( + ReflectionUtils.findAllResourcesInPackage("org.junit", ResourceFilter.of(__ -> true))), ReflectionSupport.findAllResourcesInPackage("org.junit", allResources)); } /** * @since 1.11 */ - @SuppressWarnings("DataFlowIssue") + @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void findAllResourcesInPackagePreconditions() { assertPreconditionViolationNotNullOrBlankFor("basePackageName", @@ -253,18 +266,21 @@ void findAllResourcesInPackagePreconditions() { /** * @since 1.11 */ + @SuppressWarnings("removal") @Test void streamAllResourcesInPackageDelegates() { assertNotEquals(0, ReflectionSupport.streamAllResourcesInPackage("org.junit", allResources).count()); - assertEquals(ReflectionUtils.streamAllResourcesInPackage("org.junit", allResources).toList(), + assertEquals( + toSupportResourcesStream( + ReflectionUtils.streamAllResourcesInPackage("org.junit", ResourceFilter.of(__ -> true))).toList(), ReflectionSupport.streamAllResourcesInPackage("org.junit", allResources).toList()); } /** * @since 1.11 */ - @SuppressWarnings("DataFlowIssue") + @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void streamAllResourcesInPackagePreconditions() { assertPreconditionViolationNotNullOrBlankFor("basePackageName", @@ -294,44 +310,49 @@ void findAllClassesInModulePreconditions() { /** * @since 1.11 */ + @SuppressWarnings("removal") @Test void findAllResourcesInModuleDelegates() { - assertEquals(ReflectionUtils.findAllResourcesInModule("org.junit.platform.commons", allResources), + assertEquals( + ReflectionUtils.findAllResourcesInModule("org.junit.platform.commons", ResourceFilter.of(__ -> true)), ReflectionSupport.findAllResourcesInModule("org.junit.platform.commons", allResources)); } /** * @since 1.11 */ - @SuppressWarnings("DataFlowIssue") + @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void findAllResourcesInModulePreconditions() { var exception = assertThrows(PreconditionViolationException.class, () -> ReflectionSupport.findAllResourcesInModule(null, allResources)); assertEquals("Module name must not be null or empty", exception.getMessage()); - assertPreconditionViolationNotNullFor("Resource filter", + assertPreconditionViolationNotNullFor("resourceFilter", () -> ReflectionSupport.findAllResourcesInModule("org.junit.platform.commons", null)); } /** * @since 1.11 */ + @SuppressWarnings("removal") @Test void streamAllResourcesInModuleDelegates() { - assertEquals(ReflectionUtils.streamAllResourcesInModule("org.junit.platform.commons", allResources).toList(), + assertEquals( + toSupportResourcesStream(ReflectionUtils.streamAllResourcesInModule("org.junit.platform.commons", + ResourceFilter.of(__ -> true))).toList(), ReflectionSupport.streamAllResourcesInModule("org.junit.platform.commons", allResources).toList()); } /** * @since 1.11 */ - @SuppressWarnings("DataFlowIssue") + @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void streamAllResourcesInModulePreconditions() { var exception = assertThrows(PreconditionViolationException.class, () -> ReflectionSupport.streamAllResourcesInModule(null, allResources)); assertEquals("Module name must not be null or empty", exception.getMessage()); - assertPreconditionViolationNotNullFor("Resource filter", + assertPreconditionViolationNotNullFor("resourceFilter", () -> ReflectionSupport.streamAllResourcesInModule("org.junit.platform.commons", null)); } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/ResourceSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/ResourceSupportTests.java new file mode 100644 index 000000000000..9797f28d58b1 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/ResourceSupportTests.java @@ -0,0 +1,225 @@ +/* + * Copyright 2015-2025 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; +import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; +import static org.junit.platform.commons.util.ClassLoaderUtils.getDefaultClassLoader; + +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.io.ResourceFilter; +import org.junit.platform.commons.util.ReflectionUtils; + +class ResourceSupportTests { + + private static final ResourceFilter allResources = ResourceFilter.of(__ -> true); + + /** + * @since 1.12 + */ + @SuppressWarnings("DataFlowIssue") + @Test + void tryToGetResourcesPreconditions() { + assertPreconditionViolationNotNullOrBlankFor("Resource name", () -> ResourceSupport.tryToGetResources(null)); + assertPreconditionViolationNotNullOrBlankFor("Resource name", () -> ResourceSupport.tryToGetResources("")); + assertPreconditionViolationNotNullFor("Class loader", + () -> ResourceSupport.tryToGetResources("default-package.resource", null)); + assertPreconditionViolationNotNullFor("Class loader", + () -> ResourceSupport.tryToGetResources("default-package.resource", null)); + } + + /** + * @since 1.12 + */ + @Test + void tryToGetResources() { + assertEquals(ReflectionUtils.tryToGetResources("default-package.resource").toOptional(), + ResourceSupport.tryToGetResources("default-package.resource").toOptional()); + assertEquals( + ReflectionUtils.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional(), // + ResourceSupport.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional()); + } + + /** + * @since 1.11 + */ + @TestFactory + List findAllResourcesInClasspathRootDelegates() throws Throwable { + List tests = new ArrayList<>(); + List paths = new ArrayList<>(); + paths.add(Path.of(".").toRealPath()); + paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); + for (var path : paths) { + var root = path.toUri(); + tests.add(DynamicTest.dynamicTest(createDisplayName(root), + () -> assertThat(ReflectionUtils.findAllResourcesInClasspathRoot(root, allResources)) // + .containsExactlyElementsOf( + ResourceSupport.findAllResourcesInClasspathRoot(root, allResources)))); + } + return tests; + } + + /** + * @since 1.11 + */ + @SuppressWarnings("DataFlowIssue") + @Test + void findAllResourcesInClasspathRootPreconditions() { + var path = Path.of(".").toUri(); + assertPreconditionViolationNotNullFor("root", + () -> ResourceSupport.findAllResourcesInClasspathRoot(null, allResources)); + assertPreconditionViolationNotNullFor("resourceFilter", + () -> ResourceSupport.findAllResourcesInClasspathRoot(path, null)); + } + + /** + * @since 1.11 + */ + @TestFactory + List streamAllResourcesInClasspathRootDelegates() throws Throwable { + List tests = new ArrayList<>(); + List paths = new ArrayList<>(); + paths.add(Path.of(".").toRealPath()); + paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); + for (var path : paths) { + var root = path.toUri(); + tests.add(DynamicTest.dynamicTest(createDisplayName(root), + () -> assertThat(ReflectionUtils.streamAllResourcesInClasspathRoot(root, allResources)) // + .containsExactlyElementsOf( + ResourceSupport.streamAllResourcesInClasspathRoot(root, allResources).toList()))); + } + return tests; + } + + /** + * @since 1.11 + */ + @SuppressWarnings("DataFlowIssue") + @Test + void streamAllResourcesInClasspathRootPreconditions() { + var path = Path.of(".").toUri(); + assertPreconditionViolationNotNullFor("root", + () -> ResourceSupport.streamAllResourcesInClasspathRoot(null, allResources)); + assertPreconditionViolationNotNullFor("resourceFilter", + () -> ResourceSupport.streamAllResourcesInClasspathRoot(path, null)); + } + + /** + * @since 1.11 + */ + @Test + void findAllResourcesInPackageDelegates() { + assertNotEquals(0, ResourceSupport.findAllResourcesInPackage("org.junit", allResources).size()); + + assertEquals(ReflectionUtils.findAllResourcesInPackage("org.junit", allResources), + ResourceSupport.findAllResourcesInPackage("org.junit", allResources)); + } + + /** + * @since 1.11 + */ + @SuppressWarnings("DataFlowIssue") + @Test + void findAllResourcesInPackagePreconditions() { + assertPreconditionViolationNotNullOrBlankFor("basePackageName", + () -> ResourceSupport.findAllResourcesInPackage(null, allResources)); + assertPreconditionViolationNotNullFor("resourceFilter", + () -> ResourceSupport.findAllResourcesInPackage("org.junit", null)); + } + + /** + * @since 1.11 + */ + @Test + void streamAllResourcesInPackageDelegates() { + assertNotEquals(0, ResourceSupport.streamAllResourcesInPackage("org.junit", allResources).count()); + + assertEquals(ReflectionUtils.streamAllResourcesInPackage("org.junit", allResources).toList(), + ResourceSupport.streamAllResourcesInPackage("org.junit", allResources).toList()); + } + + /** + * @since 1.11 + */ + @SuppressWarnings("DataFlowIssue") + @Test + void streamAllResourcesInPackagePreconditions() { + assertPreconditionViolationNotNullOrBlankFor("basePackageName", + () -> ResourceSupport.streamAllResourcesInPackage(null, allResources)); + assertPreconditionViolationNotNullFor("resourceFilter", + () -> ResourceSupport.streamAllResourcesInPackage("org.junit", null)); + } + + /** + * @since 1.11 + */ + @Test + void findAllResourcesInModuleDelegates() { + assertEquals(ReflectionUtils.findAllResourcesInModule("org.junit.platform.commons", allResources), + ResourceSupport.findAllResourcesInModule("org.junit.platform.commons", allResources)); + } + + /** + * @since 1.11 + */ + @SuppressWarnings("DataFlowIssue") + @Test + void findAllResourcesInModulePreconditions() { + var exception = assertThrows(PreconditionViolationException.class, + () -> ResourceSupport.findAllResourcesInModule(null, allResources)); + assertEquals("Module name must not be null or empty", exception.getMessage()); + assertPreconditionViolationNotNullFor("Resource filter", + () -> ResourceSupport.findAllResourcesInModule("org.junit.platform.commons", null)); + } + + /** + * @since 1.11 + */ + @Test + void streamAllResourcesInModuleDelegates() { + assertEquals(ReflectionUtils.streamAllResourcesInModule("org.junit.platform.commons", allResources).toList(), + ResourceSupport.streamAllResourcesInModule("org.junit.platform.commons", allResources).toList()); + } + + /** + * @since 1.11 + */ + @SuppressWarnings("DataFlowIssue") + @Test + void streamAllResourcesInModulePreconditions() { + var exception = assertThrows(PreconditionViolationException.class, + () -> ResourceSupport.streamAllResourcesInModule(null, allResources)); + assertEquals("Module name must not be null or empty", exception.getMessage()); + assertPreconditionViolationNotNullFor("Resource filter", + () -> ResourceSupport.streamAllResourcesInModule("org.junit.platform.commons", null)); + } + + private static String createDisplayName(URI root) { + var displayName = root.getPath(); + if (displayName.length() > 42) { + displayName = "..." + displayName.substring(displayName.length() - 42); + } + return displayName; + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/DefaultClasspathScannerTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/DefaultClasspathScannerTests.java index 575d96ac06ac..b2dad7b0cf0e 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/DefaultClasspathScannerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/DefaultClasspathScannerTests.java @@ -47,8 +47,9 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.io.Resource; +import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.support.Resource; import org.junit.platform.commons.support.scanning.ClassFilter; /** @@ -59,8 +60,8 @@ @TrackLogRecords class DefaultClasspathScannerTests { - private static final ClassFilter allClasses = ClassFilter.of(type -> true); - private static final Predicate allResources = type -> true; + private static final ClassFilter allClasses = ClassFilter.of(__ -> true); + private static final ResourceFilter allResources = ResourceFilter.of(__ -> true); private final List> loadedClasses = new ArrayList<>(); @@ -146,7 +147,8 @@ void scanForResourcesInClasspathRootWhenGenericRuntimeExceptionOccurs(LogRecordL } private void assertResourcesScannedWhenExceptionIsThrown(Predicate filter) { - var resources = this.classpathScanner.scanForResourcesInClasspathRoot(getTestClasspathResourceRoot(), filter); + var resources = this.classpathScanner.scanForResourcesInClasspathRoot(getTestClasspathResourceRoot(), + ResourceFilter.of(filter)); assertThat(resources).hasSizeGreaterThanOrEqualTo(150); } @@ -401,7 +403,7 @@ void scanForClassesInDefaultPackage() { @Test void scanForResourcesInDefaultPackage() { - Predicate resourceFilter = this::inDefaultPackage; + var resourceFilter = ResourceFilter.of(this::inDefaultPackage); var resources = classpathScanner.scanForResourcesInPackage("", resourceFilter); assertThat(resources).as("number of resources found in default package").isNotEmpty(); @@ -418,8 +420,8 @@ void scanForClassesInPackageWithFilter() { @Test void scanForResourcesInPackageWithFilter() { - Predicate thisResourceOnly = resource -> "org/junit/platform/commons/example.resource".equals( - resource.getName()); + var thisResourceOnly = ResourceFilter.of( + resource -> "org/junit/platform/commons/example.resource".equals(resource.getName())); var resources = classpathScanner.scanForResourcesInPackage("org.junit.platform.commons", thisResourceOnly); assertThat(resources).extracting(Resource::getName).containsExactly( "org/junit/platform/commons/example.resource"); @@ -427,8 +429,8 @@ void scanForResourcesInPackageWithFilter() { @Test void resourcesCanBeRead() throws IOException { - Predicate thisResourceOnly = resource -> "org/junit/platform/commons/example.resource".equals( - resource.getName()); + var thisResourceOnly = ResourceFilter.of( + resource -> "org/junit/platform/commons/example.resource".equals(resource.getName())); var resources = classpathScanner.scanForResourcesInPackage("org.junit.platform.commons", thisResourceOnly); Resource resource = resources.getFirst(); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index 37febacc9eb3..ad89f877393e 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -69,8 +69,8 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.support.Resource; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ReflectionUtils.CycleErrorHandling; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.ClassWithNestedClasses.Nested1; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java index 87164068f2a9..164ee1d7e834 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java @@ -23,6 +23,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClassesByName; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResourceByName; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; @@ -55,7 +56,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.DiscoverySelector; @@ -306,17 +307,17 @@ void selectClasspathResourcesPreconditions() { assertViolatesPrecondition(() -> selectClasspathResource("/ ")); assertViolatesPrecondition(() -> selectClasspathResource("\t")); assertViolatesPrecondition(() -> selectClasspathResource("/\t")); - assertViolatesPrecondition(() -> selectClasspathResource((Set) null)); - assertViolatesPrecondition(() -> selectClasspathResource(Collections.emptySet())); - assertViolatesPrecondition(() -> selectClasspathResource(Collections.singleton(null))); - assertViolatesPrecondition(() -> selectClasspathResource(Set.of(new StubResource(null)))); - assertViolatesPrecondition(() -> selectClasspathResource(Set.of(new StubResource("")))); + assertViolatesPrecondition(() -> selectClasspathResourceByName(null)); + assertViolatesPrecondition(() -> selectClasspathResourceByName(Collections.emptySet())); + assertViolatesPrecondition(() -> selectClasspathResourceByName(Collections.singleton(null))); + assertViolatesPrecondition(() -> selectClasspathResourceByName(Set.of(new StubResource(null)))); + assertViolatesPrecondition(() -> selectClasspathResourceByName(Set.of(new StubResource("")))); assertViolatesPrecondition( - () -> selectClasspathResource(Set.of(new StubResource("a"), new StubResource("b")))); + () -> selectClasspathResourceByName(Set.of(new StubResource("a"), new StubResource("b")))); } @Test - void selectClasspathResources() { + void selectIndividualClasspathResources() { // with unnecessary "/" prefix var selector = selectClasspathResource("/foo/bar/spec.xml"); assertEquals("foo/bar/spec.xml", selector.getClasspathResourceName()); @@ -329,7 +330,7 @@ void selectClasspathResources() { @Test void getSelectedClasspathResources() { var selector = selectClasspathResource("org/junit/platform/commons/example.resource"); - var classpathResources = selector.getClasspathResources(); + var classpathResources = selector.getResources(); assertAll(() -> assertThat(classpathResources).hasSize(1), // () -> assertThat(classpathResources) // .extracting(Resource::getName) // @@ -340,7 +341,7 @@ void getSelectedClasspathResources() { @Test void getMissingClasspathResources() { var selector = selectClasspathResource("org/junit/platform/commons/no-such-example.resource"); - assertViolatesPrecondition(selector::getClasspathResources); + assertViolatesPrecondition(selector::getResources); } @SuppressWarnings("DataFlowIssue") diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolverTest.java b/platform-tests/src/test/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolverTest.java index 39ae53c11235..cb7a52825449 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolverTest.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolverTest.java @@ -21,10 +21,9 @@ import java.nio.file.Path; import java.util.Optional; import java.util.Set; -import java.util.function.Predicate; import org.junit.jupiter.api.Test; -import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClasspathResourceSelector; @@ -36,10 +35,10 @@ class ResourceContainerSelectorResolverTest { final TestDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("resource-engine"), "Resource Engine"); - final Predicate isResource = resource -> resource.getName().endsWith(".resource"); + final ResourceFilter resourceFilter = ResourceFilter.of(resource -> resource.getName().endsWith(".resource")); final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() // - .addResourceContainerSelectorResolver(isResource) // + .addResourceContainerSelectorResolver(resourceFilter) // .addSelectorResolver(new ResourceSelectorResolver()) // .build(); 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 dcc0038d88fb..65f6236bc4fb 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 @@ -2,6 +2,7 @@ org.junit.platform.commons@${version} jar:file:.+/junit-platform-commons-\d.+\.j exports org.junit.platform.commons exports org.junit.platform.commons.annotation exports org.junit.platform.commons.function +exports org.junit.platform.commons.io exports org.junit.platform.commons.support exports org.junit.platform.commons.support.conversion exports org.junit.platform.commons.support.scanning diff --git a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java index 4a82cec89446..7d545ea2c88c 100644 --- a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java +++ b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java @@ -63,10 +63,6 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.MediaType; -import org.junit.platform.commons.support.Resource; -import org.junit.platform.commons.support.scanning.ClasspathScanner; -import org.junit.platform.commons.util.ModuleUtils; -import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; @@ -163,16 +159,6 @@ void freeOfPackageCycles(JavaClasses classes) throws Exception { // https://github.com/junit-team/junit-framework/issues/4886 .ignoreDependency(TestReporter.class, MediaType.class) // - // https://github.com/junit-team/junit-framework/issues/4885 - .ignoreDependency(ModuleUtils.class, Resource.class) // - .ignoreDependency( - Class.forName("org.junit.platform.commons.util.ModuleUtils$ModuleReferenceResourceScanner"), - Resource.class) // - .ignoreDependency(ReflectionUtils.class, Resource.class) // - .ignoreDependency(ClasspathScanner.class, Resource.class) // - .ignoreDependency(Class.forName("org.junit.platform.commons.util.DefaultClasspathScanner"), - Resource.class) // - // https://github.com/junit-team/junit-framework/issues/4919 .ignoreDependency(org.junit.jupiter.params.support.ParameterInfo.class, org.junit.jupiter.params.ParameterInfo.class)