1919import static org .junit .jupiter .api .Assertions .assertTrue ;
2020import static org .junit .jupiter .api .Assertions .fail ;
2121import static org .junit .jupiter .api .DynamicTest .dynamicTest ;
22+ import static org .junit .platform .commons .util .ExceptionUtils .throwAsUncheckedException ;
2223import static org .junit .platform .engine .discovery .DiscoverySelectors .selectClass ;
2324import static org .junit .platform .engine .discovery .DiscoverySelectors .selectIteration ;
2425import static org .junit .platform .engine .discovery .DiscoverySelectors .selectMethod ;
3940import static org .junit .platform .testkit .engine .EventConditions .test ;
4041import static org .junit .platform .testkit .engine .EventConditions .uniqueId ;
4142import static org .junit .platform .testkit .engine .TestExecutionResultConditions .message ;
43+ import static org .junit .platform .testkit .engine .TestExecutionResultConditions .suppressed ;
4244
4345import java .util .Collection ;
4446import java .util .List ;
4547import java .util .Map ;
48+ import java .util .function .Function ;
4649import java .util .stream .IntStream ;
4750import java .util .stream .Stream ;
4851
8992import org .junit .platform .engine .UniqueId ;
9093import org .junit .platform .engine .discovery .DiscoverySelectors ;
9194import org .junit .platform .engine .reporting .ReportEntry ;
95+ import org .junit .platform .testkit .engine .EngineExecutionResults ;
96+ import org .opentest4j .AssertionFailedError ;
97+ import org .opentest4j .TestAbortedException ;
9298
9399/**
94100 * @since 5.13
@@ -856,13 +862,8 @@ void executesLifecycleCallbacksInNestedContainerTemplates() {
856862 results .containerEvents ().assertStatistics (stats -> stats .started (10 ).succeeded (10 ));
857863 results .testEvents ().assertStatistics (stats -> stats .started (8 ).succeeded (8 ));
858864
859- var callSequence = results .allEvents ().reportingEntryPublished () //
860- .map (event -> event .getRequiredPayload (ReportEntry .class )) //
861- .map (ReportEntry ::getKeyValuePairs ) //
862- .map (Map ::values ) //
863- .flatMap (Collection ::stream );
864865 // @formatter:off
865- assertThat (callSequence ).containsExactly (
866+ assertThat (allReportEntryValues ( results ) ).containsExactly (
866867 "beforeAll: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase" ,
867868 "beforeContainerTemplateInvocation: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase" ,
868869 "beforeAll: NestedTestCase" ,
@@ -932,27 +933,43 @@ void guaranteesWrappingBehaviorForCallbacks() {
932933 results .containerEvents ().assertStatistics (stats -> stats .started (4 ).succeeded (4 ));
933934 results .testEvents ().assertStatistics (stats -> stats .started (2 ).succeeded (2 ));
934935
935- var callSequence = results .allEvents ().reportingEntryPublished () //
936- .map (event -> event .getRequiredPayload (ReportEntry .class )) //
937- .map (ReportEntry ::getKeyValuePairs ) //
938- .map (Map ::values ) //
939- .flatMap (Collection ::stream );
940936 // @formatter:off
941- assertThat (callSequence ).containsExactly (
937+ assertThat (allReportEntryValues ( results ) ).containsExactly (
942938 "1st -> beforeContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
943- "2nd -> beforeContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
944- "test" ,
945- "2nd -> afterContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
939+ "2nd -> beforeContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
940+ "test" ,
941+ "2nd -> afterContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
946942 "1st -> afterContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
947943 "1st -> beforeContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
948- "2nd -> beforeContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
949- "test" ,
950- "2nd -> afterContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
944+ "2nd -> beforeContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
945+ "test" ,
946+ "2nd -> afterContainerTemplateInvocation: CallbackWrappingBehaviorTestCase" ,
951947 "1st -> afterContainerTemplateInvocation: CallbackWrappingBehaviorTestCase"
952948 );
953949 // @formatter:on
954950 }
955951
952+ @ Test
953+ void propagatesExceptionsFromCallbacks () {
954+
955+ var results = executeTestsForClass (CallbackExceptionBehaviorTestCase .class );
956+
957+ results .allEvents ().assertStatistics (stats -> stats .started (4 ).failed (2 ).succeeded (2 ));
958+
959+ results .containerEvents ().assertThatEvents () //
960+ .haveExactly (2 , finishedWithFailure ( //
961+ message ("2nd -> afterContainerTemplateInvocation: CallbackExceptionBehaviorTestCase" ), //
962+ suppressed (0 ,
963+ message ("1st -> beforeContainerTemplateInvocation: CallbackExceptionBehaviorTestCase" )), //
964+ suppressed (1 ,
965+ message ("1st -> afterContainerTemplateInvocation: CallbackExceptionBehaviorTestCase" ))));
966+
967+ assertThat (allReportEntryValues (results ).distinct ()) //
968+ .containsExactly ("1st -> beforeContainerTemplateInvocation: CallbackExceptionBehaviorTestCase" , //
969+ "2nd -> afterContainerTemplateInvocation: CallbackExceptionBehaviorTestCase" , //
970+ "1st -> afterContainerTemplateInvocation: CallbackExceptionBehaviorTestCase" );
971+ }
972+
956973 @ Test
957974 void templateWithPreparations () {
958975 var results = executeTestsForClass (ContainerTemplateWithPreparationsTestCase .class );
@@ -963,6 +980,14 @@ void templateWithPreparations() {
963980
964981 // -------------------------------------------------------------------
965982
983+ private static Stream <String > allReportEntryValues (EngineExecutionResults results ) {
984+ return results .allEvents ().reportingEntryPublished () //
985+ .map (event -> event .getRequiredPayload (ReportEntry .class )) //
986+ .map (ReportEntry ::getKeyValuePairs ) //
987+ .map (Map ::values ) //
988+ .flatMap (Collection ::stream );
989+ }
990+
966991 @ SuppressWarnings ("JUnitMalformedDeclaration" )
967992 @ ContainerTemplate
968993 @ ExtendWith (TwoInvocationsContainerTemplateInvocationContextProvider .class )
@@ -1428,36 +1453,71 @@ static class CallbackWrappingBehaviorTestCase {
14281453 static Extension second = new ContainerTemplateInvocationCallbacks ("2nd -> " );
14291454
14301455 @ Test
1431- @ DisplayName ("test" )
14321456 void test (TestReporter testReporter ) {
14331457 testReporter .publishEntry ("test" );
14341458 }
14351459 }
14361460
1461+ @ SuppressWarnings ("JUnitMalformedDeclaration" )
1462+ @ ExtendWith (TwoInvocationsContainerTemplateInvocationContextProvider .class )
1463+ @ ContainerTemplate
1464+ static class CallbackExceptionBehaviorTestCase {
1465+
1466+ @ RegisterExtension
1467+ @ Order (1 )
1468+ static Extension first = new ContainerTemplateInvocationCallbacks ("1st -> " , TestAbortedException ::new );
1469+
1470+ @ RegisterExtension
1471+ @ Order (2 )
1472+ static Extension second = new ContainerTemplateInvocationCallbacks ("2nd -> " , AssertionFailedError ::new );
1473+
1474+ @ Test
1475+ void test () {
1476+ fail ("should not be called" );
1477+ }
1478+ }
1479+
14371480 static class ContainerTemplateInvocationCallbacks
14381481 implements BeforeContainerTemplateInvocationCallback , AfterContainerTemplateInvocationCallback {
14391482
14401483 private final String prefix ;
1484+ private final Function <String , Throwable > exceptionFactory ;
14411485
14421486 @ SuppressWarnings ("unused" )
14431487 ContainerTemplateInvocationCallbacks () {
14441488 this ("" );
14451489 }
14461490
14471491 ContainerTemplateInvocationCallbacks (String prefix ) {
1492+ this (prefix , __ -> null );
1493+ }
1494+
1495+ ContainerTemplateInvocationCallbacks (String prefix , Function <String , Throwable > exceptionFactory ) {
14481496 this .prefix = prefix ;
1497+ this .exceptionFactory = exceptionFactory ;
14491498 }
14501499
14511500 @ Override
14521501 public void beforeContainerTemplateInvocation (ExtensionContext context ) {
1453- context .publishReportEntry (
1454- prefix + "beforeContainerTemplateInvocation: " + context .getRequiredTestClass ().getSimpleName ());
1502+ handle ("beforeContainerTemplateInvocation" , context );
14551503 }
14561504
14571505 @ Override
14581506 public void afterContainerTemplateInvocation (ExtensionContext context ) {
1459- context .publishReportEntry (
1460- prefix + "afterContainerTemplateInvocation: " + context .getRequiredTestClass ().getSimpleName ());
1507+ handle ("afterContainerTemplateInvocation" , context );
1508+ }
1509+
1510+ private void handle (String methodName , ExtensionContext context ) {
1511+ var message = format (methodName , context );
1512+ context .publishReportEntry (message );
1513+ var throwable = exceptionFactory .apply (message );
1514+ if (throwable != null ) {
1515+ throw throwAsUncheckedException (throwable );
1516+ }
1517+ }
1518+
1519+ private String format (String methodName , ExtensionContext context ) {
1520+ return "%s%s: %s" .formatted (prefix , methodName , context .getRequiredTestClass ().getSimpleName ());
14611521 }
14621522 }
14631523
0 commit comments