1717import java .util .LinkedHashMap ;
1818import java .util .Map ;
1919import java .util .Set ;
20- import java .util .stream .Collectors ;
2120
2221import org .eclipse .core .runtime .CoreException ;
2322import org .eclipse .core .runtime .IProgressMonitor ;
3231
3332public class JUnitLaunchValidationOperation extends LaunchValidationOperation {
3433
35- private static final Set <String > JUNIT_PLATFORM_ENGINE_BUNLDES = Set .of (new String [] { //
36- "junit-platform-engine" , //$NON-NLS-1$
37- "org.junit.platform.engine" , //$NON-NLS-1$
38- });
39-
4034 private final Map <Object , Object []> fErrors = new HashMap <>(2 );
4135
4236 public JUnitLaunchValidationOperation (ILaunchConfiguration configuration , Set <IPluginModelBase > models ) {
@@ -45,54 +39,117 @@ public JUnitLaunchValidationOperation(ILaunchConfiguration configuration, Set<IP
4539
4640 @ Override
4741 public void run (IProgressMonitor monitor ) throws CoreException {
42+ // First run parent validation to resolve the state
43+ super .run (monitor );
44+
45+ // Then check for JUnit version conflicts in the resolved state
4846 try {
49- checkJunitVersion (fLaunchConfiguration , fModels );
47+ checkJunitVersionConflicts (fLaunchConfiguration );
5048 } catch (CoreException e ) {
5149 PDELaunchingPlugin .log (e );
5250 }
5351 }
5452
5553 @ SuppressWarnings ("restriction" )
56- private void checkJunitVersion (ILaunchConfiguration configuration , Set < IPluginModelBase > models ) throws CoreException {
54+ private void checkJunitVersionConflicts (ILaunchConfiguration configuration ) throws CoreException {
5755 org .eclipse .jdt .internal .junit .launcher .ITestKind testKind = org .eclipse .jdt .internal .junit .launcher .JUnitLaunchConfigurationConstants .getTestRunnerKind (configuration );
5856 if (testKind .isNull ()) {
5957 return ;
6058 }
61- Set < Version > junitPlatformBundlesVersions = junitPlatformBundleVersions ( models );
59+
6260 String testKindId = testKind .getId ();
6361 switch (testKindId ) {
6462 case TestKindRegistry .JUNIT3_TEST_KIND_ID , TestKindRegistry .JUNIT4_TEST_KIND_ID -> {
65- } // nothing to check
63+ // nothing to check for JUnit 3 and 4
64+ }
6665 case TestKindRegistry .JUNIT5_TEST_KIND_ID -> {
67- // JUnit 5 platform bundles have version range [1.0,2.0)
68- junitPlatformBundlesVersions .stream ().map (Version ::getMajor ).filter (i -> i .intValue () != 1 ).findFirst ().ifPresent (otherVersion -> {
69- String message = NLS .bind (PDEMessages .JUnitLaunchConfiguration_error_JUnitLaunchAndRuntimeMissmatch , 5 , otherVersion );
70- addError (message );
71- });
66+ checkForJUnit6InJUnit5Launch ();
7267 }
7368 default -> throw new CoreException (Status .error ("Unsupported test kind: " + testKindId )); //$NON-NLS-1$
7469 }
7570 }
71+
72+ /**
73+ * Check if JUnit 6+ bundles are included in a JUnit 5 launch.
74+ * This can cause runtime errors like "org.junit.jupiter.engine.JupiterTestEngine not a subtype".
75+ */
76+ private void checkForJUnit6InJUnit5Launch () {
77+ if (getState () == null ) {
78+ return ;
79+ }
80+
81+ // Get all bundles in the resolved state
82+ BundleDescription [] bundles = getState ().getBundles ();
83+
84+ // Find all junit-jupiter-engine bundles with major version >= 6
85+ for (BundleDescription bundle : bundles ) {
86+ if ("junit-jupiter-engine" .equals (bundle .getSymbolicName ())) { //$NON-NLS-1$
87+ Version version = bundle .getVersion ();
88+ if (version .getMajor () >= 6 ) {
89+ // Found a JUnit 6+ bundle, now find which bundle required it
90+ String requiringBundle = findBundleRequiringJUnit6 (bundles , bundle );
91+ String message ;
92+ if (requiringBundle != null ) {
93+ message = NLS .bind (PDEMessages .JUnitLaunchConfiguration_error_JUnitLaunchAndRuntimeMissmatch_withRequiringBundle ,
94+ new Object [] { Integer .valueOf (5 ), Integer .valueOf (version .getMajor ()), requiringBundle });
95+ } else {
96+ message = NLS .bind (PDEMessages .JUnitLaunchConfiguration_error_JUnitLaunchAndRuntimeMissmatch ,
97+ Integer .valueOf (5 ), Integer .valueOf (version .getMajor ()));
98+ }
99+ addError (message );
100+ }
101+ }
102+ }
103+ }
104+
105+ /**
106+ * Find which bundle is requiring a JUnit 6 bundle.
107+ * This helps users understand why JUnit 6 was included in their JUnit 5 launch.
108+ *
109+ * @param bundles all bundles in the resolved state
110+ * @param junitBundle the JUnit 6 bundle to find requirements for
111+ * @return the symbolic name of the bundle requiring the JUnit 6 bundle, or null if not found
112+ */
113+ private String findBundleRequiringJUnit6 (BundleDescription [] bundles , BundleDescription junitBundle ) {
114+ for (BundleDescription bundle : bundles ) {
115+ if (bundle .isResolved ()) {
116+ // Check Require-Bundle dependencies
117+ BundleDescription [] requiredBundles = bundle .getResolvedRequires ();
118+ for (BundleDescription required : requiredBundles ) {
119+ if (required .equals (junitBundle )) {
120+ return bundle .getSymbolicName ();
121+ }
122+ }
123+
124+ // Check Import-Package dependencies
125+ // If a bundle imports packages from junit-jupiter-engine, it contributes to pulling it in
126+ org .eclipse .osgi .service .resolver .ExportPackageDescription [] imports = bundle .getResolvedImports ();
127+ for (org .eclipse .osgi .service .resolver .ExportPackageDescription export : imports ) {
128+ if (export .getExporter ().equals (junitBundle )) {
129+ return bundle .getSymbolicName ();
130+ }
131+ }
132+ }
133+ }
134+ return null ;
135+ }
76136
77137 private void addError (String message ) {
78138 fErrors .put (message .replaceAll ("\\ R" , " " ), null ); //$NON-NLS-1$//$NON-NLS-2$
79139 }
80140
81141 @ Override
82142 public boolean hasErrors () {
83- return !fErrors .isEmpty ();
143+ return super . hasErrors () || !fErrors .isEmpty ();
84144 }
85145
86146 @ Override
87147 public Map <Object , Object []> getInput () {
88148 Map <Object , Object []> map = new LinkedHashMap <>();
149+ // Add parent validation errors first
150+ map .putAll (super .getInput ());
151+ // Then add JUnit-specific validation errors
89152 map .putAll (fErrors );
90153 return map ;
91154 }
92-
93- private static Set <Version > junitPlatformBundleVersions (Set <IPluginModelBase > models ) {
94- return models .stream ().map (IPluginModelBase ::getBundleDescription ) //
95- .filter (d -> JUNIT_PLATFORM_ENGINE_BUNLDES .contains (d .getSymbolicName ())) //
96- .map (BundleDescription ::getVersion ).collect (Collectors .toSet ());
97- }
98155}
0 commit comments