Skip to content
Draft
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ val vintageProjects by extra(listOf(
dependencyProject(projects.junitVintageEngine)
))

val mavenizedProjects by extra(platformProjects + jupiterProjects + vintageProjects)
val mavenizedProjects by extra(listOf(dependencyProject(projects.junitStart)) + platformProjects + jupiterProjects + vintageProjects)
val modularProjects by extra(mavenizedProjects - setOf(dependencyProject(projects.junitPlatformConsoleStandalone)))

dependencies {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
package org.junit.platform.commons.util;

import static java.util.stream.Collectors.joining;
import static org.junit.platform.commons.util.ClasspathFilters.CLASS_FILE_SUFFIX;
import static org.junit.platform.commons.util.SearchPathUtils.PACKAGE_SEPARATOR_CHAR;
import static org.junit.platform.commons.util.SearchPathUtils.PACKAGE_SEPARATOR_STRING;
import static org.junit.platform.commons.util.SearchPathUtils.determineSimpleClassName;
import static org.junit.platform.commons.util.StringUtils.isNotBlank;

import java.io.IOException;
Expand Down Expand Up @@ -57,8 +59,6 @@ class DefaultClasspathScanner implements ClasspathScanner {
private static final char CLASSPATH_RESOURCE_PATH_SEPARATOR = '/';
private static final String CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING = String.valueOf(
CLASSPATH_RESOURCE_PATH_SEPARATOR);
private static final char PACKAGE_SEPARATOR_CHAR = '.';
private static final String PACKAGE_SEPARATOR_STRING = String.valueOf(PACKAGE_SEPARATOR_CHAR);

/**
* Malformed class name InternalError like reported in #401.
Expand Down Expand Up @@ -132,7 +132,7 @@ private List<Class<?>> findClassesForUris(List<URI> baseUris, String basePackage
private List<Class<?>> findClassesForUri(URI baseUri, String basePackageName, ClassFilter classFilter) {
List<Class<?>> classes = new ArrayList<>();
// @formatter:off
walkFilesForUri(baseUri, ClasspathFilters.classFiles(),
walkFilesForUri(baseUri, SearchPathUtils::isClassOrSourceFile,
(baseDir, file) ->
processClassFileSafely(baseDir, basePackageName, classFilter, file, classes::add));
// @formatter:on
Expand All @@ -156,7 +156,7 @@ private List<Resource> findResourcesForUris(List<URI> baseUris, String basePacka
private List<Resource> findResourcesForUri(URI baseUri, String basePackageName, ResourceFilter resourceFilter) {
List<Resource> resources = new ArrayList<>();
// @formatter:off
walkFilesForUri(baseUri, ClasspathFilters.resourceFiles(),
walkFilesForUri(baseUri, SearchPathUtils::isResourceFile,
(baseDir, file) ->
processResourceFileSafely(baseDir, basePackageName, resourceFilter, file, resources::add));
// @formatter:on
Expand All @@ -182,10 +182,10 @@ private static void walkFilesForUri(URI baseUri, Predicate<Path> filter, BiConsu
}
}

private void processClassFileSafely(Path baseDir, String basePackageName, ClassFilter classFilter, Path classFile,
private void processClassFileSafely(Path baseDir, String basePackageName, ClassFilter classFilter, Path file,
Consumer<Class<?>> classConsumer) {
try {
String fullyQualifiedClassName = determineFullyQualifiedClassName(baseDir, basePackageName, classFile);
String fullyQualifiedClassName = determineFullyQualifiedClassName(baseDir, basePackageName, file);
if (classFilter.match(fullyQualifiedClassName)) {
try {
// @formatter:off
Expand All @@ -196,12 +196,12 @@ private void processClassFileSafely(Path baseDir, String basePackageName, ClassF
// @formatter:on
}
catch (InternalError internalError) {
handleInternalError(classFile, fullyQualifiedClassName, internalError);
handleInternalError(file, fullyQualifiedClassName, internalError);
}
}
}
catch (Throwable throwable) {
handleThrowable(classFile, throwable);
handleThrowable(file, throwable);
}
}

Expand All @@ -221,12 +221,12 @@ private void processResourceFileSafely(Path baseDir, String basePackageName, Res
}
}

private String determineFullyQualifiedClassName(Path baseDir, String basePackageName, Path classFile) {
private String determineFullyQualifiedClassName(Path baseDir, String basePackageName, Path file) {
// @formatter:off
return Stream.of(
basePackageName,
determineSubpackageName(baseDir, classFile),
determineSimpleClassName(classFile)
determineSubpackageName(baseDir, file),
determineSimpleClassName(file)
)
.filter(value -> !value.isEmpty()) // Handle default package appropriately.
.collect(joining(PACKAGE_SEPARATOR_STRING));
Expand All @@ -253,24 +253,14 @@ private String determineFullyQualifiedResourceName(Path baseDir, String basePack
// @formatter:on
}

private String determineSimpleClassName(Path classFile) {
String fileName = classFile.getFileName().toString();
return fileName.substring(0, fileName.length() - CLASS_FILE_SUFFIX.length());
}

private String determineSimpleResourceName(Path resourceFile) {
return resourceFile.getFileName().toString();
}

private String determineSubpackageName(Path baseDir, Path classFile) {
Path relativePath = baseDir.relativize(classFile.getParent());
private String determineSubpackageName(Path baseDir, Path file) {
Path relativePath = baseDir.relativize(file.getParent());
String pathSeparator = baseDir.getFileSystem().getSeparator();
String subpackageName = relativePath.toString().replace(pathSeparator, PACKAGE_SEPARATOR_STRING);
if (subpackageName.endsWith(pathSeparator)) {
// Workaround for JDK bug: https://bugs.openjdk.java.net/browse/JDK-8153248
subpackageName = subpackageName.substring(0, subpackageName.length() - pathSeparator.length());
}
return subpackageName;
return relativePath.toString().replace(pathSeparator, PACKAGE_SEPARATOR_STRING);
}

private void handleInternalError(Path classFile, String fullyQualifiedClassName, InternalError ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
@API(status = INTERNAL, since = "1.0")
public final class ExceptionUtils {

private static final String JUNIT_START_PACKAGE_PREFIX = "org.junit.start.";

private static final String JUNIT_PLATFORM_LAUNCHER_PACKAGE_PREFIX = "org.junit.platform.launcher.";

private static final Predicate<String> STACK_TRACE_ELEMENT_FILTER = ClassNamePatternFilterUtils //
Expand Down Expand Up @@ -139,6 +141,9 @@ public static void pruneStackTrace(Throwable throwable, List<String> classNames)
prunedStackTrace.addAll(stackTrace.subList(i, stackTrace.size()));
break;
}
else if (className.startsWith(JUNIT_START_PACKAGE_PREFIX)) {
prunedStackTrace.clear();
}
else if (className.startsWith(JUNIT_PLATFORM_LAUNCHER_PACKAGE_PREFIX)) {
prunedStackTrace.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -253,9 +254,10 @@ List<Class<?>> scan(ModuleReference reference) {
try (ModuleReader reader = reference.open()) {
try (Stream<String> names = reader.list()) {
// @formatter:off
return names.filter(name -> name.endsWith(".class"))
.map(this::className)
.filter(name -> !"module-info".equals(name))
return names.filter(name -> !name.endsWith("/")) // remove directories
.map(Path::of)
.filter(SearchPathUtils::isClassOrSourceFile)
.map(SearchPathUtils::determineFullyQualifiedClassName)
.filter(classFilter::match)
.<Class<?>> map(this::loadClassUnchecked)
.filter(classFilter::match)
Expand All @@ -268,15 +270,6 @@ List<Class<?>> scan(ModuleReference reference) {
}
}

/**
* Convert resource name to binary class name.
*/
private String className(String resourceName) {
resourceName = resourceName.substring(0, resourceName.length() - 6); // 6 = ".class".length()
resourceName = resourceName.replace('/', '.');
return resourceName;
}

/**
* Load class by its binary name.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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.util;

import static java.util.stream.Collectors.joining;

import java.nio.file.Path;
import java.util.stream.IntStream;

import org.junit.platform.commons.JUnitException;

/**
* @since 1.11
*/
class SearchPathUtils {

static final char PACKAGE_SEPARATOR_CHAR = '.';
static final String PACKAGE_SEPARATOR_STRING = String.valueOf(PACKAGE_SEPARATOR_CHAR);
private static final char FILE_NAME_EXTENSION_SEPARATOR_CHAR = '.';

private static final String CLASS_FILE_SUFFIX = ".class";
private static final String SOURCE_FILE_SUFFIX = ".java";

private static final String PACKAGE_INFO_FILE_NAME = "package-info";
private static final String MODULE_INFO_FILE_NAME = "module-info";

// System property defined since Java 12: https://bugs.java/bugdatabase/JDK-8210877
private static final boolean SOURCE_MODE = System.getProperty("jdk.launcher.sourcefile") != null;

static boolean isResourceFile(Path file) {
return !isClassFile(file);
}

static boolean isClassOrSourceFile(Path file) {
var fileName = file.getFileName().toString();
return isClassOrSourceFile(fileName) && !isModuleInfoOrPackageInfo(fileName);
}

private static boolean isModuleInfoOrPackageInfo(String fileName) {
var fileNameWithoutExtension = removeExtension(fileName);
return PACKAGE_INFO_FILE_NAME.equals(fileNameWithoutExtension) //
|| MODULE_INFO_FILE_NAME.equals(fileNameWithoutExtension);
}

static String determineFullyQualifiedClassName(Path path) {
var simpleClassName = determineSimpleClassName(path);
var parent = path.getParent();
return parent == null ? simpleClassName : joinPathNamesWithPackageSeparator(parent.resolve(simpleClassName));
}

private static String joinPathNamesWithPackageSeparator(Path path) {
return IntStream.range(0, path.getNameCount()) //
.mapToObj(i -> path.getName(i).toString()) //
.collect(joining(PACKAGE_SEPARATOR_STRING));
}

static String determineSimpleClassName(Path file) {
return removeExtension(file.getFileName().toString());
}

private static String removeExtension(String fileName) {
int lastDot = fileName.lastIndexOf(FILE_NAME_EXTENSION_SEPARATOR_CHAR);
if (lastDot < 0) {
throw new JUnitException("Expected file name with file extension, but got: " + fileName);
}
return fileName.substring(0, lastDot);
}

private static boolean isClassOrSourceFile(String name) {
return name.endsWith(CLASS_FILE_SUFFIX) || (SOURCE_MODE && name.endsWith(SOURCE_FILE_SUFFIX));
}

private static boolean isClassFile(Path file) {
return file.getFileName().toString().endsWith(CLASS_FILE_SUFFIX);
}

private SearchPathUtils() {
}
}
1 change: 1 addition & 0 deletions junit-platform-console/junit-platform-console.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies {
tasks {
compileJava {
options.compilerArgs.addAll(listOf(
"-Xlint:-module", // due to qualified exports
"--add-modules", "info.picocli",
"--add-reads", "${javaModuleName}=info.picocli"
))
Expand Down
2 changes: 2 additions & 0 deletions junit-platform-console/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@
requires org.junit.platform.launcher;
requires org.junit.platform.reporting;

exports org.junit.platform.console.output to org.junit.start;

provides java.util.spi.ToolProvider with org.junit.platform.console.ConsoleLauncherToolProvider;
}
21 changes: 21 additions & 0 deletions junit-start/junit-start.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
id("junitbuild.java-library-conventions")
}

description = "JUnit Start Module"

dependencies {
api(platform(projects.junitBom))
api(projects.junitJupiter)

compileOnlyApi(libs.apiguardian)
compileOnlyApi(libs.jspecify)
compileOnlyApi(projects.junitJupiterEngine)

implementation(projects.junitPlatformLauncher)
implementation(projects.junitPlatformConsole)
}

backwardCompatibilityChecks {
enabled = false // TODO enable after initial release
}
37 changes: 37 additions & 0 deletions junit-start/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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
*/

/**
* Defines the API of the JUnit Start module for writing and running tests.
* <p>
* Usage example:
* <pre>{@code
* import module org.junit.start;
*
* void main() {
* JUnit.run();
* }
*
* @Test
* void addition() {
* Assertions.assertEquals(2, 1 + 1, "Addition error detected!");
* }
* }</pre>
*/
module org.junit.start {
requires static transitive org.apiguardian.api;
requires static transitive org.jspecify;

requires transitive org.junit.jupiter;
requires org.junit.platform.launcher;
requires org.junit.platform.console;

exports org.junit.start;
}
Loading