Skip to content

Commit ffce646

Browse files
committed
Validate that a JUnit 5 launch has no dependencies to JUnit 6 bundles
This change introduces support for custom OSGi ResolverHooks in the PDE launch and validation workflow, specifically enabling exclusion of JUnit 6 bundles when running JUnit 5 tests. The changes add a mechanism to inject a ResolverHook into the bundle validation process and implement a hook that filters out JUnit 6 bundles, improving compatibility and reliability of JUnit 5 plug-in test launches. Fixes: #2045
1 parent 5878cb4 commit ffce646

File tree

8 files changed

+154
-6
lines changed

8 files changed

+154
-6
lines changed

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleValidationOperation.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import org.eclipse.osgi.util.NLS;
3232
import org.eclipse.pde.core.plugin.IPluginModelBase;
3333
import org.eclipse.pde.internal.build.BundleHelper;
34+
import org.osgi.framework.hooks.resolver.ResolverHook;
3435

3536
public class BundleValidationOperation implements IWorkspaceRunnable {
3637

3738
private static StateObjectFactory FACTORY;
3839

3940
private final Set<IPluginModelBase> fModels;
4041
private final Dictionary<String, String>[] fProperties;
42+
private final ResolverHook fResolverHook;
4143
private State fState;
4244

4345
@SuppressWarnings("unchecked")
@@ -46,8 +48,13 @@ public BundleValidationOperation(Set<IPluginModelBase> models) {
4648
}
4749

4850
public BundleValidationOperation(Set<IPluginModelBase> models, Dictionary<String, String>[] properties) {
51+
this(models, properties, null);
52+
}
53+
54+
public BundleValidationOperation(Set<IPluginModelBase> models, Dictionary<String, String>[] properties, ResolverHook resolverHook) {
4955
fModels = models;
5056
fProperties = properties;
57+
fResolverHook = resolverHook;
5158
}
5259

5360
@Override
@@ -57,6 +64,9 @@ public void run(IProgressMonitor monitor) throws CoreException {
5764
}
5865
SubMonitor subMonitor = SubMonitor.convert(monitor, fModels.size() + 1);
5966
fState = FACTORY.createState(true);
67+
if (fResolverHook != null) {
68+
fState.setResolverHookFactory(c -> fResolverHook);
69+
}
6070
long id = 1;
6171
for (IPluginModelBase fModel : fModels) {
6272
BundleDescription bundle = fModel.getBundleDescription();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<component id="org.eclipse.pde.launching" version="2">
3+
<resource path="src/org/eclipse/pde/launching/JUnitEclipsePluginValidationOperation.java" type="org.eclipse.pde.launching.JUnitEclipsePluginValidationOperation">
4+
<filter id="576720909">
5+
<message_arguments>
6+
<message_argument value="EclipsePluginValidationOperation"/>
7+
<message_argument value="JUnitEclipsePluginValidationOperation"/>
8+
</message_arguments>
9+
</filter>
10+
</resource>
11+
</component>

ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/PDEMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class PDEMessages extends NLS {
3636
public static String WorkbenchLauncherConfigurationDelegate_noStartup;
3737
public static String JUnitLaunchConfiguration_error_notaplugin;
3838
public static String JUnitLaunchConfiguration_error_missingPlugin;
39+
public static String JUnitLaunchConfiguration_error_invalidJunitVersion;
3940

4041
public static String OSGiLaunchConfiguration_cannotFindLaunchConfiguration;
4142
public static String OSGiLaunchConfiguration_selected;

ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchValidationOperation.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,14 @@ public LaunchValidationOperation(ILaunchConfiguration configuration, Set<IPlugin
5858

5959
@Override
6060
public void run(IProgressMonitor monitor) throws CoreException {
61-
fOperation = new BundleValidationOperation(fModels, getPlatformProperties());
61+
fOperation = createOperation(getPlatformProperties());
6262
fOperation.run(monitor);
6363
}
6464

65+
protected BundleValidationOperation createOperation(Dictionary<String, String>[] properties) throws CoreException {
66+
return new BundleValidationOperation(fModels, properties);
67+
}
68+
6569
@SuppressWarnings("unchecked")
6670
protected Dictionary<String, String>[] getPlatformProperties() throws CoreException {
6771
IExecutionEnvironment[] envs = getMatchingEnvironments();

ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/pderesources.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ WorkbenchLauncherConfigurationDelegate_jrePathNotFound = The installation path t
2525
WorkbenchLauncherConfigurationDelegate_noStartup = Launching failed. Bootstrap code cannot be found.
2626
JUnitLaunchConfiguration_error_notaplugin = Could not launch the JUnit plug-in tests because project ''{0}'' is not a plug-in project.
2727
JUnitLaunchConfiguration_error_missingPlugin = Required plug-in ''{0}'' could not be found.
28+
JUnitLaunchConfiguration_error_invalidJunitVersion = {0} {1} is incompatible with JUnit {2} runtime selected for the launch.
2829

2930
OSGiLaunchConfiguration_cannotFindLaunchConfiguration=Cannot find the {0} OSGi framework.
3031
OSGiLaunchConfiguration_selected=selected
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package org.eclipse.pde.launching;
2+
3+
import java.util.Arrays;
4+
import java.util.Collection;
5+
import java.util.Dictionary;
6+
import java.util.HashMap;
7+
import java.util.Iterator;
8+
import java.util.Map;
9+
import java.util.Set;
10+
11+
import org.eclipse.core.runtime.CoreException;
12+
import org.eclipse.core.runtime.IStatus;
13+
import org.eclipse.core.runtime.Status;
14+
import org.eclipse.debug.core.ILaunchConfiguration;
15+
import org.eclipse.osgi.service.resolver.BundleDescription;
16+
import org.eclipse.osgi.util.NLS;
17+
import org.eclipse.pde.core.plugin.IPluginModelBase;
18+
import org.eclipse.pde.internal.core.BundleValidationOperation;
19+
import org.eclipse.pde.internal.launching.PDELaunchingPlugin;
20+
import org.eclipse.pde.internal.launching.PDEMessages;
21+
import org.eclipse.pde.internal.launching.launcher.EclipsePluginValidationOperation;
22+
import org.osgi.framework.Constants;
23+
import org.osgi.framework.Version;
24+
import org.osgi.framework.VersionRange;
25+
import org.osgi.framework.hooks.resolver.ResolverHook;
26+
import org.osgi.framework.wiring.BundleCapability;
27+
import org.osgi.framework.wiring.BundleRequirement;
28+
import org.osgi.framework.wiring.BundleRevision;
29+
30+
public final class JUnitEclipsePluginValidationOperation extends EclipsePluginValidationOperation implements ResolverHook {
31+
32+
private static final VersionRange JUNIT5_VERSION_RANGE = new VersionRange("[1.0.0,6.0.0)"); //$NON-NLS-1$
33+
34+
private static final String JUNIT_BUNDLE_PREFIX = "junit"; //$NON-NLS-1$
35+
36+
private final Map<BundleDescription, IStatus[]> errors;
37+
private final VersionRange junitVersionRange;
38+
private final int junitVersion;
39+
40+
@SuppressWarnings("restriction")
41+
public JUnitEclipsePluginValidationOperation(ILaunchConfiguration configuration, Set<IPluginModelBase> models) {
42+
super(configuration, models);
43+
errors = new HashMap<>(2);
44+
org.eclipse.jdt.internal.junit.launcher.ITestKind testKind = org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants.getTestRunnerKind(configuration);
45+
switch (testKind.getId()) {
46+
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT5_TEST_KIND_ID -> {
47+
junitVersionRange = JUNIT5_VERSION_RANGE;
48+
junitVersion = 5;
49+
}
50+
default -> {
51+
junitVersionRange = null;
52+
junitVersion = -1;
53+
}
54+
}
55+
}
56+
57+
protected BundleValidationOperation createOperation(Dictionary<String, String>[] properties) throws CoreException {
58+
ResolverHook hook = junitVersionRange != null ? this : null;
59+
BundleValidationOperation op = new BundleValidationOperation(fModels, properties, hook);
60+
return op;
61+
}
62+
63+
@Override
64+
public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
65+
if (!isOptional(requirement)) {
66+
BundleRevision requirementRevision = requirement.getRevision();
67+
String requirementName = requirementRevision.getSymbolicName();
68+
if (!requirementName.startsWith(JUNIT_BUNDLE_PREFIX)) {
69+
Iterator<BundleCapability> iterator = candidates.iterator();
70+
while (iterator.hasNext()) {
71+
BundleCapability candidate = iterator.next();
72+
BundleRevision candidateRevision = candidate.getRevision();
73+
String name = candidateRevision.getSymbolicName();
74+
Version version = candidateRevision.getVersion();
75+
if (!junitVersionRange.includes(version) && name.startsWith(JUNIT_BUNDLE_PREFIX)) {
76+
Version requirementVersion = requirementRevision.getVersion();
77+
BundleDescription bundle = getState().getBundle(requirementName, requirementVersion);
78+
if (bundle != null) {
79+
String error = NLS.bind(PDEMessages.JUnitLaunchConfiguration_error_invalidJunitVersion, new Object[] {name, version, Integer.valueOf(junitVersion)});
80+
IStatus[] bundleErrors = errors.computeIfAbsent(bundle, b -> new Status[0]);
81+
if (!Arrays.stream(bundleErrors).map(IStatus::getMessage).anyMatch(m -> error.equals(m))) {
82+
IStatus[] newBundleErrors = Arrays.copyOf(bundleErrors, bundleErrors.length + 1);
83+
newBundleErrors[bundleErrors.length] = Status.error(error);
84+
errors.put(bundle, newBundleErrors);
85+
}
86+
} else {
87+
PDELaunchingPlugin.log(Status.error("Bundle not found: " + requirementName + " " + requirementVersion, new IllegalStateException())); //$NON-NLS-1$ //$NON-NLS-2$
88+
}
89+
}
90+
}
91+
}
92+
}
93+
}
94+
95+
private boolean isOptional(BundleRequirement requirement) {
96+
return Constants.RESOLUTION_OPTIONAL.equals(requirement.getDirectives().get(Constants.RESOLUTION_DIRECTIVE));
97+
}
98+
99+
@Override
100+
public void filterResolvable(Collection<BundleRevision> candidates) {
101+
}
102+
103+
@Override
104+
public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) {
105+
}
106+
107+
@Override
108+
public void end() {
109+
}
110+
111+
@Override
112+
public boolean hasErrors() {
113+
return super.hasErrors() || !errors.isEmpty();
114+
}
115+
116+
@Override
117+
public Map<Object, Object[]> getInput() {
118+
Map<Object, Object[]> map = super.getInput();
119+
map.putAll(errors);
120+
return map;
121+
}
122+
123+
}

ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/JUnitLaunchConfigurationDelegate.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
import org.eclipse.pde.internal.launching.PDELaunchingPlugin;
7575
import org.eclipse.pde.internal.launching.PDEMessages;
7676
import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
77-
import org.eclipse.pde.internal.launching.launcher.EclipsePluginValidationOperation;
7877
import org.eclipse.pde.internal.launching.launcher.LaunchArgumentsHelper;
7978
import org.eclipse.pde.internal.launching.launcher.LaunchConfigurationHelper;
8079
import org.eclipse.pde.internal.launching.launcher.LaunchPluginValidator;
@@ -610,7 +609,7 @@ protected void validateProjectDependencies(ILaunchConfiguration configuration, I
610609
* a progress monitor
611610
*/
612611
protected void validatePluginDependencies(ILaunchConfiguration configuration, IProgressMonitor monitor) throws CoreException {
613-
EclipsePluginValidationOperation op = new EclipsePluginValidationOperation(configuration, fModels.keySet(), launchMode);
612+
JUnitEclipsePluginValidationOperation op = new JUnitEclipsePluginValidationOperation(configuration, fModels.keySet());
614613
LaunchPluginValidator.runValidationOperation(op, monitor);
615614
}
616615
}

ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/launcher/JUnitPluginLaunchConfigurationDelegate.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@
8787
import org.eclipse.pde.internal.launching.IPDEConstants;
8888
import org.eclipse.pde.internal.launching.JUnitLaunchRequirements;
8989
import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
90-
import org.eclipse.pde.internal.launching.launcher.EclipsePluginValidationOperation;
9190
import org.eclipse.pde.internal.launching.launcher.LaunchArgumentsHelper;
9291
import org.eclipse.pde.internal.launching.launcher.LaunchConfigurationHelper;
9392
import org.eclipse.pde.internal.launching.launcher.LaunchPluginValidator;
@@ -1224,8 +1223,8 @@ protected void validateProjectDependencies(ILaunchConfiguration configuration, I
12241223
*/
12251224
protected void validatePluginDependencies(ILaunchConfiguration configuration, IProgressMonitor monitor)
12261225
throws CoreException {
1227-
EclipsePluginValidationOperation op = new EclipsePluginValidationOperation(configuration, fModels.keySet(),
1228-
launchMode);
1226+
JUnitEclipsePluginValidationOperation op = new JUnitEclipsePluginValidationOperation(configuration,
1227+
fModels.keySet());
12291228
LaunchPluginValidator.runValidationOperation(op, monitor);
12301229
}
12311230
}

0 commit comments

Comments
 (0)