You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2022/01/22 23:48:38 UTC

[maven-surefire] branch master updated: [SUREFIRE-1926] Console logs should be synchronized

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

tibordigana 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 f2fe7a5  [SUREFIRE-1926] Console logs should be synchronized
f2fe7a5 is described below

commit f2fe7a5c4d32c482e11fd2289c40fefab968919f
Author: Tibor Digana <ti...@gmail.com>
AuthorDate: Tue Dec 28 22:17:57 2021 +0100

    [SUREFIRE-1926] Console logs should be synchronized
---
 .../plugin/surefire/booterclient/ForkStarter.java  | 32 ++++++++++------
 .../surefire/booterclient/output/ForkClient.java   | 19 +++++++---
 .../output/NativeStdErrStreamConsumer.java         | 16 ++++----
 .../output/NativeStdOutStreamConsumer.java         | 16 ++++----
 .../surefire/extensions/SurefireForkChannel.java   |  2 +-
 .../plugin/surefire/log/PluginConsoleLogger.java   | 20 ++++++----
 .../surefire/report/DefaultReporterFactory.java    |  8 +++-
 .../plugin/surefire/report/ReportsMerger.java      | 36 ++++++++++++++++++
 .../plugin/surefire/report/TestSetRunListener.java | 44 +++++++++++++++++-----
 .../surefire/booterclient/ForkStarterTest.java     | 23 +++++++++--
 .../booterclient/ForkingRunListenerTest.java       |  7 ++++
 .../booterclient/output/ForkClientTest.java        |  7 ++++
 .../maven/plugin/surefire/extensions/E2ETest.java  |  7 ++++
 .../extensions/EventConsumerThreadTest.java        |  7 ++++
 .../extensions/ForkedProcessEventNotifierTest.java |  7 ++++
 .../maven/surefire/extensions/ForkChannelTest.java |  7 ++++
 .../maven/surefire/stream/EventDecoderTest.java    |  7 ++++
 .../maven/surefire/api/fork/ForkNodeArguments.java |  3 ++
 .../api/stream/AbstractStreamDecoderTest.java      |  7 ++++
 .../maven/surefire/booter/ForkedNodeArg.java       |  9 ++++-
 .../booter/spi/CommandChannelDecoderTest.java      |  7 ++++
 21 files changed, 234 insertions(+), 57 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index a999069..29bc31e 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
@@ -33,6 +33,7 @@ import org.apache.maven.plugin.surefire.booterclient.output.NativeStdErrStreamCo
 import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
+import org.apache.maven.plugin.surefire.report.ReportsMerger;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
@@ -155,7 +156,7 @@ public class ForkStarter
 
     private final ConsoleLogger log;
 
-    private final DefaultReporterFactory defaultReporterFactory;
+    private final ReportsMerger reportMerger;
 
     private final Collection<DefaultReporterFactory> defaultReporterFactories;
 
@@ -206,7 +207,7 @@ public class ForkStarter
                     // if tests failed, but if this does not happen then printing warning to console is the only way to
                     // inform the users.
                     String msg = "ForkStarter IOException: " + e.getLocalizedMessage() + ".";
-                    File reportsDir = defaultReporterFactory.getReportsDirectory();
+                    File reportsDir = reportMerger.getReportsDirectory();
                     File dump = InPluginProcessDumpSingleton.getSingleton()
                                         .dumpStreamException( e, msg, reportsDir, jvmRun );
                     log.warning( msg + " See the dump file " + dump.getAbsolutePath() );
@@ -247,8 +248,8 @@ public class ForkStarter
         this.startupConfiguration = startupConfiguration;
         this.startupReportConfiguration = startupReportConfiguration;
         this.log = log;
-        defaultReporterFactory = new DefaultReporterFactory( startupReportConfiguration, log );
-        defaultReporterFactory.runStarting();
+        reportMerger = new DefaultReporterFactory( startupReportConfiguration, log );
+        reportMerger.runStarting();
         defaultReporterFactories = new ConcurrentLinkedQueue<>();
         currentForkClients = new ConcurrentLinkedQueue<>();
         timeoutCheckScheduler = createTimeoutCheckScheduler();
