This is an automated email from the ASF dual-hosted git repository.

olamy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git


The following commit(s) were added to refs/heads/master by this push:
     new 479bf2082 Fix empty and missing testcase entries for JUnit 5 
@BeforeAll/@AfterAll failures (#3329)
479bf2082 is described below

commit 479bf20820d597293b94c4380bd532e9f5bc9b2a
Author: Milan Tyagi <[email protected]>
AuthorDate: Mon Mar 30 09:51:33 2026 +0530

    Fix empty and missing testcase entries for JUnit 5 @BeforeAll/@AfterAll 
failures (#3329)
    
    * Fix: JUnit 5 @BeforeAll and @AfterAll failures produce empty testcase name
---
 .../surefire/report/DefaultReporterFactory.java    |  6 +-
 .../surefire/report/StatelessXmlReporter.java      |  4 +-
 .../its/JUnit5BeforeAllContainerFailureIT.java     | 54 +++++++++++++++++
 .../its/jiras/Surefire943ReportContentIT.java      |  2 +-
 .../test/java/junitplatform/AlwaysFailingTest.java |  2 +-
 .../junit5-beforeall-container-failure/pom.xml     | 68 ++++++++++++++++++++++
 .../junitplatform/AlwaysFailingBeforeAllTest.java  | 17 ++++++
 .../surefire/junitplatform/RunListenerAdapter.java | 43 +++++++++-----
 .../junitplatform/JUnitPlatformProviderTest.java   |  4 +-
 .../junitplatform/RunListenerAdapterTest.java      |  6 +-
 10 files changed, 180 insertions(+), 26 deletions(-)

diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactory.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactory.java
index 9c9288000..fd386b519 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactory.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactory.java
@@ -276,9 +276,11 @@ private void mergeTestHistoryResult() {
             List<TestMethodStats> testMethodStats = entry.getValue();
             String testClassMethodName = entry.getKey();
 
-            // Handle @BeforeAll failures (null, empty, or ends with ".null" 
method names)
+            // Handle @BeforeAll failures (null, empty, ends with ".null" or 
".initializationError" method names)
             // But only if they actually failed (ERROR or FAILURE), not if 
they were skipped
-            if ((StringUtils.isBlank(testClassMethodName) || 
testClassMethodName.endsWith(".null"))
+            if ((StringUtils.isBlank(testClassMethodName)
+                            || testClassMethodName.endsWith(".null")
+                            || 
testClassMethodName.endsWith(".initializationError"))
                     && (testClassMethodName == null || 
!testClassMethodName.contains("$"))) {
 
                 // Check if this is actually a failure/error (not skipped or 
success)
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
index 17547db9b..20a577381 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
@@ -406,9 +406,9 @@ private TestResultType 
getTestResultTypeWithBeforeAllHandling(
             Map<String, List<WrappedReportEntry>> methodStatistics) {
         TestResultType resultType = getTestResultType(methodRuns);
 
-        // Special handling for @BeforeAll failures (null method name or 
method name is "null")
+        // Special handling for @BeforeAll failures (null method name or 
method name is "null" or "initializationError")
         // If @BeforeAll failed but any actual test methods succeeded, treat 
it as a flake
-        if ((methodName == null || methodName.equals("null"))
+        if ((methodName == null || methodName.equals("null") || 
methodName.equals("initializationError"))
                 && (resultType == TestResultType.ERROR || resultType == 
TestResultType.FAILURE)) {
             // Check if any actual test methods (non-null and not "null" 
names) succeeded
             boolean hasSuccessfulTestMethods = 
methodStatistics.entrySet().stream()
diff --git 
a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5BeforeAllContainerFailureIT.java
 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5BeforeAllContainerFailureIT.java
new file mode 100644
index 000000000..184d18ec8
--- /dev/null
+++ 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5BeforeAllContainerFailureIT.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.surefire.its;
+
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration test for JUnit 5 container-level failures (e.g. @BeforeAll).
+ * Verifies that such failures generate a valid valid testcase named
+ * "initializationError" in the XML report.
+ */
+public class JUnit5BeforeAllContainerFailureIT extends 
SurefireJUnit4IntegrationTestCase {
+
+    @Test
+    public void testBeforeAllContainerFailure() {
+        OutputValidator outputValidator = 
unpack("junit5-beforeall-container-failure")
+                .setJUnitVersion("5.9.1")
+                .maven()
+                .debugLogging()
+                .withFailure()
+                .executeTest();
+
+        // One test fails at the container level: meaning tests run = 1, 
errors = 1, failures = 0, skipped = 0
+        outputValidator.assertTestSuiteResults(1, 1, 0, 0);
+
+        outputValidator
+                
.getSurefireReportsXmlFile("TEST-junitplatform.AlwaysFailingBeforeAllTest.xml")
+                .assertContainsText("tests=\"1\" errors=\"1\" skipped=\"0\" 
failures=\"0\"")
+                .assertContainsText(
+                        "<testcase name=\"initializationError\" 
classname=\"junitplatform.AlwaysFailingBeforeAllTest\" time=")
+                .assertContainsText("<error message=\"BeforeAll always fails\" 
type=\"java.lang.RuntimeException\">")
+                .assertContainsText("<![CDATA[java.lang.RuntimeException: 
BeforeAll always fails")
+                .assertContainsText(
+                        "at 
junitplatform.AlwaysFailingBeforeAllTest.setup(AlwaysFailingBeforeAllTest.java:10)");
+    }
+}
diff --git 
a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
index 21b61f679..f695ced01 100644
--- 
a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
+++ 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
@@ -69,7 +69,7 @@ private void validateFailInBeforeClass(OutputValidator 
validator, String classNa
         Xpp3Dom child = children[0];
 
         Assertions.assertEquals(className, child.getAttribute("classname"));
-        Assertions.assertEquals("", child.getAttribute("name"));
+        Assertions.assertEquals("initializationError", 
child.getAttribute("name"));
 
         Assertions.assertEquals(
                 1,
diff --git 
a/surefire-its/src/test/resources/junit-platform-rerun-failing-before-all/src/test/java/junitplatform/AlwaysFailingTest.java
 
b/surefire-its/src/test/resources/junit-platform-rerun-failing-before-all/src/test/java/junitplatform/AlwaysFailingTest.java
index 511026a39..b1bc79df6 100644
--- 
a/surefire-its/src/test/resources/junit-platform-rerun-failing-before-all/src/test/java/junitplatform/AlwaysFailingTest.java
+++ 
b/surefire-its/src/test/resources/junit-platform-rerun-failing-before-all/src/test/java/junitplatform/AlwaysFailingTest.java
@@ -19,4 +19,4 @@ static void setup() {
     public void testThatNeverRuns() {
         System.out.println("This test never runs because beforeAll always 
fails");
     }
-}
\ No newline at end of file
+}
diff --git 
a/surefire-its/src/test/resources/junit5-beforeall-container-failure/pom.xml 
b/surefire-its/src/test/resources/junit5-beforeall-container-failure/pom.xml
new file mode 100644
index 000000000..4f0a8f182
--- /dev/null
+++ b/surefire-its/src/test/resources/junit5-beforeall-container-failure/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit5-beforeall-container-failure</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>Test for BeforeAll container failures in JUnit 5/Platform</name>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>${surefire.version}</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/surefire-its/src/test/resources/junit5-beforeall-container-failure/src/test/java/junitplatform/AlwaysFailingBeforeAllTest.java
 
b/surefire-its/src/test/resources/junit5-beforeall-container-failure/src/test/java/junitplatform/AlwaysFailingBeforeAllTest.java
new file mode 100644
index 000000000..03a91a1e8
--- /dev/null
+++ 
b/surefire-its/src/test/resources/junit5-beforeall-container-failure/src/test/java/junitplatform/AlwaysFailingBeforeAllTest.java
@@ -0,0 +1,17 @@
+package junitplatform;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class AlwaysFailingBeforeAllTest {
+
+    @BeforeAll
+    public static void setup() {
+        throw new RuntimeException("BeforeAll always fails");
+    }
+
+    @Test
+    public void testPassingTest() {
+        // this test should be skipped or fail due to beforeAll failure
+    }
+}
diff --git 
a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java
 
b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java
index f8e4e5aa7..9c885e501 100644
--- 
a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java
+++ 
b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java
@@ -58,6 +58,8 @@
  * @since 2.22.0
  */
 final class RunListenerAdapter implements TestExecutionListener, 
TestOutputReceiver<OutputReportEntry>, RunModeSetter {
+    private static final String INITIALIZATION_ERROR = "initializationError";
+
     private final ClassMethodIndexer classMethodIndexer = new 
ClassMethodIndexer();
     private final ConcurrentMap<TestIdentifier, Long> testStartTime = new 
ConcurrentHashMap<>();
     private final ConcurrentMap<TestIdentifier, TestExecutionResult> failures 
= new ConcurrentHashMap<>();
@@ -236,6 +238,15 @@ private SimpleReportEntry createReportEntry(
         boolean failed = testExecutionResult == null || 
testExecutionResult.getStatus() == FAILED;
         String methodName = failed || testIdentifier.isTest() ? 
classMethodName.getMethodSignature() : null;
         String methodText = failed || testIdentifier.isTest() ? 
classMethodName.getMethodDisplayName() : null;
+
+        if (testExecutionResult != null
+                && testExecutionResult.getStatus() != 
org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL
+                && testIdentifier.isContainer()
+                && methodName == null) {
+            methodName = INITIALIZATION_ERROR;
+            methodText = INITIALIZATION_ERROR;
+        }
+
         if (Objects.equals(methodName, methodText)) {
             methodText = null;
         }
@@ -304,7 +315,8 @@ private TestIdentifier findTopParent(TestIdentifier 
testIdentifier) {
     }
 
     /**
-     * Checks if the test identifier has a parent ID but using reflection as 
it's only available from 1.8.
+     * Checks if the test identifier has a parent ID but using reflection as 
it's
+     * only available from 1.8.
      *
      * @param testIdentifier the test identifier to check
      * @return true if the test identifier has a parent ID, false otherwise
@@ -342,10 +354,10 @@ private TestIdentifier 
findFirstParentContainerAndSourceClass(TestIdentifier tes
 
     /**
      * <ul>
-     *     <li>[0] class name - used in stacktrace parser</li>
-     *     <li>[1] class display name</li>
-     *     <li>[2] method signature - used in stacktrace parser</li>
-     *     <li>[3] method display name</li>
+     * <li>[0] class name - used in stacktrace parser</li>
+     * <li>[1] class display name</li>
+     * <li>[2] method signature - used in stacktrace parser</li>
+     * <li>[3] method display name</li>
      * </ul>
      *
      * @param testIdentifier a class or method
@@ -353,7 +365,8 @@ private TestIdentifier 
findFirstParentContainerAndSourceClass(TestIdentifier tes
      */
     private ResultDisplay toClassMethodName(TestIdentifier testIdentifier) {
 
-        // find the first class or method source in the hierarchy just below 
the root level
+        // find the first class or method source in the hierarchy just below 
the root
+        // level
         // without parent and with ClassSource
         Optional<String> classLevelName = Optional.empty();
         TestIdentifier parent = findTopParent(testIdentifier);
@@ -413,18 +426,18 @@ private ResultDisplay toClassMethodName(TestIdentifier 
testIdentifier) {
             String methodDisp = hasDisplayName ? methodDisplay : methodDesc;
 
             // The behavior of methods getLegacyReportingName() and 
getDisplayName().
-            //     junit4    ||  legacy  |  display
+            // junit4 || legacy | display
             // ==============||==========|==========
-            //     normal    ||     m    |     m
-            //     param     ||   m[0]   |   m[0]
-            //  param+displ  || m[displ] | m[displ]
+            // normal || m | m
+            // param || m[0] | m[0]
+            // param+displ || m[displ] | m[displ]
 
-            //     junit5    ||  legacy  |  display
+            // junit5 || legacy | display
             // ==============||==========|==========
-            //    normal     ||    m()   |    m()
-            //  normal+displ ||    m()   |   displ
-            //     param     ||  m()[1]  | [1] <param>
-            //  param+displ  ||  m()[1]  |   displ
+            // normal || m() | m()
+            // normal+displ || m() | displ
+            // param || m()[1] | [1] <param>
+            // param+displ || m()[1] | displ
 
             return new ResultDisplay(
                     classLevelName.orElse(source.getClassName()),
diff --git 
a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
 
b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
index 3976afca5..bd237e945 100644
--- 
a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
+++ 
b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
@@ -130,7 +130,7 @@ public void shouldFailClassOnBeforeAll() throws Exception {
 
         
assertThat(testCaptor.getValue().getSourceName()).isEqualTo(FailingBeforeAllJupiterTest.class.getName());
 
-        assertThat(testCaptor.getValue().getName()).isNull();
+        
assertThat(testCaptor.getValue().getName()).isEqualTo("initializationError");
 
         assertThat(testSetCaptor.getAllValues().get(1).getSourceName())
                 .isEqualTo(FailingBeforeAllJupiterTest.class.getName());
@@ -171,7 +171,7 @@ public void shouldErrorClassOnBeforeAll() throws Exception {
         assertThat(testCaptor.getValue().getSourceName())
                 
.isEqualTo(FailingWithErrorBeforeAllJupiterTest.class.getName());
 
-        assertThat(testCaptor.getValue().getName()).isNull();
+        
assertThat(testCaptor.getValue().getName()).isEqualTo("initializationError");
 
         assertThat(testSetCaptor.getAllValues().get(1).getSourceName())
                 
.isEqualTo(FailingWithErrorBeforeAllJupiterTest.class.getName());
diff --git 
a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTest.java
 
b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTest.java
index a30adb1a9..9e5c2fb35 100644
--- 
a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTest.java
+++ 
b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTest.java
@@ -394,7 +394,7 @@ public void notifiedWhenClassExecutionAborted() {
         TestSkippedException t = new TestSkippedException("skipped");
         adapter.executionFinished(newClassIdentifier(), aborted(t));
         String source = MyTestClass.class.getName();
-        StackTraceWriter stw = new DefaultStackTraceWriter(source, null, t);
+        StackTraceWriter stw = new DefaultStackTraceWriter(source, 
"initializationError", t);
         ArgumentCaptor<SimpleReportEntry> report = 
ArgumentCaptor.forClass(SimpleReportEntry.class);
         verify(listener).testSetCompleted(report.capture());
         assertThat(report.getValue().getSourceName()).isEqualTo(source);
@@ -438,7 +438,7 @@ public void 
notifiedWithCorrectNamesWhenClassExecutionFailed() {
 
         ReportEntry entry = entryCaptor.getValue();
         assertEquals(MyTestClass.class.getTypeName(), entry.getSourceName());
-        assertNull(entry.getName());
+        assertEquals("initializationError", entry.getName());
         assertNotNull(entry.getStackTraceWriter());
         assertNotNull(entry.getStackTraceWriter().getThrowable());
         
assertThat(entry.getStackTraceWriter().getThrowable().getTarget()).isInstanceOf(AssertionError.class);
@@ -457,7 +457,7 @@ public void 
notifiedWithCorrectNamesWhenClassExecutionErrored() {
 
         ReportEntry entry = entryCaptor.getValue();
         assertEquals(MyTestClass.class.getTypeName(), entry.getSourceName());
-        assertNull(entry.getName());
+        assertEquals("initializationError", entry.getName());
         assertNotNull(entry.getStackTraceWriter());
         assertNotNull(entry.getStackTraceWriter().getThrowable());
         
assertThat(entry.getStackTraceWriter().getThrowable().getTarget()).isInstanceOf(RuntimeException.class);

Reply via email to