This is an automated email from the ASF dual-hosted git repository. olamy pushed a commit to branch SUREFIRE-1643-junit5-parallel in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit d7f380f3bee252e328855c635b74fe54c3fccc34 Author: Olivier Lamy <ol...@apache.org> AuthorDate: Sun Mar 16 21:49:54 2025 +1000 [SUREFIRE-1643] Make surefire junit5 supporting parallel execution and not mixing result when re running tests Signed-off-by: Olivier Lamy <ol...@apache.org> --- .../surefire/StartupReportConfiguration.java | 2 +- .../surefire/report/StatelessXmlReporter.java | 6 +-- .../plugin/surefire/report/TestSetRunListener.java | 54 ++++++++++++++-------- surefire-providers/surefire-junit-platform/pom.xml | 5 ++ .../junitplatform/JUnitPlatformProvider.java | 18 ++++++-- 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java index 89e6614f3..673ccd8a0 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java @@ -220,7 +220,7 @@ public StatelessReportEventListener<WrappedReportEntry, TestSetStats> instantiat // In the in-plugin execution of parallel JUnit4.7 with rerun the map must be shared because reports and // listeners are in ThreadLocal, see Surefire1122ParallelAndFlakyTestsIT. Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory = - isForking ? new ConcurrentHashMap<String, Deque<WrappedReportEntry>>() : this.testClassMethodRunHistory; + isForking ? new ConcurrentHashMap<>() : this.testClassMethodRunHistory; DefaultStatelessReportMojoConfiguration xmlReporterConfig = new DefaultStatelessReportMojoConfiguration( resolveReportsDirectory(forkNumber), 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 7724b1210..aab9d665b 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 @@ -20,12 +20,12 @@ import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Deque; import java.util.LinkedHashMap; @@ -140,7 +140,7 @@ public StatelessXmlReporter( this.rerunFailingTestsCount = rerunFailingTestsCount; this.testClassMethodRunHistoryMap = testClassMethodRunHistoryMap; this.xsdSchemaLocation = xsdSchemaLocation; - this.xsdVersion = xsdVersion; + this.xsdVersion = xsdVersion;SUREFIRE-1643 this.phrasedFileName = phrasedFileName; this.phrasedSuiteName = phrasedSuiteName; this.phrasedClassName = phrasedClassName; @@ -392,7 +392,7 @@ private OutputStream getOutputStream(WrappedReportEntry testSetReportEntry) thro reportFile.delete(); //noinspection ResultOfMethodCallIgnored reportDir.mkdirs(); - return new BufferedOutputStream(new FileOutputStream(reportFile), 64 * 1024); + return new BufferedOutputStream(Files.newOutputStream(reportFile.toPath()), 64 * 1024); } private static OutputStreamWriter getWriter(OutputStream fos) { diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java index 5e90fb055..716fca87e 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java @@ -22,7 +22,9 @@ import java.util.Collections; import java.util.List; import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; import org.apache.maven.plugin.surefire.runorder.StatisticsReporter; import org.apache.maven.surefire.api.report.ReportEntry; @@ -48,7 +50,7 @@ public class TestSetRunListener implements TestReportListener<TestOutputReportEntry> { private final Queue<TestMethodStats> testMethodStats = new ConcurrentLinkedQueue<>(); - private final TestSetStats detailsForThis; + private final ConcurrentMap<String, TestSetStats> detailsForThis = new ConcurrentHashMap<>(); private final ConsoleOutputReportEventListener testOutputReceiver; @@ -64,6 +66,10 @@ public class TestSetRunListener implements TestReportListener<TestOutputReportEn private final Object lock; + private final boolean trimStackTrace; + + private final boolean isPlainFormat; + private Utf8RecodingDeferredFileOutputStream testStdOut = initDeferred("stdout"); private Utf8RecodingDeferredFileOutputStream testStdErr = initDeferred("stderr"); @@ -85,7 +91,9 @@ public TestSetRunListener( this.simpleXMLReporter = simpleXMLReporter; this.testOutputReceiver = testOutputReceiver; this.briefOrPlainFormat = briefOrPlainFormat; - detailsForThis = new TestSetStats(trimStackTrace, isPlainFormat); + this.trimStackTrace = trimStackTrace; + this.isPlainFormat = isPlainFormat; + // detailsForThis = new TestSetStats(trimStackTrace, isPlainFormat); this.lock = lock; } @@ -166,7 +174,9 @@ public void writeTestOutput(TestOutputReportEntry reportEntry) { @Override public void testSetStarting(TestSetReportEntry report) { - detailsForThis.testSetStart(); + detailsForThis + .computeIfAbsent(report.getSourceName(), s -> new TestSetStats(trimStackTrace, isPlainFormat)) + .testSetStart(); consoleReporter.testSetStarting(report); testOutputReceiver.testSetStarting(report); } @@ -187,20 +197,20 @@ private void clearCapture() { @Override public void testSetCompleted(TestSetReportEntry report) { final WrappedReportEntry wrap = wrapTestSet(report); - final List<String> testResults = - briefOrPlainFormat ? detailsForThis.getTestResults() : Collections.<String>emptyList(); - fileReporter.testSetCompleted(wrap, detailsForThis, testResults); - simpleXMLReporter.testSetCompleted(wrap, detailsForThis); + TestSetStats testSetStats = detailsForThis.get(report.getSourceName()); + final List<String> testResults = briefOrPlainFormat ? testSetStats.getTestResults() : Collections.emptyList(); + fileReporter.testSetCompleted(wrap, testSetStats, testResults); + simpleXMLReporter.testSetCompleted(wrap, testSetStats); statisticsReporter.testSetCompleted(); - consoleReporter.testSetCompleted(wrap, detailsForThis, testResults); + consoleReporter.testSetCompleted(wrap, testSetStats, testResults); testOutputReceiver.testSetCompleted(wrap); consoleReporter.reset(); wrap.getStdout().free(); wrap.getStdErr().free(); - addTestMethodStats(); - detailsForThis.reset(); + addTestMethodStats(report); + testSetStats.reset(); clearCapture(); } @@ -210,13 +220,15 @@ public void testSetCompleted(TestSetReportEntry report) { @Override public void testStarting(ReportEntry report) { - detailsForThis.testStart(); + detailsForThis + .computeIfAbsent(report.getSourceName(), s -> new TestSetStats(trimStackTrace, isPlainFormat)) + .testSetStart(); } @Override public void testSucceeded(ReportEntry reportEntry) { WrappedReportEntry wrapped = wrap(reportEntry, SUCCESS); - detailsForThis.testSucceeded(wrapped); + detailsForThis.get(reportEntry.getSourceName()).testSucceeded(wrapped); statisticsReporter.testSucceeded(reportEntry); clearCapture(); } @@ -224,7 +236,7 @@ public void testSucceeded(ReportEntry reportEntry) { @Override public void testError(ReportEntry reportEntry) { WrappedReportEntry wrapped = wrap(reportEntry, ERROR); - detailsForThis.testError(wrapped); + detailsForThis.get(reportEntry.getSourceName()).testError(wrapped); statisticsReporter.testError(reportEntry); clearCapture(); } @@ -232,7 +244,7 @@ public void testError(ReportEntry reportEntry) { @Override public void testFailed(ReportEntry reportEntry) { WrappedReportEntry wrapped = wrap(reportEntry, FAILURE); - detailsForThis.testFailure(wrapped); + detailsForThis.get(reportEntry.getSourceName()).testFailure(wrapped); statisticsReporter.testFailed(reportEntry); clearCapture(); } @@ -244,7 +256,7 @@ public void testFailed(ReportEntry reportEntry) { @Override public void testSkipped(ReportEntry reportEntry) { WrappedReportEntry wrapped = wrap(reportEntry, SKIPPED); - detailsForThis.testSkipped(wrapped); + detailsForThis.get(reportEntry.getSourceName()).testSkipped(wrapped); statisticsReporter.testSkipped(reportEntry); clearCapture(); } @@ -263,7 +275,8 @@ private WrappedReportEntry wrap(ReportEntry other, ReportEntryType reportEntryTy int estimatedElapsed = 0; if (reportEntryType != SKIPPED) { Integer etime = other.getElapsed(); - estimatedElapsed = etime == null ? detailsForThis.getElapsedSinceLastStart() : etime; + estimatedElapsed = + etime == null ? detailsForThis.get(other.getSourceName()).getElapsedSinceLastStart() : etime; } return new WrappedReportEntry(other, reportEntryType, estimatedElapsed, testStdOut, testStdErr); @@ -273,7 +286,9 @@ private WrappedReportEntry wrapTestSet(TestSetReportEntry other) { return new WrappedReportEntry( other, null, - other.getElapsed() != null ? other.getElapsed() : detailsForThis.getElapsedSinceTestSetStart(), + other.getElapsed() != null + ? other.getElapsed() + : detailsForThis.get(other.getSourceName()).getElapsedSinceTestSetStart(), testStdOut, testStdErr, other.getSystemProperties()); @@ -283,8 +298,9 @@ public void close() { testOutputReceiver.close(); } - private void addTestMethodStats() { - for (WrappedReportEntry reportEntry : detailsForThis.getReportEntries()) { + private void addTestMethodStats(TestSetReportEntry report) { + for (WrappedReportEntry reportEntry : + detailsForThis.get(report.getSourceName()).getReportEntries()) { TestMethodStats methodStats = new TestMethodStats( reportEntry.getClassMethodName(), reportEntry.getReportEntryType(), diff --git a/surefire-providers/surefire-junit-platform/pom.xml b/surefire-providers/surefire-junit-platform/pom.xml index b850fc855..a6c1da758 100644 --- a/surefire-providers/surefire-junit-platform/pom.xml +++ b/surefire-providers/surefire-junit-platform/pom.xml @@ -104,6 +104,11 @@ <artifactId>junit-platform-launcher</artifactId> <version>${junitPlatformVersion}</version> </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-reporting</artifactId> + <version>${junitPlatformVersion}</version> + </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java index 5e8255490..13dd3be31 100644 --- a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java +++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java @@ -18,7 +18,9 @@ */ package org.apache.maven.surefire.junitplatform; +import java.io.File; import java.io.IOException; +import java.io.PrintWriter; import java.io.StringReader; import java.io.UncheckedIOException; import java.util.ArrayList; @@ -46,8 +48,10 @@ import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TagFilter; +import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; import static java.util.Arrays.stream; import static java.util.Collections.emptyMap; @@ -172,6 +176,13 @@ private void invokeAllTests(TestsToRun testsToRun, RunListenerAdapter adapter) { } private void execute(TestsToRun testsToRun, RunListenerAdapter adapter) { + + TestExecutionListener[] testExecutionListeners = new TestExecutionListener[] { + adapter, + new LegacyXmlReportGeneratingListener( + new File("target", "junit-platform").toPath(), new PrintWriter(System.out)) + }; + if (testsToRun.allowEagerReading()) { List<DiscoverySelector> selectors = new ArrayList<>(); testsToRun.iterator().forEachRemaining(c -> selectors.add(selectClass(c.getName()))); @@ -180,15 +191,14 @@ private void execute(TestsToRun testsToRun, RunListenerAdapter adapter) { .filters(filters) .configurationParameters(configurationParameters) .selectors(selectors); - - launcher.execute(builder.build(), adapter); + launcher.execute(builder.build(), testExecutionListeners); } else { testsToRun.iterator().forEachRemaining(c -> { LauncherDiscoveryRequestBuilder builder = request() .filters(filters) .configurationParameters(configurationParameters) .selectors(selectClass(c.getName())); - launcher.execute(builder.build(), adapter); + launcher.execute(builder.build(), testExecutionListeners); }); } } @@ -237,7 +247,7 @@ private Filter<?>[] newFilters() { .map(EngineFilter::excludeEngines) .ifPresent(filters::add); - return filters.toArray(new Filter<?>[filters.size()]); + return filters.toArray(new Filter<?>[0]); } Filter<?>[] getFilters() {