Skip to content

Commit 40c3b0a

Browse files
authored
Add Module instance support in ModuleSelector
Closes #4852
1 parent 343017b commit 40c3b0a

File tree

13 files changed

+201
-10
lines changed

13 files changed

+201
-10
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-6.1.0-M1.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ repository on GitHub.
2626
[[release-notes-6.1.0-M1-junit-platform-new-features-and-improvements]]
2727
==== New Features and Improvements
2828

29-
* ❓
29+
* Support for creating a `ModuleSelector` from a `java.lang.Module` and using
30+
its classloader for test discovery.
3031

3132

3233
[[release-notes-6.1.0-M1-junit-jupiter]]

junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.junit.platform.commons.support;
1212

1313
import static org.apiguardian.api.API.Status.DEPRECATED;
14+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
1415
import static org.apiguardian.api.API.Status.MAINTAINED;
1516

1617
import java.lang.reflect.Field;
@@ -391,6 +392,31 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, Predicate
391392
return ReflectionUtils.findAllClassesInModule(moduleName, classFilter, classNameFilter);
392393
}
393394

395+
/**
396+
* Find all {@linkplain Class classes} in the supplied {@code module}
397+
* that match the specified {@code classFilter} and {@code classNameFilter}
398+
* predicates.
399+
*
400+
* <p>The module-path scanning algorithm searches recursively in all
401+
* packages contained in the module.
402+
*
403+
* @param module the module to scan; never {@code null} or <em>unnamed</em>
404+
* @param classFilter the class type filter; never {@code null}
405+
* @param classNameFilter the class name filter; never {@code null}
406+
* @return an immutable list of all such classes found; never {@code null}
407+
* but potentially empty
408+
* @since 6.1
409+
* @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate)
410+
* @see #findAllClassesInPackage(String, Predicate, Predicate)
411+
* @see ResourceSupport#findAllResourcesInModule(String, ResourceFilter)
412+
*/
413+
@API(status = EXPERIMENTAL, since = "6.1")
414+
public static List<Class<?>> findAllClassesInModule(Module module, Predicate<Class<?>> classFilter,
415+
Predicate<String> classNameFilter) {
416+
417+
return ReflectionUtils.findAllClassesInModule(module, classFilter, classNameFilter);
418+
}
419+
394420
/**
395421
* Find all {@linkplain Resource resources} in the supplied {@code moduleName}
396422
* that match the specified {@code resourceFilter} predicate.

junit-platform-commons/src/main/java/org/junit/platform/commons/support/ResourceSupport.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
package org.junit.platform.commons.support;
1212

13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
1314
import static org.apiguardian.api.API.Status.MAINTAINED;
1415

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

197+
/**
198+
* Find all {@linkplain Resource resources} in the supplied {@code module}
199+
* that match the specified {@code resourceFilter}.
200+
*
201+
* <p>The module-path scanning algorithm searches recursively in all
202+
* packages contained in the module.
203+
*
204+
* @param module the module to scan; never {@code null} or <em>unnamed</em>
205+
* @param resourceFilter the resource type filter; never {@code null}
206+
* @return an immutable list of all such resources found; never {@code null}
207+
* but potentially empty
208+
* @since 6.1
209+
* @see #findAllResourcesInClasspathRoot(URI, ResourceFilter)
210+
* @see #findAllResourcesInPackage(String, ResourceFilter)
211+
* @see ReflectionSupport#findAllClassesInModule(String, Predicate, Predicate)
212+
*/
213+
@API(status = EXPERIMENTAL, since = "6.1")
214+
public static List<Resource> findAllResourcesInModule(Module module, ResourceFilter resourceFilter) {
215+
return ReflectionUtils.findAllResourcesInModule(module, resourceFilter);
216+
}
217+
196218
/**
197219
* Find all {@linkplain Resource resources} in the supplied {@code moduleName}
198220
* that match the specified {@code resourceFilter}.

junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,27 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, ClassFilt
109109
return scan(moduleReferences, filter, ModuleUtils.class.getClassLoader());
110110
}
111111

112+
/**
113+
* Find all {@linkplain Class classes} for the given module.
114+
*
115+
* @param module the module to scan; never {@code null} or <em>unnamed</em>
116+
* @param filter the class filter to apply; never {@code null}
117+
* @return an immutable list of all such classes found; never {@code null}
118+
* but potentially empty
119+
* @since 6.1
120+
*/
121+
@API(status = INTERNAL, since = "6.1")
122+
public static List<Class<?>> findAllClassesInModule(Module module, ClassFilter filter) {
123+
Preconditions.notNull(module, "Module must not be null");
124+
Preconditions.condition(module.isNamed(), "Module must not be unnamed");
125+
Preconditions.notNull(filter, "Class filter must not be null");
126+
127+
String name = module.getName();
128+
logger.debug(() -> "Looking for classes in module: " + name);
129+
var reference = module.getLayer().configuration().findModule(name).orElseThrow().reference();
130+
return scan(Set.of(reference), filter, module.getClassLoader());
131+
}
132+
112133
/**
113134
* Find all {@linkplain Resource resources} for the given module name.
114135
*
@@ -124,7 +145,7 @@ public static List<Resource> findAllResourcesInModule(String moduleName, Resourc
124145
Preconditions.notBlank(moduleName, "Module name must not be null or empty");
125146
Preconditions.notNull(filter, "Resource filter must not be null");
126147

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

157+
/**
158+
* Find all {@linkplain Resource resources} for the given module.
159+
*
160+
* @param module the module to scan; never {@code null} or <em>empty</em>
161+
* @param filter the class filter to apply; never {@code null}
162+
* @return an immutable list of all such resources found; never {@code null}
163+
* but potentially empty
164+
* @since 6.1
165+
*/
166+
@API(status = INTERNAL, since = "6.1")
167+
public static List<Resource> findAllResourcesInModule(Module module, ResourceFilter filter) {
168+
Preconditions.notNull(module, "Module must not be null");
169+
Preconditions.condition(module.isNamed(), "Module must not be unnamed");
170+
Preconditions.notNull(filter, "Resource filter must not be null");
171+
172+
String name = module.getName();
173+
logger.debug(() -> "Looking for resources in module: " + name);
174+
var reference = module.getLayer().configuration().findModule(name).orElseThrow().reference();
175+
return scan(Set.of(reference), filter, module.getClassLoader());
176+
}
177+
136178
/**
137179
* Stream resolved modules from current (or boot) module layer.
138180
*/
@@ -175,18 +217,18 @@ private static List<Class<?>> scan(Set<ModuleReference> references, ClassFilter
175217
}
176218

