@@ -239,6 +239,178 @@ public void notifiedWithCompatibleNameForMethodWithArguments() throws Exception
239239 assertNull (entry .getStackTraceWriter ());
240240 }
241241
242+ @ Test
243+ public void distinguishedJUnit6ParameterizedClassInvocationsByIndex () throws Exception {
244+ // JUnit 6 @ParameterizedClass uses [class-template-invocation:#N] parents with a ClassSource.
245+ // The method's own legacy name has no [N] suffix (unlike JUnit 5.14 / @ParameterizedTest),
246+ // so invocations must still be reported under distinct names or rerunFailingTestsCount
247+ // aggregates a pass+fail pair as a flake (#3303).
248+ EngineDescriptor engine = new EngineDescriptor (UniqueId .forEngine ("junit-jupiter" ), "JUnit Jupiter" );
249+ TestDescriptor classTemplate = newParameterizedClassTemplateDescriptor (engine .getUniqueId ());
250+ engine .addChild (classTemplate );
251+
252+ TestDescriptor invocation1 = newParameterizedClassInvocationDescriptor (classTemplate .getUniqueId (), 1 );
253+ TestDescriptor method1 = newUnparameterizedMethodDescriptor (invocation1 .getUniqueId ());
254+ classTemplate .addChild (invocation1 );
255+ invocation1 .addChild (method1 );
256+
257+ TestDescriptor invocation2 = newParameterizedClassInvocationDescriptor (classTemplate .getUniqueId (), 2 );
258+ TestDescriptor method2 = newUnparameterizedMethodDescriptor (invocation2 .getUniqueId ());
259+ classTemplate .addChild (invocation2 );
260+ invocation2 .addChild (method2 );
261+
262+ TestPlan plan = TestPlan .from (false , singletonList (engine ), CONFIG_PARAMS , OUTPUT_DIRECTORY );
263+ adapter .testPlanExecutionStarted (plan );
264+
265+ adapter .executionStarted (TestIdentifier .from (engine ));
266+ adapter .executionStarted (TestIdentifier .from (classTemplate ));
267+ adapter .executionStarted (TestIdentifier .from (invocation1 ));
268+ adapter .executionStarted (TestIdentifier .from (method1 ));
269+ adapter .executionFinished (TestIdentifier .from (method1 ), successful ());
270+ adapter .executionFinished (TestIdentifier .from (invocation1 ), successful ());
271+ adapter .executionStarted (TestIdentifier .from (invocation2 ));
272+ adapter .executionStarted (TestIdentifier .from (method2 ));
273+ adapter .executionFinished (TestIdentifier .from (method2 ), failed (new AssertionError ("fail" )));
274+
275+ ArgumentCaptor <ReportEntry > started = ArgumentCaptor .forClass (ReportEntry .class );
276+ verify (listener , times (2 )).testStarting (started .capture ());
277+ assertEquals (
278+ MY_TEST_METHOD_NAME + "()[1]" , started .getAllValues ().get (0 ).getName ());
279+ assertEquals (
280+ MY_TEST_METHOD_NAME + "()[2]" , started .getAllValues ().get (1 ).getName ());
281+
282+ ArgumentCaptor <ReportEntry > failed = ArgumentCaptor .forClass (ReportEntry .class );
283+ verify (listener ).testFailed (failed .capture ());
284+ assertEquals (MY_TEST_METHOD_NAME + "()[2]" , failed .getValue ().getName ());
285+ assertThat (failed .getValue ().getName ())
286+ .isNotEqualTo (started .getAllValues ().get (0 ).getName ());
287+ }
288+
289+ @ Test
290+ public void distinguishedNestedJUnit6ParameterizedClassInvocations () throws Exception {
291+ EngineDescriptor engine = new EngineDescriptor (UniqueId .forEngine ("junit-jupiter" ), "JUnit Jupiter" );
292+ TestDescriptor outerTemplate = newParameterizedClassTemplateDescriptor (engine .getUniqueId ());
293+ engine .addChild (outerTemplate );
294+
295+ TestDescriptor outer1 = newParameterizedClassInvocationDescriptor (outerTemplate .getUniqueId (), 1 );
296+ TestDescriptor innerTemplate1 = newParameterizedClassTemplateDescriptor (outer1 .getUniqueId ());
297+ TestDescriptor inner1 = newParameterizedClassInvocationDescriptor (innerTemplate1 .getUniqueId (), 1 );
298+ TestDescriptor method11 = newUnparameterizedMethodDescriptor (inner1 .getUniqueId ());
299+ outerTemplate .addChild (outer1 );
300+ outer1 .addChild (innerTemplate1 );
301+ innerTemplate1 .addChild (inner1 );
302+ inner1 .addChild (method11 );
303+
304+ TestDescriptor outer2 = newParameterizedClassInvocationDescriptor (outerTemplate .getUniqueId (), 2 );
305+ TestDescriptor innerTemplate2 = newParameterizedClassTemplateDescriptor (outer2 .getUniqueId ());
306+ TestDescriptor inner2 = newParameterizedClassInvocationDescriptor (innerTemplate2 .getUniqueId (), 1 );
307+ TestDescriptor method21 = newUnparameterizedMethodDescriptor (inner2 .getUniqueId ());
308+ outerTemplate .addChild (outer2 );
309+ outer2 .addChild (innerTemplate2 );
310+ innerTemplate2 .addChild (inner2 );
311+ inner2 .addChild (method21 );
312+
313+ TestPlan plan = TestPlan .from (false , singletonList (engine ), CONFIG_PARAMS , OUTPUT_DIRECTORY );
314+ adapter .testPlanExecutionStarted (plan );
315+
316+ adapter .executionStarted (TestIdentifier .from (engine ));
317+ adapter .executionStarted (TestIdentifier .from (outerTemplate ));
318+ adapter .executionStarted (TestIdentifier .from (outer1 ));
319+ adapter .executionStarted (TestIdentifier .from (innerTemplate1 ));
320+ adapter .executionStarted (TestIdentifier .from (inner1 ));
321+ adapter .executionStarted (TestIdentifier .from (method11 ));
322+ adapter .executionFinished (TestIdentifier .from (method11 ), successful ());
323+ adapter .executionStarted (TestIdentifier .from (outer2 ));
324+ adapter .executionStarted (TestIdentifier .from (innerTemplate2 ));
325+ adapter .executionStarted (TestIdentifier .from (inner2 ));
326+ adapter .executionStarted (TestIdentifier .from (method21 ));
327+ adapter .executionFinished (TestIdentifier .from (method21 ), failed (new AssertionError ("fail" )));
328+
329+ ArgumentCaptor <ReportEntry > started = ArgumentCaptor .forClass (ReportEntry .class );
330+ verify (listener , times (2 )).testStarting (started .capture ());
331+ assertEquals (
332+ MY_TEST_METHOD_NAME + "()[1][1]" , started .getAllValues ().get (0 ).getName ());
333+ assertEquals (
334+ MY_TEST_METHOD_NAME + "()[2][1]" , started .getAllValues ().get (1 ).getName ());
335+ }
336+
337+ @ Test
338+ public void doesNotReappendClassIndexWhenLegacyNameAlreadyHasIt () throws Exception {
339+ // JUnit 6.1+ parameterized methods already report method()[class][method].
340+ EngineDescriptor engine = new EngineDescriptor (UniqueId .forEngine ("junit-jupiter" ), "JUnit Jupiter" );
341+ TestDescriptor classTemplate = newParameterizedClassTemplateDescriptor (engine .getUniqueId ());
342+ engine .addChild (classTemplate );
343+
344+ TestDescriptor invocation = newParameterizedClassInvocationDescriptor (classTemplate .getUniqueId (), 1 );
345+ TestDescriptor method = newLegacyIndexedMethodDescriptor (invocation .getUniqueId (), "()[1][2]" , 2 );
346+ classTemplate .addChild (invocation );
347+ invocation .addChild (method );
348+
349+ TestPlan plan = TestPlan .from (false , singletonList (engine ), CONFIG_PARAMS , OUTPUT_DIRECTORY );
350+ adapter .testPlanExecutionStarted (plan );
351+
352+ adapter .executionStarted (TestIdentifier .from (engine ));
353+ adapter .executionStarted (TestIdentifier .from (classTemplate ));
354+ adapter .executionStarted (TestIdentifier .from (invocation ));
355+ adapter .executionStarted (TestIdentifier .from (method ));
356+ adapter .executionFinished (TestIdentifier .from (method ), successful ());
357+
358+ ArgumentCaptor <ReportEntry > started = ArgumentCaptor .forClass (ReportEntry .class );
359+ verify (listener ).testStarting (started .capture ());
360+ assertEquals (MY_TEST_METHOD_NAME + "()[1][2]" , started .getValue ().getName ());
361+ }
362+
363+ @ Test
364+ public void classIndexGoesBeforeMethodIndexWhenLegacyNameOnlyHasMethodIndex () throws Exception {
365+ // JUnit 6.0 @ParameterizedTest under @ParameterizedClass: legacy is foo()[m] only.
366+ EngineDescriptor engine = new EngineDescriptor (UniqueId .forEngine ("junit-jupiter" ), "JUnit Jupiter" );
367+ TestDescriptor classTemplate = newParameterizedClassTemplateDescriptor (engine .getUniqueId ());
368+ engine .addChild (classTemplate );
369+
370+ TestDescriptor invocation = newParameterizedClassInvocationDescriptor (classTemplate .getUniqueId (), 2 );
371+ TestDescriptor method = newLegacyIndexedMethodDescriptor (invocation .getUniqueId (), "()[1]" , 1 );
372+ classTemplate .addChild (invocation );
373+ invocation .addChild (method );
374+
375+ TestPlan plan = TestPlan .from (false , singletonList (engine ), CONFIG_PARAMS , OUTPUT_DIRECTORY );
376+ adapter .testPlanExecutionStarted (plan );
377+
378+ adapter .executionStarted (TestIdentifier .from (engine ));
379+ adapter .executionStarted (TestIdentifier .from (classTemplate ));
380+ adapter .executionStarted (TestIdentifier .from (invocation ));
381+ adapter .executionStarted (TestIdentifier .from (method ));
382+ adapter .executionFinished (TestIdentifier .from (method ), successful ());
383+
384+ ArgumentCaptor <ReportEntry > started = ArgumentCaptor .forClass (ReportEntry .class );
385+ verify (listener ).testStarting (started .capture ());
386+ assertEquals (MY_TEST_METHOD_NAME + "()[2][1]" , started .getValue ().getName ());
387+ }
388+
389+ @ Test
390+ public void classAndMethodIndexOnTheDiagonalStayDistinct () throws Exception {
391+ EngineDescriptor engine = new EngineDescriptor (UniqueId .forEngine ("junit-jupiter" ), "JUnit Jupiter" );
392+ TestDescriptor classTemplate = newParameterizedClassTemplateDescriptor (engine .getUniqueId ());
393+ engine .addChild (classTemplate );
394+
395+ TestDescriptor invocation = newParameterizedClassInvocationDescriptor (classTemplate .getUniqueId (), 2 );
396+ TestDescriptor method = newLegacyIndexedMethodDescriptor (invocation .getUniqueId (), "()[2]" , 2 );
397+ classTemplate .addChild (invocation );
398+ invocation .addChild (method );
399+
400+ TestPlan plan = TestPlan .from (false , singletonList (engine ), CONFIG_PARAMS , OUTPUT_DIRECTORY );
401+ adapter .testPlanExecutionStarted (plan );
402+
403+ adapter .executionStarted (TestIdentifier .from (engine ));
404+ adapter .executionStarted (TestIdentifier .from (classTemplate ));
405+ adapter .executionStarted (TestIdentifier .from (invocation ));
406+ adapter .executionStarted (TestIdentifier .from (method ));
407+ adapter .executionFinished (TestIdentifier .from (method ), successful ());
408+
409+ ArgumentCaptor <ReportEntry > started = ArgumentCaptor .forClass (ReportEntry .class );
410+ verify (listener ).testStarting (started .capture ());
411+ assertEquals (MY_TEST_METHOD_NAME + "()[2][2]" , started .getValue ().getName ());
412+ }
413+
242414 @ Test
243415 public void notifiedEagerlyForTestSetWhenClassExecutionStarted () throws Exception {
244416 EngineDescriptor engine = newEngineDescriptor ();
@@ -973,6 +1145,60 @@ public Type getType() {
9731145 assertEquals ("Run a dummy cucumber test" , entry .getName ());
9741146 }
9751147
1148+ private static TestDescriptor newParameterizedClassTemplateDescriptor (UniqueId engineId ) {
1149+ return new ClassTestDescriptor (
1150+ engineId .append ("class-template" , MyTestClass .class .getName ()),
1151+ MyTestClass .class ,
1152+ new DefaultJupiterConfiguration (CONFIG_PARAMS , OUTPUT_DIRECTORY ));
1153+ }
1154+
1155+ private static TestDescriptor newParameterizedClassInvocationDescriptor (UniqueId classTemplateId , int index ) {
1156+ return new AbstractTestDescriptor (
1157+ classTemplateId .append ("class-template-invocation" , "#" + index ),
1158+ "Parameterization with index: [" + index + "]" ,
1159+ ClassSource .from (MyTestClass .class )) {
1160+ @ Override
1161+ public Type getType () {
1162+ return CONTAINER ;
1163+ }
1164+
1165+ @ Override
1166+ public String getLegacyReportingName () {
1167+ return MyTestClass .class .getSimpleName () + "[" + index + "]" ;
1168+ }
1169+ };
1170+ }
1171+
1172+ private static TestDescriptor newUnparameterizedMethodDescriptor (UniqueId parentId ) throws Exception {
1173+ return newLegacyIndexedMethodDescriptor (parentId , "()" );
1174+ }
1175+
1176+ private static TestDescriptor newLegacyIndexedMethodDescriptor (UniqueId parentId , String legacySuffix )
1177+ throws Exception {
1178+ return newLegacyIndexedMethodDescriptor (parentId , legacySuffix , 0 );
1179+ }
1180+
1181+ private static TestDescriptor newLegacyIndexedMethodDescriptor (
1182+ UniqueId parentId , String legacySuffix , int testTemplateInvocationIndex ) throws Exception {
1183+ Method method = MyTestClass .class .getDeclaredMethod (MY_TEST_METHOD_NAME );
1184+ UniqueId methodId = parentId .append ("method" , method .getName () + "()" );
1185+ if (testTemplateInvocationIndex > 0 ) {
1186+ methodId = methodId .append ("test-template-invocation" , "#" + testTemplateInvocationIndex );
1187+ }
1188+ return new AbstractTestDescriptor (
1189+ methodId , method .getName () + "()" , MethodSource .from (MyTestClass .class , method )) {
1190+ @ Override
1191+ public Type getType () {
1192+ return TEST ;
1193+ }
1194+
1195+ @ Override
1196+ public String getLegacyReportingName () {
1197+ return method .getName () + legacySuffix ;
1198+ }
1199+ };
1200+ }
1201+
9761202 private static TestIdentifier newMethodIdentifier () throws Exception {
9771203 return TestIdentifier .from (newMethodDescriptor ());
9781204 }
0 commit comments