1414package org .eclipse .pde .internal .launching ;
1515
1616import java .util .ArrayList ;
17+ import java .util .Arrays ;
1718import java .util .Collection ;
19+ import java .util .IdentityHashMap ;
1820import java .util .LinkedHashSet ;
1921import java .util .List ;
2022import java .util .Map ;
23+ import java .util .Objects ;
2124import java .util .Optional ;
2225import java .util .Set ;
2326import java .util .function .Function ;
27+ import java .util .stream .Collectors ;
2428import java .util .stream .Stream ;
2529
2630import org .eclipse .core .runtime .CoreException ;
2731import org .eclipse .core .runtime .Status ;
2832import org .eclipse .debug .core .ILaunchConfiguration ;
2933import org .eclipse .osgi .service .resolver .BundleDescription ;
34+ import org .eclipse .osgi .service .resolver .ResolverError ;
35+ import org .eclipse .osgi .service .resolver .State ;
36+ import org .eclipse .osgi .service .resolver .StateObjectFactory ;
3037import org .eclipse .osgi .util .NLS ;
3138import org .eclipse .pde .core .plugin .IPluginModelBase ;
3239import org .eclipse .pde .core .plugin .PluginRegistry ;
40+ import org .eclipse .pde .internal .build .BundleHelper ;
3341import org .eclipse .pde .internal .core .DependencyManager ;
3442import org .eclipse .pde .internal .core .PDECore ;
3543import org .eclipse .pde .internal .launching .launcher .BundleLauncherHelper ;
44+ import org .eclipse .pde .internal .launching .launcher .LaunchValidationOperation ;
45+ import org .osgi .framework .hooks .resolver .ResolverHook ;
46+ import org .osgi .framework .hooks .resolver .ResolverHookFactory ;
47+ import org .osgi .framework .wiring .BundleCapability ;
48+ import org .osgi .framework .wiring .BundleRequirement ;
3649import org .osgi .framework .wiring .BundleRevision ;
3750
3851public class JUnitLaunchRequirements {
3952
53+ private static final StateObjectFactory FACTORY = BundleHelper .getPlatformAdmin ().getFactory ();
4054 private static final String PDE_JUNIT_RUNTIME = "org.eclipse.pde.junit.runtime" ; //$NON-NLS-1$
4155 private static final String JUNIT4_JDT_RUNTIME_PLUGIN = "org.eclipse.jdt.junit4.runtime" ; //$NON-NLS-1$
4256 private static final String JUNIT5_JDT_RUNTIME_PLUGIN = "org.eclipse.jdt.junit5.runtime" ; //$NON-NLS-1$
4357
4458 public static void addRequiredJunitRuntimePlugins (ILaunchConfiguration configuration , Map <String , List <IPluginModelBase >> collectedModels , Map <IPluginModelBase , String > startLevelMap ) throws CoreException {
45- Collection <String > runtimePlugins = getRequiredJunitRuntimeEclipsePlugins (configuration );
46- Set <BundleDescription > addedRuntimeBundles = addAbsentRequirements (runtimePlugins , collectedModels , startLevelMap );
47- Set <BundleDescription > runtimeRequirements = DependencyManager .findRequirementsClosure (addedRuntimeBundles );
59+ Collection <IPluginModelBase > runtimeBundles = getEclipseJunitRuntimePlugins (configuration , collectedModels , startLevelMap );
60+ List <BundleDescription > roots = runtimeBundles .stream ().map (p -> p .getBundleDescription ()).filter (Objects ::nonNull ).toList ();
61+ Set <BundleDescription > closure = DependencyManager .findRequirementsClosure (roots );
62+ Collection <BundleDescription > runtimeRequirements = filterRequirementsByState (closure , runtimeBundles , configuration );
4863 addAbsentRequirements (runtimeRequirements , collectedModels , startLevelMap );
4964 }
5065
66+ private static Collection <BundleDescription > filterRequirementsByState (Collection <BundleDescription > bundles , Collection <IPluginModelBase > rootBundles , ILaunchConfiguration configuration ) throws CoreException {
67+ //lookup that maps a copy to the original description from the bundles parameter
68+ Map <BundleRevision , BundleDescription > descriptionnMap = new IdentityHashMap <>();
69+ Set <BundleRevision > rootSet = rootBundles .stream ().map (p -> p .getBundleDescription ()).filter (Objects ::nonNull ).collect (Collectors .toSet ());
70+ State state = FACTORY .createState (true );
71+ State targetState = PDECore .getDefault ().getModelManager ().getState ().getState ();
72+ List <BundleDescription > resolveRoots = new ArrayList <>();
73+ long id = 1 ;
74+ for (BundleDescription bundle : bundles ) {
75+ BundleDescription copy = FACTORY .createBundleDescription (id ++, bundle );
76+ descriptionnMap .put (copy , bundle );
77+ state .addBundle (copy );
78+ if (rootSet .contains (bundle )) {
79+ resolveRoots .add (copy );
80+ }
81+ }
82+ state .setPlatformProperties (LaunchValidationOperation .getPlatformProperties (LaunchValidationOperation .getMatchingEnvironments (configuration , rootBundles )));
83+ state .setResolverHookFactory (new ResolverHookFactory () {
84+
85+ @ Override
86+ public ResolverHook begin (Collection <BundleRevision > triggers ) {
87+ return new ResolverHook () {
88+
89+ @ Override
90+ public void filterSingletonCollisions (BundleCapability singleton , Collection <BundleCapability > collisionCandidates ) {
91+ }
92+
93+ @ Override
94+ public void filterResolvable (Collection <BundleRevision > candidates ) {
95+ }
96+
97+ @ Override
98+ public void filterMatches (BundleRequirement requirement , Collection <BundleCapability > candidates ) {
99+ List <BundleCapability > list = candidates .stream ().filter (cp -> isFromDifferentState (cp )).toList ();
100+ if (list .isEmpty ()) {
101+ //nothing to do here...
102+ return ;
103+ }
104+ //iterate in reverse order so we remove lower ranked candidates first ...
105+ for (int i = list .size () - 1 ; i >= 0 && candidates .size () > 1 ; i --) {
106+ BundleCapability capability = list .get (i );
107+ candidates .remove (capability );
108+ }
109+ }
110+
111+ private boolean isFromDifferentState (BundleCapability capability ) {
112+ BundleRevision resource = capability .getResource ();
113+ BundleDescription original = descriptionnMap .get (resource );
114+ if (original != null ) {
115+ return original .getContainingState () != targetState ;
116+ }
117+ return false ;
118+ }
119+
120+ @ Override
121+ public void end () {
122+ }
123+ };
124+ }
125+ });
126+ state .resolve (false );
127+ for (BundleDescription rootBundle : resolveRoots ) {
128+ ResolverError [] errors = state .getResolverErrors (rootBundle );
129+ if (errors .length > 0 ) {
130+ throw new CoreException (Status .error (String .format ("%s can not be resolved: %s" , rootBundle , Arrays .toString (errors )))); //$NON-NLS-1$
131+ }
132+ }
133+ Collection <BundleDescription > closure = DependencyManager .findRequirementsClosure (resolveRoots );
134+ // map back to the originals!
135+ return closure .stream ().map (bd -> descriptionnMap .get (bd )).filter (Objects ::nonNull ).toList ();
136+ }
137+
51138 @ SuppressWarnings ("restriction" )
52139 public static Collection <String > getRequiredJunitRuntimeEclipsePlugins (ILaunchConfiguration configuration ) {
53140 org .eclipse .jdt .internal .junit .launcher .ITestKind testKind = org .eclipse .jdt .internal .junit .launcher .JUnitLaunchConfigurationConstants .getTestRunnerKind (configuration );
@@ -59,7 +146,7 @@ public static Collection<String> getRequiredJunitRuntimeEclipsePlugins(ILaunchCo
59146 return List .of (PDE_JUNIT_RUNTIME );
60147 } // Nothing to add for JUnit-3
61148 case org .eclipse .jdt .internal .junit .launcher .TestKindRegistry .JUNIT4_TEST_KIND_ID -> {
62- return List .of (PDE_JUNIT_RUNTIME ,JUNIT4_JDT_RUNTIME_PLUGIN );
149+ return List .of (PDE_JUNIT_RUNTIME , JUNIT4_JDT_RUNTIME_PLUGIN );
63150 }
64151 case org .eclipse .jdt .internal .junit .launcher .TestKindRegistry .JUNIT5_TEST_KIND_ID -> {
65152 return List .of (PDE_JUNIT_RUNTIME , JUNIT5_JDT_RUNTIME_PLUGIN );
@@ -68,21 +155,27 @@ public static Collection<String> getRequiredJunitRuntimeEclipsePlugins(ILaunchCo
68155 }
69156 }
70157
71- private static Set <BundleDescription > addAbsentRequirements (Collection <String > requirements , Map <String , List <IPluginModelBase >> collectedModels , Map <IPluginModelBase , String > startLevelMap ) throws CoreException {
72- Set <BundleDescription > addedRequirements = new LinkedHashSet <>();
73- for (String id : requirements ) {
74- List <IPluginModelBase > models = collectedModels .computeIfAbsent (id , k -> new ArrayList <>());
75- if (models .stream ().noneMatch (p -> p .getBundleDescription ().isResolved ())) {
76- IPluginModelBase model = findRequiredPluginInTargetOrHost (PluginRegistry .findModel (id ), plugins -> plugins .max (PDECore .VERSION ), id );
77- models .add (model );
78- BundleLauncherHelper .addDefaultStartingBundle (startLevelMap , model );
79- addedRequirements .add (model .getBundleDescription ());
80- }
158+ private static Collection <IPluginModelBase > getEclipseJunitRuntimePlugins (ILaunchConfiguration configuration , Map <String , List <IPluginModelBase >> collectedModels , Map <IPluginModelBase , String > startLevelMap ) throws CoreException {
159+ Set <IPluginModelBase > descriptions = new LinkedHashSet <>();
160+ for (String id : getRequiredJunitRuntimeEclipsePlugins (configuration )) {
161+ addIfAbsent (id , collectedModels , startLevelMap ).ifPresent (descriptions ::add );
81162 }
82- return addedRequirements ;
163+ return descriptions ;
164+ }
165+
166+ private static Optional <IPluginModelBase > addIfAbsent (String id , Map <String , List <IPluginModelBase >> collectedModels , Map <IPluginModelBase , String > startLevelMap ) throws CoreException {
167+ List <IPluginModelBase > models = collectedModels .computeIfAbsent (id , k -> new ArrayList <>());
168+ if (models .stream ().noneMatch (m -> m .getBundleDescription ().isResolved ())) {
169+ IPluginModelBase model = findRequiredPluginInTargetOrHost (PluginRegistry .findModel (id ), plugins -> plugins .max (PDECore .VERSION ), id );
170+ models .add (model );
171+ BundleLauncherHelper .addDefaultStartingBundle (startLevelMap , model );
172+ return Optional .of (model );
173+ }
174+
175+ return models .stream ().filter (m -> m .getBundleDescription ().isResolved ()).findFirst ();
83176 }
84177
85- private static void addAbsentRequirements (Set <BundleDescription > requirements , Map <String , List <IPluginModelBase >> collectedModels , Map <IPluginModelBase , String > startLevelMap ) throws CoreException {
178+ private static void addAbsentRequirements (Collection <BundleDescription > requirements , Map <String , List <IPluginModelBase >> collectedModels , Map <IPluginModelBase , String > startLevelMap ) throws CoreException {
86179 for (BundleRevision bundle : requirements ) {
87180 String id = bundle .getSymbolicName ();
88181 List <IPluginModelBase > models = collectedModels .computeIfAbsent (id , k -> new ArrayList <>());
0 commit comments