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);