@@ -268,8 +269,8 @@ public class ForkStarter
         }
         finally
         {
-            defaultReporterFactory.mergeFromOtherFactories( defaultReporterFactories );
-            defaultReporterFactory.close();
+            reportMerger.mergeFromOtherFactories( defaultReporterFactories );
+            reportMerger.close();
             pingThreadScheduler.shutdownNow();
             timeoutCheckScheduler.shutdownNow();
             for ( String line : logsAtEnd )
@@ -574,8 +575,8 @@ public class ForkStarter
         File dumpLogDir = replaceForkThreadsInPath( startupReportConfiguration.getReportsDirectory(), forkNumber );
         try
         {
-            ForkNodeArguments forkNodeArguments =
-                new ForkedNodeArg( forkConfiguration.isDebug(), forkNumber, dumpLogDir, randomUUID().toString() );
+            ForkNodeArguments forkNodeArguments = new ForkedNodeArg( forkConfiguration.isDebug(), forkNumber,
+                dumpLogDir, randomUUID().toString() );
             forkChannel = forkNodeFactory.createForkChannel( forkNodeArguments );
             closer.addCloseable( forkChannel );
             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
@@ -639,7 +640,7 @@ public class ForkStarter
             CommandlineStreams streams = exec.execute();
             closer.addCloseable( streams );
 
-            err = bindErrorStream( forkNumber, countdownCloseable, streams );
+            err = bindErrorStream( forkNumber, countdownCloseable, streams, forkClient.getConsoleOutputReceiver() );
 
             forkChannel.bindCommandReader( commandReader, streams.getStdInChannel() );
 
@@ -753,10 +754,10 @@ public class ForkStarter
         return runResult;
     }
 
-    private Stoppable bindErrorStream( int forkNumber, CountdownCloseable countdownCloseable,
-                                       CommandlineStreams streams )
+    private static Stoppable bindErrorStream( int forkNumber, CountdownCloseable countdownCloseable,
+                                              CommandlineStreams streams, Object errorStreamLock )
     {
-        EventHandler<String> errConsumer = new NativeStdErrStreamConsumer( log );
+        EventHandler<String> errConsumer = new NativeStdErrStreamConsumer( errorStreamLock );
         LineConsumerThread stdErr = new LineConsumerThread( "fork-" + forkNumber + "-err-thread",
             streams.getStdErrChannel(), errConsumer, countdownCloseable );
         stdErr.start();
@@ -938,6 +939,13 @@ public class ForkStarter
             return log;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return log;
+        }
+
         @Override
         public File getEventStreamBinaryFile()
         {
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
index a1e54b1..1011fbf 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
@@ -78,7 +78,7 @@ public class ForkClient
 
     private final int forkNumber;
 
-    private RunListener testSetReporter;
+    private volatile RunListener testSetReporter;
 
     /**
      * Written by one Thread and read by another: Main Thread and ForkStarter's Thread.
@@ -340,7 +340,7 @@ public class ForkClient
     {
         if ( forkedProcessTimeoutInSeconds > 0 )
         {
-            final long forkedProcessTimeoutInMillis = 1000 * forkedProcessTimeoutInSeconds;
+            final long forkedProcessTimeoutInMillis = 1000L * forkedProcessTimeoutInSeconds;
             final long startedAt = testSetStartedAt.get();
             if ( startedAt > START_TIME_ZERO && currentTimeMillis - startedAt >= forkedProcessTimeoutInMillis )
             {
@@ -376,11 +376,20 @@ public class ForkClient
         return testSetStartedAt.get() == START_TIME_NEGATIVE_TIMEOUT;
     }
 
+    /**
+     * Only {@link #getConsoleOutputReceiver()} may call this method in another Thread.
+     */
     private RunListener getTestSetReporter()
     {
         if ( testSetReporter == null )
         {
-            testSetReporter = defaultReporterFactory.createReporter();
+            synchronized ( this )
+            {
+                if ( testSetReporter == null )
+                {
+                    testSetReporter = defaultReporterFactory.createReporter();
+                }
+            }
         }
         return testSetReporter;
     }
@@ -394,7 +403,7 @@ public class ForkClient
 
     private void writeTestOutput( String output, boolean newLine, boolean isStdout )
     {
-        getOrCreateConsoleOutputReceiver()
+        getConsoleOutputReceiver()
                 .writeTestOutput( output, newLine, isStdout );
     }
 
@@ -414,7 +423,7 @@ public class ForkClient
         return getTestSetReporter();
     }
 
-    private ConsoleOutputReceiver getOrCreateConsoleOutputReceiver()
+    public ConsoleOutputReceiver getConsoleOutputReceiver()
     {
         return (ConsoleOutputReceiver) getTestSetReporter();
     }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
index d7136ff..70e36f1 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
@@ -19,13 +19,12 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.extensions.EventHandler;
 
 import javax.annotation.Nonnull;
 
 /**
- * The error logger for the error stream of the forked JMV,
+ * The standard error logger for the error stream of the forked JMV,
  * see {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
@@ -35,16 +34,19 @@ import javax.annotation.Nonnull;
 public final class NativeStdErrStreamConsumer
     implements EventHandler<String>
 {
-    private final ConsoleLogger logger;
+    private final Object errStreamLock;
 
-    public NativeStdErrStreamConsumer( ConsoleLogger logger )
+    public NativeStdErrStreamConsumer( Object errStreamLock )
     {
-        this.logger = logger;
+        this.errStreamLock = errStreamLock;
     }
 
     @Override
-    public void handleEvent( @Nonnull String line )
+    public void handleEvent( @Nonnull String message )
     {
-        logger.error( line );
+        synchronized ( errStreamLock )
+        {
+            System.err.println( message );
+        }
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
index 0d9cef2..4d0a268 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
@@ -19,13 +19,12 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.extensions.EventHandler;
 
 import javax.annotation.Nonnull;
 
 /**
- * The output/INFO logger for the output stream of the forked JMV,
+ * The standard output logger for the output stream of the forked JMV,
  * see org.apache.maven.plugin.surefire.extensions.SurefireForkChannel.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
@@ -34,16 +33,19 @@ import javax.annotation.Nonnull;
 public final class NativeStdOutStreamConsumer
     implements EventHandler<String>
 {
-    private final ConsoleLogger logger;
+    private final Object outStreamLock;
 
-    public NativeStdOutStreamConsumer( ConsoleLogger logger )
+    public NativeStdOutStreamConsumer( Object outStreamLock )
     {
-        this.logger = logger;
+        this.outStreamLock = outStreamLock;
     }
 
     @Override
-    public void handleEvent( @Nonnull String event )
+    public void handleEvent( @Nonnull String message )
     {
-        logger.info( event );
+        synchronized ( outStreamLock )
+        {
+            System.out.println( message );
+        }
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
index e070679..f0c5bbf 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
@@ -143,7 +143,7 @@ final class SurefireForkChannel extends ForkChannel
     {
         ForkNodeArguments args = getArguments();
         out = new LineConsumerThread( "fork-" + args.getForkChannelId() + "-out-thread", stdOut,
-            new NativeStdOutStreamConsumer( args.getConsoleLogger() ), countdown );
+            new NativeStdOutStreamConsumer( args.getConsoleLock() ), countdown );
         out.start();
 
         eventBindings = new EventBindings( eventHandler, countdown );
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/log/PluginConsoleLogger.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/log/PluginConsoleLogger.java
index 88ac446..e5b4041 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/log/PluginConsoleLogger.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/log/PluginConsoleLogger.java
@@ -25,6 +25,10 @@ import org.codehaus.plexus.logging.Logger;
 /**
  * Wrapper logger of miscellaneous implementations of {@link Logger}.
  *
+ * This instance is synchronized. The logger operations are mutually exclusive to standard out, standard err and console
+ * err/warn/info/debug logger operations, see {@link org.apache.maven.plugin.surefire.report.DefaultReporterFactory},
+ * {@link org.apache.maven.plugin.surefire.report.TestSetRunListener}.
+ *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.20
  * @see ConsoleLogger
@@ -46,12 +50,12 @@ public final class PluginConsoleLogger
     }
 
     @Override
-    public void debug( String message )
+    public synchronized void debug( String message )
     {
         plexusLogger.debug( message );
     }
 
-    public void debug( CharSequence content, Throwable error )
+    public synchronized void debug( CharSequence content, Throwable error )
     {
         plexusLogger.debug( content == null ? "" : content.toString(), error );
     }
@@ -63,7 +67,7 @@ public final class PluginConsoleLogger
     }
 
     @Override
-    public void info( String message )
+    public synchronized void info( String message )
     {
         plexusLogger.info( message );
     }
@@ -75,12 +79,12 @@ public final class PluginConsoleLogger
     }
 
     @Override
-    public void warning( String message )
+    public synchronized void warning( String message )
     {
         plexusLogger.warn( message );
     }
 
-    public void warning( CharSequence content, Throwable error )
+    public synchronized void warning( CharSequence content, Throwable error )
     {
         plexusLogger.warn( content == null ? "" : content.toString(), error );
     }
@@ -92,19 +96,19 @@ public final class PluginConsoleLogger
     }
 
     @Override
-    public void error( String message )
+    public synchronized void error( String message )
     {
         plexusLogger.error( message );
     }
 
     @Override
-    public void error( String message, Throwable t )
+    public synchronized void error( String message, Throwable t )
     {
         plexusLogger.error( message, t );
     }
 
     @Override
-    public void error( Throwable t )
+    public synchronized void error( Throwable t )
     {
         plexusLogger.error( "", t );
     }
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 7b0c18d..693e63d 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
@@ -65,7 +65,7 @@ import static org.apache.maven.surefire.api.util.internal.ObjectUtils.useNonNull
  * @author Kristian Rosenvold
  */
 public class DefaultReporterFactory
-    implements ReporterFactory
+    implements ReporterFactory, ReportsMerger
 {
     private final Collection<TestSetRunListener> listeners = new ConcurrentLinkedQueue<>();
     private final StartupReportConfiguration reportConfiguration;
@@ -107,11 +107,13 @@ public class DefaultReporterFactory
                                     createStatisticsReporter(),
                                     reportConfiguration.isTrimStackTrace(),
                                     PLAIN.equals( reportConfiguration.getReportFormat() ),
-                                    reportConfiguration.isBriefOrPlainFormat() );
+                                    reportConfiguration.isBriefOrPlainFormat(),
+                                    consoleLogger );
         addListener( testSetRunListener );
         return testSetRunListener;
     }
 