177219
/**
178-
* Scan for classes using the supplied set of module references, class
220+
* Scan for resources using the supplied set of module references, class
179221
* filter, and loader.
180222
*/
181223
private static List<Resource> scan(Set<ModuleReference> references, ResourceFilter filter, ClassLoader loader) {
182224
logger.debug(() -> "Scanning " + references.size() + " module references: " + references);
183225
ModuleReferenceResourceScanner scanner = new ModuleReferenceResourceScanner(filter, loader);
184-
List<Resource> classes = new ArrayList<>();
226+
List<Resource> resources = new ArrayList<>();
185227
for (ModuleReference reference : references) {
186-
classes.addAll(scanner.scan(reference));
228+
resources.addAll(scanner.scan(reference));
187229
}
188-
logger.debug(() -> "Found " + classes.size() + " classes: " + classes);
189-
return List.copyOf(classes);
230+
logger.debug(() -> "Found " + resources.size() + " resources: " + resources);
231+
return List.copyOf(resources);
190232
}
191233

192234
/**

junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,16 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, Predicate
10611061
return findAllClassesInModule(moduleName, ClassFilter.of(classNameFilter, classFilter));
10621062
}
10631063

1064+
/**
1065+
* @since 6.1
1066+
* @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInModule(Module, Predicate, Predicate)
1067+
*/
1068+
public static List<Class<?>> findAllClassesInModule(Module module, Predicate<Class<?>> classFilter,
1069+
Predicate<String> classNameFilter) {
1070+
// unmodifiable since returned by public, non-internal method(s)
1071+
return findAllClassesInModule(module, ClassFilter.of(classNameFilter, classFilter));
1072+
}
1073+
10641074
/**
10651075
* @since 1.10
10661076
* @see org.junit.platform.commons.support.ReflectionSupport#streamAllClassesInModule(String, Predicate, Predicate)
@@ -1077,13 +1087,27 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, ClassFilt
10771087
return List.copyOf(ModuleUtils.findAllClassesInModule(moduleName, classFilter));
10781088
}
10791089

1090+
/**
1091+
* @since 6.1
1092+
*/
1093+
public static List<Class<?>> findAllClassesInModule(Module module, ClassFilter classFilter) {
1094+
return List.copyOf(ModuleUtils.findAllClassesInModule(module, classFilter));
1095+
}
1096+
10801097
/**
10811098
* @since 1.11
10821099
*/
10831100
public static List<Resource> findAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) {
10841101
return List.copyOf(ModuleUtils.findAllResourcesInModule(moduleName, resourceFilter));
10851102
}
10861103

