Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ repository on GitHub.
[[release-notes-6.1.0-M1-junit-platform-new-features-and-improvements]]
==== New Features and Improvements

* ❓
* Support for creating a `ModuleSelector` from a `java.lang.Module` and using
its classloader for test discovery.


[[release-notes-6.1.0-M1-junit-jupiter]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.junit.platform.commons.support;

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 java.lang.reflect.Field;
Expand Down Expand Up @@ -391,6 +392,31 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, Predicate
return ReflectionUtils.findAllClassesInModule(moduleName, classFilter, classNameFilter);
}

/**
* Find all {@linkplain Class classes} in the supplied {@code module}
* that match the specified {@code classFilter} and {@code classNameFilter}
* predicates.
*
* <p>The module-path scanning algorithm searches recursively in all
* packages contained in the module.
*
* @param module the module to scan; never {@code null} or <em>unnamed</em>
* @param classFilter the class type filter; never {@code null}
* @param classNameFilter the class name filter; never {@code null}
* @return an immutable list of all such classes found; never {@code null}
* but potentially empty
* @since 6.1
* @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate)
* @see #findAllClassesInPackage(String, Predicate, Predicate)
* @see ResourceSupport#findAllResourcesInModule(String, ResourceFilter)
*/
@API(status = EXPERIMENTAL, since = "6.1")
public static List<Class<?>> findAllClassesInModule(Module module, Predicate<Class<?>> classFilter,
Predicate<String> classNameFilter) {

return ReflectionUtils.findAllClassesInModule(module, classFilter, classNameFilter);
}

/**
* Find all {@linkplain Resource resources} in the supplied {@code moduleName}
* that match the specified {@code resourceFilter} predicate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.platform.commons.support;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.MAINTAINED;

import java.net.URI;
Expand Down Expand Up @@ -193,6 +194,27 @@ public static List<Resource> findAllResourcesInModule(String moduleName, Resourc
return ReflectionUtils.findAllResourcesInModule(moduleName, resourceFilter);
}

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

/**
* Find all {@linkplain Resource resources} in the supplied {@code moduleName}
* that match the specified {@code resourceFilter}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,27 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, ClassFilt
return scan(moduleReferences, filter, ModuleUtils.class.getClassLoader());
}

/**
* Find all {@linkplain Class classes} for the given module.
*
* @param module the module to scan; never {@code null} or <em>unnamed</em>
* @param filter the class filter to apply; never {@code null}
* @return an immutable list of all such classes found; never {@code null}
* but potentially empty
* @since 6.1
*/
@API(status = INTERNAL, since = "6.1")
public static List<Class<?>> findAllClassesInModule(Module module, ClassFilter filter) {
Preconditions.notNull(module, "Module must not be null");
Preconditions.condition(module.isNamed(), "Module must not be unnamed");
Preconditions.notNull(filter, "Class filter must not be null");

String name = module.getName();
logger.debug(() -> "Looking for classes in module: " + name);
var reference = module.getLayer().configuration().findModule(name).orElseThrow().reference();
return scan(Set.of(reference), filter, module.getClassLoader());
}

/**
* Find all {@linkplain Resource resources} for the given module name.
*
Expand All @@ -124,7 +145,7 @@ public static List<Resource> findAllResourcesInModule(String moduleName, Resourc
Preconditions.notBlank(moduleName, "Module name must not be null or empty");
Preconditions.notNull(filter, "Resource filter must not be null");

logger.debug(() -> "Looking for classes in module: " + moduleName);
logger.debug(() -> "Looking for resources in module: " + moduleName);
// @formatter:off
Set<ModuleReference> moduleReferences = streamResolvedModules(isEqual(moduleName))
.map(ResolvedModule::reference)
Expand All @@ -133,6 +154,27 @@ public static List<Resource> findAllResourcesInModule(String moduleName, Resourc
return scan(moduleReferences, filter, ModuleUtils.class.getClassLoader());
}

/**
* Find all {@linkplain Resource resources} for the given module.
*
* @param module the module to scan; never {@code null} or <em>empty</em>
* @param filter the class filter to apply; never {@code null}
* @return an immutable list of all such resources found; never {@code null}
* but potentially empty
* @since 6.1
*/
@API(status = INTERNAL, since = "6.1")
public static List<Resource> findAllResourcesInModule(Module module, ResourceFilter filter) {
Preconditions.notNull(module, "Module must not be null");
Preconditions.condition(module.isNamed(), "Module must not be unnamed");
Preconditions.notNull(filter, "Resource filter must not be null");

String name = module.getName();
logger.debug(() -> "Looking for resources in module: " + name);
var reference = module.getLayer().configuration().findModule(name).orElseThrow().reference();
return scan(Set.of(reference), filter, module.getClassLoader());
}