+    @Override
     public File getReportsDirectory()
     {
         return reportConfiguration.getReportsDirectory();
@@ -151,6 +153,7 @@ public class DefaultReporterFactory
         return useNonNull( statisticsReporter, NullStatisticsReporter.INSTANCE );
     }
 
+    @Override
     public void mergeFromOtherFactories( Collection<DefaultReporterFactory> factories )
     {
         for ( DefaultReporterFactory factory : factories )
@@ -176,6 +179,7 @@ public class DefaultReporterFactory
         return globalStats.getRunResult();
     }
 
+    @Override
     public void runStarting()
     {
         if ( reportConfiguration.isPrintSummary() )
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ReportsMerger.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ReportsMerger.java
new file mode 100644
index 0000000..8722062
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ReportsMerger.java
@@ -0,0 +1,36 @@
+package org.apache.maven.plugin.surefire.report;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.surefire.api.suite.RunResult;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * This interface is used to merge reports in {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
+ */
+public interface ReportsMerger
+{
+    void runStarting();
+    void mergeFromOtherFactories( Collection<DefaultReporterFactory> factories );
+    File getReportsDirectory();
+    RunResult close();
+}
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 15b7ed8..9ab198c 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
@@ -69,6 +69,8 @@ public class TestSetRunListener
 
     private final StatisticsReporter statisticsReporter;
 
+    private final Object lock;
+
     private Utf8RecodingDeferredFileOutputStream testStdOut = initDeferred( "stdout" );
 
     private Utf8RecodingDeferredFileOutputStream testStdErr = initDeferred( "stderr" );
@@ -83,7 +85,7 @@ public class TestSetRunListener
                                StatelessReportEventListener<WrappedReportEntry, TestSetStats> simpleXMLReporter,
                                ConsoleOutputReportEventListener consoleOutputReceiver,
                                StatisticsReporter statisticsReporter, boolean trimStackTrace,
-                               boolean isPlainFormat, boolean briefOrPlainFormat )
+                               boolean isPlainFormat, boolean briefOrPlainFormat, Object lock )
     {
         this.consoleReporter = consoleReporter;
         this.fileReporter = fileReporter;
@@ -92,6 +94,7 @@ public class TestSetRunListener
         this.consoleOutputReceiver = consoleOutputReceiver;
         this.briefOrPlainFormat = briefOrPlainFormat;
         detailsForThis = new TestSetStats( trimStackTrace, isPlainFormat );
+        this.lock = lock;
     }
 
     @Override
