Skip to content

Commit

Permalink
Add Java 9 module path scanning and selection support
Browse files Browse the repository at this point in the history
This commit includes the service providing interface `ModuleClassFinder`
in package `org.junit.platform.commons.util`. Its default implementation
will rely on Java 9 features introduced by the Java Platform Module
System. The usage of the new module selection options on Java 8 will
result in no-op as there is no module system in place.

Summary of changes:

 * Add ModuleSelector to the Platform discovery package.
 * Add ModuleSelector to the Jupiter discovery package.
 * Add ModuleSelector to the Vintage discovery package.
 * Add ModuleUtils to the platform commons utilities toolbox.
 * Add option `--select-module` to the console launcher.
 * Add option `--scan-module-path` to the console launcher.

Addresses #425
  • Loading branch information
sormuras committed Sep 14, 2017
1 parent 87c5d44 commit 5416db7
Show file tree
Hide file tree
Showing 19 changed files with 536 additions and 0 deletions.
54 changes: 54 additions & 0 deletions documentation/src/docs/asciidoc/release-notes-5.1.0.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[[release-notes-5.1.0]]
=== 5.1.0

*Date of Release:* ?

*Scope:* ?


[[release-notes-5.1.0-junit-platform]]
==== JUnit Platform

===== Bug Fixes

?

===== Deprecations and Breaking Changes

?

===== New Features and Improvements

* Added support of `--scan-module-path` and `--select-module` launcher options.


[[release-notes-5.1.0-junit-jupiter]]
==== JUnit Jupiter


===== Bug Fixes

?

===== Deprecations and Breaking Changes

?

===== New Features and Improvements

* Added support of `--scan-module-path` and `--select-module` launcher options.


[[release-notes-5.1.0-junit-vintage]]
==== JUnit Vintage

===== Bug Fixes

?

===== Deprecations and Breaking Changes

?
===== New Features and Improvements

* Added support of `--scan-module-path` and `--select-module` launcher options.
5 changes: 5 additions & 0 deletions documentation/src/docs/asciidoc/running-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,11 @@ Option Description
option can be repeated.
-r, --select-resource <String> Select a classpath resource for test
discovery. This option can be repeated.
--scan-module-path, --scan-modulepath EXPERIMENTAL: Scan all modules on the
module-path for test discovery.
-o, --select-module <String: module name> EXPERIMENTAL: Select single module for
test discovery. This option can be
repeated.
-n, --include-classname <String> Provide a regular expression to include
only classes whose fully qualified names
match. To avoid loading classes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
package org.junit.jupiter.engine.discovery;

import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.platform.commons.util.ModuleUtils.findAllClassesInModule;
import static org.junit.platform.commons.util.ModuleUtils.findAllClassesInModulepath;
import static org.junit.platform.commons.util.ReflectionUtils.findAllClassesInClasspathRoot;
import static org.junit.platform.commons.util.ReflectionUtils.findAllClassesInPackage;
import static org.junit.platform.engine.support.filter.ClasspathScanningSupport.buildClassNamePredicate;
Expand All @@ -26,6 +28,8 @@
import org.junit.platform.engine.discovery.ClassSelector;
import org.junit.platform.engine.discovery.ClasspathRootSelector;
import org.junit.platform.engine.discovery.MethodSelector;
import org.junit.platform.engine.discovery.ModuleSelector;
import org.junit.platform.engine.discovery.ModulepathSelector;
import org.junit.platform.engine.discovery.PackageSelector;
import org.junit.platform.engine.discovery.UniqueIdSelector;