/**
* Stream resolved modules from current (or boot) module layer.
*/
Expand Down Expand Up @@ -175,18 +217,18 @@ private static List<Class<?>> scan(Set<ModuleReference> references, ClassFilter
}

/**
* Scan for classes using the supplied set of module references, class
* Scan for resources using the supplied set of module references, class
* filter, and loader.
*/
private static List<Resource> scan(Set<ModuleReference> references, ResourceFilter filter, ClassLoader loader) {
logger.debug(() -> "Scanning " + references.size() + " module references: " + references);
ModuleReferenceResourceScanner scanner = new ModuleReferenceResourceScanner(filter, loader);
List<Resource> classes = new ArrayList<>();
List<Resource> resources = new ArrayList<>();
for (ModuleReference reference : references) {
classes.addAll(scanner.scan(reference));
resources.addAll(scanner.scan(reference));
}
logger.debug(() -> "Found " + classes.size() + " classes: " + classes);
return List.copyOf(classes);
logger.debug(() -> "Found " + resources.size() + " resources: " + resources);
return List.copyOf(resources);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,16 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, Predicate
return findAllClassesInModule(moduleName, ClassFilter.of(classNameFilter, classFilter));
}

/**
* @since 6.1
* @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInModule(Module, Predicate, Predicate)
*/
public static List<Class<?>> findAllClassesInModule(Module module, Predicate<Class<?>> classFilter,
Predicate<String> classNameFilter) {
// unmodifiable since returned by public, non-internal method(s)
return findAllClassesInModule(module, ClassFilter.of(classNameFilter, classFilter));
}

/**
* @since 1.10
* @see org.junit.platform.commons.support.ReflectionSupport#streamAllClassesInModule(String, Predicate, Predicate)
Expand All @@ -1077,13 +1087,27 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, ClassFilt
return List.copyOf(ModuleUtils.findAllClassesInModule(moduleName, classFilter));
}

/**
* @since 6.1
*/
public static List<Class<?>> findAllClassesInModule(Module module, ClassFilter classFilter) {
return List.copyOf(ModuleUtils.findAllClassesInModule(module, classFilter));
}

/**
* @since 1.11
*/
public static List<Resource> findAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) {
return List.copyOf(ModuleUtils.findAllResourcesInModule(moduleName, resourceFilter));
}

/**
* @since 6.1
*/
public static List<Resource> findAllResourcesInModule(Module module, ResourceFilter resourceFilter) {
return List.copyOf(ModuleUtils.findAllResourcesInModule(module, resourceFilter));
}

/**
* @since 1.10
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,22 @@ public static ModuleSelector selectModule(String moduleName) {
return new ModuleSelector(moduleName.strip());
}

/**
* Create a {@code ModuleSelector} for the supplied module.
*
* <p>The unnamed module is not supported.
*
* @param module the module to select; never {@code null} or <em>unnamed</em>
* @since 6.1
* @see ModuleSelector
*/
@API(status = EXPERIMENTAL, since = "6.1")
public static ModuleSelector selectModule(Module module) {
Preconditions.notNull(module, "Module must not be null");
Preconditions.condition(module.isNamed(), "Module must be named");
return new ModuleSelector(module);
}

/**
* Create a list of {@code ModuleSelectors} for the supplied module names.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@

package org.junit.platform.engine.discovery;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.apiguardian.api.API.Status.STABLE;

import java.util.Objects;
import java.util.Optional;

import org.apiguardian.api.API;
import org.jspecify.annotations.Nullable;
import org.junit.platform.commons.util.ToStringBuilder;
import org.junit.platform.engine.DiscoverySelector;
import org.junit.platform.engine.DiscoverySelectorIdentifier;
Expand All @@ -33,12 +35,31 @@
@API(status = STABLE, since = "1.1")
public final class ModuleSelector implements DiscoverySelector {

@Nullable
private final Module module;
private final String moduleName;

ModuleSelector(Module module) {
this.module = module;
this.moduleName = module.getName();
}

ModuleSelector(String moduleName) {
this.module = null;
this.moduleName = moduleName;
}

/**
* {@return the selected {@link Module}, if available}
*
* @since 6.1
* @see DiscoverySelectors#selectModule(Module)
*/
@API(status = EXPERIMENTAL, since = "6.1")
public Optional<Module> getModule() {
return Optional.ofNullable(module);
}

