Skip to content

Commit 8bca50d

Browse files
committed
Use bundle versions when adding JUnit to launch
This change adjusts JUnitLaunchConfigurationDelegate, to use bundle versions when adding required JUnit bundles. E.g. when adding JUnit bundles for a JUnit 5 test, JUnit 5 versions are added. This avoids conflicts when both JUnit 5 and JUnit 6 bundles are in the platform, since they share symbolic names. Fixes: #2006
1 parent c06c4ef commit 8bca50d

File tree

5 files changed

+167
-137
lines changed

5 files changed

+167
-137
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2006, 2025 IBM Corporation and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* IBM Corporation - initial API and implementation
13+
* Advantest - GH issue 2006 - JUnit 5 bundle clashes with JUnit 6
14+
*******************************************************************************/
15+
package org.eclipse.pde.internal.launching;
16+
17+
import java.util.ArrayList;
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
import java.util.LinkedHashSet;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Set;
24+
25+
import org.eclipse.core.runtime.CoreException;
26+
import org.eclipse.core.runtime.IStatus;
27+
import org.eclipse.core.runtime.Status;
28+
import org.eclipse.debug.core.ILaunchConfiguration;
29+
import org.eclipse.osgi.service.resolver.BundleDescription;
30+
import org.eclipse.osgi.util.NLS;
31+
import org.eclipse.pde.core.plugin.IPluginModelBase;
32+
import org.eclipse.pde.core.plugin.PluginRegistry;
33+
import org.eclipse.pde.internal.core.DependencyManager;
34+
import org.eclipse.pde.internal.core.PDECore;
35+
import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
36+
import org.osgi.framework.VersionRange;
37+
import org.osgi.framework.wiring.BundleRevision;
38+
39+
public class JUnitLaunchRequirements {
40+
41+
public static final String JUNIT4_RUNTIME_PLUGIN = "org.eclipse.jdt.junit4.runtime"; //$NON-NLS-1$
42+
public static final String JUNIT5_RUNTIME_PLUGIN = "org.eclipse.jdt.junit5.runtime"; //$NON-NLS-1$
43+
44+
private static final VersionRange JUNIT5_VERSIONS = new VersionRange("[1, 5)"); //$NON-NLS-1$
45+
46+
// we add launcher and jupiter.engine to support @RunWith(JUnitPlatform.class)
47+
private static final String[] JUNIT5_RUN_WITH_PLUGINS = {"junit-platform-launcher", //$NON-NLS-1$
48+
"junit-jupiter-engine", //$NON-NLS-1$
49+
};
50+
51+
public static void addRequiredJunitRuntimePlugins(ILaunchConfiguration configuration, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
52+
Collection<String> plugins = getRequiredJunitRuntimePlugins(configuration);
53+
addPlugins(plugins, allBundles, allModels);
54+
if (plugins.contains(JUNIT5_RUNTIME_PLUGIN) && (allBundles.containsKey("junit-platform-runner") || allBundles.containsKey("org.junit.platform.runner"))) { //$NON-NLS-1$ //$NON-NLS-2$
55+
Set<BundleDescription> descriptions = JUnitLaunchRequirements.junit5PlatformRequirements();
56+
Set<BundleDescription> junitRquirements = DependencyManager.findRequirementsClosure(descriptions);
57+
addAbsentRequirements(junitRquirements, allBundles, allModels);
58+
}
59+
}
60+
61+
@SuppressWarnings("restriction")
62+
public static Collection<String> getRequiredJunitRuntimePlugins(ILaunchConfiguration configuration) {
63+
org.eclipse.jdt.internal.junit.launcher.ITestKind testKind = org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants.getTestRunnerKind(configuration);
64+
if (testKind.isNull()) {
65+
return Collections.emptyList();
66+
}
67+
List<String> plugins = new ArrayList<>();
68+
plugins.add("org.eclipse.pde.junit.runtime"); //$NON-NLS-1$
69+
70+
if (org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT4_TEST_KIND_ID.equals(testKind.getId())) {
71+
plugins.add(JUNIT4_RUNTIME_PLUGIN);
72+
} else if (org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT5_TEST_KIND_ID.equals(testKind.getId())) {
73+
plugins.add(JUNIT5_RUNTIME_PLUGIN);
74+
}
75+
return plugins;
76+
}
77+
78+
private static void addPlugins(Collection<String> plugins, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
79+
Set<String> requiredPlugins = new LinkedHashSet<>(plugins);
80+
81+
Set<BundleDescription> addedRequirements = new LinkedHashSet<>();
82+
addAbsentRequirements(requiredPlugins, addedRequirements, allBundles, allModels);
83+
84+
Set<BundleDescription> requirementsOfRequirements = DependencyManager.findRequirementsClosure(addedRequirements);
85+
addAbsentRequirements(requirementsOfRequirements, allBundles, allModels);
86+
}
87+
88+
private static void addAbsentRequirements(Collection<String> requirements, Set<BundleDescription> addedRequirements, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
89+
for (String id : requirements) {
90+
List<IPluginModelBase> models = allBundles.computeIfAbsent(id, k -> new ArrayList<>());
91+
if (models.stream().noneMatch(m -> m.getBundleDescription().isResolved())) {
92+
IPluginModelBase model = JUnitLaunchRequirements.findRequiredPluginInTargetOrHost(id);
93+
models.add(model);
94+
BundleLauncherHelper.addDefaultStartingBundle(allModels, model);
95+
if (addedRequirements != null) {
96+
addedRequirements.add(model.getBundleDescription());
97+
}
98+
}
99+
}
100+
}
101+
102+
private static void addAbsentRequirements(Set<BundleDescription> toAdd, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
103+
for (BundleDescription requirement : toAdd) {
104+
String id = requirement.getSymbolicName();
105+
List<IPluginModelBase> models = allBundles.computeIfAbsent(id, k -> new ArrayList<>());
106+
boolean replace = !models.isEmpty() && models.stream().noneMatch(m -> m.getBundleDescription().getVersion().equals(requirement.getVersion()));
107+
if (replace || models.stream().noneMatch(m -> m.getBundleDescription().isResolved())) {
108+
IPluginModelBase model = JUnitLaunchRequirements.findRequiredPluginInTargetOrHost(requirement);
109+
if (replace) {
110+
String startLevel = null;
111+
for (IPluginModelBase m : models) {
112+
startLevel = allModels.remove(m);
113+
}
114+
models.clear();
115+
allModels.put(model, startLevel);
116+
}
117+
models.add(model);
118+
BundleLauncherHelper.addDefaultStartingBundle(allModels, model);
119+
}
120+
}
121+
}
122+
123+
public static Set<BundleDescription> junit5PlatformRequirements() throws CoreException {
124+
Set<BundleDescription> descriptions = new LinkedHashSet<>();
125+
for (String id : JUNIT5_RUN_WITH_PLUGINS) {
126+
IPluginModelBase model = findRequiredPluginInTargetOrHost(id, JUNIT5_VERSIONS);
127+
if (model != null) {
128+
BundleDescription description = model.getBundleDescription();
129+
descriptions.add(description);
130+
}
131+
}
132+
return descriptions;
133+
}
134+
135+
public static IPluginModelBase findRequiredPluginInTargetOrHost(String id) throws CoreException {
136+
return findRequiredPluginInTargetOrHost(id, PluginRegistry.findModel(id));
137+
}
138+
139+
private static IPluginModelBase findRequiredPluginInTargetOrHost(String id, VersionRange versionRange) throws CoreException {
140+
return findRequiredPluginInTargetOrHost(id, PluginRegistry.findModel(id, versionRange));
141+
}
142+
143+
public static IPluginModelBase findRequiredPluginInTargetOrHost(BundleRevision bundleRevision) throws CoreException {
144+
String id = bundleRevision.getSymbolicName();
145+
return findRequiredPluginInTargetOrHost(id, PluginRegistry.findModel(bundleRevision));
146+
}
147+
148+
private static IPluginModelBase findRequiredPluginInTargetOrHost(String id, IPluginModelBase model) throws CoreException {
149+
if (model == null || !model.getBundleDescription().isResolved()) {
150+
// prefer bundle from host over unresolved bundle from target
151+
model = PDECore.getDefault().findPluginInHost(id);
152+
}
153+
if (model == null) {
154+
String message = NLS.bind(PDEMessages.JUnitLaunchConfiguration_error_missingPlugin, id);
155+
Status error = new Status(IStatus.ERROR, IPDEConstants.PLUGIN_ID, IStatus.OK, message, null);
156+
throw new CoreException(error);
157+
}
158+
return model;
159+
}
160+
161+
}

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

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,12 @@
2424
import java.nio.file.Path;
2525
import java.util.ArrayList;
2626
import java.util.Arrays;
27-
import java.util.Collection;
2827
import java.util.Collections;
2928
import java.util.Comparator;
30-
import java.util.HashSet;
3129
import java.util.LinkedHashMap;
32-
import java.util.LinkedHashSet;
3330
import java.util.List;
3431
import java.util.Map;
3532
import java.util.Properties;
36-
import java.util.Set;
3733
import java.util.jar.JarOutputStream;
3834
import java.util.jar.Manifest;
3935
import java.util.stream.Collectors;
@@ -58,7 +54,6 @@
5854
import org.eclipse.jdt.launching.IVMInstall;
5955
import org.eclipse.jdt.launching.IVMRunner;
6056
import org.eclipse.jdt.launching.JavaRuntime;
61-
import org.eclipse.osgi.service.resolver.BundleDescription;
6257
import org.eclipse.osgi.util.ManifestElement;
6358
import org.eclipse.osgi.util.NLS;
6459
import org.eclipse.pde.core.plugin.IFragment;
@@ -69,14 +64,13 @@
6964
import org.eclipse.pde.core.plugin.TargetPlatform;
7065
import org.eclipse.pde.internal.build.IPDEBuildConstants;
7166
import org.eclipse.pde.internal.core.ClasspathHelper;
72-
import org.eclipse.pde.internal.core.DependencyManager;
7367
import org.eclipse.pde.internal.core.ICoreConstants;
74-
import org.eclipse.pde.internal.core.PDECore;
7568
import org.eclipse.pde.internal.core.TargetPlatformHelper;
7669
import org.eclipse.pde.internal.core.bnd.PdeProjectAnalyzer;
7770
import org.eclipse.pde.internal.core.util.CoreUtility;
7871
import org.eclipse.pde.internal.core.util.VersionUtil;
7972
import org.eclipse.pde.internal.launching.IPDEConstants;
73+
import org.eclipse.pde.internal.launching.JUnitLaunchRequirements;
8074
import org.eclipse.pde.internal.launching.PDELaunchingPlugin;
8175
import org.eclipse.pde.internal.launching.PDEMessages;
8276
import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
@@ -393,18 +387,6 @@ protected String getApplication(ILaunchConfiguration configuration) {
393387
return application;
394388
}
395389