1104+
/**
1105+
* @since 6.1
1106+
*/
1107+
public static List<Resource> findAllResourcesInModule(Module module, ResourceFilter resourceFilter) {
1108+
return List.copyOf(ModuleUtils.findAllResourcesInModule(module, resourceFilter));
1109+
}
1110+
10871111
/**
10881112
* @since 1.10
10891113
*/

junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,22 @@ public static ModuleSelector selectModule(String moduleName) {
428428
return new ModuleSelector(moduleName.strip());
429429
}
430430

431+
/**
432+
* Create a {@code ModuleSelector} for the supplied module.
433+
*
434+
* <p>The unnamed module is not supported.
435+
*
436+
* @param module the module to select; never {@code null} or <em>unnamed</em>
437+
* @since 6.1
438+
* @see ModuleSelector
439+
*/
440+
@API(status = EXPERIMENTAL, since = "6.1")
441+
public static ModuleSelector selectModule(Module module) {
442+
Preconditions.notNull(module, "Module must not be null");
443+
Preconditions.condition(module.isNamed(), "Module must be named");
444+
return new ModuleSelector(module);
445+
}
446+
431447
/**
432448
* Create a list of {@code ModuleSelectors} for the supplied module names.
433449
*

junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010

1111
package org.junit.platform.engine.discovery;
1212

13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
1314
import static org.apiguardian.api.API.Status.INTERNAL;
1415
import static org.apiguardian.api.API.Status.STABLE;
1516

1617
import java.util.Objects;
1718
import java.util.Optional;
1819

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

38+
@Nullable
39+
private final Module module;
3640
private final String moduleName;
3741

42+
ModuleSelector(Module module) {
43+
this.module = module;
44+
this.moduleName = module.getName();
45+
}
46+
3847
ModuleSelector(String moduleName) {
48+
this.module = null;
3949
this.moduleName = moduleName;
4050
}
4151

52+
/**
53+
* {@return the selected {@link Module}, if available}
54+
*
55+
* @since 6.1
56+
* @see DiscoverySelectors#selectModule(Module)
57+
*/
58+
@API(status = EXPERIMENTAL, since = "6.1")
59+
public Optional<Module> getModule() {
60+
return Optional.ofNullable(module);
61+
}
62+
4263
/**
4364
* Get the selected module name.
4465
*/

junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public Resolution resolve(ClasspathRootSelector selector, Context context) {
4646

4747
@Override
4848
public Resolution resolve(ModuleSelector selector, Context context) {
49+
if (selector.getModule().isPresent()) {
50+
Module module = selector.getModule().get();
51+
return classSelectors(findAllClassesInModule(module, classFilter, classNameFilter));
52+
}
4953
return classSelectors(findAllClassesInModule(selector.getModuleName(), classFilter, classNameFilter));
5054
}
5155

junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ public Resolution resolve(ClasspathRootSelector selector, Context context) {
4949

5050
@Override
5151
public Resolution resolve(ModuleSelector selector, Context context) {
52+
if (selector.getModule().isPresent()) {
53+
Module module = selector.getModule().get();
54+
return resourceSelectors(findAllResourcesInModule(module, resourceFilter));
55+
}
5256
return resourceSelectors(findAllResourcesInModule(selector.getModuleName(), resourceFilter));
5357
}
5458

platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,9 @@ void findAllClassesInModuleDelegates() {
299299
@Test
300300
void findAllClassesInModulePreconditions() {
301301
assertPreconditionViolationNotNullOrEmptyFor("Module name",
302-
() -> ReflectionSupport.findAllClassesInModule(null, allTypes, allNames));
302+
() -> ReflectionSupport.findAllClassesInModule((String) null, allTypes, allNames));
303+
assertPreconditionViolationNotNullFor("Module",
304+
() -> ReflectionSupport.findAllClassesInModule((Module) null, allTypes, allNames));
303305
assertPreconditionViolationNotNullFor("class predicate",
304306
() -> ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", null, allNames));
305307
assertPreconditionViolationNotNullFor("name predicate",

0 commit comments

Comments
 (0)