@@ -103,7 +106,10 @@ public class TestSetRunListener
     @Override
     public void debug( String message )
     {
-        consoleReporter.getConsoleLogger().debug( trimTrailingNewLine( message ) );
+        synchronized ( lock )
+        {
+            consoleReporter.getConsoleLogger().debug( trimTrailingNewLine( message ) );
+        }
     }
 
     @Override
@@ -115,7 +121,10 @@ public class TestSetRunListener
     @Override
     public void info( String message )
     {
-        consoleReporter.getConsoleLogger().info( trimTrailingNewLine( message ) );
+        synchronized ( lock )
+        {
+            consoleReporter.getConsoleLogger().info( trimTrailingNewLine( message ) );
+        }
     }
 
     @Override
@@ -127,7 +136,10 @@ public class TestSetRunListener
     @Override
     public void warning( String message )
     {
-        consoleReporter.getConsoleLogger().warning( trimTrailingNewLine( message ) );
+        synchronized ( lock )
+        {
+            consoleReporter.getConsoleLogger().warning( trimTrailingNewLine( message ) );
+        }
     }
 
     @Override
@@ -139,19 +151,28 @@ public class TestSetRunListener
     @Override
     public void error( String message )
     {
-        consoleReporter.getConsoleLogger().error( trimTrailingNewLine( message ) );
+        synchronized ( lock )
+        {
+            consoleReporter.getConsoleLogger().error( trimTrailingNewLine( message ) );
+        }
     }
 
     @Override
     public void error( String message, Throwable t )
     {
-        consoleReporter.getConsoleLogger().error( trimTrailingNewLine( message ), t );
+        synchronized ( lock )
+        {
+            consoleReporter.getConsoleLogger().error( trimTrailingNewLine( message ), t );
+        }
     }
 
     @Override
     public void error( Throwable t )
     {
-        consoleReporter.getConsoleLogger().error( t );
+        synchronized ( lock )
+        {
+            consoleReporter.getConsoleLogger().error( t );
+        }
     }
 
     @Override