396-
private IPluginModelBase findRequiredPluginInTargetOrHost(String id) throws CoreException {
397-
IPluginModelBase model = PluginRegistry.findModel(id);
398-
if (model == null || !model.getBundleDescription().isResolved()) {
399-
// prefer bundle from host over unresolved bundle from target
400-
model = PDECore.getDefault().findPluginInHost(id);
401-
}
402-
if (model == null) {
403-
abort(NLS.bind(PDEMessages.JUnitLaunchConfiguration_error_missingPlugin, id), null, IStatus.OK);
404-
}
405-
return model;
406-
}
407-
408390
@Override
409391
public String getProgramArguments(ILaunchConfiguration configuration) throws CoreException {
410392
return LaunchArgumentsHelper.getUserProgramArguments(configuration);
@@ -537,7 +519,7 @@ protected void preLaunchCheck(ILaunchConfiguration configuration, ILaunch launch
537519
launchMode = launch.getLaunchMode();
538520

539521
// implicitly add the plug-ins required for JUnit testing if necessary
540-
addRequiredJunitRuntimePlugins(configuration);
522+
JUnitLaunchRequirements.addRequiredJunitRuntimePlugins(configuration, fAllBundles, fModels);
541523

542524
String attribute = launch.getAttribute(PDE_JUNIT_SHOW_COMMAND);
543525
boolean isShowCommand = false;
@@ -558,58 +540,6 @@ protected void preLaunchCheck(ILaunchConfiguration configuration, ILaunch launch
558540
synchronizeManifests(configuration, subMonitor.split(1));
559541
}
560542

561-
private void addRequiredJunitRuntimePlugins(ILaunchConfiguration configuration) throws CoreException {
562-
Set<String> requiredPlugins = new LinkedHashSet<>(getRequiredJunitRuntimePlugins(configuration));
563-
564-
if (fAllBundles.containsKey("junit-platform-runner") || fAllBundles.containsKey("org.junit.platform.runner")) { //$NON-NLS-1$ //$NON-NLS-2$
565-
// add launcher and jupiter.engine to support @RunWith(JUnitPlatform.class)
566-
requiredPlugins.add("junit-platform-launcher"); //$NON-NLS-1$
567-
requiredPlugins.add("junit-jupiter-engine"); //$NON-NLS-1$
568-
}
569-
Set<BundleDescription> addedRequirements = new HashSet<>();
570-
addAbsentRequirements(requiredPlugins, addedRequirements);
571-
572-
Set<BundleDescription> requirementsOfRequirements = DependencyManager.findRequirementsClosure(addedRequirements);
573-
Set<String> rorIds = requirementsOfRequirements.stream().map(BundleDescription::getSymbolicName).collect(Collectors.toSet());
574-
addAbsentRequirements(rorIds, null);
575-
}
576-
577-
private void addAbsentRequirements(Collection<String> requirements, Set<BundleDescription> addedRequirements) throws CoreException {
578-
for (String id : requirements) {
579-
List<IPluginModelBase> models = fAllBundles.computeIfAbsent(id, k -> new ArrayList<>());
580-
if (models.stream().noneMatch(m -> m.getBundleDescription().isResolved())) {
581-
IPluginModelBase model = findRequiredPluginInTargetOrHost(id);
582-
models.add(model);
583-
BundleLauncherHelper.addDefaultStartingBundle(fModels, model);
584-
if (addedRequirements != null) {
585-
addedRequirements.add(model.getBundleDescription());
586-
}
587-
}
588-
}
589-
}
590-
591-
/**
592-
* @noreference This method is not intended to be referenced by clients.
593-
* @param configuration non null config
594-
* @return required plugins
595-
*/
596-
@SuppressWarnings("restriction")
597-
public static Collection<String> getRequiredJunitRuntimePlugins(ILaunchConfiguration configuration) {
598-
org.eclipse.jdt.internal.junit.launcher.ITestKind testKind = org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants.getTestRunnerKind(configuration);
599-
if (testKind.isNull()) {
600-
return Collections.emptyList();
601-
}
602-
List<String> plugins = new ArrayList<>();
603-
plugins.add("org.eclipse.pde.junit.runtime"); //$NON-NLS-1$
604-
605-
if (org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT4_TEST_KIND_ID.equals(testKind.getId())) {
606-
plugins.add("org.eclipse.jdt.junit4.runtime"); //$NON-NLS-1$
607-
} else if (org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT5_TEST_KIND_ID.equals(testKind.getId())) {
608-
plugins.add("org.eclipse.jdt.junit5.runtime"); //$NON-NLS-1$
609-
}
610-
return plugins;
611-
}
612-
613543
/**
614544
* Checks for old-style plugin.xml files that have become stale since the last launch.
615545
* For any stale plugin.xml files found, the corresponding MANIFEST.MF is deleted

ui/org.eclipse.pde.ui/.settings/.api_filters

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,6 @@
9595
</message_arguments>
9696
</filter>
9797
</resource>
98-
<resource path="src/org/eclipse/pde/internal/ui/launcher/PluginBlock.java" type="org.eclipse.pde.internal.ui.launcher.PluginBlock">
99-
<filter id="640712815">
100-
<message_arguments>
101-
<message_argument value="JUnitLaunchConfigurationDelegate"/>
102-
<message_argument value="PluginBlock"/>
103-
<message_argument value="getRequiredJunitRuntimePlugins(ILaunchConfiguration)"/>
104-
</message_arguments>
105-
</filter>
106-
</resource>
10798
<resource path="src/org/eclipse/pde/internal/ui/refactoring/RenamePluginProcessor.java" type="org.eclipse.pde.internal.ui.refactoring.RenamePluginProcessor">
10899
<filter id="572522506">
109100
<message_arguments>

ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/PluginBlock.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@
2727
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
2828
import org.eclipse.pde.core.plugin.IPluginModelBase;
2929
import org.eclipse.pde.core.plugin.PluginRegistry;
30+
import org.eclipse.pde.internal.launching.JUnitLaunchRequirements;
3031
import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
3132
import org.eclipse.pde.internal.launching.launcher.EclipsePluginValidationOperation;
3233
import org.eclipse.pde.internal.launching.launcher.LaunchValidationOperation;
3334
import org.eclipse.pde.internal.launching.launcher.RequirementHelper;
3435
import org.eclipse.pde.internal.ui.PDEPlugin;
3536
import org.eclipse.pde.launching.IPDELauncherConstants;
36-
import org.eclipse.pde.launching.JUnitLaunchConfigurationDelegate;
3737
import org.eclipse.pde.ui.launcher.AbstractLauncherTab;
3838

3939
public class PluginBlock extends AbstractPluginBlock {
@@ -165,7 +165,7 @@ protected void addRequiredPlugins() {
165165
// Check that the application or product we are launching has its requirements included
166166
try {
167167
List<String> requiredIds = RequirementHelper.getApplicationLaunchRequirements(fLaunchConfig);
168-
Collection<String> requiredPlugins = JUnitLaunchConfigurationDelegate.getRequiredJunitRuntimePlugins(fLaunchConfig);
168+
Collection<String> requiredPlugins = JUnitLaunchRequirements.getRequiredJunitRuntimePlugins(fLaunchConfig);
169169
Stream.concat(requiredPlugins.stream(), requiredIds.stream()).forEach(requiredId -> {
170170
// see if launcher plugin is already included
171171
IPluginModelBase base = findPlugin(requiredId);

0 commit comments

Comments
 (0)