Expand All @@ -51,6 +55,14 @@ public void resolveSelectors(EngineDiscoveryRequest request, TestDescriptor engi
findAllClassesInClasspathRoot(selector.getClasspathRoot(), isScannableTestClass,
classNamePredicate).forEach(javaElementsResolver::resolveClass);
});
request.getSelectorsByType(ModulepathSelector.class).forEach(selector -> {
findAllClassesInModulepath(isScannableTestClass, classNamePredicate).forEach(
javaElementsResolver::resolveClass);
});
request.getSelectorsByType(ModuleSelector.class).forEach(selector -> {
findAllClassesInModule(selector.getModuleName(), isScannableTestClass, classNamePredicate).forEach(
javaElementsResolver::resolveClass);
});
request.getSelectorsByType(PackageSelector.class).forEach(selector -> {
findAllClassesInPackage(selector.getPackageName(), isScannableTestClass, classNamePredicate).forEach(
javaElementsResolver::resolveClass);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2015-2017 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
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.commons.util;

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

import java.util.List;
import java.util.function.Predicate;

import org.apiguardian.api.API;

/**
* Class finder service providing interface.
*
* <h3>DISCLAIMER</h3>
*
* <p>These utilities are intended solely for usage within the JUnit framework
* itself. <strong>Any usage by external parties is not supported.</strong>
* Use at your own risk!
*
* @since 1.1
*/
@API(status = INTERNAL, since = "1.1")
public interface ModuleClassFinder {

/**
* Special name indicating a search for all classes in all modules of the module-path.
*/
String ALL_MODULE_PATH = "ALL-MODULE-PATH";

/**
* Return list of classes of the passed-in module that contains potential testable methods.
*
* @param moduleName name of the module to inspect or {@code ALL-MODULE-PATH}
* @param classTester filter to apply to each class instance
* @param classNameFilter filter to apply to the fully qualified class name
* @return list of classes
*
* @see #ALL_MODULE_PATH
*/
List<Class<?>> findAllClassesInModule(String moduleName, Predicate<Class<?>> classTester,
Predicate<String> classNameFilter);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2015-2017 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
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.commons.util;

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.Predicate;

import org.apiguardian.api.API;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;

/**
* Collection of utilities for working with modules.
*
* <h3>DISCLAIMER</h3>
*
* <p>These utilities are intended solely for usage within the JUnit framework
* itself. <strong>Any usage by external parties is not supported.</strong>
* Use at your own risk!
*
* @since 1.1
*/
@API(status = INTERNAL, since = "1.1")
public final class ModuleUtils {

private static final Logger logger = LoggerFactory.getLogger(ModuleUtils.class);

///CLOVER:OFF
private ModuleUtils() {
/* no-op */
}
///CLOVER:ON

/**
* Convenient short-cut for finding all classes in all modules that are on the module-path.
*/
public static List<Class<?>> findAllClassesInModulepath(Predicate<Class<?>> classTester,
Predicate<String> classNameFilter) {
return findAllClassesInModule(ModuleClassFinder.ALL_MODULE_PATH, classTester, classNameFilter);
}

/**
* Find all classes in the specified module.
*
* @param moduleName name of the module to inspect or {@code ALL-MODULE-PATH}
* @param classTester filter to apply to each class instance
* @param classNameFilter filter to apply to the fully qualified class name
* @return list of classes matching the passed-in criteria
*/
public static List<Class<?>> findAllClassesInModule(String moduleName, Predicate<Class<?>> classTester,
Predicate<String> classNameFilter) {
Preconditions.notBlank(moduleName, "module name must not be null or blank");
Preconditions.notNull(classTester, "class tester must not be null");
Preconditions.notNull(classNameFilter, "class name filter must not be null");

ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader();
List<Class<?>> classes = new ArrayList<>();

logger.config(() -> "Loading auto-detected class finders...");
int serviceCounter = 0;
for (ModuleClassFinder classFinder : ServiceLoader.load(ModuleClassFinder.class, classLoader)) {
classes.addAll(classFinder.findAllClassesInModule(moduleName, classTester, classNameFilter));
serviceCounter++;
}
if (serviceCounter == 0) {
logger.warn(() -> "No module class finder service registered! No test classes found.");
}
return Collections.unmodifiableList(classes);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class AvailableOptions {
private final OptionSpec<URI> selectedUris;
private final OptionSpec<String> selectedFiles;
private final OptionSpec<String> selectedDirectories;
private final OptionSpec<Void> scanModulePath;
private final OptionSpec<String> selectedModules;
private final OptionSpec<String> selectedPackages;
private final OptionSpec<String> selectedClasses;
private final OptionSpec<String> selectedMethods;
Expand Down Expand Up @@ -150,6 +152,16 @@ class AvailableOptions {
"Select a classpath resource for test discovery. This option can be repeated.") //
.withRequiredArg();

// --- Java Platform Module System -------------------------------------

scanModulePath = parser.acceptsAll(asList("scan-modulepath", "scan-module-path"), //
"EXPERIMENTAL: Scan all modules on the module-path for test discovery.");

selectedModules = parser.acceptsAll(asList("o", "select-module"), //
"EXPERIMENTAL: Select single module for test discovery. This option can be repeated.") //
.withRequiredArg() //
.describedAs("module name");

// --- Filters ---------------------------------------------------------

includeClassNamePattern = parser.acceptsAll(asList("n", "include-classname"),
Expand Down Expand Up @@ -217,6 +229,8 @@ CommandLineOptions toCommandLineOptions(OptionSet detectedOptions) {
result.setSelectedUris(detectedOptions.valuesOf(this.selectedUris));
result.setSelectedFiles(detectedOptions.valuesOf(this.selectedFiles));
result.setSelectedDirectories(detectedOptions.valuesOf(this.selectedDirectories));
result.setScanModulepath(detectedOptions.has(this.scanModulePath));
result.setSelectedModules(detectedOptions.valuesOf(this.selectedModules));
result.setSelectedPackages(detectedOptions.valuesOf(this.selectedPackages));
result.setSelectedClasses(detectedOptions.valuesOf(this.selectedClasses));
result.setSelectedMethods(detectedOptions.valuesOf(this.selectedMethods));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ public class CommandLineOptions {
private boolean scanClasspath;
private List<Path> selectedClasspathEntries = emptyList();

private boolean scanModulepath;

private List<URI> selectedUris = emptyList();
private List<String> selectedFiles = emptyList();
private List<String> selectedDirectories = emptyList();
private List<String> selectedModules = emptyList();
private List<String> selectedPackages = emptyList();
private List<String> selectedClasses = emptyList();
private List<String> selectedMethods = emptyList();
Expand Down Expand Up @@ -92,6 +95,14 @@ public void setScanClasspath(boolean scanClasspath) {
this.scanClasspath = scanClasspath;
}

public boolean isScanModulepath() {
return scanModulepath;
}

public void setScanModulepath(boolean scanModulepath) {
this.scanModulepath = scanModulepath;
}

public Details getDetails() {
return details;
}
Expand Down Expand Up @@ -132,6 +143,14 @@ public void setSelectedDirectories(List<String> selectedDirectories) {
this.selectedDirectories = selectedDirectories;
}

public List<String> getSelectedModules() {
return selectedModules;
}

public void setSelectedModules(List<String> selectedModules) {
this.selectedModules = selectedModules;
}

public List<String> getSelectedPackages() {
return selectedPackages;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -55,6 +56,9 @@ private List<? extends DiscoverySelector> createDiscoverySelectors(CommandLineOp
"Scanning the classpath and using explicit selectors at the same time is not supported");
return createClasspathRootSelectors(options);
}
if (options.isScanModulepath()) {
return Collections.singletonList(DiscoverySelectors.selectModulepath());
}
return createExplicitDiscoverySelectors(options);
}

Expand All @@ -77,6 +81,7 @@ private List<DiscoverySelector> createExplicitDiscoverySelectors(CommandLineOpti
options.getSelectedUris().stream().map(DiscoverySelectors::selectUri).forEach(selectors::add);
options.getSelectedFiles().stream().map(DiscoverySelectors::selectFile).forEach(selectors::add);
options.getSelectedDirectories().stream().map(DiscoverySelectors::selectDirectory).forEach(selectors::add);
options.getSelectedModules().stream().map(DiscoverySelectors::selectModule).forEach(selectors::add);
options.getSelectedPackages().stream().map(DiscoverySelectors::selectPackage).forEach(selectors::add);
options.getSelectedClasses().stream().map(DiscoverySelectors::selectClass).forEach(selectors::add);
options.getSelectedMethods().stream().map(DiscoverySelectors::selectMethod).forEach(selectors::add);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.platform.engine.discovery;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.STABLE;
import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList;

Expand Down Expand Up @@ -240,6 +241,31 @@ public static ClasspathResourceSelector selectClasspathResource(String classpath
return new ClasspathResourceSelector(classpathResourceName);
}

/**
* Create a {@code ModulepathSelector} for scanning all modules on the module-path.
*
* @see ModulepathSelector
*/
@API(status = EXPERIMENTAL, since = "1.1")
public static ModulepathSelector selectModulepath() {
return new ModulepathSelector();
}

/**
* Create a {@code ModuleSelector} for the supplied module name.
*
* <p>The unnamed module is not supported.
*
* @param moduleName the module name to select; never {@code null} and
* never blank
* @see ModuleSelector
*/
@API(status = EXPERIMENTAL, since = "1.1")
public static ModuleSelector selectModule(String moduleName) {
Preconditions.notBlank(moduleName, "Module name must not be null or blank");
return new ModuleSelector(moduleName.trim());
}

/**
* Create a {@code PackageSelector} for the supplied package name.
*
Expand Down
Loading

0 comments on commit 5416db7

Please sign in to comment.