@@ -159,9 +180,12 @@ public class TestSetRunListener
     {
         try
         {
-            Utf8RecodingDeferredFileOutputStream stream = stdout ? testStdOut : testStdErr;
-            stream.write( output, newLine );
-            consoleOutputReceiver.writeTestOutput( output, newLine, stdout );
+            synchronized ( lock )
+            {
+                Utf8RecodingDeferredFileOutputStream stream = stdout ? testStdOut : testStdErr;
+                stream.write( output, newLine );
+                consoleOutputReceiver.writeTestOutput( output, newLine, stdout );
+            }
         }
         catch ( IOException e )
         {
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
index b1e1c91..b730336 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
@@ -28,6 +28,9 @@ import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessIn
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
 import org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory;
+import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
@@ -155,15 +158,21 @@ public class ForkStarterTest
         when( forkConfiguration.createCommandLine( eq( startupConfiguration ), eq( 1 ), eq( tmp ) ) )
             .thenReturn( cli );
 
+        SurefireStatelessTestsetInfoReporter statelessTestsetInfoReporter = new SurefireStatelessTestsetInfoReporter();
+        SurefireConsoleOutputReporter outputReporter = new SurefireConsoleOutputReporter();
+        SurefireStatelessReporter xmlReporter = new SurefireStatelessReporter( true, "3" );
+
         StartupReportConfiguration startupReportConfiguration = new StartupReportConfiguration( true, true, null,
-            false, tmp, true, "", null, false, 0, null, null, true, null, null, null );
+            false, tmp, true, "", null, false, 0, null, null, true,
+            xmlReporter, outputReporter, statelessTestsetInfoReporter );
 
         ConsoleLogger logger = mock( ConsoleLogger.class );
 
         ForkStarter forkStarter = new ForkStarter( providerConfiguration, startupConfiguration, forkConfiguration,
             0, startupReportConfiguration, logger );
 
-        DefaultReporterFactory reporterFactory = new DefaultReporterFactory( startupReportConfiguration, logger, 1 );
+        DefaultReporterFactory reporterFactory =
+            new DefaultReporterFactory( startupReportConfiguration, logger, 1 );
 
         e.expect( SurefireBooterForkException.class );
         e.expectMessage( containsString( "Process Exit Code: 1" ) );
@@ -214,15 +223,21 @@ public class ForkStarterTest
         when( forkConfiguration.createCommandLine( eq( startupConfiguration ), eq( 1 ), eq( tmp ) ) )
             .thenReturn( cli );
 
+        SurefireStatelessTestsetInfoReporter statelessTestsetInfoReporter = new SurefireStatelessTestsetInfoReporter();
+        SurefireConsoleOutputReporter outputReporter = new SurefireConsoleOutputReporter();
+        SurefireStatelessReporter xmlReporter = new SurefireStatelessReporter( true, "3" );
+
         StartupReportConfiguration startupReportConfiguration = new StartupReportConfiguration( true, true, null,
-            false, tmp, true, "", null, false, 0, null, null, true, null, null, null );
+            false, tmp, true, "", null, false, 0, null, null, true,
+            xmlReporter, outputReporter, statelessTestsetInfoReporter );
 
         ConsoleLogger logger = mock( ConsoleLogger.class );
 
         ForkStarter forkStarter = new ForkStarter( providerConfiguration, startupConfiguration, forkConfiguration,
             0, startupReportConfiguration, logger );
 
