This is an automated email from the ASF dual-hosted git repository.
elharo 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 4a47a1c44 Properly work with test failures caused during beforeAll
phase (#3194)
4a47a1c44 is described below
commit 4a47a1c44f3abe18164cc3a5d1287f440bb40684
Author: Jakub Stejskal <[email protected]>
AuthorDate: Wed Oct 8 14:02:27 2025 +0200
Properly work with test failures caused during beforeAll phase (#3194)
Signed-off-by: Jakub Stejskal <[email protected]>
---
.../surefire/report/DefaultReporterFactory.java | 86 ++++++++++++++++++++++
.../report/DefaultReporterFactoryTest.java | 46 ++++++++++--
2 files changed, 126 insertions(+), 6 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 f95c061ab..520febab2 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
@@ -72,6 +72,9 @@ public class DefaultReporterFactory implements
ReporterFactory, ReportsMerger {
private RunStatistics globalStats = new RunStatistics();
+ // from "<testclass>.<testmethod>" -> statistics about all the runs for
success tests
+ private Map<String, List<TestMethodStats>> successTests;
+
// from "<testclass>.<testmethod>" -> statistics about all the runs for
flaky tests
private Map<String, List<TestMethodStats>> flakyTests;
@@ -243,6 +246,7 @@ static TestResultType
getTestResultType(List<ReportEntryType> reportEntries, int
*/
private void mergeTestHistoryResult() {
globalStats = new RunStatistics();
+ successTests = new TreeMap<>();
flakyTests = new TreeMap<>();
failedTests = new TreeMap<>();
errorTests = new TreeMap<>();
@@ -265,10 +269,32 @@ private void mergeTestHistoryResult() {
// Update globalStatistics by iterating through mergedTestHistoryResult
int completedCount = 0, skipped = 0;
+ Map<String, List<TestMethodStats>> beforeAllFailures = new HashMap<>();
for (Map.Entry<String, List<TestMethodStats>> entry :
mergedTestHistoryResult.entrySet()) {
List<TestMethodStats> testMethodStats = entry.getValue();
String testClassMethodName = entry.getKey();
+
+ // Handle @BeforeAll failures (null method names)
+ if (testClassMethodName == null) {
+ for (TestMethodStats methodStats : testMethodStats) {
+ String className =
extractClassNameFromStackTrace(methodStats);
+ if (className != null) {
+ if (beforeAllFailures.containsKey(className)) {
+ List<TestMethodStats> previousMethodStats =
beforeAllFailures.get(className);
+ previousMethodStats.add(methodStats);
+ beforeAllFailures.put(className,
previousMethodStats);
+ } else {
+ List<TestMethodStats> initMethodStats = new
ArrayList<>();
+ initMethodStats.add(methodStats);
+ beforeAllFailures.put(className, initMethodStats);
+ }
+ }
+ }
+ // Skip normal processing of @BeforeAll failures because it
needs special care
+ continue;
+ }
+
completedCount++;
List<ReportEntryType> resultTypes = new ArrayList<>();
@@ -286,6 +312,16 @@ private void mergeTestHistoryResult() {
}
}
completedCount += successCount - 1;
+ successTests.put(testClassMethodName, testMethodStats);
+
+ // If current test belong to class that failed during
beforeAll store that info to proper log info
+ String className =
extractClassNameFromMethodName(testClassMethodName);
+ if (beforeAllFailures.containsKey(className)) {
+ List<TestMethodStats> previousMethodStats =
beforeAllFailures.get(className);
+ previousMethodStats.addAll(testMethodStats);
+ beforeAllFailures.put(className, previousMethodStats);
+ }
+
break;
case SKIPPED:
skipped++;
@@ -304,6 +340,21 @@ private void mergeTestHistoryResult() {
}
}
+ // Process @BeforeAll failures after we know which classes have
successful tests
+ for (Map.Entry<String, List<TestMethodStats>> entry :
beforeAllFailures.entrySet()) {
+ String className = entry.getKey();
+ List<TestMethodStats> testMethodStats = entry.getValue();
+ String classNameKey = className + ".<beforeAll>";
+
+ if (reportConfiguration.getRerunFailingTestsCount() > 0
+ && testMethodStats.stream().anyMatch(methodStats ->
methodStats.getTestClassMethodName() != null)) {
+ flakyTests.put(classNameKey, testMethodStats);
+ } else {
+ errorTests.put(classNameKey, testMethodStats);
+ completedCount++;
+ }
+ }
+
globalStats.set(completedCount, errorTests.size(), failedTests.size(),
skipped, flakyTests.size());
}
@@ -362,6 +413,41 @@ boolean printTestFailures(TestResultType type) {
return printed;
}
+ /**
+ * Extract class name from test class method name like
"com.example.TestClass.methodName"
+ */
+ private static String extractClassNameFromMethodName(String
testClassMethodName) {
+ if (testClassMethodName == null || testClassMethodName.isEmpty()) {
+ return null;
+ }
+ int lastDotIndex = testClassMethodName.lastIndexOf('.');
+ if (lastDotIndex > 0) {
+ return testClassMethodName.substring(0, lastDotIndex);
+ }
+ return null;
+ }
+
+ /**
+ * Extract class name from stack trace when method name is null (e.g.,
@BeforeAll failures)
+ */
+ private static String extractClassNameFromStackTrace(TestMethodStats
stats) {
+ if (stats.getStackTraceWriter() == null) {
+ return null;
+ }
+ String stackTrace =
stats.getStackTraceWriter().smartTrimmedStackTrace();
+ if (stackTrace == null || stackTrace.isEmpty()) {
+ return null;
+ }
+
+ // Strip everything after the first whitespace character
+ int firstWhitespace = stackTrace.indexOf(' ');
+ if (firstWhitespace > 0) {
+ stackTrace = stackTrace.substring(0, firstWhitespace);
+ }
+
+ return extractClassNameFromMethodName(stackTrace);
+ }
+
// Describe the result of a given test
enum TestResultType {
ERROR("Errors: "),
diff --git
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
index 86c23c82a..76479f815 100644
---
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
+++
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
@@ -68,6 +68,12 @@ public class DefaultReporterFactoryTest extends TestCase {
private static final String ERROR = "error";
+ private static final String TEST_BEFORE_ALL_FLAKE =
"com.example.FlakyClass";
+
+ private static final String TEST_BEFORE_ALL_ERROR =
"com.example.AlwaysFailClass";
+
+ private static final String TEST_ERROR_SUFFIX = "-- Time elapsed: 292.2 s
<<< ERROR!";
+
public void testMergeTestHistoryResult() throws Exception {
MessageUtils.setColorEnabled(false);
File target = new File(System.getProperty("user.dir"), "target");
@@ -97,7 +103,7 @@ public void testMergeTestHistoryResult() throws Exception {
DefaultReporterFactory factory = new
DefaultReporterFactory(reportConfig, reporter);
- // First run, four tests failed and one passed
+ // First run, four tests failed and one passed, plus @BeforeAll failure
Queue<TestMethodStats> firstRunStats = new ArrayDeque<>();
firstRunStats.add(new TestMethodStats(TEST_ONE, ReportEntryType.ERROR,
new DummyStackTraceWriter(ERROR)));
firstRunStats.add(new TestMethodStats(TEST_TWO, ReportEntryType.ERROR,
new DummyStackTraceWriter(ERROR)));
@@ -106,6 +112,11 @@ public void testMergeTestHistoryResult() throws Exception {
firstRunStats.add(
new TestMethodStats(TEST_FOUR, ReportEntryType.FAILURE, new
DummyStackTraceWriter(ASSERTION_FAIL)));
firstRunStats.add(new TestMethodStats(TEST_FIVE,
ReportEntryType.SUCCESS, null));
+ // @BeforeAll failure for a test class that will eventually succeed
+ firstRunStats.add(new TestMethodStats(
+ null,
+ ReportEntryType.ERROR,
+ new DummyStackTraceWriter(TEST_BEFORE_ALL_FLAKE + ".null " +
TEST_ERROR_SUFFIX)));
// Second run, two tests passed
Queue<TestMethodStats> secondRunStats = new ArrayDeque<>();
@@ -114,11 +125,21 @@ public void testMergeTestHistoryResult() throws Exception
{
secondRunStats.add(new TestMethodStats(TEST_TWO,
ReportEntryType.SUCCESS, null));
secondRunStats.add(new TestMethodStats(TEST_THREE,
ReportEntryType.ERROR, new DummyStackTraceWriter(ERROR)));
secondRunStats.add(new TestMethodStats(TEST_FOUR,
ReportEntryType.SUCCESS, null));
+ // Successful test from the class that had @BeforeAll failure
+ secondRunStats.add(new TestMethodStats(TEST_BEFORE_ALL_FLAKE +
".testSucceed", ReportEntryType.SUCCESS, null));
+ // @BeforeAll failure for a different class that will stay as error
+ secondRunStats.add(new TestMethodStats(
+ null,
+ ReportEntryType.ERROR,
+ new DummyStackTraceWriter(TEST_BEFORE_ALL_ERROR + ".null " +
TEST_ERROR_SUFFIX)));
// Third run, another test passed
Queue<TestMethodStats> thirdRunStats = new ArrayDeque<>();
thirdRunStats.add(new TestMethodStats(TEST_ONE,
ReportEntryType.SUCCESS, null));
thirdRunStats.add(new TestMethodStats(TEST_THREE,
ReportEntryType.ERROR, new DummyStackTraceWriter(ERROR)));
+ // Another @BeforeAll failure for the always-failing class
+ thirdRunStats.add(new TestMethodStats(
+ null, ReportEntryType.ERROR, new
DummyStackTraceWriter(TEST_BEFORE_ALL_ERROR + ".null")));
TestSetRunListener firstRunListener = mock(TestSetRunListener.class);
TestSetRunListener secondRunListener = mock(TestSetRunListener.class);
@@ -134,17 +155,21 @@ public void testMergeTestHistoryResult() throws Exception
{
invokeMethod(factory, "mergeTestHistoryResult");
RunStatistics mergedStatistics = factory.getGlobalRunStatistics();
- // Only TEST_THREE is a failing test, other three are flaky tests
- assertEquals(5, mergedStatistics.getCompletedCount());
- assertEquals(1, mergedStatistics.getErrors());
+ // TEST_THREE and AlwaysFailClass.null are failing tests, regular
tests + FlakyClass.null are flaky
+ assertEquals(7, mergedStatistics.getCompletedCount());
+ assertEquals(2, mergedStatistics.getErrors());
assertEquals(0, mergedStatistics.getFailures());
- assertEquals(3, mergedStatistics.getFlakes());
+ assertEquals(4, mergedStatistics.getFlakes());
assertEquals(0, mergedStatistics.getSkipped());
// Now test the result will be printed out correctly
factory.printTestFailures(TestResultType.FLAKE);
String[] expectedFlakeOutput = {
"Flakes: ",
+ TEST_BEFORE_ALL_FLAKE + ".<beforeAll>",
+ " Run 1: " + TEST_BEFORE_ALL_FLAKE + ".null " + TEST_ERROR_SUFFIX,
+ " Run 2: PASS",
+ "",
TEST_FOUR,
" Run 1: " + ASSERTION_FAIL,
" Run 2: PASS",
@@ -164,7 +189,16 @@ public void testMergeTestHistoryResult() throws Exception {
reporter.reset();
factory.printTestFailures(TestResultType.ERROR);
String[] expectedFailureOutput = {
- "Errors: ", TEST_THREE, " Run 1: " + ASSERTION_FAIL, " Run 2: "
+ ERROR, " Run 3: " + ERROR, ""
+ "Errors: ",
+ TEST_BEFORE_ALL_ERROR + ".<beforeAll>",
+ " Run 1: " + TEST_BEFORE_ALL_ERROR + ".null " + TEST_ERROR_SUFFIX,
+ " Run 2: " + TEST_BEFORE_ALL_ERROR + ".null",
+ "",
+ TEST_THREE,
+ " Run 1: " + ASSERTION_FAIL,
+ " Run 2: " + ERROR,
+ " Run 3: " + ERROR,
+ ""
};
assertEquals(asList(expectedFailureOutput), reporter.getMessages());