Skip to content

Commit e69e043

Browse files
trancexpressHannesWellmerks
committed
Consider bundle versions when adding JUnit to launch
and remove unnecessary separate attempts to add junit-launcher and jupiter.engine bundles to the runtime, intending to support @RunWith(JUnitPlatform.class), because these bundles always are already added to a runtime. This change adjusts JUnitLaunchConfigurationDelegate, to consider 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. Furthermore handle multiple versions of a bundle when searching them in the host. Fixes: #2006 Co-authored-by: Hannes Wellmann <wellmann.hannes1@gmx.net> Co-authored-by: Ed Merks <ed.merks@gmail.com>
1 parent 9e44ce3 commit e69e043

File tree

6 files changed

+112
-148
lines changed

6 files changed

+112
-148
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 - Prevent clashes of JUnit 5 and JUnit 6 bundles
14+
*******************************************************************************/
15+
package org.eclipse.pde.internal.launching;
16+
17+
import java.util.ArrayList;
18+
import java.util.Collection;
19+
import java.util.Comparator;
20+
import java.util.LinkedHashSet;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Optional;
24+
import java.util.Set;
25+
import java.util.function.Function;
26+
import java.util.stream.Stream;
27+
28+
import org.eclipse.core.runtime.CoreException;
29+
import org.eclipse.core.runtime.Status;
30+
import org.eclipse.debug.core.ILaunchConfiguration;
31+
import org.eclipse.osgi.service.resolver.BundleDescription;
32+
import org.eclipse.osgi.util.NLS;
33+
import org.eclipse.pde.core.plugin.IPluginModelBase;
34+
import org.eclipse.pde.core.plugin.PluginRegistry;
35+
import org.eclipse.pde.internal.core.DependencyManager;
36+
import org.eclipse.pde.internal.core.PDECore;
37+
import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
38+
import org.osgi.framework.wiring.BundleRevision;
39+
40+
public class JUnitLaunchRequirements {
41+
42+
public static final String JUNIT4_JDT_RUNTIME_PLUGIN = "org.eclipse.jdt.junit4.runtime"; //$NON-NLS-1$
43+
public static final String JUNIT5_JDT_RUNTIME_PLUGIN = "org.eclipse.jdt.junit5.runtime"; //$NON-NLS-1$
44+
private static final Comparator<IPluginModelBase> VERSION = Comparator.comparing(p -> p.getBundleDescription().getVersion());
45+
46+
public static void addRequiredJunitRuntimePlugins(ILaunchConfiguration configuration, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
47+
Collection<String> runtimePlugins = getRequiredJunitRuntimeEclipsePlugins(configuration);
48+
Set<BundleDescription> addedRuntimeBundles = addAbsentRequirements(runtimePlugins, allBundles, allModels);
49+
Set<BundleDescription> runtimeRequirements = DependencyManager.findRequirementsClosure(addedRuntimeBundles);
50+
addAbsentRequirements(runtimeRequirements, allBundles, allModels);
51+
}
52+
53+
@SuppressWarnings("restriction")
54+
public static Collection<String> getRequiredJunitRuntimeEclipsePlugins(ILaunchConfiguration configuration) {
55+
org.eclipse.jdt.internal.junit.launcher.ITestKind testKind = org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants.getTestRunnerKind(configuration);
56+
if (testKind.isNull()) {
57+
return List.of();
58+
}
59+
List<String> plugins = new ArrayList<>();
60+
plugins.add("org.eclipse.pde.junit.runtime"); //$NON-NLS-1$
61+
switch (testKind.getId()) {
62+
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT3_TEST_KIND_ID -> {
63+
} // Nothing to add for JUnit-3
64+
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT4_TEST_KIND_ID -> plugins.add(JUNIT4_JDT_RUNTIME_PLUGIN);
65+
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT5_TEST_KIND_ID -> plugins.add(JUNIT5_JDT_RUNTIME_PLUGIN);
66+
default -> throw new IllegalArgumentException("Unsupported junit test kind: " + testKind.getId()); //$NON-NLS-1$
67+
}
68+
return plugins;
69+
}
70+
71+
private static Set<BundleDescription> addAbsentRequirements(Collection<String> requirements, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
72+
Set<BundleDescription> addedRequirements = new LinkedHashSet<>();
73+
for (String id : requirements) {
74+
List<IPluginModelBase> models = allBundles.computeIfAbsent(id, k -> new ArrayList<>());
75+
if (models.stream().noneMatch(p -> p.getBundleDescription().isResolved())) {
76+
IPluginModelBase model = findRequiredPluginInTargetOrHost(PluginRegistry.findModel(id), plugins -> plugins.max(VERSION), id);
77+
models.add(model);
78+
BundleLauncherHelper.addDefaultStartingBundle(allModels, model);
79+
addedRequirements.add(model.getBundleDescription());
80+
}
81+
}
82+
return addedRequirements;
83+
}
84+
85+
private static void addAbsentRequirements(Set<BundleDescription> requirements, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
86+
for (BundleRevision bundle : requirements) {
87+
String id = bundle.getSymbolicName();
88+
List<IPluginModelBase> models = allBundles.computeIfAbsent(id, k -> new ArrayList<>());
89+
if (models.stream().map(IPluginModelBase::getBundleDescription).noneMatch(b -> b.isResolved() && b.getVersion().equals(bundle.getVersion()))) {
90+
IPluginModelBase model = findRequiredPluginInTargetOrHost(PluginRegistry.findModel(bundle), plgs -> plgs.filter(p -> p.getBundleDescription() == bundle).findFirst(), id);
91+
models.add(model);
92+
BundleLauncherHelper.addDefaultStartingBundle(allModels, model);
93+
}
94+
}
95+
}
96+
97+
private static IPluginModelBase findRequiredPluginInTargetOrHost(IPluginModelBase model, Function<Stream<IPluginModelBase>, Optional<IPluginModelBase>> pluginSelector, String id) throws CoreException {
98+
if (model == null || !model.getBundleDescription().isResolved()) {
99+
// prefer bundle from host over unresolved bundle from target
100+
model = pluginSelector.apply(PDECore.getDefault().findPluginInHost(id)) //
101+
.orElseThrow(() -> new CoreException(Status.error(NLS.bind(PDEMessages.JUnitLaunchConfiguration_error_missingPlugin, id))));
102+
}
103+
return model;
104+
}
105+
106+
}

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).max(Comparator.comparing(p -> p.getBundleDescription().getVersion())).orElse(null);
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.getRequiredJunitRuntimeEclipsePlugins(fLaunchConfig);
169169
Stream.concat(requiredPlugins.stream(), requiredIds.stream()).forEach(requiredId -> {
170170
// see if launcher plugin is already included
171171
IPluginModelBase base = findPlugin(requiredId);

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,6 @@
88
</message_arguments>
99
</filter>
1010
</resource>
11-
<resource path="src/org/eclipse/pde/unittest/junit/launcher/JUnitPluginLaunchConfigurationDelegate.java" type="org.eclipse.pde.unittest.junit.launcher.JUnitPluginLaunchConfigurationDelegate">
12-
<filter id="640712815">
13-
<message_arguments>
14-
<message_argument value="JUnitLaunchConfigurationDelegate"/>
15-
<message_argument value="JUnitPluginLaunchConfigurationDelegate"/>
16-
<message_argument value="getRequiredJunitRuntimePlugins(ILaunchConfiguration)"/>
17-
</message_arguments>
18-
</filter>
19-
</resource>
2011
<resource path="src/org/eclipse/pde/unittest/junit/launcher/JUnitPluginTestTab.java" type="org.eclipse.pde.unittest.junit.launcher.JUnitPluginTestTab">
2112
<filter id="571473929">
2213
<message_arguments>

0 commit comments

Comments
 (0)