-        DefaultReporterFactory reporterFactory = new DefaultReporterFactory( startupReportConfiguration, logger, 1 );
+        DefaultReporterFactory reporterFactory =
+            new DefaultReporterFactory( startupReportConfiguration, logger, 1 );
 
         Class<?>[] types = {Object.class, PropertiesWrapper.class, ForkClient.class, SurefireProperties.class,
             int.class, AbstractCommandReader.class, ForkNodeFactory.class, boolean.class};
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
index 48eb35f..68e73c5 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
@@ -371,6 +371,13 @@ public class ForkingRunListenerTest
             return logger;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return logger;
+        }
+
         boolean isCalled()
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
index a0b29cd..6cb409d 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -1880,6 +1880,13 @@ public class ForkClientTest
             return logger;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return logger;
+        }
+
         boolean isCalled()
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
index a9fde90..05af3fc 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
@@ -431,6 +431,13 @@ public class E2ETest
             return logger;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return logger;
+        }
+
         @Override
         public File getEventStreamBinaryFile()
         {
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
index 0204532..e0bb416 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -229,6 +229,13 @@ public class EventConsumerThreadTest
             return null;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return new Object();
+        }
+
         @Override
         public File getEventStreamBinaryFile()
         {
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
index 9094392..f741cfa 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
@@ -1304,6 +1304,13 @@ public class ForkedProcessEventNotifierTest
             return logger;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return logger;
+        }
+
         boolean isCalled()
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
index 9655c63..cd15874 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
@@ -118,6 +118,13 @@ public class ForkChannelTest
             {
                 return reporter;
             }
+
+            @Nonnull
+            @Override
+            public Object getConsoleLock()
+            {
+                return reporter;
+            }
         };
 
         ForkNodeFactory factory = new SurefireForkNodeFactory();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
index ccfd3f0..c3c15bb 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
@@ -769,6 +769,13 @@ public class EventDecoderTest
             return null;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return new Object();
+        }
+
         @Override
         public File getEventStreamBinaryFile()
         {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
index 7d20e0e..ee5c2bf 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
@@ -55,6 +55,9 @@ public interface ForkNodeArguments
     @Nonnull
     ConsoleLogger getConsoleLogger();
 
+    @Nonnull
+    Object getConsoleLock();
+
     File getEventStreamBinaryFile();
 
     File getCommandStreamBinaryFile();
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
index 8d4e1c5..c66dbf3 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
@@ -636,6 +636,13 @@ public class AbstractStreamDecoderTest
             return null;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return new Object();
+        }
+
         @Override
         public File getEventStreamBinaryFile()
         {
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
index 4bed5ca..c7c9c57 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
@@ -83,10 +83,17 @@ public final class ForkedNodeArg implements ForkNodeArguments
         return logger;
     }
 
+    @Nonnull
+    @Override
+    public Object getConsoleLock()
+    {
+        return logger;
+    }
+
     @Override
     public File getEventStreamBinaryFile()
     {
-        return null;
+        throw new UnsupportedOperationException();
     }
 
     @Override
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
index 8ac1829..648dce0 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
@@ -400,6 +400,13 @@ public class CommandChannelDecoderTest
             return logger;
         }
 
+        @Nonnull
+        @Override
+        public Object getConsoleLock()
+        {
+            return logger;
+        }
+
         boolean isCalled()
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();