-
Notifications
You must be signed in to change notification settings - Fork 163
[WIP] Support junit6 test framework #1819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
07d8ca4
89a04b7
b10581c
0c895af
051cdc1
a52c3f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,9 +20,11 @@ | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| import org.apache.commons.lang3.StringUtils; | ||||||||||||||||||||||
| import org.eclipse.core.runtime.CoreException; | ||||||||||||||||||||||
| import org.eclipse.core.runtime.FileLocator; | ||||||||||||||||||||||
| import org.eclipse.core.runtime.IProgressMonitor; | ||||||||||||||||||||||
| import org.eclipse.core.runtime.IStatus; | ||||||||||||||||||||||
| import org.eclipse.core.runtime.NullProgressMonitor; | ||||||||||||||||||||||
| import org.eclipse.core.runtime.Platform; | ||||||||||||||||||||||
| import org.eclipse.core.runtime.Status; | ||||||||||||||||||||||
| import org.eclipse.debug.core.ILaunch; | ||||||||||||||||||||||
| import org.eclipse.debug.core.ILaunchConfiguration; | ||||||||||||||||||||||
|
|
@@ -37,6 +39,7 @@ | |||||||||||||||||||||
| import org.eclipse.jdt.core.dom.SingleVariableDeclaration; | ||||||||||||||||||||||
| import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil; | ||||||||||||||||||||||
| import org.eclipse.jdt.launching.VMRunnerConfiguration; | ||||||||||||||||||||||
| import org.osgi.framework.Bundle; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import java.io.BufferedWriter; | ||||||||||||||||||||||
| import java.io.File; | ||||||||||||||||||||||
|
|
@@ -45,13 +48,17 @@ | |||||||||||||||||||||
| import java.io.OutputStreamWriter; | ||||||||||||||||||||||
| import java.lang.reflect.InvocationTargetException; | ||||||||||||||||||||||
| import java.lang.reflect.Method; | ||||||||||||||||||||||
| import java.net.URL; | ||||||||||||||||||||||
| import java.nio.charset.StandardCharsets; | ||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||
| import java.util.Arrays; | ||||||||||||||||||||||
| import java.util.HashSet; | ||||||||||||||||||||||
| import java.util.LinkedList; | ||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||
| import java.util.Optional; | ||||||||||||||||||||||
| import java.util.Set; | ||||||||||||||||||||||
| import java.util.regex.Matcher; | ||||||||||||||||||||||
| import java.util.regex.Pattern; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public class JUnitLaunchConfigurationDelegate extends org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -60,6 +67,30 @@ public class JUnitLaunchConfigurationDelegate extends org.eclipse.jdt.junit.laun | |||||||||||||||||||||
| private static final Set<String> testNameArgs = new HashSet<>( | ||||||||||||||||||||||
| Arrays.asList("-test", "-classNames", "-packageNameFile", "-testNameFile")); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Pattern to match junit-platform-* and junit-jupiter-* jars with version numbers | ||||||||||||||||||||||
| // Supports both Maven format (junit-jupiter-api-6.0.0.jar) and | ||||||||||||||||||||||
| // OSGi bundle format (junit-jupiter-api_6.0.0.jar) | ||||||||||||||||||||||
| private static final Pattern JUNIT_VERSION_PATTERN = Pattern.compile( | ||||||||||||||||||||||
| "(junit-platform-[a-z-]+|junit-jupiter-[a-z-]+)[-_](\\d+)\\.(\\d+)\\.(\\d+)\\.jar$"); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // JUnit bundle names that need to be injected for test execution | ||||||||||||||||||||||
| private static final String[] JUNIT_BUNDLE_NAMES = { | ||||||||||||||||||||||
| "junit-jupiter-api", | ||||||||||||||||||||||
| "junit-jupiter-engine", | ||||||||||||||||||||||
| "junit-jupiter-params", | ||||||||||||||||||||||
| "junit-platform-commons", | ||||||||||||||||||||||
| "junit-platform-engine", | ||||||||||||||||||||||
| "junit-platform-launcher", | ||||||||||||||||||||||
| "junit-platform-suite-api", | ||||||||||||||||||||||
| "junit-platform-suite-engine" | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Common dependency bundle names | ||||||||||||||||||||||
| private static final String[] COMMON_BUNDLE_NAMES = { | ||||||||||||||||||||||
| "org.opentest4j", | ||||||||||||||||||||||
| "org.apiguardian.api" | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public JUnitLaunchConfigurationDelegate(Argument args) { | ||||||||||||||||||||||
| super(); | ||||||||||||||||||||||
| this.args = args; | ||||||||||||||||||||||
|
|
@@ -82,8 +113,24 @@ public Response<JUnitLaunchArguments> getJUnitLaunchArguments(ILaunchConfigurati | |||||||||||||||||||||
| launchArguments.workingDirectory = config.getWorkingDirectory(); | ||||||||||||||||||||||
| launchArguments.mainClass = config.getClassToLaunch(); | ||||||||||||||||||||||
| launchArguments.projectName = javaProject.getProject().getName(); | ||||||||||||||||||||||
| launchArguments.classpath = config.getClassPath(); | ||||||||||||||||||||||
| launchArguments.modulepath = config.getModulepath(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Debug: Log original classpath | ||||||||||||||||||||||
| JUnitPlugin.logInfo("[JUnit Launch] TestKind: " + this.args.testKind); | ||||||||||||||||||||||
| JUnitPlugin.logInfo("[JUnit Launch] Original classpath entries: " + | ||||||||||||||||||||||
| (config.getClassPath() != null ? config.getClassPath().length : 0)); | ||||||||||||||||||||||
| if (config.getClassPath() != null) { | ||||||||||||||||||||||
| for (final String cp : config.getClassPath()) { | ||||||||||||||||||||||
| JUnitPlugin.logInfo("[JUnit Launch] Classpath: " + cp); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| launchArguments.classpath = filterClasspathByTestKind(config.getClassPath(), this.args.testKind, true); | ||||||||||||||||||||||
| launchArguments.modulepath = filterClasspathByTestKind(config.getModulepath(), this.args.testKind, false); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Debug: Log filtered classpath | ||||||||||||||||||||||
| JUnitPlugin.logInfo("[JUnit Launch] Filtered classpath entries: " + | ||||||||||||||||||||||
| (launchArguments.classpath != null ? launchArguments.classpath.length : 0)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| launchArguments.vmArguments = getVmArguments(config); | ||||||||||||||||||||||
| launchArguments.programArguments = parseParameters(config.getProgramArguments()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -147,7 +194,8 @@ private void addTestItemArgs(List<String> arguments) throws CoreException { | |||||||||||||||||||||
| arguments.add("-test"); | ||||||||||||||||||||||
| final IMethod method = (IMethod) JavaCore.create(this.args.testNames[0]); | ||||||||||||||||||||||
| String testName = method.getElementName(); | ||||||||||||||||||||||
| if (this.args.testKind == TestKind.JUnit5 && method.getParameters().length > 0) { | ||||||||||||||||||||||
| if ((this.args.testKind == TestKind.JUnit5 || this.args.testKind == TestKind.JUnit6) && | ||||||||||||||||||||||
| method.getParameters().length > 0) { | ||||||||||||||||||||||
|
Comment on lines
+197
to
+198
|
||||||||||||||||||||||
| final ICompilationUnit unit = method.getCompilationUnit(); | ||||||||||||||||||||||
| if (unit == null) { | ||||||||||||||||||||||
| throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, | ||||||||||||||||||||||
|
|
@@ -206,4 +254,223 @@ private String createTestNamesFile(String[] testNames) throws CoreException { | |||||||||||||||||||||
| IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, "", e)); //$NON-NLS-1$ | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Check if the given major version is compatible with the test kind. | ||||||||||||||||||||||
| * For JUnit 5: version < 6 (1.x for platform, 5.x for jupiter) | ||||||||||||||||||||||
| * For JUnit 6: version >= 6 | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * @param majorVersion the major version number | ||||||||||||||||||||||
| * @param testKind the test framework kind | ||||||||||||||||||||||
| * @return true if compatible, false otherwise | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| private boolean isVersionCompatible(int majorVersion, TestKind testKind) { | ||||||||||||||||||||||
| switch (testKind) { | ||||||||||||||||||||||
| case JUnit5: | ||||||||||||||||||||||
| return majorVersion < 6; | ||||||||||||||||||||||
| case JUnit6: | ||||||||||||||||||||||
| return majorVersion >= 6; | ||||||||||||||||||||||
| default: | ||||||||||||||||||||||
| return true; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Filter the classpath/modulepath based on the test kind to avoid version conflicts. | ||||||||||||||||||||||
| * For JUnit 5: use junit-platform-* version 1.x and junit-jupiter-* version 5.x | ||||||||||||||||||||||
| * For JUnit 6: use junit-platform-* version 6.x and junit-jupiter-* version 6.x | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Also optionally injects the required JUnit bundles from OSGi into the classpath since | ||||||||||||||||||||||
| * Eclipse's ClasspathLocalizer only adds the junit*runtime bundle but not its Require-Bundle deps. | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * @param paths the original classpath or modulepath array | ||||||||||||||||||||||
| * @param testKind the test framework kind | ||||||||||||||||||||||
| * @param injectBundles whether to inject missing JUnit bundles (should only be true for classpath) | ||||||||||||||||||||||
| * @return filtered paths array with JUnit bundles optionally injected | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| private String[] filterClasspathByTestKind(String[] paths, TestKind testKind, boolean injectBundles) { | ||||||||||||||||||||||
| if (paths == null || testKind == null) { | ||||||||||||||||||||||
| return paths; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // For non-JUnit5/6 test kinds, no filtering needed | ||||||||||||||||||||||
| if (testKind != TestKind.JUnit5 && testKind != TestKind.JUnit6) { | ||||||||||||||||||||||
| return paths; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| final List<String> filteredPaths = new ArrayList<>(); | ||||||||||||||||||||||
| final Set<String> foundJUnitArtifacts = new HashSet<>(); | ||||||||||||||||||||||
| // Track essential artifacts for incomplete dependency detection | ||||||||||||||||||||||
| boolean hasApi = false; | ||||||||||||||||||||||
| boolean hasEngine = false; | ||||||||||||||||||||||
| boolean hasLauncher = false; | ||||||||||||||||||||||
| int includedCount = 0; | ||||||||||||||||||||||
| int excludedCount = 0; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Single pass: filter paths and detect incomplete dependencies simultaneously | ||||||||||||||||||||||
| for (final String path : paths) { | ||||||||||||||||||||||
| final String fileName = new File(path).getName(); | ||||||||||||||||||||||
| final Matcher matcher = JUNIT_VERSION_PATTERN.matcher(fileName); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!matcher.find()) { | ||||||||||||||||||||||
| // Not a junit-platform/jupiter jar, include it directly | ||||||||||||||||||||||
| filteredPaths.add(path); | ||||||||||||||||||||||
| continue; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| final String artifactName = matcher.group(1); | ||||||||||||||||||||||
| final int majorVersion = Integer.parseInt(matcher.group(2)); | ||||||||||||||||||||||
|
||||||||||||||||||||||
| final int majorVersion = Integer.parseInt(matcher.group(2)); | |
| final String versionGroup = matcher.group(2); | |
| int majorVersion; | |
| try { | |
| majorVersion = Integer.parseInt(versionGroup); | |
| } catch (NumberFormatException e) { | |
| JUnitPlugin.logInfo("[Classpath Filter] " + testKind + " - Skipping: " + fileName + " (invalid version: " + versionGroup + ")"); | |
| excludedCount++; | |
| continue; | |
| } |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop processes all paths and performs pattern matching, tracking metadata for bundle injection. However, the foundJUnitArtifacts.add(artifactName) on line 337 is inside the version-compatible block, meaning if a JUnit artifact is excluded, it won't be tracked. Later, at line 352, paths are removed using JUNIT_VERSION_PATTERN.matcher(), which will match all JUnit artifacts regardless of whether they were tracked. This could lead to removing more artifacts than intended. Consider tracking all JUnit artifacts found, not just compatible ones, or use foundJUnitArtifacts for the removal filter.
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a File directly from fileUrl.getPath() can cause issues on Windows when the URL path contains encoded characters (e.g., spaces as %20). Use new File(fileUrl.toURI()) instead to properly decode the URL path, or use FileLocator.toFileURL() consistently with proper decoding.
| return new File(fileUrl.getPath()).getAbsolutePath(); | |
| return new File(fileUrl.toURI()).getAbsolutePath(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All JUnit-related bundle dependencies have been removed from
Require-Bundle, but the code still references classes from these bundles (e.g.,JUnit5TestFinder,TestKindRegistryin JUnit6TestSearcher.java). This will cause ClassNotFoundException at runtime. The bundles should remain inRequire-Bundleor the dependencies should be marked as optional. If the intention is to load these dynamically via classpath injection, the code needs to handle missing classes gracefully.