/**
* Get the selected module name.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public Resolution resolve(ClasspathRootSelector selector, Context context) {

@Override
public Resolution resolve(ModuleSelector selector, Context context) {
if (selector.getModule().isPresent()) {
Module module = selector.getModule().get();
return classSelectors(findAllClassesInModule(module, classFilter, classNameFilter));
}
return classSelectors(findAllClassesInModule(selector.getModuleName(), classFilter, classNameFilter));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public Resolution resolve(ClasspathRootSelector selector, Context context) {

@Override
public Resolution resolve(ModuleSelector selector, Context context) {
if (selector.getModule().isPresent()) {
Module module = selector.getModule().get();
return resourceSelectors(findAllResourcesInModule(module, resourceFilter));
}
return resourceSelectors(findAllResourcesInModule(selector.getModuleName(), resourceFilter));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,9 @@ void findAllClassesInModuleDelegates() {
@Test
void findAllClassesInModulePreconditions() {
assertPreconditionViolationNotNullOrEmptyFor("Module name",
() -> ReflectionSupport.findAllClassesInModule(null, allTypes, allNames));
() -> ReflectionSupport.findAllClassesInModule((String) null, allTypes, allNames));
assertPreconditionViolationNotNullFor("Module",
() -> ReflectionSupport.findAllClassesInModule((Module) null, allTypes, allNames));
assertPreconditionViolationNotNullFor("class predicate",
() -> ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", null, allNames));
assertPreconditionViolationNotNullFor("name predicate",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ void findAllResourcesInModuleDelegates() {
@Test
void findAllResourcesInModulePreconditions() {
assertPreconditionViolationNotNullOrEmptyFor("Module name",
() -> ResourceSupport.findAllResourcesInModule(null, allResources));
() -> ResourceSupport.findAllResourcesInModule((String) null, allResources));
assertPreconditionViolationNotNullFor("Module",
() -> ResourceSupport.findAllResourcesInModule((Module) null, allResources));
assertPreconditionViolationNotNullFor("Resource filter",
() -> ResourceSupport.findAllResourcesInModule("org.junit.platform.commons", null));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForMethod;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor;
Expand Down Expand Up @@ -422,7 +423,7 @@ class SelectModuleTests {
@SuppressWarnings("DataFlowIssue")
@Test
void selectModuleByNamePreconditions() {
assertPreconditionViolationFor(() -> selectModule(null));
assertPreconditionViolationFor(() -> selectModule((String) null));
assertPreconditionViolationFor(() -> selectModule(""));
assertPreconditionViolationFor(() -> selectModule(" "));
}
Expand All @@ -433,6 +434,21 @@ void selectModuleByName() {
assertEquals("java.base", selector.getModuleName());
}

@SuppressWarnings("DataFlowIssue")
@Test
void selectModuleByInstancePreconditions() {
assertPreconditionViolationFor(() -> selectModule((Module) null));
assertPreconditionViolationFor(() -> selectModule(getClass().getClassLoader().getUnnamedModule()));
}

@Test
void selectModuleByInstance() {
var module = Object.class.getModule();
var selector = selectModule(module);
assertEquals("java.base", selector.getModuleName());
assertSame(module, selector.getModule().orElseThrow());
}

@SuppressWarnings("DataFlowIssue")
@Test
void selectModulesByNamesPreconditions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode;

import java.util.logging.Logger;

import org.junit.jupiter.api.Test;

/**
Expand All @@ -31,4 +33,13 @@ void equalsAndHashCode() {
assertEqualsAndHashCode(selector1, selector2, selector3);
}

@Test
void equalsAndHashCodeForModuleInstances() {
var selector1 = new ModuleSelector(Object.class.getModule()); // java.base
var selector2 = new ModuleSelector(Object.class.getModule()); // java.base
var selector3 = new ModuleSelector(Logger.class.getModule()); // java.logging

assertEqualsAndHashCode(selector1, selector2, selector3);
}

}