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 2020/04/08 08:22:08 UTC

[maven-surefire] branch maven2surefire-jvm-communication updated (ea06134 -> 552662a)

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

tibordigana pushed a change to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git.


 discard ea06134  don't print ClosedChannelException in dump file on exit
 discard 15a0b89  name of the thread ends with dash "-"
 discard 7b97b49  removed unused methods in CommandReader.java
 discard b1a4930  improved coverage in new code
 discard 8c7a739  E2ETest performance test
 discard 862f2d1  fixed Surefire817SystemExitIT
 discard e063963  sendExitError
 discard 979034c  fixed performance problem in TCP/Pipes communication (we do NOT flush every time, used buffered channels, used Async Sockets instead of blocking NIO Sockets)
    omit 2171cdc  E2E test for TCP
    omit c914551  investigated tests
    omit f4bb445  improved performance from 320s to 54s.
    omit 85f8c1f  fixed the IT 735
    omit afa736a  fixed the IT 735
    omit e0f5f08  fix after Enrico's findings in external project
    omit 62d0b0a  documentation and Javadoc
    omit 7c4aa0b  extended few tests with a new alternative of forkNode: TCP
    omit b20b64d  added TCP alternative in ConsoleOutputIT
    omit f866da6  [SUREFIRE-1658] TCP/IP Channel for forked Surefire JVM. Extensions API and SPI. Polymorphism for remote and local process communication.
     add cfdd9ea  [SUREFIRE-1758] JUnit Platform provider isn't mentioned in the docu about groups and excludeGroups
     add 10b2578  [SUREFIRE-1754] Upgrade Doxia and Doxia Site Tools to remove struts dependency
     add 5534bd7  [SUREFIRE-1762] skipAfterFailureCount>0 with testng 7.1.0 resulting in java.lang.NoSuchMethodError: org.testng.TestNG.addListener(Lorg/testng/ITestListener;)V
     add a7ac73e  Revert "[SUREFIRE-1762] skipAfterFailureCount>0 with testng 7.1.0 resulting in java.lang.NoSuchMethodError: org.testng.TestNG.addListener(Lorg/testng/ITestListener;)V" (#277)
     add e23253e5 [SUREFIRE-1762] skipAfterFailureCount>0 with testng 7.1.0 resulting in java.lang.NoSuchMethodError: org.testng.TestNG.addListener(Lorg/testng/ITestListener;)V
     add 348b7c8  debug log with provider class name
     add 59de698  JDK 15
     add 01e546f  [SUREFIRE-1769] Upgrade Plexus Java to 1.0.5
     add f49ac58  avoided parallel downloads of artifacts in the integration test
     new d0c9039  [SUREFIRE-1658] TCP/IP Channel for forked Surefire JVM. Extensions API and SPI. Polymorphism for remote and local process communication.
     new 7a3cdef  added TCP alternative in ConsoleOutputIT
     new 56d12df  extended few tests with a new alternative of forkNode: TCP
     new 0328f17  documentation and Javadoc
     new ff5b7c6  fix after Enrico's findings in external project
     new 93dfd0e  fixed the IT 735
     new 02ab137  fixed the IT 735
     new f3aa9c2  improved performance from 320s to 54s.
     new 43621a0  investigated tests
     new 2d87514  E2E test for TCP
     new e93e5c6  fixed performance problem in TCP/Pipes communication (we do NOT flush every time, used buffered channels, used Async Sockets instead of blocking NIO Sockets)
     new 112a508  sendExitError
     new 0986f5a  fixed Surefire817SystemExitIT
     new e1b8dd4  E2ETest performance test
     new 9877f54  improved coverage in new code
     new b8de0e4  removed unused methods in CommandReader.java
     new 1d726de  name of the thread ends with dash "-"
     new 552662a  don't print ClosedChannelException in dump file on exit

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (ea06134)
            \
             N -- N -- N   refs/heads/maven2surefire-jvm-communication (552662a)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 18 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 Jenkinsfile                                             |  2 +-
 .../maven/plugin/surefire/AbstractSurefireMojo.java     | 17 +++++++++++------
 pom.xml                                                 |  6 +++---
 .../maven/surefire/its/JUnitPlatformEnginesIT.java      |  4 ++--
 .../apache/maven/surefire/testng/TestNGExecutor.java    |  9 ++++++---
 5 files changed, 23 insertions(+), 15 deletions(-)


[maven-surefire] 12/18: sendExitError

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 112a508684b866c6e90e64dbac83b39e57b9bf4d
Author: tibordigana <ti...@apache.org>
AuthorDate: Fri Apr 3 23:09:05 2020 +0200

    sendExitError
---
 .../src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index 1983ae2..8b24d90 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -402,7 +402,7 @@ public final class ForkedBooter
         launchLastDitchDaemonShutdownThread( 0 );
         long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS );
         boolean timeoutElapsed = !acquireOnePermit( exitBarrier, timeoutMillis );
-        if ( timeoutElapsed )
+        if ( timeoutElapsed && !eventChannel.checkError() )
         {
             eventChannel.sendExitError( null, false );
         }


[maven-surefire] 18/18: don't print ClosedChannelException in dump file on exit

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 552662a45345e019c058a0980d5dd123041626e5
Author: tibordigana <ti...@apache.org>
AuthorDate: Tue Apr 7 09:58:07 2020 +0200

    don't print ClosedChannelException in dump file on exit
---
 .../surefire/booter/spi/LegacyMasterProcessChannelEncoder.java   | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
index 91d9d1b..209790d 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
@@ -83,6 +83,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     private final WritableBufferedByteChannel out;
     private final RunMode runMode;
     private final AtomicBoolean trouble = new AtomicBoolean();
+    private volatile boolean onExit;
 
     public LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out )
     {
@@ -116,6 +117,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     @Override
     public void onJvmExit()
     {
+        onExit = true;
         encodeAndPrintEvent( new StringBuilder( "\n" ), true );
     }
 
@@ -318,8 +320,11 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         }
         catch ( ClosedChannelException e )
         {
-            DumpErrorSingleton.getSingleton()
-                .dumpException( e, "Channel closed while writing the event '" + event + "'." );
+            if ( !onExit )
+            {
+                DumpErrorSingleton.getSingleton()
+                    .dumpException( e, "Channel closed while writing the event '" + event + "'." );
+            }
         }
         catch ( IOException e )
         {


[maven-surefire] 01/18: [SUREFIRE-1658] TCP/IP Channel for forked Surefire JVM. Extensions API and SPI. Polymorphism for remote and local process communication.

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit d0c9039e5faf2ff1939f52e7520324ae9d4886cf
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Jul 6 17:51:13 2019 +0200

    [SUREFIRE-1658] TCP/IP Channel for forked Surefire JVM. Extensions API and SPI. Polymorphism for remote and local process communication.
---
 .github/workflows/maven.yml                        |    2 +-
 Jenkinsfile                                        |    8 +-
 .../maven/plugin/failsafe/IntegrationTestMojo.java |   10 +
 maven-surefire-common/pom.xml                      |   16 -
 .../plugin/surefire/AbstractSurefireMojo.java      |   74 +-
 .../maven/plugin/surefire/CommonReflector.java     |   18 +-
 .../surefire/SurefireDependencyResolver.java       |    1 +
 .../AbstractClasspathForkConfiguration.java        |    7 +-
 .../surefire/booterclient/BooterSerializer.java    |    5 +-
 .../booterclient/ClasspathForkConfiguration.java   |    7 +-
 .../booterclient/DefaultForkConfiguration.java     |   13 +-
 .../surefire/booterclient/ForkConfiguration.java   |    2 +
 .../plugin/surefire/booterclient/ForkStarter.java  |  126 ++-
 .../booterclient/JarManifestForkConfiguration.java |    7 +-
 .../ModularClasspathForkConfiguration.java         |    7 +-
 ...InputStream.java => AbstractCommandReader.java} |   16 +-
 ...ommandStream.java => DefaultCommandReader.java} |   60 +-
 .../DefferedChannelCommandSender.java}             |   12 +-
 .../lazytestprovider/TestLessInputStream.java      |    7 +-
 .../lazytestprovider/TestProvidingInputStream.java |   14 +-
 .../surefire/booterclient/output/ForkClient.java   |  176 +--
 .../booterclient/output/ForkedChannelDecoder.java  |  352 ------
 .../output/ForkedProcessEventNotifier.java         |  248 ++++
 .../output/ForkedProcessExitErrorListener.java     |    4 +-
 .../ForkedProcessStackTraceEventListener.java      |    6 +-
 .../output/NativeStdErrStreamConsumer.java         |    8 +-
 ...stener.java => NativeStdOutStreamConsumer.java} |   24 +-
 .../output/ThreadedStreamConsumer.java             |   80 +-
 .../surefire/extensions/EventConsumerThread.java   |  441 ++++++++
 .../surefire/extensions/LegacyForkChannel.java     |   89 ++
 .../surefire/extensions/LegacyForkNodeFactory.java |   24 +-
 .../plugin/surefire/extensions/StreamFeeder.java   |  203 ++++
 .../surefire/extensions/SurefireForkChannel.java   |  140 +++
 .../extensions/SurefireForkNodeFactory.java        |   25 +-
 .../AbstractSurefireMojoJava7PlusTest.java         |   27 +-
 .../plugin/surefire/AbstractSurefireMojoTest.java  |   44 +-
 .../maven/plugin/surefire/CommonReflectorTest.java |   50 +
 .../maven/plugin/surefire/MojoMocklessTest.java    |    7 +
 .../plugin/surefire/SurefireReflectorTest.java     |   71 --
 ...ooterDeserializerProviderConfigurationTest.java |    8 +-
 ...BooterDeserializerStartupConfigurationTest.java |   23 +-
 .../booterclient/DefaultForkConfigurationTest.java |   48 +-
 .../booterclient/ForkConfigurationTest.java        |   17 +-
 .../surefire/booterclient/ForkStarterTest.java     |   17 +-
 .../booterclient/ForkingRunListenerTest.java       |  194 ++--
 .../plugin/surefire/booterclient/MainClass.java    |   14 +-
 .../ModularClasspathForkConfigurationTest.java     |   10 +-
 .../TestLessInputStreamBuilderTest.java            |   54 +-
 .../TestProvidingInputStreamTest.java              |  152 ++-
 .../booterclient/output/ForkClientTest.java        |  938 ++++------------
 .../output/ForkedChannelDecoderTest.java           |  901 ---------------
 .../extensions/ConsoleOutputReporterTest.java      |    8 +-
 .../extensions/ForkedProcessEventNotifierTest.java | 1183 ++++++++++++++++++++
 .../surefire/extensions/StatelessReporterTest.java |    5 +-
 .../surefire/extensions/StreamFeederTest.java      |  162 +++
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   14 +-
 .../maven/surefire/extensions/ForkChannelTest.java |  166 +++
 .../StatelessTestsetInfoReporterTest.java          |    2 +-
 .../maven/plugin/surefire/SurefirePlugin.java      |   10 +
 pom.xml                                            |    7 +-
 .../maven/surefire/booter/BaseProviderFactory.java |   79 +-
 .../org/apache/maven/surefire/booter/Command.java  |   21 +-
 ...ocessEvent.java => ForkedProcessEventType.java} |   20 +-
 .../surefire/booter/ForkingReporterFactory.java    |    5 +-
 .../maven/surefire/booter/ForkingRunListener.java  |    5 +-
 .../surefire/booter/MasterProcessCommand.java      |  147 +--
 .../surefire/booter/RunOrderParametersAware.java   |   30 -
 .../surefire/booter/TestArtifactInfoAware.java     |   30 -
 .../maven/surefire/booter/TestRequestAware.java    |   30 -
 .../surefire/eventapi/AbstractConsoleEvent.java    |   85 ++
 .../eventapi/AbstractStandardStreamEvent.java      |   93 ++
 .../eventapi/AbstractTestControlEvent.java         |   95 ++
 .../ConsoleDebugEvent.java}                        |   16 +-
 .../maven/surefire/eventapi/ConsoleErrorEvent.java |   87 ++
 .../ConsoleInfoEvent.java}                         |   18 +-
 .../ConsoleWarningEvent.java}                      |   16 +-
 .../ControlByeEvent.java}                          |   57 +-
 .../ControlNextTestEvent.java}                     |   57 +-
 .../eventapi/ControlStopOnNextTestEvent.java       |   77 ++
 .../Event.java}                                    |   32 +-
 .../maven/surefire/eventapi/JvmExitErrorEvent.java |   87 ++
 .../surefire/eventapi/StandardStreamErrEvent.java  |   19 +-
 .../StandardStreamErrWithNewLineEvent.java         |   19 +-
 .../surefire/eventapi/StandardStreamOutEvent.java  |   19 +-
 .../StandardStreamOutWithNewLineEvent.java         |   19 +-
 .../surefire/eventapi/SystemPropertyEvent.java     |  101 ++
 .../eventapi/TestAssumptionFailureEvent.java       |   20 +-
 .../maven/surefire/eventapi/TestErrorEvent.java    |   20 +-
 .../maven/surefire/eventapi/TestFailedEvent.java   |   20 +-
 .../maven/surefire/eventapi/TestSkippedEvent.java  |   20 +-
 .../maven/surefire/eventapi/TestStartingEvent.java |   20 +-
 .../surefire/eventapi/TestSucceededEvent.java      |   20 +-
 .../surefire/eventapi/TestsetCompletedEvent.java   |   20 +-
 .../surefire/eventapi/TestsetStartingEvent.java    |   20 +-
 .../CommandChainReader.java}                       |   19 +-
 .../{booter => providerapi}/CommandListener.java   |    4 +-
 .../providerapi/MasterProcessChannelDecoder.java   |   48 +
 .../providerapi/MasterProcessChannelEncoder.java   |   84 ++
 .../surefire/providerapi/ProviderParameters.java   |    8 +-
 .../apache/maven/surefire/report/ReportEntry.java  |    2 +-
 .../maven/surefire/report/SimpleReportEntry.java   |    2 +
 .../org/apache/maven/surefire/suite/RunResult.java |    2 +-
 .../maven/surefire/testset/TestListResolver.java   |    2 +-
 .../maven/surefire/util/ReflectionUtils.java       |   31 +-
 .../util/internal/DaemonThreadFactory.java         |   35 +-
 .../java/org/apache/maven/JUnit4SuiteTest.java     |   10 +-
 .../surefire/booter/ForkingRunListenerTest.java    |   25 +-
 .../surefire/booter/MasterProcessCommandTest.java  |  164 ---
 .../surefire/booter/SurefireReflectorTest.java     |  198 ----
 surefire-booter/pom.xml                            |   39 +-
 .../maven/surefire/booter/BooterConstants.java     |    1 +
 .../maven/surefire/booter/BooterDeserializer.java  |   14 +
 .../apache/maven/surefire/booter/Classpath.java    |   13 +-
 .../maven/surefire/booter/CommandReader.java       |  130 +--
 .../apache/maven/surefire/booter/ForkedBooter.java |   78 +-
 .../maven/surefire/booter/LazyTestsToRun.java      |   13 +-
 .../surefire/booter/ProviderConfiguration.java     |    2 -
 .../maven/surefire/booter/ProviderFactory.java     |    4 +-
 .../surefire/booter/StartupConfiguration.java      |   22 +-
 .../maven/surefire/booter/SurefireReflector.java   |  120 +-
 .../spi/LegacyMasterProcessChannelDecoder.java     |  190 ++++
 .../spi/LegacyMasterProcessChannelEncoder.java     |  180 +--
 ...LegacyMasterProcessChannelProcessorFactory.java |   72 ++
 ...refireMasterProcessChannelProcessorFactory.java |   90 ++
 ...refire.spi.MasterProcessChannelProcessorFactory |   32 +-
 .../surefire/booter/BooterDeserializerTest.java    |    2 +-
 .../maven/surefire/booter/ClasspathTest.java       |   88 +-
 .../maven/surefire/booter/CommandReaderTest.java   |   57 +-
 .../java/org/apache/maven/surefire/booter/Foo.java |   48 +-
 .../surefire/booter/ForkedBooterMockTest.java      |  232 +++-
 .../surefire/booter/IsolatedClassLoaderTest.java   |   66 ++
 .../maven/surefire/booter/JUnit4SuiteTest.java     |    7 +
 .../surefire/booter/NewClassLoaderRunner.java      |    0
 .../surefire/booter/SurefireReflectorTest.java     |  408 +++++++
 .../spi/LegacyMasterProcessChannelDecoderTest.java |  243 ++++
 .../spi/LegacyMasterProcessChannelEncoderTest.java |  223 ++--
 surefire-extensions-api/pom.xml                    |   38 +-
 .../surefire/extensions/CloseableDaemonThread.java |   17 +-
 .../maven/surefire/extensions/CommandReader.java   |   23 +-
 .../maven/surefire/extensions/EventHandler.java    |   12 +-
 .../maven/surefire/extensions/ForkChannel.java     |  108 ++
 .../maven/surefire/extensions/ForkNodeFactory.java |   24 +-
 .../surefire/extensions/StdOutStreamLine.java      |    8 +-
 .../extensions/util/LineConsumerThread.java        |   21 +-
 .../surefire/extensions/util/StreamFeeder.java     |   89 --
 .../extensions}/CommandlineExecutorTest.java       |   27 +-
 .../surefire/extensions}/JUnit4SuiteTest.java      |    2 +-
 surefire-extensions-spi/pom.xml                    |   42 +
 .../spi/MasterProcessChannelProcessorFactory.java  |   62 +
 .../surefire/its/fixture/SurefireLauncher.java     |    2 +
 surefire-logger-api/pom.xml                        |    2 +-
 .../maven/surefire/junit4/JUnit4Provider.java      |    9 +-
 .../maven/surefire/junit4/JUnit4ProviderTest.java  |    2 +-
 .../surefire/junitcore/JUnitCoreProvider.java      |    9 +-
 .../maven/surefire/junitcore/Surefire746Test.java  |    3 +-
 .../maven/surefire/testng/TestNGProvider.java      |    9 +-
 surefire-shadefire/pom.xml                         |    5 +
 157 files changed, 7117 insertions(+), 4193 deletions(-)

diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 5340988..ccd2646 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -39,4 +39,4 @@ jobs:
           java-version: 1.8
 
       - name: Build with Maven
-        run: mvn install -e -B -V -nsu --no-transfer-progress -P run-its
+        run: mvn install -e -B -V -nsu --no-transfer-progress -P run-its
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index 24b32c7..4b29517 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -21,10 +21,10 @@
 
 properties(
     [
-        buildDiscarder(logRotator(artifactDaysToKeepStr: env.BRANCH_NAME == 'master' ? '1' : '2',
+        buildDiscarder(logRotator(artifactDaysToKeepStr: env.BRANCH_NAME == 'master' ? '14' : '7',
                                   artifactNumToKeepStr: '50',
-                                  daysToKeepStr: env.BRANCH_NAME == 'master' ? '10' : '5',
-                                  numToKeepStr: env.BRANCH_NAME == 'master' ? '5' : '3')
+                                  daysToKeepStr: env.BRANCH_NAME == 'master' ? '30' : '14',
+                                  numToKeepStr: env.BRANCH_NAME == 'master' ? '20' : '10')
         ),
         disableConcurrentBuilds()
     ]
@@ -213,6 +213,7 @@ static def sourcesPatternCsv() {
             '**/surefire-api/src/main/java,' +
             '**/surefire-booter/src/main/java,' +
             '**/surefire-extensions-api/src/main/java,' +
+            '**/surefire-extensions-spi/src/main/java,' +
             '**/surefire-grouper/src/main/java,' +
             '**/surefire-its/src/main/java,' +
             '**/surefire-logger-api/src/main/java,' +
@@ -229,6 +230,7 @@ static def classPatternCsv() {
             '**/surefire-api/target/classes,' +
             '**/surefire-booter/target/classes,' +
             '**/surefire-extensions-api/target/classes,' +
+            '**/surefire-extensions-spi/target/classes,' +
             '**/surefire-grouper/target/classes,' +
             '**/surefire-its/target/classes,' +
             '**/surefire-logger-api/target/classes,' +
diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
index e465bb2..a6771bc 100644
--- a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
+++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
@@ -27,6 +27,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 
 import java.io.File;
@@ -384,6 +385,9 @@ public class IntegrationTestMojo
     @Parameter( property = "failsafe.useModulePath", defaultValue = "true" )
     private boolean useModulePath;
 
+    @Parameter( property = "failsafe.forkNode" )
+    private ForkNodeFactory forkNode;
+
     /**
      * You can selectively exclude individual environment variables by enumerating their keys.
      * <br>
@@ -912,6 +916,12 @@ public class IntegrationTestMojo
     }
 
     @Override
+    protected final ForkNodeFactory getForkNode()
+    {
+        return forkNode;
+    }
+
+    @Override
     protected final String[] getExcludedEnvironmentVariables()
     {
         return excludedEnvironmentVariables == null ? new String[0] : excludedEnvironmentVariables;
diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index fefe831..8ab9e98 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -120,22 +120,6 @@
     <build>
         <plugins>
             <plugin>
-                <artifactId>maven-dependency-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>build-test-classpath</id>
-                        <phase>generate-sources</phase>
-                        <goals>
-                            <goal>build-classpath</goal>
-                        </goals>
-                        <configuration>
-                            <includeScope>test</includeScope>
-                            <outputFile>target/test-classpath/cp.txt</outputFile>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
                 <executions>
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index a8c1927..7ba312a 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -25,6 +25,7 @@ import org.apache.maven.artifact.DefaultArtifact;
 import org.apache.maven.artifact.handler.ArtifactHandler;
 import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.model.Plugin;
+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;
@@ -73,6 +74,7 @@ import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
 import org.apache.maven.surefire.booter.SurefireExecutionException;
 import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.suite.RunResult;
@@ -777,8 +779,6 @@ public abstract class AbstractSurefireMojo
     @Component
     private DependencyResolver dependencyResolver;
 
-    private Artifact surefireBooterArtifact;
-
     private Toolchain toolchain;
 
     private int effectiveForkCount = -1;
@@ -835,6 +835,8 @@ public abstract class AbstractSurefireMojo
 
     protected abstract String getEnableProcessChecker();
 
+    protected abstract ForkNodeFactory getForkNode();
+
     /**
      * This plugin MOJO artifact.
      *
@@ -916,8 +918,7 @@ public abstract class AbstractSurefireMojo
                 getPluginName(), getDependencyResolver(),
                 getSession().isOffline() );
 
-        surefireBooterArtifact = getBooterArtifact();
-        if ( surefireBooterArtifact == null )
+        if ( getBooterArtifact() == null )
         {
             throw new RuntimeException( "Unable to locate surefire-booter in the list of plugin artifacts" );
         }
@@ -1751,7 +1752,7 @@ public abstract class AbstractSurefireMojo
         return new File( getBasedir(), ".surefire-" + configurationHash );
     }
 
-    private StartupConfiguration createStartupConfiguration( @Nonnull ProviderInfo provider, boolean isInprocess,
+    private StartupConfiguration createStartupConfiguration( @Nonnull ProviderInfo provider, boolean isForking,
                                                              @Nonnull ClassLoaderConfiguration classLoaderConfiguration,
                                                              @Nonnull DefaultScanResult scanResult,
                                                              @Nonnull Platform platform,
@@ -1762,7 +1763,7 @@ public abstract class AbstractSurefireMojo
         {
             Set<Artifact> providerArtifacts = provider.getProviderClasspath();
             String providerName = provider.getProviderName();
-            if ( canExecuteProviderWithModularPath( platform ) && !isInprocess )
+            if ( isForking && canExecuteProviderWithModularPath( platform ) )
             {
                 String jvmExecutable = platform.getJdkExecAttributesForTests().getJvmExecutable();
                 String javaHome = Paths.get( jvmExecutable )
@@ -1804,8 +1805,8 @@ public abstract class AbstractSurefireMojo
         getConsoleLogger().debug( testClasspath.getCompactLogMessage( "test(compact) classpath:" ) );
         getConsoleLogger().debug( providerClasspath.getCompactLogMessage( "provider(compact) classpath:" ) );
 
-        Artifact[] additionalInProcArtifacts =
-                { getCommonArtifact(), getExtensionsArtifact(), getApiArtifact(), getLoggerApiArtifact() };
+        Artifact[] additionalInProcArtifacts = { getCommonArtifact(), getBooterArtifact(), getExtensionsArtifact(),
+            getApiArtifact(), getSpiArtifact(), getLoggerApiArtifact(), getSurefireSharedUtilsArtifact() };
         Set<Artifact> inProcArtifacts = retainInProcArtifactsUnique( providerArtifacts, additionalInProcArtifacts );
         Classpath inProcClasspath = createInProcClasspath( providerClasspath, inProcArtifacts );
         getConsoleLogger().debug( inProcClasspath.getLogMessage( "in-process classpath:" ) );
@@ -1814,8 +1815,8 @@ public abstract class AbstractSurefireMojo
         ClasspathConfiguration classpathConfiguration = new ClasspathConfiguration( testClasspath, providerClasspath,
                 inProcClasspath, effectiveIsEnableAssertions(), isChildDelegation() );
 
-        return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration, isForking(),
-                false, ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
+        return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration,
+            ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
     }
 
     private static Set<Artifact> retainInProcArtifactsUnique( Set<Artifact> providerArtifacts,
@@ -1907,8 +1908,8 @@ public abstract class AbstractSurefireMojo
         ModularClasspath modularClasspath = new ModularClasspath( result.getMainModuleDescriptor().name(),
                 testModulepath.getClassPath(), packages, getTestClassesDirectory() );
 
-        Artifact[] additionalInProcArtifacts =
-                { getCommonArtifact(), getExtensionsArtifact(), getApiArtifact(), getLoggerApiArtifact() };
+        Artifact[] additionalInProcArtifacts = { getCommonArtifact(), getBooterArtifact(), getExtensionsArtifact(),
+            getApiArtifact(), getSpiArtifact(), getLoggerApiArtifact(), getSurefireSharedUtilsArtifact() };
         Set<Artifact> inProcArtifacts = retainInProcArtifactsUnique( providerArtifacts, additionalInProcArtifacts );
         Classpath inProcClasspath = createInProcClasspath( providerClasspath, inProcArtifacts );
 
@@ -1924,8 +1925,8 @@ public abstract class AbstractSurefireMojo
         getConsoleLogger().debug( inProcClasspath.getLogMessage( "in-process classpath:" ) );
         getConsoleLogger().debug( inProcClasspath.getCompactLogMessage( "in-process(compact) classpath:" ) );
 
-        return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration, isForking(),
-                false, ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
+        return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration,
+            ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
     }
 
     private Artifact getCommonArtifact()
@@ -1938,11 +1939,21 @@ public abstract class AbstractSurefireMojo
         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-extensions-api" );
     }
 
+    private Artifact getSpiArtifact()
+    {
+        return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-extensions-spi" );
+    }
+
     private Artifact getApiArtifact()
     {
         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-api" );
     }
 
+    private Artifact getSurefireSharedUtilsArtifact()
+    {
+        return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-shared-utils" );
+    }
+
     private Artifact getLoggerApiArtifact()
     {
         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-logger-api" );
@@ -2232,7 +2243,7 @@ public abstract class AbstractSurefireMojo
                                            @Nonnull TestClassPath testClasspathWrapper )
         throws MojoExecutionException, MojoFailureException
     {
-        StartupConfiguration startupConfiguration = createStartupConfiguration( provider, false,
+        StartupConfiguration startupConfiguration = createStartupConfiguration( provider, true,
                 classLoaderConfiguration, scanResult, platform, testClasspathWrapper );
         String configChecksum = getConfigChecksum();
         StartupReportConfiguration startupReportConfiguration = getStartupReportConfiguration( configChecksum, true );
@@ -2249,7 +2260,7 @@ public abstract class AbstractSurefireMojo
                                                               @Nonnull TestClassPath testClasspathWrapper )
         throws MojoExecutionException, MojoFailureException
     {
-        StartupConfiguration startupConfiguration = createStartupConfiguration( provider, true, classLoaderConfig,
+        StartupConfiguration startupConfiguration = createStartupConfiguration( provider, false, classLoaderConfig,
                 scanResult, platform, testClasspathWrapper );
         String configChecksum = getConfigChecksum();
         StartupReportConfiguration startupReportConfiguration = getStartupReportConfiguration( configChecksum, false );
@@ -2258,6 +2269,14 @@ public abstract class AbstractSurefireMojo
                                               getConsoleLogger() );
     }
 
+    // todo this is in separate method and can be better tested than whole method createForkConfiguration()
+    @Nonnull
+    private ForkNodeFactory getForkNodeFactory()
+    {
+        ForkNodeFactory forkNode = getForkNode();
+        return forkNode == null ? new LegacyForkNodeFactory() : forkNode;
+    }
+
     @Nonnull
     private ForkConfiguration createForkConfiguration( Platform platform )
     {
@@ -2265,7 +2284,11 @@ public abstract class AbstractSurefireMojo
 
         Artifact shadeFire = getShadefireArtifact();
 
-        Classpath bootClasspath = getArtifactClasspath( shadeFire != null ? shadeFire : surefireBooterArtifact );
+        Classpath bootClasspath = getArtifactClasspath( shadeFire != null ? shadeFire : getBooterArtifact() );
+
+        ForkNodeFactory forkNode = getForkNodeFactory();
+
+        getConsoleLogger().debug( "Found implementation of fork node factory: " + forkNode.getClass() );
 
         if ( canExecuteProviderWithModularPath( platform ) )
         {
@@ -2281,7 +2304,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
         else if ( getClassLoaderConfiguration().isManifestOnlyJarRequestedAndUsable() )
         {
@@ -2297,7 +2321,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
         else
         {
@@ -2313,7 +2338,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
     }
 
@@ -2924,7 +2950,7 @@ public abstract class AbstractSurefireMojo
         {
             // add the JUnit provider as default - it doesn't require JUnit to be present,
             // since it supports POJO tests.
-            String version = surefireBooterArtifact.getBaseVersion();
+            String version = getBooterArtifact().getBaseVersion();
             return surefireDependencyResolver.getProviderClasspath( "surefire-junit3", version );
         }
     }
@@ -2963,7 +2989,7 @@ public abstract class AbstractSurefireMojo
         @Nonnull
         public Set<Artifact> getProviderClasspath()
         {
-            String version = surefireBooterArtifact.getBaseVersion();
+            String version = getBooterArtifact().getBaseVersion();
             return surefireDependencyResolver.getProviderClasspath( "surefire-junit4", version );
         }
     }
@@ -3006,7 +3032,7 @@ public abstract class AbstractSurefireMojo
         @Nonnull
         public Set<Artifact> getProviderClasspath() throws MojoExecutionException
         {
-            String surefireVersion = surefireBooterArtifact.getBaseVersion();
+            String surefireVersion = getBooterArtifact().getBaseVersion();
             Map<String, Artifact> providerArtifacts =
                     surefireDependencyResolver.getProviderClasspathAsMap( "surefire-junit-platform", surefireVersion );
             Map<String, Artifact> testDependencies = testClasspath.getTestDependencies();
@@ -3177,7 +3203,7 @@ public abstract class AbstractSurefireMojo
         @Nonnull
         public Set<Artifact> getProviderClasspath()
         {
-            String version = surefireBooterArtifact.getBaseVersion();
+            String version = getBooterArtifact().getBaseVersion();
             return surefireDependencyResolver.getProviderClasspath( "surefire-junit47", version );
         }
     }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
index 835fb89..466148f 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
@@ -23,8 +23,8 @@ 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.log.api.ConsoleLoggerDecorator;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
-import org.apache.maven.surefire.booter.SurefireReflector;
 import org.apache.maven.surefire.util.SurefireReflectionException;
 
 import javax.annotation.Nonnull;
@@ -71,7 +71,7 @@ public class CommonReflector
     {
         Class<?>[] args = { this.startupReportConfiguration, this.consoleLogger };
         Object src = createStartupReportConfiguration( startupReportConfiguration );
-        Object logger = SurefireReflector.createConsoleLogger( consoleLogger, surefireClassLoader );
+        Object logger = createConsoleLogger( consoleLogger, surefireClassLoader );
         Object[] params = { src, logger };
         return instantiateObject( DefaultReporterFactory.class.getName(), args, params, surefireClassLoader );
     }
@@ -84,7 +84,6 @@ public class CommonReflector
                                                      int.class, String.class, String.class, boolean.class,
                                                      statelessTestsetReporter, consoleOutputReporter,
                                                      statelessTestsetInfoReporter );
-        //noinspection BooleanConstructorCall
         Object[] params = { reporterConfiguration.isUseFile(), reporterConfiguration.isPrintSummary(),
             reporterConfiguration.getReportFormat(), reporterConfiguration.isRedirectTestOutputToFile(),
             reporterConfiguration.getReportsDirectory(),
@@ -98,4 +97,17 @@ public class CommonReflector
         };
         return newInstance( constructor, params );
     }
+
+    static Object createConsoleLogger( ConsoleLogger consoleLogger, ClassLoader cl )
+    {
+        try
+        {
+            Class<?> decoratorClass = cl.loadClass( ConsoleLoggerDecorator.class.getName() );
+            return getConstructor( decoratorClass, Object.class ).newInstance( consoleLogger );
+        }
+        catch ( Exception e )
+        {
+            throw new SurefireReflectionException( e );
+        }
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java
index 4684563..6da4a1a 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java
@@ -74,6 +74,7 @@ final class SurefireDependencyResolver
             "surefire-junit-platform",
             "surefire-api",
             "surefire-logger-api",
+            "surefire-shared-utils",
             "common-java5",
             "common-junit3",
             "common-junit4",
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
index 692f486..f8e08ea 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
@@ -21,6 +21,7 @@ package org.apache.maven.plugin.surefire.booterclient;
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.Classpath;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -49,10 +50,12 @@ abstract class AbstractClasspathForkConfiguration
                                         int forkCount,
                                         boolean reuseForks,
                                         @Nonnull Platform pluginPlatform,
-                                        @Nonnull ConsoleLogger log )
+                                        @Nonnull ConsoleLogger log,
+                                        @Nonnull ForkNodeFactory forkNodeFactory )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+            environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
+            forkNodeFactory );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
index 7eacb74..9a04326 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
@@ -71,6 +71,7 @@ import static org.apache.maven.surefire.booter.BooterConstants.TESTARTIFACT_CLAS
 import static org.apache.maven.surefire.booter.BooterConstants.TESTARTIFACT_VERSION;
 import static org.apache.maven.surefire.booter.BooterConstants.USEMANIFESTONLYJAR;
 import static org.apache.maven.surefire.booter.BooterConstants.USESYSTEMCLASSLOADER;
+import static org.apache.maven.surefire.booter.BooterConstants.FORK_NODE_CONNECTION_STRING;
 import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
 
 /**
@@ -102,11 +103,11 @@ class BooterSerializer
      */
     File serialize( KeyValueSource sourceProperties, ProviderConfiguration providerConfiguration,
                     StartupConfiguration startupConfiguration, Object testSet, boolean readTestsFromInStream,
-                    Long pid, int forkNumber )
+                    Long pid, int forkNumber, String forkNodeConnectionString )
         throws IOException
     {
         SurefireProperties properties = new SurefireProperties( sourceProperties );
-
+        properties.setNullableProperty( FORK_NODE_CONNECTION_STRING, forkNodeConnectionString );
         properties.setProperty( PLUGIN_PID, pid );
 
         AbstractPathConfiguration cp = startupConfiguration.getClasspathConfiguration();
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
index 1ca3932..9c906c4 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
@@ -24,6 +24,7 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -48,10 +49,12 @@ public final class ClasspathForkConfiguration
                                        @Nonnull String[] excludedEnvironmentVariables,
                                        boolean debug, int forkCount,
                                        boolean reuseForks, @Nonnull Platform pluginPlatform,
-                                       @Nonnull ConsoleLogger log )
+                                       @Nonnull ConsoleLogger log,
+                                       @Nonnull ForkNodeFactory forkNodeFactory )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+            environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
+            forkNodeFactory );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
index 4ab4435..443bf45 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
@@ -26,6 +26,7 @@ import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.util.internal.ImmutableMap;
 
 import javax.annotation.Nonnull;
@@ -65,6 +66,7 @@ public abstract class DefaultForkConfiguration
     private final boolean reuseForks;
     @Nonnull private final Platform pluginPlatform;
     @Nonnull private final ConsoleLogger log;
+    @Nonnull private final ForkNodeFactory forkNodeFactory;
 
     @SuppressWarnings( "checkstyle:parameternumber" )
     protected DefaultForkConfiguration( @Nonnull Classpath booterClasspath,
@@ -79,7 +81,8 @@ public abstract class DefaultForkConfiguration
                                      int forkCount,
                                      boolean reuseForks,
                                      @Nonnull Platform pluginPlatform,
-                                     @Nonnull ConsoleLogger log )
+                                     @Nonnull ConsoleLogger log,
+                                     @Nonnull ForkNodeFactory forkNodeFactory )
     {
         this.booterClasspath = booterClasspath;
         this.tempDirectory = tempDirectory;
@@ -94,6 +97,7 @@ public abstract class DefaultForkConfiguration
         this.reuseForks = reuseForks;
         this.pluginPlatform = pluginPlatform;
         this.log = log;
+        this.forkNodeFactory = forkNodeFactory;
     }
 
     protected abstract void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
@@ -108,6 +112,13 @@ public abstract class DefaultForkConfiguration
         return jvmArgLine;
     }
 
+    @Nonnull
+    @Override
+    public final ForkNodeFactory getForkNodeFactory()
+    {
+        return forkNodeFactory;
+    }
+
     /**
      * @param config       The startup configuration
      * @param forkNumber   index of forked JVM, to be the replacement in the argLine
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
index 92bebd0..9fddf96 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
@@ -25,6 +25,7 @@ import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ForkedBooter;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -39,6 +40,7 @@ public abstract class ForkConfiguration
 {
     static final String DEFAULT_PROVIDER_CLASS = ForkedBooter.class.getName();
 
+    @Nonnull public abstract ForkNodeFactory getForkNodeFactory();
     @Nonnull public abstract File getTempDirectory();
     @Nullable protected abstract String getDebugLine();
     @Nonnull protected abstract File getWorkingDirectory();
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 0ba2f46..a41384a 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
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.booterclient;
 import org.apache.maven.plugin.surefire.CommonReflector;
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
 import org.apache.maven.plugin.surefire.SurefireProperties;
-import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractForkInputStream;
+import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractCommandReader;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
@@ -33,12 +33,6 @@ 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.surefire.extensions.util.CommandlineExecutor;
-import org.apache.maven.surefire.extensions.util.CommandlineStreams;
-import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.extensions.util.LineConsumerThread;
-import org.apache.maven.surefire.extensions.util.StreamFeeder;
-import org.apache.maven.surefire.shared.utils.cli.CommandLineException;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
@@ -47,6 +41,14 @@ import org.apache.maven.surefire.booter.Shutdown;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
 import org.apache.maven.surefire.booter.SurefireExecutionException;
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.extensions.util.CommandlineExecutor;
+import org.apache.maven.surefire.extensions.util.CommandlineStreams;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.apache.maven.surefire.extensions.util.LineConsumerThread;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.suite.RunResult;
@@ -72,14 +74,12 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static java.lang.StrictMath.min;
 import static java.lang.System.currentTimeMillis;
 import static java.lang.Thread.currentThread;
 import static java.util.Collections.addAll;
-import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.Executors.newScheduledThreadPool;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -88,12 +88,11 @@ import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILE_PREFIX;
 import static org.apache.maven.plugin.surefire.SurefireHelper.replaceForkThreadsInPath;
 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.drawNumber;
 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.returnNumber;
-import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream
-                      .TestLessInputStreamBuilder;
-import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
-import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
+import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
 import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
 import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
+import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
+import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
 import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
 import static org.apache.maven.surefire.suite.RunResult.failure;
 import static org.apache.maven.surefire.suite.RunResult.timeout;
@@ -192,7 +191,7 @@ public class ForkStarter
                 {
                     closeable.close();
                 }
-                catch ( IOException e )
+                catch ( IOException | RuntimeException e )
                 {
                     // This error does not fail a test and does not necessarily mean that the forked JVM std/out stream
                     // was not closed, see ThreadedStreamConsumer. This error means that JVM wrote messages to a native
@@ -212,11 +211,17 @@ public class ForkStarter
         @Override
         public void close()
         {
-            run();
-            testProvidingInputStream.clear();
-            if ( inputStreamCloserHook != null )
+            try
             {
-                removeShutdownHook( inputStreamCloserHook );
+                run();
+            }
+            finally
+            {
+                testProvidingInputStream.clear();
+                if ( inputStreamCloserHook != null )
+                {
+                    removeShutdownHook( inputStreamCloserHook );
+                }
             }
         }
 
@@ -287,9 +292,9 @@ public class ForkStarter
             DefaultReporterFactory forkedReporterFactory =
                     new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
             defaultReporterFactories.add( forkedReporterFactory );
-            ForkClient forkClient =
-                    new ForkClient( forkedReporterFactory, stream, log, new AtomicBoolean(), forkNumber );
-            return fork( null, props, forkClient, effectiveSystemProperties, forkNumber, stream, false );
+            ForkClient forkClient = new ForkClient( forkedReporterFactory, stream, forkNumber );
+            return fork( null, props, forkClient, effectiveSystemProperties, forkNumber, stream,
+                    forkConfiguration.getForkNodeFactory(), false );
         }
         finally
         {
@@ -350,7 +355,6 @@ public class ForkStarter
             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
             final Collection<Future<RunResult>> results = new ArrayList<>( forkCount );
-            final AtomicBoolean printedErrorStream = new AtomicBoolean();
             for ( final TestProvidingInputStream testProvidingInputStream : testStreams )
             {
                 Callable<RunResult> pf = new Callable<RunResult>()
@@ -363,8 +367,7 @@ public class ForkStarter
                         DefaultReporterFactory reporter =
                                 new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
                         defaultReporterFactories.add( reporter );
-                        ForkClient forkClient = new ForkClient( reporter, testProvidingInputStream, log,
-                                printedErrorStream, forkNumber )
+                        ForkClient forkClient = new ForkClient( reporter, testProvidingInputStream, forkNumber )
                         {
                             @Override
                             protected void stopOnNextTest()
@@ -379,7 +382,8 @@ public class ForkStarter
                         try
                         {
                             return fork( null, new PropertiesWrapper( providerProperties ), forkClient,
-                                    effectiveSystemProperties, forkNumber, testProvidingInputStream, true );
+                                    effectiveSystemProperties, forkNumber, testProvidingInputStream,
+                                    forkConfiguration.getForkNodeFactory(), true );
                         }
                         finally
                         {
@@ -423,7 +427,6 @@ public class ForkStarter
             addShutDownHook( shutdown );
             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
-            final AtomicBoolean printedErrorStream = new AtomicBoolean();
             for ( final Object testSet : getSuitesIterator() )
             {
                 Callable<RunResult> pf = new Callable<RunResult>()
@@ -437,8 +440,7 @@ public class ForkStarter
                             new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
                         defaultReporterFactories.add( forkedReporterFactory );
                         TestLessInputStream stream = builder.build();
-                        ForkClient forkClient = new ForkClient( forkedReporterFactory, stream,
-                                log, printedErrorStream, forkNumber )
+                        ForkClient forkClient = new ForkClient( forkedReporterFactory, stream, forkNumber )
                         {
                             @Override
                             protected void stopOnNextTest()
@@ -453,7 +455,8 @@ public class ForkStarter
                         {
                             return fork( testSet,
                                          new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
-                                         forkClient, effectiveSystemProperties, forkNumber, stream, false );
+                                         forkClient, effectiveSystemProperties, forkNumber, stream,
+                                         forkConfiguration.getForkNodeFactory(), false );
                         }
                         finally
                         {
@@ -549,21 +552,28 @@ public class ForkStarter
 
     private RunResult fork( Object testSet, PropertiesWrapper providerProperties, ForkClient forkClient,
                             SurefireProperties effectiveSystemProperties, int forkNumber,
-                            AbstractForkInputStream commandInputStream, boolean readTestsFromInStream )
+                            AbstractCommandReader commandReader, ForkNodeFactory forkNodeFactory,
+                            boolean readTestsFromInStream )
         throws SurefireBooterForkException
     {
+        CloseableCloser closer = new CloseableCloser( forkNumber, commandReader );
         final String tempDir;
         final File surefireProperties;
         final File systPropsFile;
+        final ForkChannel forkChannel;
         try
         {
+            forkChannel = forkNodeFactory.createForkChannel( forkNumber, log );
+            closer.addCloseable( forkChannel );
             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
             Long pluginPid = forkConfiguration.getPluginPlatform().getPluginPid();
-            surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
-                    startupConfiguration, testSet, readTestsFromInStream, pluginPid, forkNumber );
-
             log.debug( "Determined Maven Process ID " + pluginPid );
+            String connectionString = forkChannel.getForkNodeConnectionString();
+            log.debug( "Fork Channel [" + forkNumber + "] connection string '" + connectionString
+                + "' for the implementation " + forkChannel.getClass() );
+            surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
+                    startupConfiguration, testSet, readTestsFromInStream, pluginPid, forkNumber, connectionString );
 
             if ( effectiveSystemProperties != null )
             {
@@ -588,10 +598,7 @@ public class ForkStarter
         OutputStreamFlushableCommandline cli =
                 forkConfiguration.createCommandLine( startupConfiguration, forkNumber, dumpLogDir );
 
-        if ( commandInputStream != null )
-        {
-            commandInputStream.setFlushReceiverProvider( cli );
-        }
+        commandReader.setFlushReceiverProvider( cli );
 
         cli.createArg().setValue( tempDir );
         cli.createArg().setValue( DUMP_FILE_PREFIX + forkNumber );
@@ -602,38 +609,40 @@ public class ForkStarter
         }
 
         ThreadedStreamConsumer eventConsumer = new ThreadedStreamConsumer( forkClient );
-        CloseableCloser closer = new CloseableCloser( forkNumber, eventConsumer, requireNonNull( commandInputStream ) );
+        closer.addCloseable( eventConsumer );
 
         log.debug( "Forking command line: " + cli );
 
         Integer result = null;
         RunResult runResult = null;
         SurefireBooterForkException booterForkException = null;
-        StreamFeeder in = null;
-        LineConsumerThread out = null;
-        LineConsumerThread err = null;
+        CloseableDaemonThread in = null;
+        CloseableDaemonThread out = null;
+        CloseableDaemonThread err = null;
         DefaultReporterFactory reporter = forkClient.getDefaultReporterFactory();
         currentForkClients.add( forkClient );
-        CountdownCloseable countdownCloseable = new CountdownCloseable( eventConsumer, 2 );
+        CountdownCloseable countdownCloseable =
+            new CountdownCloseable( eventConsumer, 1 + ( forkChannel.useStdOut() ? 1 : 0 ) );
         try ( CommandlineExecutor exec = new CommandlineExecutor( cli, countdownCloseable ) )
         {
-            // default impl of the extension - solves everything including the encoder/decoder, Process starter,
-            // adaptation of the streams to pipes and sockets
-            // non-default impl may use another classes and not the LineConsumerThread, StreamFeeder - freedom
-            // BEGIN: beginning of the call of the extension
             CommandlineStreams streams = exec.execute();
             closer.addCloseable( streams );
-            in = new StreamFeeder( "std-in-fork-" + forkNumber, streams.getStdInChannel(), commandInputStream );
+
+            forkChannel.connectToClient();
+            log.debug( "Fork Channel [" + forkNumber + "] connected to the client." );
+
+            in = forkChannel.bindCommandReader( commandReader, streams.getStdInChannel() );
             in.start();
-            out = new LineConsumerThread( "std-out-fork-" + forkNumber, streams.getStdOutChannel(),
-                                          eventConsumer, countdownCloseable );
+
+            out = forkChannel.bindEventHandler( eventConsumer, countdownCloseable, streams.getStdOutChannel() );
             out.start();
-            NativeStdErrStreamConsumer stdErrConsumer = new NativeStdErrStreamConsumer( reporter );
-            err = new LineConsumerThread( "std-err-fork-" + forkNumber, streams.getStdErrChannel(),
-                                          stdErrConsumer, countdownCloseable );
+
+            EventHandler<String> stdErrConsumer = new NativeStdErrStreamConsumer( reporter );
+            err = new LineConsumerThread( "fork-" + forkNumber + "-err-thread-", streams.getStdErrChannel(),
+                stdErrConsumer, countdownCloseable );
             err.start();
+
             result = exec.awaitExit();
-            // END: end of the call of the extension
 
             if ( forkClient.hadTimeout() )
             {
@@ -647,13 +656,15 @@ public class ForkStarter
         }
         catch ( InterruptedException e )
         {
+            log.error( "Closing the streams after (InterruptedException) '" + e.getLocalizedMessage() + "'" );
             // maybe implement it in the Future.cancel() of the extension or similar
             in.disable();
             out.disable();
             err.disable();
         }
-        catch ( CommandLineException e )
+        catch ( Exception e )
         {
+            // CommandLineException from pipes and IOException from sockets
             runResult = failure( reporter.getGlobalRunStatistics().getRunResult(), e );
             String cliErr = e.getLocalizedMessage();
             Throwable cause = e.getCause();
@@ -662,10 +673,12 @@ public class ForkStarter
         }
         finally
         {
+            log.debug( "Closing the fork " + forkNumber + " after "
+                + ( forkClient.isSaidGoodBye() ? "saying GoodBye." : "not saying Good Bye." ) );
             currentForkClients.remove( forkClient );
             try
             {
-                Closeable c = forkClient.isSaidGoodBye() ? closer : commandInputStream;
+                Closeable c = forkClient.isSaidGoodBye() ? closer : commandReader;
                 c.close();
             }
             catch ( IOException e )
@@ -710,7 +723,7 @@ public class ForkStarter
                     //noinspection ThrowFromFinallyBlock
                     throw new SurefireBooterForkException( "There was an error in the forked process"
                                                         + detail
-                                                        + ( stackTrace == null ? "" : stackTrace ), cause );
+                                                        + ( stackTrace == null ? "" : "\n" + stackTrace ), cause );
                 }
                 if ( !forkClient.isSaidGoodBye() )
                 {
@@ -729,7 +742,6 @@ public class ForkStarter
 
             if ( booterForkException != null )
             {
-                // noinspection ThrowFromFinallyBlock
                 throw booterForkException;
             }
         }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
index 02c275c..382bec6 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
@@ -28,6 +28,7 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -70,10 +71,12 @@ public final class JarManifestForkConfiguration
                                          @Nonnull String[] excludedEnvironmentVariables,
                                          boolean debug,
                                          int forkCount, boolean reuseForks, @Nonnull Platform pluginPlatform,
-                                         @Nonnull ConsoleLogger log )
+                                         @Nonnull ConsoleLogger log,
+                                         @Nonnull ForkNodeFactory forkNodeFactory )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+            environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
+            forkNodeFactory );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
index 8f5030b..d659a6c 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
@@ -28,6 +28,7 @@ import org.apache.maven.surefire.booter.ModularClasspath;
 import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
@@ -67,10 +68,12 @@ public class ModularClasspathForkConfiguration
                                               @Nonnegative int forkCount,
                                               boolean reuseForks,
                                               @Nonnull Platform pluginPlatform,
-                                              @Nonnull ConsoleLogger log )
+                                              @Nonnull ConsoleLogger log,
+                                              @Nonnull ForkNodeFactory forkNodeFactory )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+            environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
+            forkNodeFactory );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandReader.java
similarity index 84%
rename from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java
rename to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandReader.java
index a884c15..a31e9f7 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandReader.java
@@ -19,32 +19,34 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  * under the License.
  */
 
+import org.apache.maven.surefire.extensions.CommandReader;
+
 import java.io.IOException;
-import java.io.InputStream;
 
 import static java.util.Objects.requireNonNull;
 
 /**
- * Reader stream sends bytes to forked jvm std-{@link InputStream input-stream}.
+ * Stream reader returns bytes which ar finally sent to the forked jvm std-input-stream.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
  */
-public abstract class AbstractForkInputStream
-    extends InputStream
-    implements NotifiableTestStream
+public abstract class AbstractCommandReader
+        implements CommandReader, DefferedChannelCommandSender
 {
     private volatile FlushReceiverProvider flushReceiverProvider;
 
     /**
      * @param flushReceiverProvider the provider for a flush receiver.
      */
+    @Override
     public void setFlushReceiverProvider( FlushReceiverProvider flushReceiverProvider )
     {
         this.flushReceiverProvider = requireNonNull( flushReceiverProvider );
     }
 
-    protected boolean tryFlush()
+    @Override
+    public void tryFlush()
         throws IOException
     {
         if ( flushReceiverProvider != null )
@@ -53,9 +55,7 @@ public abstract class AbstractForkInputStream
             if ( flushReceiver != null )
             {
                 flushReceiver.flush();
-                return true;
             }
         }
-        return false;
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefaultCommandReader.java
similarity index 63%
rename from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java
rename to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefaultCommandReader.java
index 31b56c4..9aa19c3 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefaultCommandReader.java
@@ -20,7 +20,6 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.MasterProcessCommand;
 
 import java.io.IOException;
 
@@ -31,14 +30,9 @@ import java.io.IOException;
  * @since 2.19
  * @see org.apache.maven.surefire.booter.Command
  */
-public abstract class AbstractCommandStream
-    extends AbstractForkInputStream
+public abstract class DefaultCommandReader
+        extends AbstractCommandReader
 {
-    private byte[] currentBuffer;
-    private int currentPos;
-
-    protected abstract boolean isClosed();
-
     /**
      * Opposite to {@link #isClosed()}.
      * @return {@code true} if not closed
@@ -62,59 +56,35 @@ public abstract class AbstractCommandStream
     protected abstract Command nextCommand();
 
     /**
-     * Returns quietly and immediately.
-     */
-    protected final void invalidateInternalBuffer()
-    {
-        currentBuffer = null;
-        currentPos = 0;
-    }
-
-    /**
      * Used by single thread in StreamFeeder class.
      *
      * @return {@inheritDoc}
      * @throws IOException {@inheritDoc}
      */
-    @SuppressWarnings( "checkstyle:magicnumber" )
     @Override
-    public int read()
+    public Command readNextCommand()
         throws IOException
     {
+        tryFlush();
+
         if ( isClosed() )
         {
-            tryFlush();
-            return -1;
+            return null;
         }
 
-        if ( currentBuffer == null )
+        if ( !canContinue() )
         {
-            tryFlush();
-
-            if ( !canContinue() )
-            {
-                close();
-                return -1;
-            }
-
-            beforeNextCommand();
-
-            if ( isClosed() )
-            {
-                return -1;
-            }
-
-            Command cmd = nextCommand();
-            MasterProcessCommand cmdType = cmd.getCommandType();
-            currentBuffer = cmdType.hasDataType() ? cmdType.encode( cmd.getData() ) : cmdType.encode();
+            close();
+            return null;
         }
 
-        int b =  currentBuffer[currentPos++] & 0xff;
-        if ( currentPos == currentBuffer.length )
+        beforeNextCommand();
+
+        if ( isClosed() )
         {
-            currentBuffer = null;
-            currentPos = 0;
+            return null;
         }
-        return b;
+
+        return nextCommand();
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefferedChannelCommandSender.java
similarity index 67%
rename from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java
rename to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefferedChannelCommandSender.java
index 8faa803..e489caa 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefferedChannelCommandSender.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.booterclient.output;
+package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,11 +19,17 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
+import java.io.Closeable;
+
 /**
+ * Physical implementation of command sender.<br>
+ * Instance of {@link AbstractCommandReader} (namely {@link TestLessInputStream} or {@link TestProvidingInputStream}).
+ *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
-public interface ForkedChannelDecoderErrorHandler
+public interface DefferedChannelCommandSender
+    extends NotifiableTestStream, Closeable
 {
-    void handledError( String line, Throwable e );
+    void setFlushReceiverProvider( FlushReceiverProvider flushReceiverProvider );
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
index 372ce00..a1060a7 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
@@ -45,7 +45,7 @@ import static org.apache.maven.surefire.booter.Command.toShutdown;
  * @since 2.19
  */
 public final class TestLessInputStream
-    extends AbstractCommandStream
+        extends DefaultCommandReader
 {
     private final Semaphore barrier = new Semaphore( 0 );
 
@@ -108,7 +108,7 @@ public final class TestLessInputStream
     }
 
     @Override
-    protected boolean isClosed()
+    public boolean isClosed()
     {
         return closed.get();
     }
@@ -141,7 +141,6 @@ public final class TestLessInputStream
     {
         if ( closed.compareAndSet( false, true ) )
         {
-            invalidateInternalBuffer();
             barrier.drainPermits();
             barrier.release();
         }
@@ -166,8 +165,6 @@ public final class TestLessInputStream
         }
         catch ( InterruptedException e )
         {
-            // help GC to free this object because StreamFeeder Thread cannot read it anyway after IOE
-            invalidateInternalBuffer();
             throw new IOException( e.getLocalizedMessage() );
         }
     }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
index a4255cc..6f3a4de 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
@@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import static org.apache.maven.surefire.booter.Command.BYE_ACK;
 import static org.apache.maven.surefire.booter.Command.NOOP;
 import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.Command.TEST_SET_FINISHED;
 import static org.apache.maven.surefire.booter.Command.toRunClass;
 import static org.apache.maven.surefire.booter.Command.toShutdown;
 
@@ -50,7 +51,7 @@ import static org.apache.maven.surefire.booter.Command.toShutdown;
  * @author Tibor Digana (tibor17)
  */
 public final class TestProvidingInputStream
-    extends AbstractCommandStream
+        extends DefaultCommandReader
 {
     private final Semaphore barrier = new Semaphore( 0 );
 
@@ -77,7 +78,7 @@ public final class TestProvidingInputStream
     {
         if ( canContinue() )
         {
-            commands.add( Command.TEST_SET_FINISHED );
+            commands.add( TEST_SET_FINISHED );
             barrier.release();
         }
     }
@@ -129,7 +130,7 @@ public final class TestProvidingInputStream
         if ( cmd == null )
         {
             String cmdData = testClassNames.poll();
-            return cmdData == null ? Command.TEST_SET_FINISHED : toRunClass( cmdData );
+            return cmdData == null ? TEST_SET_FINISHED : toRunClass( cmdData );
         }
         else
         {
@@ -145,7 +146,7 @@ public final class TestProvidingInputStream
     }
 
     @Override
-    protected boolean isClosed()
+    public boolean isClosed()
     {
         return closed.get();
     }
@@ -167,7 +168,6 @@ public final class TestProvidingInputStream
     {
         if ( closed.compareAndSet( false, true ) )
         {
-            invalidateInternalBuffer();
             barrier.drainPermits();
             barrier.release();
         }
@@ -182,9 +182,7 @@ public final class TestProvidingInputStream
         }
         catch ( InterruptedException e )
         {
-            // help GC to free this object because StreamFeeder Thread cannot read it anyway after IOE
-            invalidateInternalBuffer();
-            throw new IOException( e.getLocalizedMessage() );
+            throw new IOException( e.getLocalizedMessage(), e );
         }
     }
 }
\ No newline at end of file
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 18cfe28..b4de014 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
@@ -22,7 +22,9 @@ package org.apache.maven.plugin.surefire.booterclient.output;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
-import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
@@ -30,25 +32,20 @@ import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.report.TestSetReportEntry;
 
-import java.io.BufferedReader;
+import javax.annotation.Nonnull;
 import java.io.File;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
 import static java.lang.System.currentTimeMillis;
 import static java.util.Collections.unmodifiableMap;
 import static org.apache.maven.surefire.booter.Shutdown.KILL;
 import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
-import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
-import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
 
 // todo move to the same package with ForkStarter
 
@@ -58,9 +55,8 @@ import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
  * @author Kristian Rosenvold
  */
 public class ForkClient
-     implements StreamConsumer
+    implements EventHandler<Event>
 {
-    private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
     private static final long START_TIME_ZERO = 0L;
     private static final long START_TIME_NEGATIVE_TIMEOUT = -1L;
 
@@ -74,26 +70,14 @@ public class ForkClient
 
     /**
      * <em>testSetStartedAt</em> is set to non-zero after received
-     * {@link org.apache.maven.surefire.booter.ForkedChannelEncoder#testSetStarting(ReportEntry, boolean)}.
+     * {@link MasterProcessChannelEncoder#testSetStarting(ReportEntry, boolean)}.
      */
     private final AtomicLong testSetStartedAt = new AtomicLong( START_TIME_ZERO );
 
-    private final ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-
-    private final ConsoleLogger log;
-
-    /**
-     * prevents from printing same warning
-     */
-    private final AtomicBoolean printedErrorStream;
+    private final ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
 
     private final int forkNumber;
 
-    /**
-     * Used by single Thread started by {@link ThreadedStreamConsumer} and therefore does not need to be volatile.
-     */
-    private final ForkedChannelDecoderErrorHandler errorHandler;
-
     private RunListener testSetReporter;
 
     /**
@@ -104,41 +88,29 @@ public class ForkClient
     private volatile StackTraceWriter errorInFork;
 
     public ForkClient( DefaultReporterFactory defaultReporterFactory, NotifiableTestStream notifiableTestStream,
-                       ConsoleLogger log, AtomicBoolean printedErrorStream, int forkNumber )
+                       int forkNumber )
     {
         this.defaultReporterFactory = defaultReporterFactory;
         this.notifiableTestStream = notifiableTestStream;
-        this.log = log;
-        this.printedErrorStream = printedErrorStream;
         this.forkNumber = forkNumber;
-        decoder.setTestSetStartingListener( new TestSetStartingListener() );
-        decoder.setTestSetCompletedListener( new TestSetCompletedListener() );
-        decoder.setTestStartingListener( new TestStartingListener() );
-        decoder.setTestSucceededListener( new TestSucceededListener() );
-        decoder.setTestFailedListener( new TestFailedListener() );
-        decoder.setTestSkippedListener( new TestSkippedListener() );
-        decoder.setTestErrorListener( new TestErrorListener() );
-        decoder.setTestAssumptionFailureListener( new TestAssumptionFailureListener() );
-        decoder.setSystemPropertiesListener( new SystemPropertiesListener() );
-        decoder.setStdOutListener( new StdOutListener() );
-        decoder.setStdErrListener( new StdErrListener() );
-        decoder.setConsoleInfoListener( new ConsoleListener() );
-        decoder.setAcquireNextTestListener( new AcquireNextTestListener() );
-        decoder.setConsoleErrorListener( new ErrorListener() );
-        decoder.setByeListener( new ByeListener() );
-        decoder.setStopOnNextTestListener( new StopOnNextTestListener() );
-        decoder.setConsoleDebugListener( new DebugListener() );
-        decoder.setConsoleWarningListener( new WarningListener() );
-        errorHandler = new ErrorHandler();
-    }
-
-    private final class ErrorHandler implements ForkedChannelDecoderErrorHandler
-    {
-        @Override
-        public void handledError( String line, Throwable e )
-        {
-            logStreamWarning( line, e );
-        }
+        notifier.setTestSetStartingListener( new TestSetStartingListener() );
+        notifier.setTestSetCompletedListener( new TestSetCompletedListener() );
+        notifier.setTestStartingListener( new TestStartingListener() );
+        notifier.setTestSucceededListener( new TestSucceededListener() );
+        notifier.setTestFailedListener( new TestFailedListener() );
+        notifier.setTestSkippedListener( new TestSkippedListener() );
+        notifier.setTestErrorListener( new TestErrorListener() );
+        notifier.setTestAssumptionFailureListener( new TestAssumptionFailureListener() );
+        notifier.setSystemPropertiesListener( new SystemPropertiesListener() );
+        notifier.setStdOutListener( new StdOutListener() );
+        notifier.setStdErrListener( new StdErrListener() );
+        notifier.setConsoleInfoListener( new ConsoleListener() );
+        notifier.setAcquireNextTestListener( new AcquireNextTestListener() );
+        notifier.setConsoleErrorListener( new ErrorListener() );
+        notifier.setByeListener( new ByeListener() );
+        notifier.setStopOnNextTestListener( new StopOnNextTestListener() );
+        notifier.setConsoleDebugListener( new DebugListener() );
+        notifier.setConsoleWarningListener( new WarningListener() );
     }
 
     private final class TestSetStartingListener
@@ -167,7 +139,7 @@ public class ForkClient
         }
     }
 
-    private final class TestStartingListener implements ForkedProcessReportEventListener
+    private final class TestStartingListener implements ForkedProcessReportEventListener<ReportEntry>
     {
         @Override
         public void handle( RunMode runMode, ReportEntry reportEntry )
@@ -177,7 +149,7 @@ public class ForkClient
         }
     }
 
-    private final class TestSucceededListener implements ForkedProcessReportEventListener
+    private final class TestSucceededListener implements ForkedProcessReportEventListener<ReportEntry>
     {
         @Override
         public void handle( RunMode runMode, ReportEntry reportEntry )
@@ -187,7 +159,7 @@ public class ForkClient
         }
     }
 
-    private final class TestFailedListener implements ForkedProcessReportEventListener
+    private final class TestFailedListener implements ForkedProcessReportEventListener<ReportEntry>
     {
         @Override
         public void handle( RunMode runMode, ReportEntry reportEntry )
@@ -197,7 +169,7 @@ public class ForkClient
         }
     }
 
-    private final class TestSkippedListener implements ForkedProcessReportEventListener
+    private final class TestSkippedListener implements ForkedProcessReportEventListener<ReportEntry>
     {
         @Override
         public void handle( RunMode runMode, ReportEntry reportEntry )
@@ -207,7 +179,7 @@ public class ForkClient
         }
     }
 
-    private final class TestErrorListener implements ForkedProcessReportEventListener
+    private final class TestErrorListener implements ForkedProcessReportEventListener<ReportEntry>
     {
         @Override
         public void handle( RunMode runMode, ReportEntry reportEntry )
@@ -217,7 +189,7 @@ public class ForkClient
         }
     }
 
-    private final class TestAssumptionFailureListener implements ForkedProcessReportEventListener
+    private final class TestAssumptionFailureListener implements ForkedProcessReportEventListener<ReportEntry>
     {
         @Override
         public void handle( RunMode runMode, ReportEntry reportEntry )
@@ -276,18 +248,19 @@ public class ForkClient
     private class ErrorListener implements ForkedProcessStackTraceEventListener
     {
         @Override
-        public void handle( String msg, String smartStackTrace, String stackTrace )
+        public void handle( @Nonnull StackTraceWriter stackTrace )
         {
+            String msg = stackTrace.getThrowable().getMessage();
             if ( errorInFork == null )
             {
-                errorInFork = deserializeStackTraceWriter( msg, smartStackTrace, stackTrace );
+                errorInFork = stackTrace.writeTraceToString() != null ? stackTrace : null;
                 if ( msg != null )
                 {
                     getOrCreateConsoleLogger()
                             .error( msg );
                 }
             }
-            dumpToLoFile( msg, null );
+            dumpToLoFile( msg );
         }
     }
 
@@ -372,12 +345,9 @@ public class ForkClient
     }
 
     @Override
-    public final void consumeLine( String s )
+    public final void handleEvent( @Nonnull Event event )
     {
-        if ( isNotBlank( s ) )
-        {
-            processLine( s );
-        }
+        notifier.notifyEvent( event );
     }
 
     private void setCurrentStartTime()
@@ -404,53 +374,11 @@ public class ForkClient
         return testSetReporter;
     }
 
-    private void processLine( String event )
-    {
-        decoder.handleEvent( event, errorHandler );
-    }
-
-    private File dumpToLoFile( String msg, Throwable e )
+    void dumpToLoFile( String msg )
     {
         File reportsDir = defaultReporterFactory.getReportsDirectory();
         InPluginProcessDumpSingleton util = InPluginProcessDumpSingleton.getSingleton();
-        return e == null
-                ? util.dumpStreamText( msg, reportsDir, forkNumber )
-                : util.dumpStreamException( e, msg, reportsDir, forkNumber );
-    }
-
-    private void logStreamWarning( String event, Throwable e )
-    {
-        if ( event == null || !event.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
-        {
-            String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM " + forkNumber + ".";
-            File dump = dumpToLoFile( msg + " Stream '" + event + "'.", e );
-
-            if ( printedErrorStream.compareAndSet( false, true ) )
-            {
-                log.warning( msg + " See FAQ web page and the dump file " + dump.getAbsolutePath() );
-            }
-
-            if ( log.isDebugEnabled() && event != null )
-            {
-                log.debug( event );
-            }
-        }
-        else
-        {
-            if ( log.isDebugEnabled() )
-            {
-                log.debug( event );
-            }
-            else if ( log.isInfoEnabled() )
-            {
-                log.info( event );
-            }
-            else
-            {
-                // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
-                System.out.println( event );
-            }
-        }
+        util.dumpStreamText( msg, reportsDir, forkNumber );
     }
 
     private void writeTestOutput( String output, boolean newLine, boolean isStdout )
@@ -459,23 +387,6 @@ public class ForkClient
                 .writeTestOutput( output, newLine, isStdout );
     }
 
-    public final void consumeMultiLineContent( String s )
-            throws IOException
-    {
-        if ( isBlank( s ) )
-        {
-            logStreamWarning( s, null );
-        }
-        else
-        {
-            BufferedReader stringReader = new BufferedReader( new StringReader( s ) );
-            for ( String s1 = stringReader.readLine(); s1 != null; s1 = stringReader.readLine() )
-            {
-                consumeLine( s1 );
-            }
-        }
-    }
-
     public final Map<String, String> getTestVmSystemProperties()
     {
         return unmodifiableMap( testVmSystemProperties );
@@ -531,11 +442,4 @@ public class ForkClient
     {
         return !testsInProgress.isEmpty();
     }
-
-    private StackTraceWriter deserializeStackTraceWriter( String stackTraceMessage,
-                                                          String smartStackTrace, String stackTrace )
-    {
-        boolean hasTrace = stackTrace != null;
-        return hasTrace ? new DeserializedStacktraceWriter( stackTraceMessage, smartStackTrace, stackTrace ) : null;
-    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
deleted file mode 100644
index 0c049d6..0000000
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
+++ /dev/null
@@ -1,352 +0,0 @@
-package org.apache.maven.plugin.surefire.booterclient.output;
-
-/*
- * 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.commons.codec.binary.Base64;
-import org.apache.maven.surefire.booter.ForkedProcessEvent;
-import org.apache.maven.surefire.report.ReportEntry;
-import org.apache.maven.surefire.report.RunMode;
-import org.apache.maven.surefire.report.StackTraceWriter;
-
-import java.nio.charset.Charset;
-import java.util.Collections;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR_NEW_LINE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT_NEW_LINE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_BYE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_DEBUG;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_INFO;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_WARNING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_NEXT_TEST;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STOP_ON_NEXT_TEST;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ERROR;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_FAILED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SKIPPED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_STARTING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SUCCEEDED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_COMPLETED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_STARTING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.EVENTS;
-import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
-import static org.apache.maven.surefire.report.RunMode.MODES;
-import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
-import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
-import static java.util.Objects.requireNonNull;
-
-/**
- * magic number : run mode : opcode [: opcode specific data]*
- *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 3.0.0-M4
- */
-public final class ForkedChannelDecoder
-{
-    private static final Base64 BASE64 = new Base64();
-
-    private volatile ForkedProcessPropertyEventListener propertyEventListener;
-    private volatile ForkedProcessStackTraceEventListener consoleErrorEventListener;
-    private volatile ForkedProcessExitErrorListener exitErrorEventListener;
-
-    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessReportEventListener<?>> reportEventListeners =
-            new ConcurrentHashMap<>();
-
-    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessStandardOutErrEventListener> stdOutErrEventListeners =
-            new ConcurrentHashMap<>();
-
-    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessStringEventListener> consoleEventListeners =
-            new ConcurrentHashMap<>();
-
-    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessEventListener> controlEventListeners =
-            new ConcurrentHashMap<>();
-
-    public void setSystemPropertiesListener( ForkedProcessPropertyEventListener listener )
-    {
-        propertyEventListener = requireNonNull( listener );
-    }
-
-    public <T extends ReportEntry> void setTestSetStartingListener( ForkedProcessReportEventListener<T> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TESTSET_STARTING, requireNonNull( listener ) );
-    }
-
-    public void setTestSetCompletedListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TESTSET_COMPLETED, requireNonNull( listener ) );
-    }
-
-    public void setTestStartingListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_STARTING, requireNonNull( listener ) );
-    }
-
-    public void setTestSucceededListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_SUCCEEDED, requireNonNull( listener ) );
-    }
-
-    public void setTestFailedListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_FAILED, requireNonNull( listener ) );
-    }
-
-    public void setTestSkippedListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_SKIPPED, requireNonNull( listener ) );
-    }
-
-    public void setTestErrorListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_ERROR, requireNonNull( listener ) );
-    }
-
-    public void setTestAssumptionFailureListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_ASSUMPTIONFAILURE, requireNonNull( listener ) );
-    }
-
-    public void setStdOutListener( ForkedProcessStandardOutErrEventListener listener )
-    {
-        stdOutErrEventListeners.put( BOOTERCODE_STDOUT, requireNonNull( listener ) );
-        stdOutErrEventListeners.put( BOOTERCODE_STDOUT_NEW_LINE, requireNonNull( listener ) );
-    }
-
-    public void setStdErrListener( ForkedProcessStandardOutErrEventListener listener )
-    {
-        stdOutErrEventListeners.put( BOOTERCODE_STDERR, requireNonNull( listener ) );
-        stdOutErrEventListeners.put( BOOTERCODE_STDERR_NEW_LINE, requireNonNull( listener ) );
-    }
-
-    public void setConsoleInfoListener( ForkedProcessStringEventListener listener )
-    {
-        consoleEventListeners.put( BOOTERCODE_CONSOLE_INFO, requireNonNull( listener ) );
-    }
-
-    public void setConsoleErrorListener( ForkedProcessStackTraceEventListener listener )
-    {
-        consoleErrorEventListener = requireNonNull( listener );
-    }
-
-    public void setConsoleDebugListener( ForkedProcessStringEventListener listener )
-    {
-        consoleEventListeners.put( BOOTERCODE_CONSOLE_DEBUG, requireNonNull( listener ) );
-    }
-
-    public void setConsoleWarningListener( ForkedProcessStringEventListener listener )
-    {
-        consoleEventListeners.put( BOOTERCODE_CONSOLE_WARNING, requireNonNull( listener ) );
-    }
-
-    public void setByeListener( ForkedProcessEventListener listener )
-    {
-        controlEventListeners.put( BOOTERCODE_BYE, requireNonNull( listener ) );
-    }
-
-    public void setStopOnNextTestListener( ForkedProcessEventListener listener )
-    {
-        controlEventListeners.put( BOOTERCODE_STOP_ON_NEXT_TEST, requireNonNull( listener ) );
-    }
-
-    public void setAcquireNextTestListener( ForkedProcessEventListener listener )
-    {
-        controlEventListeners.put( BOOTERCODE_NEXT_TEST, requireNonNull( listener ) );
-    }
-
-    public void setExitErrorEventListener( ForkedProcessExitErrorListener listener )
-    {
-        exitErrorEventListener = requireNonNull( listener );
-    }
-
-    public void handleEvent( String line, ForkedChannelDecoderErrorHandler errorHandler )
-    {
-        if ( line == null || !line.startsWith( MAGIC_NUMBER ) )
-        {
-            errorHandler.handledError( line, null );
-            return;
-        }
-
-        String[] tokens = line.substring( MAGIC_NUMBER.length() ).split( ":" );
-        int index = -1;
-        String opcode = tokens.length > ++index ? tokens[index] : null;
-        ForkedProcessEvent event = opcode == null ? null : EVENTS.get( opcode );
-        if ( event == null )
-        {
-            errorHandler.handledError( line, null );
-            return;
-        }
-
-        try
-        {
-            if ( event.isControlCategory() )
-            {
-                ForkedProcessEventListener listener = controlEventListeners.get( event );
-                if ( listener != null )
-                {
-                    listener.handle();
-                }
-            }
-            else if ( event.isConsoleCategory() )
-            {
-                ForkedProcessStringEventListener listener = consoleEventListeners.get( event );
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                if ( listener != null && encoding != null )
-                {
-                    String msg = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    listener.handle( msg );
-                }
-            }
-            else if ( event.isConsoleErrorCategory() )
-            {
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                if ( consoleErrorEventListener != null && encoding != null )
-                {
-                    String msg = tokens.length > ++index ? decode( tokens[index], encoding ) : null;
-                    String smartStackTrace =
-                            tokens.length > ++index ? decode( tokens[index], encoding ) : null;
-                    String stackTrace = tokens.length > ++index ? decode( tokens[index], encoding ) : null;
-                    consoleErrorEventListener.handle( msg, smartStackTrace, stackTrace );
-                }
-            }
-            else if ( event.isStandardStreamCategory() )
-            {
-                ForkedProcessStandardOutErrEventListener listener = stdOutErrEventListeners.get( event );
-                RunMode mode = tokens.length > ++index ? MODES.get( tokens[index] ) : null;
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                if ( listener != null && encoding != null && mode != null )
-                {
-                    boolean newLine = event == BOOTERCODE_STDOUT_NEW_LINE || event == BOOTERCODE_STDERR_NEW_LINE;
-                    String output = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    listener.handle( mode, output, newLine );
-                }
-            }
-            else if ( event.isSysPropCategory() )
-            {
-                RunMode mode = tokens.length > ++index ? MODES.get( tokens[index] ) : null;
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                String key = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                if ( propertyEventListener != null && isNotBlank( key ) )
-                {
-                    String value = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    propertyEventListener.handle( mode, key, value );
-                }
-            }
-            else if ( event.isTestCategory() )
-            {
-                ForkedProcessReportEventListener listener = reportEventListeners.get( event );
-                RunMode mode = tokens.length > ++index ? MODES.get( tokens[index] ) : null;
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                if ( listener != null && encoding != null && mode != null )
-                {
-                    String sourceName = tokens.length > ++index ? tokens[index] : null;
-                    String sourceText = tokens.length > ++index ? tokens[index] : null;
-                    String name = tokens.length > ++index ? tokens[index] : null;
-                    String nameText = tokens.length > ++index ? tokens[index] : null;
-                    String group = tokens.length > ++index ? tokens[index] : null;
-                    String message = tokens.length > ++index ? tokens[index] : null;
-                    String elapsed = tokens.length > ++index ? tokens[index] : null;
-                    String traceMessage = tokens.length > ++index ? tokens[index] : null;
-                    String smartTrimmedStackTrace = tokens.length > ++index ? tokens[index] : null;
-                    String stackTrace = tokens.length > ++index ? tokens[index] : null;
-                    ReportEntry reportEntry = toReportEntry( encoding, sourceName, sourceText, name, nameText,
-                            group, message, elapsed, traceMessage, smartTrimmedStackTrace, stackTrace );
-                    listener.handle( mode, reportEntry );
-                }
-            }
-            else if ( event.isJvmExitError() )
-            {
-                if ( exitErrorEventListener != null )
-                {
-                    Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                    String message = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    String smartTrimmedStackTrace =
-                            tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    String stackTrace = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    exitErrorEventListener.handle( message, smartTrimmedStackTrace, stackTrace );
-                }
-            }
-        }
-        catch ( IllegalArgumentException e )
-        {
-            errorHandler.handledError( line, e );
-        }
-    }
-
-    static ReportEntry toReportEntry( Charset encoding,
-                   // ReportEntry:
-                   String encSource, String encSourceText, String encName, String encNameText,
-                                      String encGroup, String encMessage, String encTimeElapsed,
-                   // StackTraceWriter:
-                   String encTraceMessage, String encSmartTrimmedStackTrace, String encStackTrace )
-            throws NumberFormatException
-    {
-        if ( encoding == null )
-        {
-            // corrupted or incomplete stream
-            return null;
-        }
-
-        String source = decode( encSource, encoding );
-        String sourceText = decode( encSourceText, encoding );
-        String name = decode( encName, encoding );
-        String nameText = decode( encNameText, encoding );
-        String group = decode( encGroup, encoding );
-        StackTraceWriter stackTraceWriter =
-                decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
-        Integer elapsed = decodeToInteger( encTimeElapsed );
-        String message = decode( encMessage, encoding );
-        return reportEntry( source, sourceText, name, nameText,
-                group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
-    }
-
-    static String decode( String line, Charset encoding )
-    {
-        // ForkedChannelEncoder is encoding the stream with US_ASCII
-        return line == null || "-".equals( line )
-                ? null
-                : new String( BASE64.decode( line.getBytes( US_ASCII ) ), encoding );
-    }
-
-    static Integer decodeToInteger( String line )
-    {
-        return line == null || "-".equals( line ) ? null : Integer.decode( line );
-    }
-
-    private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
-                                                 String encSmartTrimmedStackTrace, String encStackTrace )
-    {
-        if ( isBlank( encStackTrace ) || "-".equals( encStackTrace ) )
-        {
-            return null;
-        }
-        else
-        {
-            String traceMessage = decode( encTraceMessage, encoding );
-            String stackTrace = decode( encStackTrace, encoding );
-            String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
-            return new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace );
-        }
-    }
-}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventNotifier.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventNotifier.java
new file mode 100644
index 0000000..58f9344
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventNotifier.java
@@ -0,0 +1,248 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * 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.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.eventapi.AbstractConsoleEvent;
+import org.apache.maven.surefire.eventapi.AbstractStandardStreamEvent;
+import org.apache.maven.surefire.eventapi.AbstractTestControlEvent;
+import org.apache.maven.surefire.eventapi.ConsoleErrorEvent;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.eventapi.JvmExitErrorEvent;
+import org.apache.maven.surefire.eventapi.SystemPropertyEvent;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
+
+/**
+ * magic number : run mode : opcode [: opcode specific data]*
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public final class ForkedProcessEventNotifier
+{
+    private volatile ForkedProcessPropertyEventListener propertyEventListener;
+    private volatile ForkedProcessStackTraceEventListener consoleErrorEventListener;
+    private volatile ForkedProcessExitErrorListener exitErrorEventListener;
+
+    private final ConcurrentMap<ForkedProcessEventType, ForkedProcessReportEventListener<?>> reportEventListeners =
+            new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<ForkedProcessEventType, ForkedProcessStandardOutErrEventListener>
+        stdOutErrEventListeners = new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<ForkedProcessEventType, ForkedProcessStringEventListener> consoleEventListeners =
+            new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<ForkedProcessEventType, ForkedProcessEventListener> controlEventListeners =
+            new ConcurrentHashMap<>();
+
+    public void setSystemPropertiesListener( ForkedProcessPropertyEventListener listener )
+    {
+        propertyEventListener = requireNonNull( listener );
+    }
+
+    public <T extends ReportEntry> void setTestSetStartingListener( ForkedProcessReportEventListener<T> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TESTSET_STARTING, requireNonNull( listener ) );
+    }
+
+    public void setTestSetCompletedListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TESTSET_COMPLETED, requireNonNull( listener ) );
+    }
+
+    public void setTestStartingListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_STARTING, requireNonNull( listener ) );
+    }
+
+    public void setTestSucceededListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_SUCCEEDED, requireNonNull( listener ) );
+    }
+
+    public void setTestFailedListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_FAILED, requireNonNull( listener ) );
+    }
+
+    public void setTestSkippedListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_SKIPPED, requireNonNull( listener ) );
+    }
+
+    public void setTestErrorListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_ERROR, requireNonNull( listener ) );
+    }
+
+    public void setTestAssumptionFailureListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_ASSUMPTIONFAILURE, requireNonNull( listener ) );
+    }
+
+    public void setStdOutListener( ForkedProcessStandardOutErrEventListener listener )
+    {
+        stdOutErrEventListeners.put( BOOTERCODE_STDOUT, requireNonNull( listener ) );
+        stdOutErrEventListeners.put( BOOTERCODE_STDOUT_NEW_LINE, requireNonNull( listener ) );
+    }
+
+    public void setStdErrListener( ForkedProcessStandardOutErrEventListener listener )
+    {
+        stdOutErrEventListeners.put( BOOTERCODE_STDERR, requireNonNull( listener ) );
+        stdOutErrEventListeners.put( BOOTERCODE_STDERR_NEW_LINE, requireNonNull( listener ) );
+    }
+
+    public void setConsoleInfoListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_INFO, requireNonNull( listener ) );
+    }
+
+    public void setConsoleErrorListener( ForkedProcessStackTraceEventListener listener )
+    {
+        consoleErrorEventListener = requireNonNull( listener );
+    }
+
+    public void setConsoleDebugListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_DEBUG, requireNonNull( listener ) );
+    }
+
+    public void setConsoleWarningListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_WARNING, requireNonNull( listener ) );
+    }
+
+    public void setByeListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_BYE, requireNonNull( listener ) );
+    }
+
+    public void setStopOnNextTestListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_STOP_ON_NEXT_TEST, requireNonNull( listener ) );
+    }
+
+    public void setAcquireNextTestListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_NEXT_TEST, requireNonNull( listener ) );
+    }
+
+    public void setExitErrorEventListener( ForkedProcessExitErrorListener listener )
+    {
+        exitErrorEventListener = requireNonNull( listener );
+    }
+
+    public void notifyEvent( Event event )
+    {
+        ForkedProcessEventType eventType = event.getEventType();
+        if ( event.isControlCategory() )
+        {
+            ForkedProcessEventListener listener = controlEventListeners.get( eventType );
+            if ( listener != null )
+            {
+                listener.handle();
+            }
+        }
+        else if ( event.isConsoleErrorCategory() )
+        {
+            if ( consoleErrorEventListener != null )
+            {
+                consoleErrorEventListener.handle( ( ( ConsoleErrorEvent ) event ).getStackTraceWriter() );
+            }
+        }
+        else if ( event.isConsoleCategory() )
+        {
+            ForkedProcessStringEventListener listener = consoleEventListeners.get( eventType );
+            if ( listener != null )
+            {
+                listener.handle( ( (AbstractConsoleEvent) event ).getMessage() );
+            }
+        }
+        else if ( event.isStandardStreamCategory() )
+        {
+            boolean newLine = eventType == BOOTERCODE_STDOUT_NEW_LINE || eventType == BOOTERCODE_STDERR_NEW_LINE;
+            AbstractStandardStreamEvent standardStreamEvent = (AbstractStandardStreamEvent) event;
+            ForkedProcessStandardOutErrEventListener listener = stdOutErrEventListeners.get( eventType );
+            if ( listener != null )
+            {
+                listener.handle( standardStreamEvent.getRunMode(), standardStreamEvent.getMessage(), newLine );
+            }
+        }
+        else if ( event.isSysPropCategory() )
+        {
+            SystemPropertyEvent systemPropertyEvent = (SystemPropertyEvent) event;
+            RunMode runMode = systemPropertyEvent.getRunMode();
+            String key = systemPropertyEvent.getKey();
+            String value = systemPropertyEvent.getValue();
+            if ( propertyEventListener != null )
+            {
+                propertyEventListener.handle( runMode, key, value );
+            }
+        }
+        else if ( event.isTestCategory() )
+        {
+            ForkedProcessReportEventListener listener = reportEventListeners.get( eventType );
+            AbstractTestControlEvent testControlEvent = (AbstractTestControlEvent) event;
+            RunMode mode = testControlEvent.getRunMode();
+            ReportEntry reportEntry = testControlEvent.getReportEntry();
+            if ( listener != null )
+            {
+                listener.handle( mode, reportEntry );
+            }
+        }
+        else if ( event.isJvmExitError() )
+        {
+            JvmExitErrorEvent jvmExitErrorEvent = (JvmExitErrorEvent) event;
+            if ( exitErrorEventListener != null )
+            {
+                exitErrorEventListener.handle( jvmExitErrorEvent.getStackTraceWriter() );
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException( "Unknown event type " + eventType );
+        }
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
index b14c38c..a8b4b0b 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
@@ -19,11 +19,13 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
+import org.apache.maven.surefire.report.StackTraceWriter;
+
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
 public interface ForkedProcessExitErrorListener
 {
-    void handle( String exceptionMessage, String smartTrimmedStackTrace, String stackTrace );
+    void handle( StackTraceWriter stackTrace );
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
index f54cc40..81bf53f 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
@@ -19,11 +19,15 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import javax.annotation.Nonnull;
+
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
 public interface ForkedProcessStackTraceEventListener
 {
-    void handle( String msg, String smartStackTrace, String stackTrace );
+    void handle( @Nonnull StackTraceWriter stackTrace );
 }
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 b17bfe4..bc0bc7c 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
@@ -20,7 +20,9 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  */
 
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
-import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+import org.apache.maven.surefire.extensions.EventHandler;
+
+import javax.annotation.Nonnull;
 
 /**
  * Used by forked JMV, see {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
@@ -30,7 +32,7 @@ import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
  * @see org.apache.maven.plugin.surefire.booterclient.ForkStarter
  */
 public final class NativeStdErrStreamConsumer
-    implements StreamConsumer
+    implements EventHandler<String>
 {
     private final DefaultReporterFactory defaultReporterFactory;
 
@@ -40,7 +42,7 @@ public final class NativeStdErrStreamConsumer
     }
 
     @Override
-    public void consumeLine( String line )
+    public void handleEvent( @Nonnull String line )
     {
         InPluginProcessDumpSingleton.getSingleton()
                 .dumpStreamText( line, defaultReporterFactory.getReportsDirectory() );
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
similarity index 62%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
index f54cc40..1f915ae 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
@@ -9,7 +9,7 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * "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
+ *   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
@@ -19,11 +19,25 @@ 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.StdOutStreamLine;
+
 /**
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 3.0.0-M4
+ *
  */
-public interface ForkedProcessStackTraceEventListener
+public class NativeStdOutStreamConsumer
+        implements StdOutStreamLine
 {
-    void handle( String msg, String smartStackTrace, String stackTrace );
+    private final ConsoleLogger logger;
+
+    public NativeStdOutStreamConsumer( ConsoleLogger logger )
+    {
+        this.logger = logger;
+    }
+
+    @Override
+    public void handleLine( String line )
+    {
+        logger.info( line );
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
index 853d35c..9c72a04 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
@@ -19,9 +19,12 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
+import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
 
+import javax.annotation.Nonnull;
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -36,13 +39,13 @@ import static java.lang.Thread.currentThread;
  * @author Kristian Rosenvold
  */
 public final class ThreadedStreamConsumer
-        implements StreamConsumer, Closeable
+        implements EventHandler<Event>, Closeable
 {
-    private static final String END_ITEM = "";
+    private static final Event END_ITEM = new FinalEvent();
 
     private static final int ITEM_LIMIT_BEFORE_SLEEP = 10_000;
 
-    private final BlockingQueue<String> items = new ArrayBlockingQueue<>( ITEM_LIMIT_BEFORE_SLEEP );
+    private final BlockingQueue<Event> items = new ArrayBlockingQueue<>( ITEM_LIMIT_BEFORE_SLEEP );
 
     private final AtomicBoolean stop = new AtomicBoolean();
 
@@ -53,17 +56,17 @@ public final class ThreadedStreamConsumer
     final class Pumper
             implements Runnable
     {
-        private final StreamConsumer target;
+        private final EventHandler<Event> target;
 
         private final MultipleFailureException errors = new MultipleFailureException();
 
-        Pumper( StreamConsumer target )
+        Pumper( EventHandler<Event> target )
         {
             this.target = target;
         }
 
         /**
-         * Calls {@link ForkClient#consumeLine(String)} which may throw any {@link RuntimeException}.<br>
+         * Calls {@link ForkClient#handleEvent(Event)} which may throw any {@link RuntimeException}.<br>
          * Even if {@link ForkClient} is not fault-tolerant, this method MUST be fault-tolerant and thus the
          * try-catch block must be inside of the loop which prevents from loosing events from {@link StreamConsumer}.
          * <br>
@@ -80,12 +83,12 @@ public final class ThreadedStreamConsumer
             {
                 try
                 {
-                    String item = ThreadedStreamConsumer.this.items.take();
+                    Event item = ThreadedStreamConsumer.this.items.take();
                     if ( shouldStopQueueing( item ) )
                     {
                         return;
                     }
-                    target.consumeLine( item );
+                    target.handleEvent( item );
                 }
                 catch ( Throwable t )
                 {
@@ -105,7 +108,7 @@ public final class ThreadedStreamConsumer
         }
     }
 
-    public ThreadedStreamConsumer( StreamConsumer target )
+    public ThreadedStreamConsumer( EventHandler<Event> target )
     {
         pumper = new Pumper( target );
         thread = DaemonThreadFactory.newDaemonThread( pumper, "ThreadedStreamConsumer" );
@@ -113,7 +116,7 @@ public final class ThreadedStreamConsumer
     }
 
     @Override
-    public void consumeLine( String s )
+    public void handleEvent( @Nonnull Event event )
     {
         if ( stop.get() )
         {
@@ -127,7 +130,7 @@ public final class ThreadedStreamConsumer
 
         try
         {
-            items.put( s );
+            items.put( event );
         }
         catch ( InterruptedException e )
         {
@@ -164,8 +167,61 @@ public final class ThreadedStreamConsumer
      * @param item    element from <code>items</code>
      * @return {@code true} if tail of the queue
      */
-    private boolean shouldStopQueueing( String item )
+    private boolean shouldStopQueueing( Event item )
     {
         return item == END_ITEM;
     }
+
+    /**
+     *
+     */
+    private static class FinalEvent extends Event
+    {
+        FinalEvent()
+        {
+            super( null );
+        }
+
+        @Override
+        public boolean isControlCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isConsoleCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isConsoleErrorCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isStandardStreamCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isSysPropCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isTestCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isJvmExitError()
+        {
+            return false;
+        }
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
new file mode 100644
index 0000000..cc33c6c
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -0,0 +1,441 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.commons.codec.binary.Base64;
+import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.eventapi.ConsoleDebugEvent;
+import org.apache.maven.surefire.eventapi.ConsoleErrorEvent;
+import org.apache.maven.surefire.eventapi.ConsoleInfoEvent;
+import org.apache.maven.surefire.eventapi.ConsoleWarningEvent;
+import org.apache.maven.surefire.eventapi.ControlByeEvent;
+import org.apache.maven.surefire.eventapi.ControlNextTestEvent;
+import org.apache.maven.surefire.eventapi.ControlStopOnNextTestEvent;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.eventapi.JvmExitErrorEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamErrEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamErrWithNewLineEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamOutEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamOutWithNewLineEvent;
+import org.apache.maven.surefire.eventapi.SystemPropertyEvent;
+import org.apache.maven.surefire.eventapi.TestAssumptionFailureEvent;
+import org.apache.maven.surefire.eventapi.TestErrorEvent;
+import org.apache.maven.surefire.eventapi.TestFailedEvent;
+import org.apache.maven.surefire.eventapi.TestSkippedEvent;
+import org.apache.maven.surefire.eventapi.TestStartingEvent;
+import org.apache.maven.surefire.eventapi.TestSucceededEvent;
+import org.apache.maven.surefire.eventapi.TestsetCompletedEvent;
+import org.apache.maven.surefire.eventapi.TestsetStartingEvent;
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.apache.maven.surefire.report.RunMode;
+import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.TestSetReportEntry;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.MAGIC_NUMBER;
+import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
+import static org.apache.maven.surefire.report.RunMode.MODES;
+
+/**
+ *
+ */
+public class EventConsumerThread extends CloseableDaemonThread
+{
+    private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
+    private static final Base64 BASE64 = new Base64();
+
+    private final ReadableByteChannel channel;
+    private final EventHandler<Event> eventHandler;
+    private final CountdownCloseable countdownCloseable;
+    private final ConsoleLogger logger;
+    private volatile boolean disabled;
+
+    public EventConsumerThread( @Nonnull String threadName,
+                                @Nonnull ReadableByteChannel channel,
+                                @Nonnull EventHandler<Event> eventHandler,
+                                @Nonnull CountdownCloseable countdownCloseable,
+                                @Nonnull ConsoleLogger logger )
+    {
+        super( threadName );
+        this.channel = channel;
+        this.eventHandler = eventHandler;
+        this.countdownCloseable = countdownCloseable;
+        this.logger = logger;
+    }
+
+    @Override
+    public void run()
+    {
+        try ( ReadableByteChannel stream = channel;
+              CountdownCloseable c = countdownCloseable; )
+        {
+            decode();
+        }
+        catch ( IOException e )
+        {
+            // not needed
+        }
+    }
+
+    @Override
+    public void disable()
+    {
+        disabled = true;
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        channel.close();
+    }
+
+    @SuppressWarnings( "checkstyle:innerassignment" )
+    private void decode() throws IOException
+    {
+        List<String> tokens = new ArrayList<>();
+        StringBuilder line = new StringBuilder();
+        StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
+        ByteBuffer buffer = ByteBuffer.allocate( 1 );
+        boolean endOfStream;
+
+        start:
+        do
+        {
+            line.setLength( 0 );
+            tokens.clear();
+            token.setLength( 0 );
+            FrameCompletion completion = null;
+            for ( boolean frameStarted = false; !( endOfStream = channel.read( buffer ) == -1 ) ; completion = null )
+            {
+                buffer.flip();
+                char c = (char) buffer.get();
+                buffer.clear();
+
+                if ( c == '\n' || c == '\r' )
+                {
+                    printExistingLine( line );
+                    continue start;
+                }
+
+                line.append( c );
+
+                if ( !frameStarted )
+                {
+                    if ( c == ':' )
+                    {
+                        frameStarted = true;
+                        token.setLength( 0 );
+                        tokens.clear();
+                    }
+                }
+                else
+                {
+                    if ( c == ':' )
+                    {
+                        tokens.add( token.toString() );
+                        token.setLength( 0 );
+                        completion = frameCompleteness( tokens );
+                        if ( completion == FrameCompletion.COMPLETE )
+                        {
+                            line.setLength( 0 );
+                            break;
+                        }
+                        else if ( completion == FrameCompletion.MALFORMED )
+                        {
+                            printExistingLine( line );
+                            continue start;
+                        }
+                    }
+                    else
+                    {
+                        token.append( c );
+                    }
+                }
+            }
+
+            if ( completion == FrameCompletion.COMPLETE )
+            {
+                Event event = toEvent( tokens );
+                if ( !disabled && event != null )
+                {
+                    eventHandler.handleEvent( event );
+                }
+            }
+
+            if ( endOfStream )
+            {
+                printExistingLine( line );
+                return;
+            }
+        }
+        while ( true );
+    }
+
+    private void printExistingLine( StringBuilder line )
+    {
+        if ( line.length() != 0 )
+        {
+            String s = line.toString();
+            if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+            {
+                logger.info( s );
+            }
+            else
+            {
+                logger.error( s );
+            }
+        }
+    }
+
+    private Event toEvent( List<String> tokensInFrame )
+    {
+        Iterator<String> tokens = tokensInFrame.iterator();
+        String header = tokens.next();
+        assert header != null;
+
+        ForkedProcessEventType event = ForkedProcessEventType.byOpcode( tokens.next() );
+
+        if ( event == null )
+        {
+            return null;
+        }
+
+        if ( event.isControlCategory() )
+        {
+            switch ( event )
+            {
+                case BOOTERCODE_BYE:
+                    return new ControlByeEvent();
+                case BOOTERCODE_STOP_ON_NEXT_TEST:
+                    return new ControlStopOnNextTestEvent();
+                case BOOTERCODE_NEXT_TEST:
+                    return new ControlNextTestEvent();
+                default:
+                    throw new IllegalStateException( "Unknown enum " + event );
+            }
+        }
+        else if ( event.isConsoleErrorCategory() || event.isJvmExitError() )
+        {
+            Charset encoding = Charset.forName( tokens.next() );
+            StackTraceWriter stackTraceWriter = decodeTrace( encoding, tokens.next(), tokens.next(), tokens.next() );
+            return event.isConsoleErrorCategory()
+                ? new ConsoleErrorEvent( stackTraceWriter )
+                : new JvmExitErrorEvent( stackTraceWriter );
+        }
+        else if ( event.isConsoleCategory() )
+        {
+            Charset encoding = Charset.forName( tokens.next() );
+            String msg = decode( tokens.next(), encoding );
+            switch ( event )
+            {
+                case BOOTERCODE_CONSOLE_INFO:
+                    return new ConsoleInfoEvent( msg );
+                case BOOTERCODE_CONSOLE_DEBUG:
+                    return new ConsoleDebugEvent( msg );
+                case BOOTERCODE_CONSOLE_WARNING:
+                    return new ConsoleWarningEvent( msg );
+                default:
+                    throw new IllegalStateException( "Unknown enum " + event );
+            }
+        }
+        else if ( event.isStandardStreamCategory() )
+        {
+            RunMode mode = MODES.get( tokens.next() );
+            Charset encoding = Charset.forName( tokens.next() );
+            String output = decode( tokens.next(), encoding );
+            switch ( event )
+            {
+                case BOOTERCODE_STDOUT:
+                    return new StandardStreamOutEvent( mode, output );
+                case BOOTERCODE_STDOUT_NEW_LINE:
+                    return new StandardStreamOutWithNewLineEvent( mode, output );
+                case BOOTERCODE_STDERR:
+                    return new StandardStreamErrEvent( mode, output );
+                case BOOTERCODE_STDERR_NEW_LINE:
+                    return new StandardStreamErrWithNewLineEvent( mode, output );
+                default:
+                    throw new IllegalStateException( "Unknown enum " + event );
+            }
+        }
+        else if ( event.isSysPropCategory() )
+        {
+            RunMode mode = MODES.get( tokens.next() );
+            Charset encoding = Charset.forName( tokens.next() );
+            String key = decode( tokens.next(), encoding );
+            String value = decode( tokens.next(), encoding );
+            return new SystemPropertyEvent( mode, key, value );
+        }
+        else if ( event.isTestCategory() )
+        {
+            RunMode mode = MODES.get( tokens.next() );
+            Charset encoding = Charset.forName( tokens.next() );
+            TestSetReportEntry reportEntry =
+                decodeReportEntry( encoding, tokens.next(), tokens.next(), tokens.next(), tokens.next(),
+                    tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next() );
+
+            switch ( event )
+            {
+                case BOOTERCODE_TESTSET_STARTING:
+                    return new TestsetStartingEvent( mode, reportEntry );
+                case BOOTERCODE_TESTSET_COMPLETED:
+                    return new TestsetCompletedEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_STARTING:
+                    return new TestStartingEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_SUCCEEDED:
+                    return new TestSucceededEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_FAILED:
+                    return new TestFailedEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_SKIPPED:
+                    return new TestSkippedEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_ERROR:
+                    return new TestErrorEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
+                    return new TestAssumptionFailureEvent( mode, reportEntry );
+                default:
+                    throw new IllegalStateException( "Unknown enum " + event );
+            }
+        }
+
+        throw new IllegalStateException( "Missing a branch for the event type " + event );
+    }
+
+    private static FrameCompletion frameCompleteness( List<String> tokens )
+    {
+        if ( !tokens.isEmpty() && !MAGIC_NUMBER.equals( tokens.get( 0 ) ) )
+        {
+            return FrameCompletion.MALFORMED;
+        }
+
+        if ( tokens.size() >= 2 )
+        {
+            String opcode = tokens.get( 1 );
+            ForkedProcessEventType event = ForkedProcessEventType.byOpcode( opcode );
+            if ( event == null )
+            {
+                return FrameCompletion.MALFORMED;
+            }
+            else if ( event.isControlCategory() )
+            {
+                return FrameCompletion.COMPLETE;
+            }
+            else if ( event.isConsoleErrorCategory() )
+            {
+                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isConsoleCategory() )
+            {
+                return tokens.size() == 4 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isStandardStreamCategory() )
+            {
+                return tokens.size() == 5 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isSysPropCategory() )
+            {
+                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isTestCategory() )
+            {
+                return tokens.size() == 14 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isJvmExitError() )
+            {
+                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+        }
+        return FrameCompletion.NOT_COMPLETE;
+    }
+
+    static String decode( String line, Charset encoding )
+    {
+        // ForkedChannelEncoder is encoding the stream with US_ASCII
+        return line == null || "-".equals( line )
+            ? null
+            : new String( BASE64.decode( line.getBytes( US_ASCII ) ), encoding );
+    }
+
+    private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
+                                                 String encSmartTrimmedStackTrace, String encStackTrace )
+    {
+        String traceMessage = decode( encTraceMessage, encoding );
+        String stackTrace = decode( encStackTrace, encoding );
+        String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
+        boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
+        return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
+    }
+
+    static TestSetReportEntry decodeReportEntry( Charset encoding,
+                                                 // ReportEntry:
+                                                 String encSource, String encSourceText, String encName,
+                                                 String encNameText, String encGroup, String encMessage,
+                                                 String encTimeElapsed,
+                                                 // StackTraceWriter:
+                                                 String encTraceMessage,
+                                                 String encSmartTrimmedStackTrace, String encStackTrace )
+        throws NumberFormatException
+    {
+        if ( encoding == null )
+        {
+            // corrupted or incomplete stream
+            return null;
+        }
+
+        String source = decode( encSource, encoding );
+        String sourceText = decode( encSourceText, encoding );
+        String name = decode( encName, encoding );
+        String nameText = decode( encNameText, encoding );
+        String group = decode( encGroup, encoding );
+        StackTraceWriter stackTraceWriter =
+            decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
+        Integer elapsed = decodeToInteger( encTimeElapsed );
+        String message = decode( encMessage, encoding );
+        return reportEntry( source, sourceText, name, nameText,
+            group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
+    }
+
+    static Integer decodeToInteger( String line )
+    {
+        return line == null || "-".equals( line ) ? null : Integer.decode( line );
+    }
+
+    /**
+     * Determines whether the frame is complete or malformed.
+     */
+    private enum FrameCompletion
+    {
+        NOT_COMPLETE,
+        COMPLETE,
+        MALFORMED
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
new file mode 100644
index 0000000..a554edf
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
@@ -0,0 +1,89 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.CommandReader;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+
+import javax.annotation.Nonnull;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ * The main purpose of this class is to bind the
+ * {@link #bindCommandReader(CommandReader, WritableByteChannel) command reader} reading the commands from
+ * {@link CommandReader}, serializing them and writing the stream to the
+ * {@link WritableByteChannel sub-process}. It binds the
+ * {@link #bindEventHandler(EventHandler, CountdownCloseable, ReadableByteChannel) event handler} deserializing
+ * a received event and sends the event object to the {@link EventHandler event handler}.
+ */
+final class LegacyForkChannel extends ForkChannel
+{
+    private final ConsoleLogger logger;
+
+    protected LegacyForkChannel( int forkChannelId, ConsoleLogger logger )
+    {
+        super( forkChannelId );
+        this.logger = logger;
+    }
+
+    @Override
+    public void connectToClient()
+    {
+    }
+
+    @Override
+    public String getForkNodeConnectionString()
+    {
+        return "pipe://" + getForkChannelId();
+    }
+
+    @Override
+    public boolean useStdOut()
+    {
+        return true;
+    }
+
+    @Override
+    public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands,
+                                                    WritableByteChannel stdIn )
+    {
+        return new StreamFeeder( "std-in-fork-" + getForkChannelId(), stdIn, commands, logger );
+    }
+
+    @Override
+    public CloseableDaemonThread bindEventHandler( @Nonnull EventHandler<Event> eventHandler,
+                                                   @Nonnull CountdownCloseable countdownCloseable,
+                                                   ReadableByteChannel stdOut )
+    {
+        return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", stdOut,
+            eventHandler, countdownCloseable, logger );
+    }
+
+    @Override
+    public void close()
+    {
+    }
+}
diff --git a/surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/JUnit4SuiteTest.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
similarity index 58%
copy from surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/JUnit4SuiteTest.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
index df9cca1..ca2010b 100644
--- a/surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions.util;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,20 +19,22 @@ package org.apache.maven.surefire.extensions.util;
  * under the License.
  */
 
-import junit.framework.JUnit4TestAdapter;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
 
 /**
- *
+ * The factory of {@link LegacyForkChannel}.
  */
-public class JUnit4SuiteTest extends TestCase
+public class LegacyForkNodeFactory implements ForkNodeFactory
 {
-    public static Test suite()
+    @Nonnull
+    @Override
+    public ForkChannel createForkChannel( @Nonnegative int forkChannelId, ConsoleLogger logger )
     {
-        TestSuite suite = new TestSuite();
-        suite.addTest( new JUnit4TestAdapter( CommandlineExecutorTest.class ) );
-        return suite;
+        return new LegacyForkChannel( forkChannelId, logger );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java
new file mode 100644
index 0000000..604ca33
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java
@@ -0,0 +1,203 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.CommandReader;
+import org.apache.maven.surefire.util.internal.ImmutableMap;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.WritableByteChannel;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.MAGIC_NUMBER;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED;
+
+/**
+ * Commands which are sent from plugin to the forked jvm.
+ * <br>
+ *     <br>
+ * magic number : opcode [: opcode specific data]*
+ * <br>
+ *     or data encoded with Base64
+ * <br>
+ * magic number : opcode [: Base64(opcode specific data)]*
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M5
+ */
+public class StreamFeeder extends CloseableDaemonThread
+{
+    private static final Map<MasterProcessCommand, String> COMMAND_OPCODES = opcodesToStrings();
+
+    private final WritableByteChannel channel;
+    private final CommandReader commandReader;
+    private final ConsoleLogger logger;
+
+    private volatile boolean disabled;
+    private volatile Throwable exception;
+
+    public StreamFeeder( @Nonnull String threadName, @Nonnull WritableByteChannel channel,
+                         @Nonnull CommandReader commandReader, @Nonnull ConsoleLogger logger )
+    {
+        super( threadName );
+        this.channel = channel;
+        this.commandReader = commandReader;
+        this.logger = logger;
+    }
+
+    @Override
+    @SuppressWarnings( "checkstyle:innerassignment" )
+    public void run()
+    {
+        try ( WritableByteChannel c = channel )
+        {
+            for ( Command cmd; ( cmd = commandReader.readNextCommand() ) != null; )
+            {
+                if ( !disabled )
+                {
+                    MasterProcessCommand cmdType = cmd.getCommandType();
+                    byte[] data = cmdType.hasDataType() ? encode( cmdType, cmd.getData() ) : encode( cmdType );
+                    c.write( ByteBuffer.wrap( data ) );
+                }
+            }
+        }
+        catch ( ClosedChannelException e )
+        {
+            // closed externally
+        }
+        catch ( IOException | NonWritableChannelException e )
+        {
+            exception = e.getCause() == null ? e : e.getCause();
+        }
+        catch ( IllegalArgumentException e )
+        {
+            logger.error( e.getLocalizedMessage() );
+        }
+    }
+
+    public void disable()
+    {
+        disabled = true;
+    }
+
+    public Throwable getException()
+    {
+        return exception;
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        channel.close();
+    }
+
+    /**
+     * Public method for testing purposes.
+     *
+     * @param cmdType command type
+     * @param data data to encode
+     * @return command with data encoded to bytes
+     */
+    public static byte[] encode( MasterProcessCommand cmdType, String data )
+    {
+        if ( !cmdType.hasDataType() )
+        {
+            throw new IllegalArgumentException( "cannot use data without data type" );
+        }
+
+        if ( cmdType.getDataType() != String.class )
+        {
+            throw new IllegalArgumentException( "Data type can be only " + String.class );
+        }
+
+        return encode( COMMAND_OPCODES.get( cmdType ), data )
+            .toString()
+            .getBytes( US_ASCII );
+    }
+
+    /**
+     * Public method for testing purposes.
+     *
+     * @param cmdType command type
+     * @return command without data encoded to bytes
+     */
+    public static byte[] encode( MasterProcessCommand cmdType )
+    {
+        if ( cmdType.getDataType() != Void.class )
+        {
+            throw new IllegalArgumentException( "Data type can be only " + cmdType.getDataType() );
+        }
+
+        return encode( COMMAND_OPCODES.get( cmdType ), null )
+            .toString()
+            .getBytes( US_ASCII );
+    }
+
+    /**
+     * Encodes opcode and data.
+     *
+     * @param operation opcode
+     * @param data   data
+     * @return encoded command
+     */
+    private static StringBuilder encode( String operation, String data )
+    {
+        StringBuilder s = new StringBuilder( 128 )
+            .append( ':' )
+            .append( MAGIC_NUMBER )
+            .append( ':' )
+            .append( operation );
+
+        if ( data != null )
+        {
+            s.append( ':' )
+                .append( data );
+        }
+
+        return s.append( ':' );
+    }
+
+    private static Map<MasterProcessCommand, String> opcodesToStrings()
+    {
+        Map<MasterProcessCommand, String> opcodes = new HashMap<>();
+        opcodes.put( RUN_CLASS, "run-testclass" );
+        opcodes.put( TEST_SET_FINISHED, "testset-finished" );
+        opcodes.put( SKIP_SINCE_NEXT_TEST, "skip-since-next-test" );
+        opcodes.put( SHUTDOWN, "shutdown" );
+        opcodes.put( NOOP, "noop" );
+        opcodes.put( BYE_ACK, "bye-ack" );
+        return new ImmutableMap<>( opcodes );
+    }
+}
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
new file mode 100644
index 0000000..0aa790c
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
@@ -0,0 +1,140 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.CommandReader;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketOption;
+import java.nio.channels.Channel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.WritableByteChannel;
+
+import static java.net.StandardSocketOptions.SO_KEEPALIVE;
+import static java.net.StandardSocketOptions.SO_REUSEADDR;
+import static java.net.StandardSocketOptions.TCP_NODELAY;
+import static java.nio.channels.ServerSocketChannel.open;
+
+/**
+ * The TCP/IP server accepting only one client connection. The forked JVM connects to the server using the
+ * {@link #getForkNodeConnectionString() connection string}.
+ * The main purpose of this class is to {@link #connectToClient() conect with tthe client}, bind the
+ * {@link #bindCommandReader(CommandReader, WritableByteChannel) command reader} to the internal socket's
+ * {@link java.io.InputStream}, and bind the
+ * {@link #bindEventHandler(EventHandler, CountdownCloseable, ReadableByteChannel) event handler} writing the event
+ * objects to the {@link EventHandler event handler}.
+ * <br>
+ * The objects {@link WritableByteChannel} and {@link ReadableByteChannel} are forked process streams
+ * (standard input and output). Both are ignored in this implementation but they are used in {@link LegacyForkChannel}.
+ * <br>
+ * The channel is closed after the forked JVM has finished normally or the shutdown hook is executed in the plugin.
+ */
+final class SurefireForkChannel extends ForkChannel
+{
+    private static final byte[] LOCAL_LOOPBACK_IP_ADDRESS = new byte[]{127, 0, 0, 1};
+
+    private final ConsoleLogger logger;
+    private final ServerSocketChannel server;
+    private final int localPort;
+    private volatile SocketChannel channel;
+
+    SurefireForkChannel( int forkChannelId, ConsoleLogger logger ) throws IOException
+    {
+        super( forkChannelId );
+        this.logger = logger;
+        server = open();
+        setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
+        InetAddress ip = Inet4Address.getByAddress( LOCAL_LOOPBACK_IP_ADDRESS );
+        server.bind( new InetSocketAddress( ip, 0 ), 1 );
+        localPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort();
+    }
+
+    @Override
+    public void connectToClient() throws IOException
+    {
+        if ( channel != null )
+        {
+            throw new IllegalStateException( "already accepted TCP client connection" );
+        }
+        channel = server.accept();
+    }
+
+    @SafeVarargs
+    private final void setTrueOptions( SocketOption<Boolean>... options ) throws IOException
+    {
+        for ( SocketOption<Boolean> option : options )
+        {
+            if ( server.supportedOptions().contains( option ) )
+            {
+                server.setOption( option, true );
+            }
+        }
+    }
+
+    @Override
+    public String getForkNodeConnectionString()
+    {
+        return "tcp://127.0.0.1:" + localPort;
+    }
+
+    @Override
+    public boolean useStdOut()
+    {
+        return false;
+    }
+
+    @Override
+    public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands,
+                                                    WritableByteChannel stdIn )
+    {
+        return new StreamFeeder( "commands-fork-" + getForkChannelId(), channel, commands, logger );
+    }
+
+    @Override
+    public CloseableDaemonThread bindEventHandler( @Nonnull EventHandler<Event> eventHandler,
+                                                   @Nonnull CountdownCloseable countdownCloseable,
+                                                   ReadableByteChannel stdOut )
+    {
+        return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", channel,
+            eventHandler, countdownCloseable, logger );
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        //noinspection EmptyTryBlock
+        try ( Channel c1 = channel; Channel c2 = server )
+        {
+            // only close all channels
+        }
+    }
+}
diff --git a/surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/JUnit4SuiteTest.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
similarity index 56%
copy from surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/JUnit4SuiteTest.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
index df9cca1..1c4aabc 100644
--- a/surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions.util;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,20 +19,23 @@ package org.apache.maven.surefire.extensions.util;
  * under the License.
  */
 
-import junit.framework.JUnit4TestAdapter;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import java.io.IOException;
 
 /**
- *
+ * The factory of {@link SurefireForkChannel}.
  */
-public class JUnit4SuiteTest extends TestCase
+public class SurefireForkNodeFactory implements ForkNodeFactory
 {
-    public static Test suite()
+    @Nonnull
+    @Override
+    public ForkChannel createForkChannel( @Nonnegative int forkChannelId, ConsoleLogger logger ) throws IOException
     {
-        TestSuite suite = new TestSuite();
-        suite.addTest( new JUnit4TestAdapter( CommandlineExecutorTest.class ) );
-        return suite;
+        return new SurefireForkChannel( forkChannelId, logger );
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
index 9faa4eb..abd896e 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
@@ -28,6 +28,7 @@ import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.util.DefaultScanResult;
 import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
@@ -180,11 +181,26 @@ public class AbstractSurefireMojoJava7PlusTest
                 "jar", "", handler );
         loggerApi.setFile( mockFile( "surefire-logger-api.jar" ) );
 
+        Artifact spi = new DefaultArtifact( "org.apache.maven.surefire", "surefire-extensions-spi",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        spi.setFile( mockFile( "surefire-extensions-spi.jar" ) );
+
+        Artifact booter = new DefaultArtifact( "org.apache.maven.surefire", "surefire-booter",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        booter.setFile( mockFile( "surefire-booter.jar" ) );
+
+        Artifact utils = new DefaultArtifact( "org.apache.maven.surefire", "surefire-shared-utils",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        utils.setFile( mockFile( "surefire-shared-utils.jar" ) );
+
         Map<String, Artifact> artifacts = new HashMap<>();
         artifacts.put( "org.apache.maven.surefire:maven-surefire-common", common );
         artifacts.put( "org.apache.maven.surefire:surefire-extensions-api", ext );
         artifacts.put( "org.apache.maven.surefire:surefire-api", api );
         artifacts.put( "org.apache.maven.surefire:surefire-logger-api", loggerApi );
+        artifacts.put( "org.apache.maven.surefire:surefire-extensions-spi", spi );
+        artifacts.put( "org.apache.maven.surefire:surefire-booter", booter );
+        artifacts.put( "org.apache.maven.surefire:surefire-shared-utils", utils );
         when( mojo.getPluginArtifactMap() ).thenReturn( artifacts );
 
         StartupConfiguration conf = invokeMethod( mojo, "newStartupConfigWithModularPath",
@@ -193,7 +209,6 @@ public class AbstractSurefireMojoJava7PlusTest
 
         verify( mojo, times( 1 ) ).effectiveIsEnableAssertions();
         verify( mojo, times( 1 ) ).isChildDelegation();
-        verify( mojo, times( 1 ) ).getEffectiveForkCount();
         verify( mojo, times( 1 ) ).getTestClassesDirectory();
         verify( scanResult, times( 1 ) ).getClasses();
         verifyStatic( ResolvePathsRequest.class, times( 1 ) );
@@ -219,8 +234,8 @@ public class AbstractSurefireMojoJava7PlusTest
                         "test(compact) classpath:  non-modular.jar  junit.jar  hamcrest.jar",
                         "test(compact) modulepath:  modular.jar  classes",
                         "provider(compact) classpath:  surefire-provider.jar",
-                        "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar",
-                        "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar"
+                        "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-booter.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-extensions-spi.jar  surefire-logger-api.jar  surefire-shared-utils.jar",
+                        "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-booter.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-extensions-spi.jar  surefire-logger-api.jar  surefire-shared-utils.jar"
                 );
 
         assertThat( conf ).isNotNull();
@@ -632,6 +647,12 @@ public class AbstractSurefireMojoJava7PlusTest
         }
 
         @Override
+        protected ForkNodeFactory getForkNode()
+        {
+            return null;
+        }
+
+        @Override
         protected Artifact getMojoArtifact()
         {
             return null;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
index b1e7c1e..0553de7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
@@ -42,6 +42,7 @@ import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
 import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 import org.codehaus.plexus.logging.Logger;
 import org.junit.Before;
@@ -312,11 +313,26 @@ public class AbstractSurefireMojoTest
                 createFromVersion( "1" ), "runtime", "jar", "", handler );
         loggerApi.setFile( mockFile( "surefire-logger-api.jar" ) );
 
+        Artifact spi = new DefaultArtifact( "org.apache.maven.surefire", "surefire-extensions-spi",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        spi.setFile( mockFile( "surefire-extensions-spi.jar" ) );
+
+        Artifact booter = new DefaultArtifact( "org.apache.maven.surefire", "surefire-booter",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        booter.setFile( mockFile( "surefire-booter.jar" ) );
+
+        Artifact utils = new DefaultArtifact( "org.apache.maven.surefire", "surefire-shared-utils",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        utils.setFile( mockFile( "surefire-shared-utils.jar" ) );
+
         Map<String, Artifact> providerArtifactsMap = new HashMap<>();
         providerArtifactsMap.put( "org.apache.maven.surefire:maven-surefire-common", common );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-api", ext );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-api", api );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-logger-api", loggerApi );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-spi", spi );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-booter", booter );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-shared-utils", utils );
 
         when( mojo.getPluginArtifactMap() )
                 .thenReturn( providerArtifactsMap );
@@ -358,7 +374,6 @@ public class AbstractSurefireMojoTest
 
         verify( mojo, times( 1 ) ).effectiveIsEnableAssertions();
         verify( mojo, times( 1 ) ).isChildDelegation();
-        verify( mojo, times( 1 ) ).getEffectiveForkCount();
         ArgumentCaptor<String> argument = ArgumentCaptor.forClass( String.class );
         verify( logger, times( 6 ) ).debug( argument.capture() );
         assertThat( argument.getAllValues() )
@@ -366,8 +381,8 @@ public class AbstractSurefireMojoTest
                 "provider classpath:  surefire-provider.jar",
                 "test(compact) classpath:  test-classes  classes  junit.jar  hamcrest.jar",
                 "provider(compact) classpath:  surefire-provider.jar",
-                "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar",
-                "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar"
+                "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-booter.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-extensions-spi.jar  surefire-logger-api.jar  surefire-shared-utils.jar",
+                "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-booter.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-extensions-spi.jar  surefire-logger-api.jar  surefire-shared-utils.jar"
                 );
 
         assertThat( conf.getClassLoaderConfiguration() )
@@ -405,7 +420,7 @@ public class AbstractSurefireMojoTest
         Artifact provider = new DefaultArtifact( "com.example", "provider", createFromVersion( "1" ), "runtime",
                 "jar", "", handler );
         provider.setFile( mockFile( "original-test-provider.jar" ) );
-        HashSet<Artifact> providerClasspath = new HashSet<>( asList( provider ) );
+        Set<Artifact> providerClasspath = singleton( provider );
         when( providerInfo.getProviderClasspath() ).thenReturn( providerClasspath );
 
         StartupConfiguration startupConfiguration = startupConfigurationForProvider( providerInfo );
@@ -448,11 +463,26 @@ public class AbstractSurefireMojoTest
                 createFromVersion( "1" ), "runtime", "jar", "", handler );
         loggerApi.setFile( mockFile( "surefire-logger-api.jar" ) );
 
+        Artifact spi = new DefaultArtifact( "org.apache.maven.surefire", "surefire-extensions-spi",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        spi.setFile( mockFile( "surefire-extensions-spi.jar" ) );
+
+        Artifact booter = new DefaultArtifact( "org.apache.maven.surefire", "surefire-booter",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        booter.setFile( mockFile( "surefire-booter.jar" ) );
+
+        Artifact utils = new DefaultArtifact( "org.apache.maven.surefire", "surefire-shared-utils",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        utils.setFile( mockFile( "surefire-shared-utils.jar" ) );
+
         Map<String, Artifact> providerArtifactsMap = new HashMap<>();
         providerArtifactsMap.put( "org.apache.maven.surefire:maven-surefire-common", common );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-api", ext );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-api", api );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-logger-api", loggerApi );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-spi", spi );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-booter", booter );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-shared-utils", utils );
 
         when( mojo.getPluginArtifactMap() ).thenReturn( providerArtifactsMap );
 
@@ -2121,6 +2151,12 @@ public class AbstractSurefireMojoTest
         }
 
         @Override
+        protected ForkNodeFactory getForkNode()
+        {
+            return null;
+        }
+
+        @Override
         protected Artifact getMojoArtifact()
         {
             return new DefaultArtifact( "org.apache.maven.surefire", "maven-surefire-plugin", createFromVersion( "1" ),
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
index 1f94a75..4f954d4 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
@@ -23,15 +23,30 @@ 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.log.api.ConsoleLoggerDecorator;
+import org.apache.maven.plugin.surefire.log.api.PrintStreamLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
+import org.hamcrest.MatcherAssert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 
 import java.io.File;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.surefire.util.ReflectionUtils.getMethod;
+import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.powermock.reflect.Whitebox.getInternalState;
 
 /**
@@ -96,4 +111,39 @@ public class CommonReflectorTest
         assertThat( reportConfiguration.getConsoleOutputReporter().toString() )
                 .isEqualTo( consoleOutputReporter.toString() );
     }
+
+    @Test
+    public void shouldProxyConsoleLogger()
+    {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        ConsoleLogger logger = spy( new PrintStreamLogger( System.out ) );
+        Object mirror = CommonReflector.createConsoleLogger( logger, cl );
+        MatcherAssert.assertThat( mirror, is( notNullValue() ) );
+        MatcherAssert.assertThat( mirror.getClass().getInterfaces()[0].getName(), is( ConsoleLogger.class.getName() ) );
+        MatcherAssert.assertThat( mirror, is( not( sameInstance( (Object) logger ) ) ) );
+        MatcherAssert.assertThat( mirror, is( instanceOf( ConsoleLoggerDecorator.class ) ) );
+        invokeMethodWithArray( mirror, getMethod( mirror, "info", String.class ), "Hi There!" );
+        verify( logger, times( 1 ) ).info( "Hi There!" );
+    }
+
+    @Test
+    public void testCreateConsoleLogger()
+    {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        ConsoleLogger consoleLogger = mock( ConsoleLogger.class );
+        ConsoleLogger decorator = (ConsoleLogger) CommonReflector.createConsoleLogger( consoleLogger, cl );
+        assertThat( decorator )
+                .isNotSameAs( consoleLogger );
+
+        assertThat( decorator.isDebugEnabled() ).isFalse();
+        when( consoleLogger.isDebugEnabled() ).thenReturn( true );
+        assertThat( decorator.isDebugEnabled() ).isTrue();
+        verify( consoleLogger, times( 2 ) ).isDebugEnabled();
+
+        decorator.info( "msg" );
+        ArgumentCaptor<String> argumentMsg = ArgumentCaptor.forClass( String.class );
+        verify( consoleLogger, times( 1 ) ).info( argumentMsg.capture() );
+        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
+        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "msg" );
+    }
 }
\ No newline at end of file
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
index ccf63f7..835ce8e 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
@@ -28,6 +28,7 @@ import org.apache.maven.plugin.MojoFailureException;
 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.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.util.DefaultScanResult;
 import org.apache.maven.toolchain.Toolchain;
@@ -751,6 +752,12 @@ public class MojoMocklessTest
         }
 
         @Override
+        protected ForkNodeFactory getForkNode()
+        {
+            return null;
+        }
+
+        @Override
         protected String getEnableProcessChecker()
         {
             return null;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java
deleted file mode 100644
index 2553617..0000000
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.apache.maven.plugin.surefire;
-
-/*
- * 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.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerDecorator;
-import org.apache.maven.plugin.surefire.log.api.PrintStreamLogger;
-import org.apache.maven.surefire.booter.IsolatedClassLoader;
-import org.apache.maven.surefire.booter.SurefireReflector;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.apache.maven.surefire.util.ReflectionUtils.getMethod;
-import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.sameInstance;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-/**
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see ConsoleLogger
- * @see SurefireReflector
- * @since 2.20
- */
-public class SurefireReflectorTest
-{
-    private ConsoleLogger logger;
-    private ClassLoader cl;
-
-    @Before
-    public void prepareData()
-    {
-        logger = spy( new PrintStreamLogger( System.out ) );
-        cl = new IsolatedClassLoader( Thread.currentThread().getContextClassLoader(), false, "role" );
-    }
-
-    @Test
-    public void shouldProxyConsoleLogger()
-    {
-        Object mirror = SurefireReflector.createConsoleLogger( logger, cl );
-        assertThat( mirror, is( notNullValue() ) );
-        assertThat( mirror.getClass().getInterfaces()[0].getName(), is( ConsoleLogger.class.getName() ) );
-        assertThat( mirror, is( not( sameInstance( (Object) logger ) ) ) );
-        assertThat( mirror, is( instanceOf( ConsoleLoggerDecorator.class ) ) );
-        invokeMethodWithArray( mirror, getMethod( mirror, "info", String.class ), "Hi There!" );
-        verify( logger, times( 1 ) ).info( "Hi There!" );
-    }
-}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
index ca42c44..10563a8 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
@@ -25,7 +25,6 @@ import org.apache.maven.surefire.shared.io.FileUtils;
 import org.apache.maven.surefire.booter.BooterDeserializer;
 import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
-import org.apache.maven.surefire.booter.ProcessCheckerType;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
 import org.apache.maven.surefire.booter.Shutdown;
@@ -52,6 +51,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 
+import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
 import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
 import static org.apache.maven.surefire.cli.CommandLineOption.REACTOR_FAIL_FAST;
 import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
@@ -260,9 +260,10 @@ public class BooterDeserializerProviderConfigurationTest
             test = "aTest";
         }
         final File propsTest = booterSerializer.serialize( props, booterConfiguration, testProviderConfiguration, test,
-                                                           readTestsFromInStream, 51L, 1 );
+                                                           readTestsFromInStream, 51L, 1, "pipe://1" );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
         assertEquals( "51", (Object) booterDeserializer.getPluginPid() );
+        assertEquals( "pipe://1", booterDeserializer.getConnectionString() );
         return booterDeserializer.deserialize();
     }
 
@@ -285,8 +286,7 @@ public class BooterDeserializerProviderConfigurationTest
     {
         ClasspathConfiguration classpathConfiguration = new ClasspathConfiguration( true, true );
 
-        return new StartupConfiguration( "com.provider", classpathConfiguration, classLoaderConfiguration, false,
-                                         false, ProcessCheckerType.ALL );
+        return new StartupConfiguration( "com.provider", classpathConfiguration, classLoaderConfiguration, ALL );
     }
 
     private File getTestSourceDirectory()
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
index 4e9bbc9..63f6162 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
@@ -20,24 +20,23 @@ package org.apache.maven.plugin.surefire.booterclient;
  */
 
 import junit.framework.TestCase;
-import org.apache.maven.surefire.shared.io.FileUtils;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.BooterDeserializer;
+import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
-import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
-import org.apache.maven.surefire.booter.ProcessCheckerType;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
-import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.Shutdown;
+import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.shared.io.FileUtils;
 import org.apache.maven.surefire.testset.DirectoryScannerParameters;
 import org.apache.maven.surefire.testset.RunOrderParameters;
 import org.apache.maven.surefire.testset.TestArtifactInfo;
-import org.apache.maven.surefire.testset.TestRequest;
 import org.apache.maven.surefire.testset.TestListResolver;
+import org.apache.maven.surefire.testset.TestRequest;
 import org.apache.maven.surefire.util.RunOrder;
 import org.junit.After;
 import org.junit.Before;
@@ -50,6 +49,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 
+import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
 import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
 import static org.apache.maven.surefire.cli.CommandLineOption.REACTOR_FAIL_FAST;
 import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
@@ -105,7 +105,7 @@ public class BooterDeserializerStartupConfigurationTest
 
     public void testProcessChecker() throws IOException
     {
-        assertEquals( ProcessCheckerType.ALL, getReloadedStartupConfiguration().getProcessChecker() );
+        assertEquals( ALL, getReloadedStartupConfiguration().getProcessChecker() );
     }
 
     private void assertCpConfigEquals( ClasspathConfiguration expectedConfiguration,
@@ -136,13 +136,13 @@ public class BooterDeserializerStartupConfigurationTest
 
     public void testProcessCheckerAll() throws IOException
     {
-        assertEquals( ProcessCheckerType.ALL, getReloadedStartupConfiguration().getProcessChecker() );
+        assertEquals( ALL, getReloadedStartupConfiguration().getProcessChecker() );
     }
 
     public void testProcessCheckerNull() throws IOException
     {
         StartupConfiguration startupConfiguration = new StartupConfiguration( "com.provider", classpathConfiguration,
-                getManifestOnlyJarForkConfiguration(), false, false, null );
+                getManifestOnlyJarForkConfiguration(), null );
         assertNull( saveAndReload( startupConfiguration ).getProcessChecker() );
     }
 
@@ -178,15 +178,15 @@ public class BooterDeserializerStartupConfigurationTest
         BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
         String aTest = "aTest";
         File propsTest = booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest,
-                false, null, 1 );
+                false, null, 1, "tcp://127.0.0.1:63003" );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
         assertNull( booterDeserializer.getPluginPid() );
+        assertEquals( "tcp://127.0.0.1:63003", booterDeserializer.getConnectionString() );
         return booterDeserializer.getStartupConfiguration();
     }
 
     private ProviderConfiguration getProviderConfiguration()
     {
-
         File cwd = new File( "." );
         DirectoryScannerParameters directoryScannerParameters =
             new DirectoryScannerParameters( cwd, new ArrayList<String>(), new ArrayList<String>(),
@@ -204,8 +204,7 @@ public class BooterDeserializerStartupConfigurationTest
 
     private StartupConfiguration getTestStartupConfiguration( ClassLoaderConfiguration classLoaderConfiguration )
     {
-        return new StartupConfiguration( "com.provider", classpathConfiguration, classLoaderConfiguration, false,
-                                         false, ProcessCheckerType.ALL );
+        return new StartupConfiguration( "com.provider", classpathConfiguration, classLoaderConfiguration, ALL );
     }
 
     private File getTestSourceDirectory()
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
index 06fe754..45a6b4a 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
@@ -27,7 +27,7 @@ import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
 import org.apache.maven.surefire.booter.ForkedBooter;
 import org.apache.maven.surefire.booter.StartupConfiguration;
-import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -80,6 +80,7 @@ public class DefaultForkConfigurationTest
     private boolean reuseForks;
     private Platform pluginPlatform;
     private ConsoleLogger log;
+    private ForkNodeFactory forkNodeFactory;
 
     @Before
     public void setup()
@@ -97,6 +98,7 @@ public class DefaultForkConfigurationTest
         reuseForks = true;
         pluginPlatform = new Platform();
         log = mock( ConsoleLogger.class );
+        forkNodeFactory = mock( ForkNodeFactory.class );
     }
 
     @Test
@@ -104,16 +106,15 @@ public class DefaultForkConfigurationTest
     {
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -130,16 +131,15 @@ public class DefaultForkConfigurationTest
         argLine = "";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -156,16 +156,15 @@ public class DefaultForkConfigurationTest
         argLine = "\n\r";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -182,16 +181,15 @@ public class DefaultForkConfigurationTest
         argLine = "-Dfile.encoding=UTF-8";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -209,16 +207,15 @@ public class DefaultForkConfigurationTest
         argLine = "-Dfile.encoding=@{encoding}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -235,16 +232,15 @@ public class DefaultForkConfigurationTest
         argLine = "a\n\rb";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -261,16 +257,15 @@ public class DefaultForkConfigurationTest
         argLine = "-Dthread=${surefire.threadNumber}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -287,16 +282,15 @@ public class DefaultForkConfigurationTest
         argLine = "-Dthread=${surefire.forkNumber}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -313,7 +307,7 @@ public class DefaultForkConfigurationTest
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
         ClasspathConfiguration cc = new ClasspathConfiguration( true, true );
         StartupConfiguration conf = new StartupConfiguration( "org.apache.maven.shadefire.surefire.MyProvider",
-                cc, clc, false, false, null );
+                cc, clc, null );
         StartupConfiguration confMock = spy( conf );
         mockStatic( Relocator.class );
         when( Relocator.relocate( anyString() ) ).thenCallRealMethod();
@@ -334,7 +328,7 @@ public class DefaultForkConfigurationTest
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
         ClasspathConfiguration cc = new ClasspathConfiguration( true, true );
         StartupConfiguration conf =
-                new StartupConfiguration( "org.apache.maven.surefire.MyProvider", cc, clc, false, false, null );
+                new StartupConfiguration( "org.apache.maven.surefire.MyProvider", cc, clc, null );
         StartupConfiguration confMock = spy( conf );
         mockStatic( Relocator.class );
         when( Relocator.relocate( anyString() ) ).thenCallRealMethod();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
index 72e5372..bc01ee8 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
@@ -30,6 +30,7 @@ import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,6 +48,7 @@ import static org.fest.util.Files.temporaryFolder;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
 
 /**
  *
@@ -55,10 +57,7 @@ public class ForkConfigurationTest
 {
     private static final StartupConfiguration STARTUP_CONFIG = new StartupConfiguration( "",
             new ClasspathConfiguration( true, true ),
-            new ClassLoaderConfiguration( true, true ),
-            false,
-            false,
-            ALL );
+            new ClassLoaderConfiguration( true, true ), ALL );
 
     private static int idx = 0;
 
@@ -91,7 +90,7 @@ public class ForkConfigurationTest
         ClasspathConfiguration cpConfig = new ClasspathConfiguration( new Classpath( cp ), emptyClasspath(),
                 emptyClasspath(), true, true );
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
-        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, false, false, ALL );
+        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, ALL );
 
         Commandline cli = config.createCommandLine( startup, 1, temporaryFolder() );
 
@@ -111,7 +110,7 @@ public class ForkConfigurationTest
         ClasspathConfiguration cpConfig = new ClasspathConfiguration( new Classpath( cp ), emptyClasspath(),
                 emptyClasspath(), true, true );
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
-        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, false, false, ALL );
+        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, ALL );
 
         Commandline commandLine = config.createCommandLine( startup, 1, temporaryFolder() );
         assertTrue( commandLine.toString().contains( "abc def" ) );
@@ -126,7 +125,7 @@ public class ForkConfigurationTest
         ClasspathConfiguration cpConfig = new ClasspathConfiguration( emptyClasspath(), emptyClasspath(),
                 emptyClasspath(), true, true );
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
-        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, false, false, ALL );
+        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, ALL );
         ForkConfiguration config = getForkConfiguration( cwd.getCanonicalFile() );
         Commandline commandLine = config.createCommandLine( startup, 1, temporaryFolder() );
 
@@ -225,7 +224,7 @@ public class ForkConfigurationTest
         return new JarManifestForkConfiguration( emptyClasspath(), tmpDir, null,
                 cwd, new Properties(), argLine,
                 Collections.<String, String>emptyMap(), new String[0], false, 1, false,
-                platform, new NullConsoleLogger() );
+                platform, new NullConsoleLogger(), mock( ForkNodeFactory.class ) );
     }
 
     // based on http://stackoverflow.com/questions/2591083/getting-version-of-java-in-runtime
@@ -233,6 +232,6 @@ public class ForkConfigurationTest
     private static boolean isJavaVersionAtLeast7u60()
     {
         String[] javaVersionElements = System.getProperty( "java.runtime.version" ).split( "\\.|_|-b" );
-        return Integer.valueOf( javaVersionElements[1] ) >= 7 && Integer.valueOf( javaVersionElements[3] ) >= 60;
+        return Integer.parseInt( javaVersionElements[1] ) >= 7 && Integer.parseInt( javaVersionElements[3] ) >= 60;
     }
 }
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 e0874f0..55f789f 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
@@ -21,12 +21,13 @@ package org.apache.maven.plugin.surefire.booterclient;
 
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
 import org.apache.maven.plugin.surefire.SurefireProperties;
-import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractForkInputStream;
+import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractCommandReader;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
 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.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
@@ -37,6 +38,7 @@ import org.apache.maven.surefire.booter.ProviderConfiguration;
 import org.apache.maven.surefire.booter.Shutdown;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.shared.compress.archivers.zip.Zip64Mode;
 import org.apache.maven.surefire.shared.compress.archivers.zip.ZipArchiveEntry;
@@ -55,7 +57,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayDeque;
 import java.util.Collections;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.jar.Manifest;
 import java.util.zip.Deflater;
 
@@ -170,12 +171,12 @@ public class ForkStarterTest
         e.expectMessage( containsString( "VM crash or System.exit called?" ) );
 
         Class<?>[] types = {Object.class, PropertiesWrapper.class, ForkClient.class, SurefireProperties.class,
-            int.class, AbstractForkInputStream.class, boolean.class};
+            int.class, AbstractCommandReader.class, ForkNodeFactory.class, boolean.class};
         TestProvidingInputStream testProvidingInputStream = new TestProvidingInputStream( new ArrayDeque<String>() );
         invokeMethod( forkStarter, "fork", types, null,
             new PropertiesWrapper( Collections.<String, String>emptyMap() ),
-            new ForkClient( reporterFactory, null, logger, new AtomicBoolean(), 1 ),
-            new SurefireProperties(), 1, testProvidingInputStream, true );
+            new ForkClient( reporterFactory, null, 1 ),
+            new SurefireProperties(), 1, testProvidingInputStream, new LegacyForkNodeFactory(), true );
         testProvidingInputStream.close();
     }
 
@@ -224,12 +225,12 @@ public class ForkStarterTest
         DefaultReporterFactory reporterFactory = new DefaultReporterFactory( startupReportConfiguration, logger, 1 );
 
         Class<?>[] types = {Object.class, PropertiesWrapper.class, ForkClient.class, SurefireProperties.class,
-            int.class, AbstractForkInputStream.class, boolean.class};
+            int.class, AbstractCommandReader.class, ForkNodeFactory.class, boolean.class};
         TestLessInputStream testLessInputStream = new TestLessInputStreamBuilder().build();
         invokeMethod( forkStarter, "fork", types, null,
             new PropertiesWrapper( Collections.<String, String>emptyMap() ),
-            new ForkClient( reporterFactory, testLessInputStream, logger, new AtomicBoolean(), 1 ),
-            new SurefireProperties(), 1, testLessInputStream, true );
+            new ForkClient( reporterFactory, testLessInputStream, 1 ),
+            new SurefireProperties(), 1, testLessInputStream, new LegacyForkNodeFactory(), true );
         testLessInputStream.close();
     }
 
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 79c6f0e..4ca19ad 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
@@ -19,14 +19,16 @@ package org.apache.maven.plugin.surefire.booterclient;
  * under the License.
  */
 
-import junit.framework.Assert;
 import junit.framework.TestCase;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
+import org.apache.maven.plugin.surefire.extensions.EventConsumerThread;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
-import org.apache.maven.surefire.booter.ForkedChannelEncoder;
 import org.apache.maven.surefire.booter.ForkingRunListener;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.apache.maven.surefire.report.CategorizedReportEntry;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
@@ -36,17 +38,23 @@ import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.report.TestSetReportEntry;
-import org.hamcrest.MatcherAssert;
 
+import javax.annotation.Nonnull;
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.IOException;
+import java.io.Closeable;
 import java.io.PrintStream;
+import java.nio.channels.ReadableByteChannel;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
-import static org.hamcrest.Matchers.is;
+import static java.nio.channels.Channels.newChannel;
+import static org.mockito.Mockito.mock;
 
 /**
  * @author Kristian Rosenvold
@@ -74,8 +82,7 @@ public class ForkingRunListenerTest
         content.reset();
     }
 
-    public void testSetStarting()
-        throws ReporterException, IOException
+    public void testSetStarting() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         TestSetReportEntry expected = createDefaultReportEntry();
@@ -83,8 +90,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.SET_STARTING, expected );
     }
 
-    public void testSetCompleted()
-        throws ReporterException, IOException
+    public void testSetCompleted() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         TestSetReportEntry expected = createDefaultReportEntry();
@@ -92,8 +98,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.SET_COMPLETED, expected );
     }
 
-    public void testStarting()
-        throws ReporterException, IOException
+    public void testStarting() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ReportEntry expected = createDefaultReportEntry();
@@ -101,8 +106,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.TEST_STARTING, expected );
     }
 
-    public void testSucceded()
-        throws ReporterException, IOException
+    public void testSucceeded() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ReportEntry expected = createDefaultReportEntry();
@@ -110,8 +114,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.TEST_SUCCEEDED, expected );
     }
 
-    public void testFailed()
-        throws ReporterException, IOException
+    public void testFailed() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ReportEntry expected = createReportEntryWithStackTrace();
@@ -119,8 +122,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.TEST_FAILED, expected );
     }
 
-    public void testFailedWithCommaInMessage()
-        throws ReporterException, IOException
+    public void testFailedWithCommaInMessage() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ReportEntry expected = createReportEntryWithSpecialMessage( "We, the people" );
@@ -128,8 +130,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.TEST_FAILED, expected );
     }
 
-    public void testFailedWithUnicodeEscapeInMessage()
-        throws ReporterException, IOException
+    public void testFailedWithUnicodeEscapeInMessage() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ReportEntry expected = createReportEntryWithSpecialMessage( "We, \\u0177 people" );
@@ -137,8 +138,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.TEST_FAILED, expected );
     }
 
-    public void testFailure()
-        throws ReporterException, IOException
+    public void testFailure() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ReportEntry expected = createDefaultReportEntry();
@@ -146,8 +146,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.TEST_ERROR, expected );
     }
 
-    public void testSkipped()
-        throws ReporterException, IOException
+    public void testSkipped() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ReportEntry expected = createDefaultReportEntry();
@@ -155,8 +154,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.TEST_SKIPPED, expected );
     }
 
-    public void testAssumptionFailure()
-        throws ReporterException, IOException
+    public void testAssumptionFailure() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ReportEntry expected = createDefaultReportEntry();
@@ -164,8 +162,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.TEST_ASSUMPTION_FAIL, expected );
     }
 
-    public void testConsole()
-        throws ReporterException, IOException
+    public void testConsole() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ConsoleLogger directConsoleReporter = (ConsoleLogger) standardTestRun.run();
@@ -173,8 +170,7 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.CONSOLE_INFO, "HeyYou" );
     }
 
-    public void testConsoleOutput()
-        throws ReporterException, IOException
+    public void testConsoleOutput() throws Exception
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         ConsoleOutputReceiver directConsoleReporter = (ConsoleOutputReceiver) standardTestRun.run();
@@ -182,30 +178,36 @@ public class ForkingRunListenerTest
         standardTestRun.assertExpected( MockReporter.STDOUT, "HeyYou" );
     }
 
-    public void testSystemProperties()
-        throws ReporterException, IOException
+    public void testSystemProperties() throws Exception
     {
-        final StandardTestRun standardTestRun = new StandardTestRun();
+        StandardTestRun standardTestRun = new StandardTestRun();
         standardTestRun.run();
 
         reset();
         createForkingRunListener();
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
-        NullConsoleLogger log = new NullConsoleLogger();
-        ForkClient forkStreamClient =
-                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, new AtomicBoolean(), 1 );
+        ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), 1 );
 
-        forkStreamClient.consumeMultiLineContent( ":maven:surefire:std:out:sys-prop:normal-run:UTF-8:azE=:djE="
-                + "\n:maven:surefire:std:out:sys-prop:normal-run:UTF-8:azI=:djI=" );
+        byte[] cmd = ":maven-surefire-event:sys-prop:normal-run:UTF-8:azE=:djE=:\n".getBytes();
+        for ( Event e : streamToEvent( cmd ) )
+        {
+            forkStreamClient.handleEvent( e );
+        }
+        cmd = "\n:maven-surefire-event:sys-prop:normal-run:UTF-8:azI=:djI=:\n".getBytes();
+        for ( Event e : streamToEvent( cmd ) )
+        {
+            forkStreamClient.handleEvent( e );
+        }
 
-        MatcherAssert.assertThat( forkStreamClient.getTestVmSystemProperties().size(), is( 2 ) );
+        assertTrue( forkStreamClient.getTestVmSystemProperties().size() == 2 );
+        assertTrue( forkStreamClient.getTestVmSystemProperties().containsKey( "k1" ) );
+        assertTrue( forkStreamClient.getTestVmSystemProperties().containsKey( "k2" ) );
     }
 
-    public void testMultipleEntries()
-        throws ReporterException, IOException
+    public void testMultipleEntries() throws Exception
     {
-        final StandardTestRun standardTestRun = new StandardTestRun();
+        StandardTestRun standardTestRun = new StandardTestRun();
         standardTestRun.run();
 
         reset();
@@ -218,11 +220,13 @@ public class ForkingRunListenerTest
         forkingReporter.testSetCompleted( reportEntry );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
-        NullConsoleLogger log = new NullConsoleLogger();
         ForkClient forkStreamClient =
-                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, null, 1 );
+                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), 1 );
 
-        forkStreamClient.consumeMultiLineContent( content.toString( "UTF-8" ) );
+        for ( Event e : streamToEvent( content.toByteArray() ) )
+        {
+            forkStreamClient.handleEvent( e );
+        }
 
         final MockReporter reporter = (MockReporter) forkStreamClient.getReporter();
         final List<String> events = reporter.getEvents();
@@ -233,36 +237,82 @@ public class ForkingRunListenerTest
     }
 
     public void test2DifferentChannels()
-        throws ReporterException, IOException
+        throws Exception
     {
         reset();
         ReportEntry expected = createDefaultReportEntry();
-        final SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
+        SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
 
-        new ForkingRunListener( new ForkedChannelEncoder( printStream ), false )
+        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( printStream ) ), false )
                 .testStarting( expected );
 
-        new ForkingRunListener( new ForkedChannelEncoder( anotherPrintStream ), false )
+        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( anotherPrintStream ) ), false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
         NotifiableTestStream notifiableTestStream = new MockNotifiableTestStream();
-        NullConsoleLogger log = new NullConsoleLogger();
 
-        ForkClient forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, log, null, 1 );
-        forkStreamClient.consumeMultiLineContent( content.toString( "UTF-8" ) );
+        ForkClient forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, 1 );
+        for ( Event e : streamToEvent( content.toByteArray() ) )
+        {
+            forkStreamClient.handleEvent( e );
+        }
 
         MockReporter reporter = (MockReporter) forkStreamClient.getReporter();
-        Assert.assertEquals( MockReporter.TEST_STARTING, reporter.getFirstEvent() );
-        Assert.assertEquals( expected, reporter.getFirstData() );
-        Assert.assertEquals( 1, reporter.getEvents().size() );
+        assertEquals( MockReporter.TEST_STARTING, reporter.getFirstEvent() );
+        assertEquals( expected, reporter.getFirstData() );
+        assertEquals( 1, reporter.getEvents().size() );
 
-        forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, log, null, 2 );
-        forkStreamClient.consumeMultiLineContent( anotherContent.toString( "UTF-8" ) );
+        forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, 2 );
+        for ( Event e : streamToEvent( anotherContent.toByteArray() ) )
+        {
+            forkStreamClient.handleEvent( e );
+        }
         MockReporter reporter2 = (MockReporter) forkStreamClient.getReporter();
-        Assert.assertEquals( MockReporter.TEST_SKIPPED, reporter2.getFirstEvent() );
-        Assert.assertEquals( secondExpected, reporter2.getFirstData() );
-        Assert.assertEquals( 1, reporter2.getEvents().size() );
+        assertEquals( MockReporter.TEST_SKIPPED, reporter2.getFirstEvent() );
+        assertEquals( secondExpected, reporter2.getFirstData() );
+        assertEquals( 1, reporter2.getEvents().size() );
+    }
+
+    private static List<Event> streamToEvent( byte[] stream ) throws Exception
+    {
+        List<Event> events = new ArrayList<>();
+        EH handler = new EH();
+        CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
+        ConsoleLogger logger = mock( ConsoleLogger.class );
+        ReadableByteChannel channel = newChannel( new ByteArrayInputStream( stream ) );
+        try ( EventConsumerThread t = new EventConsumerThread( "t", channel, handler, countdown, logger ) )
+        {
+            t.start();
+            countdown.awaitClosed();
+            for ( int i = 0, size = handler.countEventsInCache(); i < size; i++ )
+            {
+                events.add( handler.pullEvent() );
+            }
+            assertEquals( 0, handler.countEventsInCache() );
+            return events;
+        }
+    }
+
+    private static class EH implements EventHandler<Event>
+    {
+        private final BlockingQueue<Event> cache = new LinkedBlockingQueue<>();
+
+        Event pullEvent() throws InterruptedException
+        {
+            return cache.poll( 1, TimeUnit.MINUTES );
+        }
+
+        int countEventsInCache()
+        {
+            return cache.size();
+        }
+
+        @Override
+        public void handleEvent( @Nonnull Event event )
+        {
+            cache.add( event );
+        }
     }
 
     // Todo: Test weird characters
@@ -312,7 +362,7 @@ public class ForkingRunListenerTest
 
     private RunListener createForkingRunListener()
     {
-        return new ForkingRunListener( new ForkedChannelEncoder( printStream ), false );
+        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( printStream ) ), false );
     }
 
     private class StandardTestRun
@@ -326,15 +376,15 @@ public class ForkingRunListenerTest
             return createForkingRunListener();
         }
 
-        public void clientReceiveContent()
-            throws ReporterException, IOException
+        public void clientReceiveContent() throws Exception
         {
             TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
-            NullConsoleLogger log = new NullConsoleLogger();
-            final ForkClient forkStreamClient =
-                    new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, null, 1 );
-            forkStreamClient.consumeMultiLineContent( content.toString( ) );
-            reporter = (MockReporter) forkStreamClient.getReporter();
+            ForkClient handler = new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), 1 );
+            for ( Event e : streamToEvent( content.toByteArray() ) )
+            {
+                handler.handleEvent( e );
+            }
+            reporter = (MockReporter) handler.getReporter();
         }
 
         public String getFirstEvent()
@@ -347,8 +397,7 @@ public class ForkingRunListenerTest
             return (ReportEntry) reporter.getData().get( 0 );
         }
 
-        private void assertExpected( String actionCode, ReportEntry expected )
-            throws IOException, ReporterException
+        private void assertExpected( String actionCode, ReportEntry expected ) throws Exception
         {
             clientReceiveContent();
             assertEquals( actionCode, getFirstEvent() );
@@ -368,8 +417,7 @@ public class ForkingRunListenerTest
             }
         }
 
-        private void assertExpected( String actionCode, String expected )
-            throws IOException, ReporterException
+        private void assertExpected( String actionCode, String expected ) throws Exception
         {
             clientReceiveContent();
             assertEquals( actionCode, getFirstEvent() );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
index d90a128..dc435b5 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
@@ -34,15 +34,11 @@ public class MainClass
         }
         else
         {
-            System.out.println( ":maven:surefire:std:out:bye" );
-            if ( System.in.read() == 0
-                && System.in.read() == 0
-                && System.in.read() == 0
-                && System.in.read() == 5
-                && System.in.read() == 0
-                && System.in.read() == 0
-                && System.in.read() == 0
-                && System.in.read() == 0 )
+            System.out.println( ":maven-surefire-event:bye:" );
+            String byeAck = ":maven-surefire-command:bye-ack:";
+            byte[] cmd = new byte[byeAck.length()];
+            int len = System.in.read( cmd );
+            if ( len != -1 && new String( cmd, 0, len ).equals( byeAck ) )
             {
                 System.exit( 0 );
             }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
index 492c5c0..cfa7dce 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
@@ -27,6 +27,7 @@ import org.apache.maven.surefire.booter.ForkedBooter;
 import org.apache.maven.surefire.booter.ModularClasspath;
 import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.junit.Test;
 
 import java.io.File;
@@ -43,9 +44,10 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.nio.file.Files.readAllLines;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singleton;
-import static org.apache.maven.surefire.shared.utils.StringUtils.replace;
 import static org.apache.maven.surefire.booter.Classpath.emptyClasspath;
+import static org.apache.maven.surefire.shared.utils.StringUtils.replace;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
@@ -66,7 +68,7 @@ public class ModularClasspathForkConfigurationTest
         ModularClasspathForkConfiguration config = new ModularClasspathForkConfiguration( booter, tmp, "", pwd,
                 new Properties(), "",
                 Collections.<String, String>emptyMap(), new String[0], true, 1, true,
-                new Platform(), new NullConsoleLogger() );
+                new Platform(), new NullConsoleLogger(), mock( ForkNodeFactory.class ) );
 
         File patchFile = new File( "target" + separatorChar + "test-classes" );
         File descriptor = new File( tmp, "module-info.class" );
@@ -141,8 +143,8 @@ public class ModularClasspathForkConfigurationTest
                 new ModularClasspathConfiguration( modularClasspath, testClasspathUrls, surefireClasspathUrls,
                         emptyClasspath(), true, true );
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
-        StartupConfiguration startupConfiguration =
-                new StartupConfiguration( "JUnitCoreProvider", modularClasspathConfiguration, clc, true, true, null );
+        StartupConfiguration startupConfiguration = new StartupConfiguration( "JUnitCoreProvider",
+            modularClasspathConfiguration, clc, null );
         OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
         config.resolveClasspath( cli, ForkedBooter.class.getName(), startupConfiguration,
                 createTempFile( "surefire", "surefire-reports" ) );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
index bbc85d4..2aab4c2 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
@@ -21,22 +21,24 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 
 import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import java.io.DataInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
+import static java.nio.channels.Channels.newChannel;
 import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
-import static org.apache.maven.surefire.booter.Command.NOOP;
 import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
 import static org.apache.maven.surefire.booter.Shutdown.EXIT;
 import static org.apache.maven.surefire.booter.Shutdown.KILL;
+import static org.apache.maven.plugin.surefire.extensions.StreamFeeder.encode;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
@@ -87,7 +89,7 @@ public class TestLessInputStreamBuilderTest
         assertThat( is.availablePermits(), is( 1 ) );
         is.beforeNextCommand();
         assertThat( is.availablePermits(), is( 0 ) );
-        assertThat( is.nextCommand(), is( NOOP ) );
+        assertThat( is.nextCommand(), is( Command.NOOP ) );
         assertThat( is.availablePermits(), is( 0 ) );
         e.expect( NoSuchElementException.class );
         is.nextCommand();
@@ -105,7 +107,7 @@ public class TestLessInputStreamBuilderTest
         assertThat( is.availablePermits(), is( 2 ) );
         is.beforeNextCommand();
         assertThat( is.availablePermits(), is( 1 ) );
-        assertThat( is.nextCommand(), is( NOOP ) );
+        assertThat( is.nextCommand(), is( Command.NOOP ) );
         assertThat( is.availablePermits(), is( 1 ) );
         builder.getCachableCommands().skipSinceNextTest();
         assertThat( is.availablePermits(), is( 1 ) );
@@ -123,7 +125,7 @@ public class TestLessInputStreamBuilderTest
         builder.getCachableCommands().shutdown( EXIT );
         assertThat( is.availablePermits(), is( 2 ) );
         is.beforeNextCommand();
-        assertThat( is.nextCommand(), is( NOOP ) );
+        assertThat( is.nextCommand(), is( Command.NOOP ) );
         assertThat( is.availablePermits(), is( 1 ) );
         is.beforeNextCommand();
         assertThat( is.nextCommand().getCommandType(), is( SHUTDOWN ) );
@@ -137,15 +139,47 @@ public class TestLessInputStreamBuilderTest
             throws IOException
     {
         TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
-        TestLessInputStream pluginIs = builder.build();
+        final TestLessInputStream pluginIs = builder.build();
+        InputStream is = new InputStream()
+        {
+            private byte[] buffer;
+            private int idx;
+
+            @Override
+            public int read() throws IOException
+            {
+                if ( buffer == null )
+                {
+                    idx = 0;
+                    Command cmd = pluginIs.readNextCommand();
+                    if ( cmd != null )
+                    {
+                        MasterProcessCommand cmdType = cmd.getCommandType();
+                        buffer = cmdType.hasDataType() ? encode( cmdType, cmd.getData() ) : encode( cmdType );
+                    }
+                }
+
+                if ( buffer != null )
+                {
+                    byte b = buffer[idx++];
+                    if ( idx == buffer.length )
+                    {
+                        buffer = null;
+                        idx = 0;
+                    }
+                    return b;
+                }
+                throw new IOException();
+            }
+        };
+        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         builder.getImmediateCommands().shutdown( KILL );
         builder.getImmediateCommands().noop();
-        DataInputStream is = new DataInputStream( pluginIs );
-        Command bye = decode( is );
+        Command bye = decoder.decode();
         assertThat( bye, is( notNullValue() ) );
         assertThat( bye.getCommandType(), is( SHUTDOWN ) );
         assertThat( bye.getData(), is( KILL.name() ) );
-        Command noop = decode( is );
+        Command noop = decoder.decode();
         assertThat( noop, is( notNullValue() ) );
         assertThat( noop.getCommandType(), is( MasterProcessCommand.NOOP ) );
     }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
index 21bc663..1d348ce 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
@@ -20,11 +20,14 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
+import org.apache.maven.plugin.surefire.extensions.StreamFeeder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 import org.junit.Test;
 
-import java.io.DataInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.lang.Thread.State;
 import java.util.ArrayDeque;
 import java.util.Queue;
 import java.util.concurrent.Callable;
@@ -32,11 +35,15 @@ import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
+import static java.nio.channels.Channels.newChannel;
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Asserts that this stream properly reads bytes from queue.
@@ -46,14 +53,15 @@ import static org.hamcrest.Matchers.notNullValue;
  */
 public class TestProvidingInputStreamTest
 {
+    private static final int WAIT_LOOPS = 100;
     @Test
-    public void closedStreamShouldReturnEndOfStream()
+    public void closedStreamShouldReturnNullAsEndOfStream()
         throws IOException
     {
         Queue<String> commands = new ArrayDeque<>();
         TestProvidingInputStream is = new TestProvidingInputStream( commands );
         is.close();
-        assertThat( is.read(), is( -1 ) );
+        assertThat( is.readNextCommand(), is( nullValue() ) );
     }
 
     @Test
@@ -63,22 +71,22 @@ public class TestProvidingInputStreamTest
         Queue<String> commands = new ArrayDeque<>();
         final TestProvidingInputStream is = new TestProvidingInputStream( commands );
         final Thread streamThread = Thread.currentThread();
-        FutureTask<Thread.State> futureTask = new FutureTask<>( new Callable<Thread.State>()
+        FutureTask<State> futureTask = new FutureTask<>( new Callable<State>()
         {
             @Override
-            public Thread.State call()
+            public State call()
             {
-                sleep( 1000 );
-                Thread.State state = streamThread.getState();
+                sleep( 1000L );
+                State state = streamThread.getState();
                 is.close();
                 return state;
             }
         } );
         Thread assertionThread = new Thread( futureTask );
         assertionThread.start();
-        assertThat( is.read(), is( -1 ) );
-        Thread.State state = futureTask.get();
-        assertThat( state, is( Thread.State.WAITING ) );
+        assertThat( is.readNextCommand(), is( nullValue() ) );
+        State state = futureTask.get();
+        assertThat( state, is( State.WAITING ) );
     }
 
     @Test
@@ -96,16 +104,23 @@ public class TestProvidingInputStreamTest
                 is.provideNewTest();
             }
         } ).start();
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 1 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
+
+        Command cmd = is.readNextCommand();
+        assertThat( cmd.getData(), is( nullValue() ) );
+        String stream = new String( StreamFeeder.encode( cmd.getCommandType() ), US_ASCII );
+
+        cmd = is.readNextCommand();
+        assertThat( cmd.getData(), is( nullValue() ) );
+        stream += new String( StreamFeeder.encode( cmd.getCommandType() ), US_ASCII );
+
+        assertThat( stream,
+            is( ":maven-surefire-command:testset-finished::maven-surefire-command:testset-finished:" ) );
+
+        boolean emptyStream = isInputStreamEmpty( is );
+
         is.close();
-        assertThat( is.read(), is( -1 ) );
+        assertTrue( emptyStream );
+        assertThat( is.readNextCommand(), is( nullValue() ) );
     }
 
     @Test
@@ -123,34 +138,55 @@ public class TestProvidingInputStreamTest
                 is.provideNewTest();
             }
         } ).start();
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 4 ) );
-        assertThat( is.read(), is( (int) 'T' ) );
-        assertThat( is.read(), is( (int) 'e' ) );
-        assertThat( is.read(), is( (int) 's' ) );
-        assertThat( is.read(), is( (int) 't' ) );
+
+        Command cmd = is.readNextCommand();
+        assertThat( cmd.getData(), is( "Test" ) );
+
+        is.close();
     }
 
     @Test
     public void shouldDecodeTwoCommands()
             throws IOException
     {
-        TestProvidingInputStream pluginIs = new TestProvidingInputStream( new ConcurrentLinkedQueue<String>() );
+        final TestProvidingInputStream pluginIs = new TestProvidingInputStream( new ConcurrentLinkedQueue<String>() );
+        InputStream is = new InputStream()
+        {
+            private byte[] buffer;
+            private int idx;
+
+            @Override
+            public int read() throws IOException
+            {
+                if ( buffer == null )
+                {
+                    idx = 0;
+                    Command cmd = pluginIs.readNextCommand();
+                    buffer = cmd == null ? null : StreamFeeder.encode( cmd.getCommandType() );
+                }
+
+                if ( buffer != null )
+                {
+                    byte b = buffer[idx++];
+                    if ( idx == buffer.length )
+                    {
+                        buffer = null;
+                        idx = 0;
+                    }
+                    return b;
+                }
+                throw new IOException();
+            }
+        };
+        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         pluginIs.acknowledgeByeEventReceived();
         pluginIs.noop();
-        DataInputStream is = new DataInputStream( pluginIs );
-        Command bye = decode( is );
+        Command bye = decoder.decode();
         assertThat( bye, is( notNullValue() ) );
         assertThat( bye.getCommandType(), is( BYE_ACK ) );
-        Command noop = decode( is );
+        Command noop = decoder.decode();
         assertThat( noop, is( notNullValue() ) );
-        assertThat( noop.getCommandType(), is( MasterProcessCommand.NOOP ) );
+        assertThat( noop.getCommandType(), is( NOOP ) );
     }
 
     private static void sleep( long millis )
@@ -164,4 +200,44 @@ public class TestProvidingInputStreamTest
             // do nothing
         }
     }
+
+    /**
+     * Waiting (max of 20 seconds)
+     * @param is examined stream
+     * @return {@code true} if the {@link InputStream#read()} is waiting for a new byte.
+     */
+    private static boolean isInputStreamEmpty( final TestProvidingInputStream is )
+    {
+        Thread t = new Thread( new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    is.readNextCommand();
+                }
+                catch ( IOException e )
+                {
+                    Throwable cause = e.getCause();
+                    Throwable err = cause == null ? e : cause;
+                    if ( !( err instanceof InterruptedException ) )
+                    {
+                        System.err.println( err.toString() );
+                    }
+                }
+            }
+        } );
+        t.start();
+        State state;
+        int loops = 0;
+        do
+        {
+            sleep( 100L );
+            state = t.getState();
+        }
+        while ( state == State.NEW && loops++ < WAIT_LOOPS );
+        t.interrupt();
+        return state == State.WAITING || state == State.TIMED_WAITING;
+    }
 }
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 09230bb..60f6872 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
@@ -21,47 +21,79 @@ package org.apache.maven.plugin.surefire.booterclient.output;
 
 import org.apache.maven.plugin.surefire.booterclient.MockReporter;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
+import org.apache.maven.plugin.surefire.extensions.EventConsumerThread;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.surefire.booter.Shutdown;
+import org.apache.maven.surefire.eventapi.ConsoleDebugEvent;
+import org.apache.maven.surefire.eventapi.ConsoleErrorEvent;
+import org.apache.maven.surefire.eventapi.ConsoleInfoEvent;
+import org.apache.maven.surefire.eventapi.ConsoleWarningEvent;
+import org.apache.maven.surefire.eventapi.ControlByeEvent;
+import org.apache.maven.surefire.eventapi.ControlNextTestEvent;
+import org.apache.maven.surefire.eventapi.ControlStopOnNextTestEvent;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.eventapi.StandardStreamErrEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamErrWithNewLineEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamOutEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamOutWithNewLineEvent;
+import org.apache.maven.surefire.eventapi.SystemPropertyEvent;
+import org.apache.maven.surefire.eventapi.TestAssumptionFailureEvent;
+import org.apache.maven.surefire.eventapi.TestErrorEvent;
+import org.apache.maven.surefire.eventapi.TestFailedEvent;
+import org.apache.maven.surefire.eventapi.TestSkippedEvent;
+import org.apache.maven.surefire.eventapi.TestStartingEvent;
+import org.apache.maven.surefire.eventapi.TestSucceededEvent;
+import org.apache.maven.surefire.eventapi.TestsetCompletedEvent;
+import org.apache.maven.surefire.eventapi.TestsetStartingEvent;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.SafeThrowable;
+import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 import org.junit.Test;
 
+import javax.annotation.Nonnull;
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
 import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.nio.channels.ReadableByteChannel;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
+import static java.nio.channels.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Arrays.copyOfRange;
 import static org.apache.commons.codec.binary.Base64.encodeBase64String;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_DEBUG;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_ERR;
-import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_WARN;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_INFO;
+import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_WARN;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.SET_COMPLETED;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.SET_STARTING;
-import static org.apache.maven.plugin.surefire.booterclient.MockReporter.STDOUT;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.STDERR;
+import static org.apache.maven.plugin.surefire.booterclient.MockReporter.STDOUT;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.TEST_ASSUMPTION_FAIL;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.TEST_ERROR;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.TEST_FAILED;
-import static org.apache.maven.plugin.surefire.booterclient.MockReporter.TEST_STARTING;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.TEST_SKIPPED;
+import static org.apache.maven.plugin.surefire.booterclient.MockReporter.TEST_STARTING;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.TEST_SUCCEEDED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
+import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.MapAssert.entry;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.startsWith;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -74,204 +106,74 @@ public class ForkClientTest
 {
     private static final int ELAPSED_TIME = 102;
 
-    @Test
-    public void shouldNotFailOnEmptyInput1()
+    @Test( expected = NullPointerException.class )
+    public void shouldFailOnNPE()
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
         DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
-        client.consumeLine( null );
-        assertThat( client.isSaidGoodBye() )
-                .isFalse();
-        assertThat( client.getErrorInFork() )
-                .isNull();
-        assertThat( client.isErrorInFork() )
-                .isFalse();
-        assertThat( client.hadTimeout() )
-                .isFalse();
-        assertThat( client.hasTestsInProgress() )
-                .isFalse();
-        assertThat( client.testsInProgress() )
-                .isEmpty();
-        assertThat( client.getTestVmSystemProperties() )
-                .isEmpty();
+        ForkClient client = new ForkClient( factory, null, 0 );
+        client.handleEvent( null );
     }
 
     @Test
-    public void shouldNotFailOnEmptyInput2()
+    public void shouldLogJvmMessage() throws Exception
     {
-        String cwd = System.getProperty( "user.dir" );
-        File target = new File( cwd, "target" );
-        DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
-        when( factory.getReportsDirectory() )
-                .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
+        String nativeStream = "Listening for transport dt_socket at address: bla";
+        EH eventHandler = new EH();
+        CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
         ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
-        client.consumeLine( "   " );
-        assertThat( client.isSaidGoodBye() )
-                .isFalse();
-        assertThat( client.getErrorInFork() )
-                .isNull();
-        assertThat( client.isErrorInFork() )
-                .isFalse();
-        assertThat( client.hadTimeout() )
-                .isFalse();
-        assertThat( client.hasTestsInProgress() )
-                .isFalse();
-        assertThat( client.testsInProgress() )
-                .isEmpty();
-        assertThat( client.getTestVmSystemProperties() )
-                .isEmpty();
-    }
+        ReadableByteChannel channel = newChannel( new ByteArrayInputStream( nativeStream.getBytes() ) );
+        try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+        {
+            t.start();
 
-    @Test
-    public void shouldNotFailOnEmptyInput3()
-            throws IOException
-    {
-        String cwd = System.getProperty( "user.dir" );
-        File target = new File( cwd, "target" );
-        DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
-        when( factory.getReportsDirectory() )
-                .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( null );
-        assertThat( client.isSaidGoodBye() )
-                .isFalse();
-        assertThat( client.getErrorInFork() )
-                .isNull();
-        assertThat( client.isErrorInFork() )
-                .isFalse();
-        assertThat( client.hadTimeout() )
-                .isFalse();
-        assertThat( client.hasTestsInProgress() )
-                .isFalse();
-        assertThat( client.testsInProgress() )
-                .isEmpty();
-        assertThat( client.getTestVmSystemProperties() )
-                .isEmpty();
-    }
+            countdown.awaitClosed();
 
-    @Test
-    public void shouldNotFailOnEmptyInput4()
-            throws IOException
-    {
-        String cwd = System.getProperty( "user.dir" );
-        File target = new File( cwd, "target" );
-        DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
-        when( factory.getReportsDirectory() )
-                .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        when( logger.isDebugEnabled() )
-                .thenReturn( true );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( "   " );
-        verify( logger )
-                .isDebugEnabled();
-        verify( logger )
-                .warning( startsWith( "Corrupted STDOUT by directly writing to native stream in forked JVM 0. "
-                        + "See FAQ web page and the dump file " ) );
-        verify( logger )
-                .debug( "   " );
-        verifyNoMoreInteractions( logger );
-        assertThat( client.isSaidGoodBye() )
-                .isFalse();
-        assertThat( client.getErrorInFork() )
-                .isNull();
-        assertThat( client.isErrorInFork() )
-                .isFalse();
-        assertThat( client.hadTimeout() )
-                .isFalse();
-        assertThat( client.hasTestsInProgress() )
-                .isFalse();
-        assertThat( client.testsInProgress() )
-                .isEmpty();
-        assertThat( client.getTestVmSystemProperties() )
-                .isEmpty();
-    }
+            verify( logger )
+                .info( "Listening for transport dt_socket at address: bla" );
+        }
+
+        assertThat( eventHandler.sizeOfEventCache() )
+            .isEqualTo( 0 );
 
-    @Test
-    public void shouldNotFailOnEmptyInput5()
-            throws IOException
-    {
-        String cwd = System.getProperty( "user.dir" );
-        File target = new File( cwd, "target" );
-        DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
-        when( factory.getReportsDirectory() )
-                .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        when( logger.isDebugEnabled() )
-                .thenReturn( true );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( "Listening for transport dt_socket at address: bla" );
-        verify( logger )
-                .isDebugEnabled();
-        verify( logger )
-                .debug( "Listening for transport dt_socket at address: bla" );
         verifyNoMoreInteractions( logger );
-        assertThat( client.isSaidGoodBye() )
-                .isFalse();
-        assertThat( client.getErrorInFork() )
-                .isNull();
-        assertThat( client.isErrorInFork() )
-                .isFalse();
-        assertThat( client.hadTimeout() )
-                .isFalse();
-        assertThat( client.hasTestsInProgress() )
-                .isFalse();
-        assertThat( client.testsInProgress() )
-                .isEmpty();
-        assertThat( client.getTestVmSystemProperties() )
-                .isEmpty();
     }
 
     @Test
-    public void shouldNotFailOnEmptyInput6()
-            throws IOException
+    public void shouldLogJvmMessageAndProcessEvent() throws Exception
     {
-        String cwd = System.getProperty( "user.dir" );
-        File target = new File( cwd, "target" );
-        DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
-        when( factory.getReportsDirectory() )
-                .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
+        String nativeStream = "Listening for transport dt_socket at address: bla\n:maven-surefire-event:bye:\n";
+        EH eventHandler = new EH();
+        CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
         ConsoleLogger logger = mock( ConsoleLogger.class );
         when( logger.isDebugEnabled() )
-                .thenReturn( false );
+            .thenReturn( false );
         when( logger.isInfoEnabled() )
-                .thenReturn( true );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( "Listening for transport dt_socket at address: bla" );
-        verify( logger )
-                .isDebugEnabled();
-        verify( logger )
-                .isInfoEnabled();
-        verify( logger )
+            .thenReturn( true );
+        ReadableByteChannel channel = newChannel( new ByteArrayInputStream( nativeStream.getBytes() ) );
+        try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+        {
+            t.start();
+
+            Event event = eventHandler.pullEvent();
+            assertThat( event.isControlCategory() )
+                .isTrue();
+            assertThat( event.getEventType() )
+                .isEqualTo( BOOTERCODE_BYE );
+
+            verify( logger )
                 .info( "Listening for transport dt_socket at address: bla" );
+
+            countdown.awaitClosed();
+        }
+
+        assertThat( eventHandler.sizeOfEventCache() )
+            .isEqualTo( 0 );
+
         verifyNoMoreInteractions( logger );
-        assertThat( client.isSaidGoodBye() )
-                .isFalse();
-        assertThat( client.getErrorInFork() )
-                .isNull();
-        assertThat( client.isErrorInFork() )
-                .isFalse();
-        assertThat( client.hadTimeout() )
-                .isFalse();
-        assertThat( client.hasTestsInProgress() )
-                .isFalse();
-        assertThat( client.testsInProgress() )
-                .isEmpty();
-        assertThat( client.getTestVmSystemProperties() )
-                .isEmpty();
     }
 
     @Test
@@ -279,7 +181,7 @@ public class ForkClientTest
     {
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
 
-        ForkClient client = new ForkClient( null, notifiableTestStream, null, null, 0 );
+        ForkClient client = new ForkClient( null, notifiableTestStream, 0 );
         client.kill();
 
         verify( notifiableTestStream, times( 1 ) )
@@ -288,7 +190,6 @@ public class ForkClientTest
 
     @Test
     public void shouldAcquireNextTest()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -296,10 +197,8 @@ public class ForkClientTest
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:next-test\n" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new ControlNextTestEvent() );
         verify( notifiableTestStream, times( 1 ) )
                 .provideNewTest();
         verifyNoMoreInteractions( notifiableTestStream );
@@ -322,7 +221,6 @@ public class ForkClientTest
 
     @Test
     public void shouldNotifyWithBye()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -330,11 +228,9 @@ public class ForkClientTest
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:bye\n" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new ControlByeEvent() );
         client.kill();
 
         verify( notifiableTestStream, times( 1 ) )
@@ -361,7 +257,6 @@ public class ForkClientTest
 
     @Test
     public void shouldStopOnNextTest()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -369,10 +264,8 @@ public class ForkClientTest
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
         final boolean[] verified = {false};
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 )
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 )
         {
             @Override
             protected void stopOnNextTest()
@@ -381,7 +274,7 @@ public class ForkClientTest
                 verified[0] = true;
             }
         };
-        client.consumeMultiLineContent( ":maven:surefire:std:out:stop-on-next-test\n" );
+        client.handleEvent( new ControlStopOnNextTestEvent() );
         verifyZeroInteractions( notifiableTestStream );
         verifyZeroInteractions( factory );
         assertThat( verified[0] )
@@ -404,7 +297,6 @@ public class ForkClientTest
 
     @Test
     public void shouldReceiveStdOut()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -415,10 +307,8 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:std-out-stream:normal-run:UTF-8:bXNn\n" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new StandardStreamOutEvent( NORMAL_RUN, "msg" ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -449,7 +339,6 @@ public class ForkClientTest
 
     @Test
     public void shouldReceiveStdOutNewLine()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -460,10 +349,8 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:std-out-stream-new-line:normal-run:UTF-8:bXNn\n" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new StandardStreamOutWithNewLineEvent( NORMAL_RUN, "msg" ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -494,7 +381,6 @@ public class ForkClientTest
 
     @Test
     public void shouldReceiveStdErr()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -505,10 +391,8 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:std-err-stream:normal-run:UTF-8:bXNn\n" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new StandardStreamErrEvent( NORMAL_RUN, "msg" ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -539,7 +423,6 @@ public class ForkClientTest
 
     @Test
     public void shouldReceiveStdErrNewLine()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -550,10 +433,8 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:std-err-stream-new-line:normal-run:UTF-8:bXNn\n" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new StandardStreamErrWithNewLineEvent( NORMAL_RUN, "msg" ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -584,28 +465,22 @@ public class ForkClientTest
 
     @Test
     public void shouldLogConsoleError()
-            throws IOException
     {
-        String cwd = System.getProperty( "user.dir" );
-        File target = new File( cwd, "target" );
         DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
-        when( factory.getReportsDirectory() )
-                .thenReturn( new File( target, "surefire-reports" ) );
         MockReporter receiver = new MockReporter();
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-error-log:UTF-8:"
-                + encodeBase64String( "Listening for transport dt_socket at address:".getBytes( UTF_8 ) )
-                + ":-:-:-" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        StackTraceWriter stackTrace =
+            new DeserializedStacktraceWriter( "Listening for transport dt_socket at address: 5005", null, null );
+        Event event = new ConsoleErrorEvent( stackTrace );
+        client.handleEvent( event );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
         verify( factory, times( 1 ) )
-                .getReportsDirectory();
+            .getReportsDirectory();
         verifyNoMoreInteractions( factory );
         assertThat( client.getReporter() )
                 .isNotNull();
@@ -616,7 +491,7 @@ public class ForkClientTest
         assertThat( receiver.getData() )
                 .isNotEmpty();
         assertThat( receiver.getData() )
-                .contains( "Listening for transport dt_socket at address:" );
+                .contains( "Listening for transport dt_socket at address: 5005" );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -634,66 +509,51 @@ public class ForkClientTest
     }
 
     @Test
-    public void shouldLogConsoleErrorWithStackTrace()
-            throws IOException
+    public void shouldLogConsoleErrorWithStackTrace() throws Exception
     {
-        String cwd = System.getProperty( "user.dir" );
-        File target = new File( cwd, "target" );
-        DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
-        when( factory.getReportsDirectory() )
-                .thenReturn( new File( target, "surefire-reports" ) );
-        MockReporter receiver = new MockReporter();
-        when( factory.createReporter() )
-                .thenReturn( receiver );
-        NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
+        String nativeStream = ":maven-surefire-event:console-error-log:UTF-8"
+            + ":" + encodeBase64String( "Listening for transport dt_socket at address: 5005".getBytes( UTF_8 ) )
+            + ":" + encodeBase64String( "s1".getBytes( UTF_8 ) )
+            + ":" + encodeBase64String( "s2".getBytes( UTF_8 ) ) + ":";
+        EH eventHandler = new EH();
+        CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
         ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-error-log:UTF-8"
-                + ":" + encodeBase64String( "Listening for transport dt_socket at address:".getBytes( UTF_8 ) )
-                + ":" + encodeBase64String( "s1".getBytes( UTF_8 ) )
-                + ":" + encodeBase64String( "s2".getBytes( UTF_8 ) ) );
-        verifyZeroInteractions( notifiableTestStream );
-        verify( factory, times( 1 ) )
-                .createReporter();
-        verify( factory, times( 1 ) )
-                .getReportsDirectory();
-        verifyNoMoreInteractions( factory );
-        assertThat( client.getReporter() )
-                .isNotNull();
-        assertThat( receiver.getEvents() )
-                .isNotEmpty();
-        assertThat( receiver.getEvents() )
-                .contains( CONSOLE_ERR );
-        assertThat( receiver.getData() )
-                .isNotEmpty();
-        assertThat( receiver.getData() )
-                .contains( "Listening for transport dt_socket at address:" );
-        assertThat( client.isSaidGoodBye() )
-                .isFalse();
-        assertThat( client.isErrorInFork() )
+        ReadableByteChannel channel = newChannel( new ByteArrayInputStream( nativeStream.getBytes() ) );
+        try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+        {
+            t.start();
+
+            Event event = eventHandler.pullEvent();
+            assertThat( event.isConsoleErrorCategory() )
                 .isTrue();
-        assertThat( client.getErrorInFork() )
+            assertThat( event.isConsoleCategory() )
+                .isTrue();
+            assertThat( event.getEventType() )
+                .isEqualTo( BOOTERCODE_CONSOLE_ERROR );
+
+            ConsoleErrorEvent consoleEvent = (ConsoleErrorEvent) event;
+            assertThat( consoleEvent.getStackTraceWriter() )
                 .isNotNull();
-        assertThat( client.getErrorInFork().getThrowable().getLocalizedMessage() )
-                .isEqualTo( "Listening for transport dt_socket at address:" );
-        assertThat( client.getErrorInFork().smartTrimmedStackTrace() )
+            assertThat( consoleEvent.getStackTraceWriter().getThrowable().getMessage() )
+                .isEqualTo( "Listening for transport dt_socket at address: 5005" );
+            assertThat( consoleEvent.getStackTraceWriter().smartTrimmedStackTrace() )
                 .isEqualTo( "s1" );
-        assertThat( client.getErrorInFork().writeTrimmedTraceToString() )
+            assertThat( consoleEvent.getStackTraceWriter().writeTraceToString() )
                 .isEqualTo( "s2" );
-        assertThat( client.hadTimeout() )
-                .isFalse();
-        assertThat( client.hasTestsInProgress() )
-                .isFalse();
-        assertThat( client.testsInProgress() )
-                .isEmpty();
-        assertThat( client.getTestVmSystemProperties() )
-                .isEmpty();
+
+            countdown.awaitClosed();
+
+            verifyZeroInteractions( logger );
+        }
+
+        assertThat( eventHandler.sizeOfEventCache() )
+            .isEqualTo( 0 );
+
+        verifyNoMoreInteractions( logger );
     }
 
     @Test
     public void shouldLogConsoleWarning()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -704,13 +564,8 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        when( logger.isWarnEnabled() )
-                .thenReturn( true );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-warning-log:UTF-8:"
-                + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new ConsoleWarningEvent( "s1" ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -741,7 +596,6 @@ public class ForkClientTest
 
     @Test
     public void shouldLogConsoleDebug()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -752,13 +606,8 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        when( logger.isDebugEnabled() )
-                .thenReturn( true );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-debug-log:UTF-8:"
-                + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new ConsoleDebugEvent( "s1" ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -789,7 +638,6 @@ public class ForkClientTest
 
     @Test
     public void shouldLogConsoleInfo()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -800,11 +648,8 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-info-log:UTF-8:"
-                + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new ConsoleInfoEvent( "s1" ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -835,7 +680,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendSystemProperty()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -846,11 +690,8 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:sys-prop:normal-run:UTF-8:azE=:djE="
-                + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new SystemPropertyEvent( NORMAL_RUN, "k1", "v1" ) );
         verifyZeroInteractions( notifiableTestStream );
         verifyZeroInteractions( factory );
         assertThat( client.getReporter() )
@@ -879,7 +720,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestsetStartingKilled()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -890,21 +730,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -913,7 +743,7 @@ public class ForkClientTest
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
 
-        ReportEntry reportEntry = mock( ReportEntry.class );
+        TestSetReportEntry reportEntry = mock( TestSetReportEntry.class );
         when( reportEntry.getElapsed() ).thenReturn( ELAPSED_TIME );
         when( reportEntry.getGroup() ).thenReturn( "this group" );
         when( reportEntry.getMessage() ).thenReturn( "some test" );
@@ -922,33 +752,8 @@ public class ForkClientTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + "-"
-                + ":"
-                + encodedName
-                + ":"
-                + "-"
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedTrimmedStackTrace );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new TestsetStartingEvent( NORMAL_RUN, reportEntry ) );
 
         client.tryToTimeout( System.currentTimeMillis() + 1000L, 1 );
 
@@ -983,13 +788,13 @@ public class ForkClientTest
                 .isNotNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) )
                 .getStackTraceWriter().getThrowable().getLocalizedMessage() )
-                .isEqualTo( "msg" );
+                .isEqualTo( exceptionMessage );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( "MyTest:86 >> Error" );
+                .isEqualTo( smartStackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -1010,7 +815,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestsetStarting()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1021,21 +825,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -1044,7 +838,7 @@ public class ForkClientTest
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
 
-        ReportEntry reportEntry = mock( ReportEntry.class );
+        TestSetReportEntry reportEntry = mock( TestSetReportEntry.class );
         when( reportEntry.getElapsed() ).thenReturn( ELAPSED_TIME );
         when( reportEntry.getGroup() ).thenReturn( "this group" );
         when( reportEntry.getMessage() ).thenReturn( "some test" );
@@ -1055,36 +849,8 @@ public class ForkClientTest
         when( reportEntry.getSourceText() ).thenReturn( "dn1" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedSourceText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceText() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedNameText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getNameText() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + encodedSourceText
-                + ":"
-                + encodedName
-                + ":"
-                + encodedNameText
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedTrimmedStackTrace );
-
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new TestsetStartingEvent( NORMAL_RUN, reportEntry ) );
         client.tryToTimeout( System.currentTimeMillis(), 1 );
 
         verifyZeroInteractions( notifiableTestStream );
@@ -1118,11 +884,11 @@ public class ForkClientTest
                 .getStackTraceWriter().getThrowable().getLocalizedMessage() )
                 .isEqualTo( "msg" );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( "MyTest:86 >> Error" );
+                .isEqualTo( smartStackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -1145,7 +911,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestsetCompleted()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1156,21 +921,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -1179,7 +934,7 @@ public class ForkClientTest
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
 
-        ReportEntry reportEntry = mock( ReportEntry.class );
+        TestSetReportEntry reportEntry = mock( TestSetReportEntry.class );
         when( reportEntry.getElapsed() ).thenReturn( ELAPSED_TIME );
         when( reportEntry.getGroup() ).thenReturn( "this group" );
         when( reportEntry.getMessage() ).thenReturn( "some test" );
@@ -1188,33 +943,8 @@ public class ForkClientTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:testset-completed:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + "-"
-                + ":"
-                + encodedName
-                + ":"
-                + "-"
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedTrimmedStackTrace );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new TestsetCompletedEvent( NORMAL_RUN, reportEntry ) );
 
         verifyZeroInteractions( notifiableTestStream );
         verify( factory ).createReporter();
@@ -1249,9 +979,9 @@ public class ForkClientTest
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
                 .isEqualTo( "MyTest:86 >> Error" );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -1274,7 +1004,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestStarting()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1285,21 +1014,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -1317,33 +1036,8 @@ public class ForkClientTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + "-"
-                + ":"
-                + encodedName
-                + ":"
-                + "-"
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedTrimmedStackTrace );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        client.handleEvent( new TestStartingEvent( NORMAL_RUN, reportEntry ) );
 
         verifyZeroInteractions( notifiableTestStream );
         verify( factory ).createReporter();
@@ -1381,11 +1075,11 @@ public class ForkClientTest
                 .getStackTraceWriter().getThrowable().getLocalizedMessage() )
                 .isEqualTo( "msg" );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( "MyTest:86 >> Error" );
+                .isEqualTo( smartStackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -1404,7 +1098,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestSucceeded()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1415,21 +1108,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -1447,42 +1130,15 @@ public class ForkClientTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":-:-:-:-:-:-:-:-:-" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream,  0 );
+        SimpleReportEntry testStarted = new SimpleReportEntry( reportEntry.getSourceName(), null, null, null );
+        client.handleEvent( new TestStartingEvent( NORMAL_RUN, testStarted ) );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-succeeded:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + "-"
-                + ":"
-                + encodedName
-                + ":"
-                + "-"
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedStackTrace );
+        client.handleEvent( new TestSucceededEvent( NORMAL_RUN, reportEntry ) );
 
         verifyZeroInteractions( notifiableTestStream );
         verify( factory ).createReporter();
@@ -1519,11 +1175,11 @@ public class ForkClientTest
                 .getStackTraceWriter().getThrowable().getLocalizedMessage() )
                 .isEqualTo( "msg" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( "MyTest:86 >> Error" );
+                .isEqualTo( smartStackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1\ntrace line 2" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1\ntrace line 2" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -1546,7 +1202,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestFailed()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1557,21 +1212,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -1589,42 +1234,15 @@ public class ForkClientTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":-:-:-:-:-:-:-:-:-" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        SimpleReportEntry testClass = new SimpleReportEntry( reportEntry.getSourceName(), null, null, null );
+        client.handleEvent( new TestStartingEvent( NORMAL_RUN, testClass ) );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-failed:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + "-"
-                + ":"
-                + encodedName
-                + ":"
-                + "-"
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedTrimmedStackTrace );
+        client.handleEvent( new TestFailedEvent( NORMAL_RUN, reportEntry ) );
 
         verifyZeroInteractions( notifiableTestStream );
         verify( factory ).createReporter();
@@ -1645,6 +1263,8 @@ public class ForkClientTest
                 .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getNameText() )
                 .isNull();
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getStackTraceWriter() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceText() )
@@ -1667,9 +1287,9 @@ public class ForkClientTest
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
                 .isEqualTo( "MyTest:86 >> Error" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -1692,7 +1312,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestSkipped()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1703,21 +1322,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -1735,42 +1344,15 @@ public class ForkClientTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":-:-:-:-:-:-:-:-:-" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        SimpleReportEntry testStarted = new SimpleReportEntry( reportEntry.getSourceName(), null, null, null );
+        client.handleEvent( new TestStartingEvent( NORMAL_RUN, testStarted ) );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-skipped:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + "-"
-                + ":"
-                + encodedName
-                + ":"
-                + "-"
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedStackTrace );
+        client.handleEvent( new TestSkippedEvent( NORMAL_RUN, reportEntry ) );
 
         verifyZeroInteractions( notifiableTestStream );
         verify( factory ).createReporter();
@@ -1811,11 +1393,11 @@ public class ForkClientTest
                 .getStackTraceWriter().getThrowable().getLocalizedMessage() )
                 .isEqualTo( "msg" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( "MyTest:86 >> Error" );
+                .isEqualTo( smartStackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1\ntrace line 2" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1\ntrace line 2" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -1838,7 +1420,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestError()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1849,21 +1430,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -1882,45 +1453,16 @@ public class ForkClientTest
         when( reportEntry.getSourceText() ).thenReturn( "display name" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedSourceText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceText() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + encodedSourceText
-                + ":-:':-:-:-:-:-:-" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        SimpleReportEntry testStarted =
+            new SimpleReportEntry( reportEntry.getSourceName(), reportEntry.getSourceText(), null, null );
+        client.handleEvent( new TestStartingEvent( NORMAL_RUN, testStarted ) );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-error:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + encodedSourceText
-                + ":"
-                + encodedName
-                + ":"
-                + "-"
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedTrimmedStackTrace );
+        client.handleEvent( new TestErrorEvent( NORMAL_RUN, reportEntry ) );
 
         verifyZeroInteractions( notifiableTestStream );
         verify( factory ).createReporter();
@@ -1957,11 +1499,11 @@ public class ForkClientTest
                 .getStackTraceWriter().getThrowable().getLocalizedMessage() )
                 .isEqualTo( "msg" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( "MyTest:86 >> Error" );
+                .isEqualTo( smartStackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -1984,7 +1526,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestAssumptionFailure()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1995,21 +1536,11 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -2028,43 +1559,15 @@ public class ForkClientTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getNameText() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":-:-:-:-:-:-:-:-:-" );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
+        SimpleReportEntry testStarted = new SimpleReportEntry( reportEntry.getSourceName(), null, null, null );
+        client.handleEvent( new TestStartingEvent( NORMAL_RUN, testStarted ) );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-assumption-failure:normal-run:UTF-8:"
-                + encodedSourceName
-                + ":"
-                + "-"
-                + ":"
-                + encodedName
-                + ":"
-                + encodedText
-                + ":"
-                + encodedGroup
-                + ":"
-                + encodedMessage
-                + ":"
-                + ELAPSED_TIME
-                + ":"
-
-                + encodedExceptionMsg
-                + ":"
-                + encodedSmartStackTrace
-                + ":"
-                + encodedStackTrace );
+        client.handleEvent( new TestAssumptionFailureEvent( NORMAL_RUN, reportEntry ) );
 
         verifyZeroInteractions( notifiableTestStream );
         verify( factory ).createReporter();
@@ -2101,11 +1604,11 @@ public class ForkClientTest
                 .getStackTraceWriter().getThrowable().getLocalizedMessage() )
                 .isEqualTo( "msg" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( "MyTest:86 >> Error" );
+                .isEqualTo( smartStackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTraceToString() )
-                .isEqualTo( "trace line 1\ntrace line 2" );
+                .isEqualTo( stackTrace );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getStackTraceWriter().writeTrimmedTraceToString() )
-                .isEqualTo( "trace line 1\ntrace line 2" );
+                .isEqualTo( trimmedStackTrace );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
         assertThat( client.isErrorInFork() )
@@ -2126,9 +1629,24 @@ public class ForkClientTest
                 .isSameAs( factory );
     }
 
-    private static byte[] toArray( ByteBuffer buffer )
+    private static class EH implements EventHandler<Event>
     {
-        return copyOfRange( buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.remaining() );
-    }
+        private final BlockingQueue<Event> cache = new LinkedBlockingQueue<>( 1 );
+
+        Event pullEvent() throws InterruptedException
+        {
+            return cache.poll( 1, TimeUnit.MINUTES );
+        }
 
+        int sizeOfEventCache()
+        {
+            return cache.size();
+        }
+
+        @Override
+        public void handleEvent( @Nonnull Event event )
+        {
+            cache.add( event );
+        }
+    }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
deleted file mode 100644
index 9b0d9c9..0000000
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
+++ /dev/null
@@ -1,901 +0,0 @@
-package org.apache.maven.plugin.surefire.booterclient.output;
-
-/*
- * 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.plugin.surefire.log.api.ConsoleLoggerUtils;
-import org.apache.maven.surefire.booter.ForkedChannelEncoder;
-import org.apache.maven.surefire.report.ReportEntry;
-import org.apache.maven.surefire.report.RunMode;
-import org.apache.maven.surefire.report.SafeThrowable;
-import org.apache.maven.surefire.report.StackTraceWriter;
-import org.apache.maven.surefire.util.internal.ObjectUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.runners.Enclosed;
-import org.junit.experimental.theories.DataPoints;
-import org.junit.experimental.theories.FromDataPoints;
-import org.junit.experimental.theories.Theories;
-import org.junit.experimental.theories.Theory;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.LineNumberReader;
-import java.io.PrintStream;
-import java.io.StringReader;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Map;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.codec.binary.Base64.encodeBase64String;
-import static org.apache.maven.plugin.surefire.booterclient.output.ForkedChannelDecoder.toReportEntry;
-import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.rules.ExpectedException.none;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.nullable;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-/**
- * Test for {@link ForkedChannelDecoder}.
- *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 3.0.0-M4
- */
-@RunWith( Enclosed.class )
-public class ForkedChannelDecoderTest
-{
-    /**
-     *
-     */
-    public static class DecoderOperationsTest
-    {
-        @Rule
-        public final ExpectedException rule = none();
-
-        @Test
-        public void shouldBeFailSafe()
-        {
-            assertThat( ForkedChannelDecoder.decode( null, UTF_8 ) ).isNull();
-            assertThat( ForkedChannelDecoder.decode( "-", UTF_8 ) ).isNull();
-            assertThat( ForkedChannelDecoder.decodeToInteger( null ) ).isNull();
-            assertThat( ForkedChannelDecoder.decodeToInteger( "-" ) ).isNull();
-        }
-
-        @Test
-        @SuppressWarnings( "checkstyle:innerassignment" )
-        public void shouldHaveSystemProperty() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.sendSystemProperties( ObjectUtils.systemProps() );
-
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
-            LineNumberReader reader = out.newReader( UTF_8 );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            for ( String line; ( line = reader.readLine() ) != null; )
-            {
-                decoder.handleEvent( line, errorHandler );
-            }
-            verifyZeroInteractions( errorHandler );
-            assertThat( reader.getLineNumber() ).isPositive();
-        }
-
-        @Test
-        public void shouldRecognizeEmptyStream4ReportEntry()
-        {
-            ReportEntry reportEntry = toReportEntry( null, null, null, "", "", null, null, "",
-                    "", "", null );
-            assertThat( reportEntry ).isNull();
-
-            reportEntry = toReportEntry( UTF_8, "", "", "", "", "", "", "-", "", "", "" );
-            assertThat( reportEntry ).isNotNull();
-            assertThat( reportEntry.getStackTraceWriter() ).isNull();
-            assertThat( reportEntry.getSourceName() ).isEmpty();
-            assertThat( reportEntry.getSourceText() ).isEmpty();
-            assertThat( reportEntry.getName() ).isEmpty();
-            assertThat( reportEntry.getNameText() ).isEmpty();
-            assertThat( reportEntry.getGroup() ).isEmpty();
-            assertThat( reportEntry.getNameWithGroup() ).isEmpty();
-            assertThat( reportEntry.getMessage() ).isEmpty();
-            assertThat( reportEntry.getElapsed() ).isNull();
-
-            rule.expect( NumberFormatException.class );
-            toReportEntry( UTF_8, "", "", "", "", "", "", "", "", "", "" );
-            fail();
-        }
-
-        @Test
-        @SuppressWarnings( "checkstyle:magicnumber" )
-        public void testCreatingReportEntry()
-        {
-            final String exceptionMessage = "msg";
-            final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
-            final String smartStackTrace = "MyTest:86 >> Error";
-            final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
-            final String stackTrace = "Exception: msg\ntrace line 1\ntrace line 2";
-            final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
-            final String trimmedStackTrace = "trace line 1\ntrace line 2";
-            final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
-
-            SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
-            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
-            when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
-            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
-            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
-            when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
-
-            ReportEntry reportEntry = mock( ReportEntry.class );
-            when( reportEntry.getElapsed() ).thenReturn( 102 );
-            when( reportEntry.getGroup() ).thenReturn( "this group" );
-            when( reportEntry.getMessage() ).thenReturn( "skipped test" );
-            when( reportEntry.getName() ).thenReturn( "my test" );
-            when( reportEntry.getNameText() ).thenReturn( "my display name" );
-            when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
-            when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
-            when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
-            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
-
-            String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-            String encodedSourceText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceText() ) ) );
-            String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-            String encodedText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getNameText() ) ) );
-            String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-            String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-            ReportEntry decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText,
-                    encodedName, encodedText, encodedGroup, encodedMessage, "-", null, null, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
-
-            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText, encodedName, encodedText,
-                    encodedGroup, encodedMessage, "-", encodedExceptionMsg, encodedSmartStackTrace, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isNull();
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
-
-            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText, encodedName, encodedText,
-                    encodedGroup, encodedMessage, "1003", encodedExceptionMsg, encodedSmartStackTrace, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
-
-            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText, encodedName, encodedText,
-                    encodedGroup, encodedMessage, "1003", encodedExceptionMsg, encodedSmartStackTrace,
-                    encodedStackTrace );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                    .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                    .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
-
-            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText, encodedName, encodedText,
-                    encodedGroup, encodedMessage, "1003", encodedExceptionMsg, encodedSmartStackTrace,
-                    encodedTrimmedStackTrace );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                    .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                    .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( trimmedStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() )
-                    .isEqualTo( trimmedStackTrace );
-        }
-
-        @Test
-        public void shouldSendByeEvent() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.bye();
-            String read = new String( out.toByteArray(), UTF_8 );
-            assertThat( read )
-                    .isEqualTo( ":maven:surefire:std:out:bye\n" );
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setByeListener( new EventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void shouldSendStopOnNextTestEvent() throws IOException
-        {
-
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stopOnNextTest();
-            String read = new String( out.toByteArray(), UTF_8 );
-            assertThat( read )
-                    .isEqualTo( ":maven:surefire:std:out:stop-on-next-test\n" );
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setStopOnNextTestListener( new EventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void shouldCorrectlyDecodeStackTracesWithEmptyStringTraceMessages()
-        {
-            String exceptionMessage = "";
-            String smartStackTrace = "JUnit5Test.failWithEmptyString:16";
-            String exceptionStackTrace = "org.opentest4j.AssertionFailedError: \n"
-                    + "\tat JUnit5Test.failWithEmptyString(JUnit5Test.java:16)\n";
-
-            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
-            SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
-            when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
-            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
-            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( exceptionStackTrace );
-            when( stackTraceWriter.writeTraceToString() ).thenReturn( exceptionStackTrace );
-
-            ReportEntry reportEntry = mock( ReportEntry.class );
-            when( reportEntry.getElapsed() ).thenReturn( 7 );
-            when( reportEntry.getGroup() ).thenReturn( null );
-            when( reportEntry.getMessage() ).thenReturn( null );
-            when( reportEntry.getName() ).thenReturn( "failWithEmptyString" );
-            when( reportEntry.getNameWithGroup() ).thenReturn( "JUnit5Test" );
-            when( reportEntry.getSourceName() ).thenReturn( "JUnit5Test" );
-            when( reportEntry.getSourceText() ).thenReturn( null );
-            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
-
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.testFailed( reportEntry, true );
-            String line = new String( out.toByteArray(), UTF_8 );
-
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setTestFailedListener( new ReportEventAssertionListener( reportEntry ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( line, errorHandler );
-            verifyZeroInteractions( errorHandler );
-        }
-
-        @Test
-        public void shouldSendNextTestEvent() throws IOException
-        {
-
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.acquireNextTest();
-            String read = new String( out.toByteArray(), UTF_8 );
-            assertThat( read )
-                    .isEqualTo( ":maven:surefire:std:out:next-test\n" );
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setAcquireNextTestListener( new EventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testConsole() throws IOException
-        {
-            Stream out = Stream.newStream();
-
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleInfoLog( "msg" );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setConsoleInfoListener( new StringEventAssertionListener( "msg" ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testError() throws IOException
-        {
-            Stream out = Stream.newStream();
-
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleErrorLog( "msg" );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setConsoleErrorListener( new StackTraceEventListener( "msg", null, null ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testErrorWithException() throws IOException
-        {
-            Throwable t = new Throwable( "msg" );
-
-            Stream out = Stream.newStream();
-
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleErrorLog( t );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            String stackTrace = ConsoleLoggerUtils.toString( t );
-            decoder.setConsoleErrorListener( new StackTraceEventListener( "msg", null, stackTrace ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testErrorWithStackTraceWriter() throws IOException
-        {
-            Stream out = Stream.newStream();
-
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "1", "2", "3" );
-            forkedChannelEncoder.consoleErrorLog( stackTraceWriter, false );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setConsoleErrorListener( new StackTraceEventListener( "1", "2", "3" ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testDebug() throws IOException
-        {
-            Stream out = Stream.newStream();
-
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleDebugLog( "msg" );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setConsoleDebugListener( new StringEventAssertionListener( "msg" ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testWarning() throws IOException
-        {
-            Stream out = Stream.newStream();
-
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleWarningLog( "msg" );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setConsoleWarningListener( new StringEventAssertionListener( "msg" ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testStdOutStream() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( "msg", false );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, "msg", false ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testStdOutStreamPrint() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( "", false );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, "", false ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testStdOutStreamPrintWithNull() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( null, false );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, null, false ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testStdOutStreamPrintln() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( "", true );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, "", true ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testStdOutStreamPrintlnWithNull() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( null, true );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, null, true ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void testStdErrStream() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdErr( "msg", false );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setStdErrListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, "msg", false ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-            assertThat( lines.readLine() )
-                    .isNull();
-        }
-
-        @Test
-        public void shouldCountSameNumberOfSystemProperties() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.sendSystemProperties( ObjectUtils.systemProps() );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-        }
-
-        @Test
-        public void shouldHandleErrorAfterNullLine()
-        {
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( null, errorHandler );
-            verify( errorHandler, times( 1 ) )
-                    .handledError( nullable( String.class ), nullable( Throwable.class ) );
-        }
-
-        @Test
-        public void shouldHandleErrorAfterUnknownOperation()
-        {
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( ":maven:surefire:std:out:abnormal-run:-", errorHandler );
-            verify( errorHandler, times( 1 ) )
-                    .handledError( eq( ":maven:surefire:std:out:abnormal-run:-" ), nullable( Throwable.class ) );
-        }
-
-        @Test
-        public void shouldHandleExit() throws IOException
-        {
-            Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
-            when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
-            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
-            when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
-            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-            forkedChannelEncoder.sendExitEvent( stackTraceWriter, false );
-
-            LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-            decoder.setExitErrorEventListener( new ForkedProcessExitErrorListener()
-            {
-                @Override
-                public void handle( String exceptionMessage, String smartTrimmedStackTrace, String stackTrace )
-                {
-                    assertThat( exceptionMessage ).isEqualTo( "1" );
-                    assertThat( smartTrimmedStackTrace ).isEqualTo( "2" );
-                    assertThat( stackTrace ).isEqualTo( "3" );
-                }
-            } );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-        }
-    }
-
-    /**
-     *
-     */
-    @RunWith( Theories.class )
-    public static class ReportEntryTest
-    {
-        @DataPoints( value = "operation" )
-        @SuppressWarnings( "checkstyle:visibilitymodifier" )
-        public static String[][] operations = { { "testSetStarting", "setTestSetStartingListener" },
-                                                { "testSetCompleted", "setTestSetCompletedListener" },
-                                                { "testStarting", "setTestStartingListener" },
-                                                { "testSucceeded", "setTestSucceededListener" },
-                                                { "testFailed", "setTestFailedListener" },
-                                                { "testSkipped", "setTestSkippedListener" },
-                                                { "testError", "setTestErrorListener" },
-                                                { "testAssumptionFailure", "setTestAssumptionFailureListener" }
-        };
-
-        @DataPoints( value = "reportedMessage" )
-        @SuppressWarnings( "checkstyle:visibilitymodifier" )
-        public static String[] reportedMessage = { null, "skipped test" };
-
-        @DataPoints( value = "elapsed" )
-        @SuppressWarnings( { "checkstyle:visibilitymodifier", "checkstyle:magicnumber" } )
-        public static Integer[] elapsed = { null, 102 };
-
-        @DataPoints( value = "trim" )
-        @SuppressWarnings( "checkstyle:visibilitymodifier" )
-        public static boolean[] trim = { false, true };
-
-        @DataPoints( value = "msg" )
-        @SuppressWarnings( "checkstyle:visibilitymodifier" )
-        public static boolean[] msg = { false, true };
-
-        @DataPoints( value = "smart" )
-        @SuppressWarnings( "checkstyle:visibilitymodifier" )
-        public static boolean[] smart = { false, true };
-
-        @DataPoints( value = "trace" )
-        @SuppressWarnings( "checkstyle:visibilitymodifier" )
-        public static boolean[] trace = { false, true };
-
-        @Theory
-        public void testReportEntryOperations( @FromDataPoints( "operation" ) String[] operation,
-                                               @FromDataPoints( "reportedMessage" ) String reportedMessage,
-                                               @FromDataPoints( "elapsed" ) Integer elapsed,
-                                               @FromDataPoints( "trim" ) boolean trim,
-                                               @FromDataPoints( "msg" ) boolean msg,
-                                               @FromDataPoints( "smart" ) boolean smart,
-                                               @FromDataPoints( "trace" ) boolean trace )
-                throws Exception
-        {
-            String exceptionMessage = msg ? "msg" : null;
-            String smartStackTrace = smart ? "MyTest:86 >> Error" : null;
-            String exceptionStackTrace =
-                    trace ? ( trim ? "trace line 1\ntrace line 2" : "Exception: msg\ntrace line 1\ntrace line 2" )
-                            : null;
-
-            StackTraceWriter stackTraceWriter = null;
-            if ( exceptionStackTrace != null )
-            {
-                SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
-                stackTraceWriter = mock( StackTraceWriter.class );
-                when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
-                when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
-                when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( exceptionStackTrace );
-                when( stackTraceWriter.writeTraceToString() ).thenReturn( exceptionStackTrace );
-            }
-
-            ReportEntry reportEntry = mock( ReportEntry.class );
-            when( reportEntry.getElapsed() ).thenReturn( elapsed );
-            when( reportEntry.getGroup() ).thenReturn( "this group" );
-            when( reportEntry.getMessage() ).thenReturn( reportedMessage );
-            when( reportEntry.getName() ).thenReturn( "my test" );
-            when( reportEntry.getName() ).thenReturn( "display name of test" );
-            when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
-            when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
-            when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
-            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
-
-            Stream out = Stream.newStream();
-
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-
-            ForkedChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
-                    .invoke( forkedChannelEncoder, reportEntry, trim );
-
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-
-            ForkedChannelDecoder.class.getMethod( operation[1], ForkedProcessReportEventListener.class )
-                    .invoke( decoder, new ReportEventAssertionListener( reportEntry ) );
-
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( out.newReader( UTF_8 ).readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-        }
-    }
-
-    private static class AssertionErrorHandler implements ForkedChannelDecoderErrorHandler
-    {
-        public void handledError( String line, Throwable e )
-        {
-            if ( e != null )
-            {
-                e.printStackTrace();
-            }
-            fail( line + ( e == null ? "" : "\n" + e.getLocalizedMessage() ) );
-        }
-    }
-
-    private static class PropertyEventAssertionListener implements ForkedProcessPropertyEventListener
-    {
-        private final Map sysProps = System.getProperties();
-
-        public void handle( RunMode runMode, String key, String value )
-        {
-            assertThat( runMode ).isEqualTo( NORMAL_RUN );
-            assertTrue( sysProps.containsKey( key ) );
-            assertThat( sysProps.get( key ) ).isEqualTo( value );
-        }
-    }
-
-    private static class EventAssertionListener implements ForkedProcessEventListener
-    {
-        public void handle()
-        {
-        }
-    }
-
-    private static class StringEventAssertionListener implements ForkedProcessStringEventListener
-    {
-        private final String msg;
-
-        StringEventAssertionListener( String msg )
-        {
-            this.msg = msg;
-        }
-
-        public void handle( String msg )
-        {
-            assertThat( msg )
-                    .isEqualTo( this.msg );
-        }
-    }
-
-    private static class StackTraceEventListener implements ForkedProcessStackTraceEventListener
-    {
-        private final String msg;
-        private final String smartStackTrace;
-        private final String stackTrace;
-
-        StackTraceEventListener( String msg, String smartStackTrace, String stackTrace )
-        {
-            this.msg = msg;
-            this.smartStackTrace = smartStackTrace;
-            this.stackTrace = stackTrace;
-        }
-
-        @Override
-        public void handle( String msg, String smartStackTrace, String stackTrace )
-        {
-            assertThat( msg )
-                    .isEqualTo( this.msg );
-
-            assertThat( smartStackTrace )
-                    .isEqualTo( this.smartStackTrace );
-
-            assertThat( stackTrace )
-                    .isEqualTo( this.stackTrace );
-        }
-    }
-
-    private static class StandardOutErrEventAssertionListener implements ForkedProcessStandardOutErrEventListener
-    {
-        private final RunMode runMode;
-        private final String output;
-        private final boolean newLine;
-
-        StandardOutErrEventAssertionListener( RunMode runMode, String output, boolean newLine )
-        {
-            this.runMode = runMode;
-            this.output = output;
-            this.newLine = newLine;
-        }
-
-        public void handle( RunMode runMode, String output, boolean newLine )
-        {
-            assertThat( runMode )
-                    .isEqualTo( this.runMode );
-
-            assertThat( output )
-                    .isEqualTo( this.output );
-
-            assertThat( newLine )
-                    .isEqualTo( this.newLine );
-        }
-    }
-
-    private static class ReportEventAssertionListener implements ForkedProcessReportEventListener
-    {
-        private final ReportEntry reportEntry;
-
-        ReportEventAssertionListener( ReportEntry reportEntry )
-        {
-            this.reportEntry = reportEntry;
-        }
-
-        public void handle( RunMode runMode, ReportEntry reportEntry )
-        {
-            assertThat( reportEntry.getSourceName() ).isEqualTo( this.reportEntry.getSourceName() );
-            assertThat( reportEntry.getSourceText() ).isEqualTo( this.reportEntry.getSourceText() );
-            assertThat( reportEntry.getName() ).isEqualTo( this.reportEntry.getName() );
-            assertThat( reportEntry.getNameText() ).isEqualTo( this.reportEntry.getNameText() );
-            assertThat( reportEntry.getGroup() ).isEqualTo( this.reportEntry.getGroup() );
-            assertThat( reportEntry.getMessage() ).isEqualTo( this.reportEntry.getMessage() );
-            assertThat( reportEntry.getElapsed() ).isEqualTo( this.reportEntry.getElapsed() );
-            if ( reportEntry.getStackTraceWriter() == null )
-            {
-                assertThat( this.reportEntry.getStackTraceWriter() ).isNull();
-            }
-            else
-            {
-                assertThat( this.reportEntry.getStackTraceWriter() ).isNotNull();
-
-                assertThat( reportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                        .isEqualTo( this.reportEntry.getStackTraceWriter().getThrowable().getMessage() );
-
-                assertThat( reportEntry.getStackTraceWriter().getThrowable().getLocalizedMessage() )
-                        .isEqualTo( this.reportEntry.getStackTraceWriter().getThrowable().getLocalizedMessage() );
-
-                assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                        .isEqualTo( this.reportEntry.getStackTraceWriter().smartTrimmedStackTrace() );
-            }
-        }
-    }
-
-    private static class Stream extends PrintStream
-    {
-        private final ByteArrayOutputStream out;
-
-        Stream( ByteArrayOutputStream out )
-        {
-            super( out, true );
-            this.out = out;
-        }
-
-        byte[] toByteArray()
-        {
-            return out.toByteArray();
-        }
-
-        LineNumberReader newReader( Charset streamCharset )
-        {
-            return new LineNumberReader( new StringReader( new String( toByteArray(), streamCharset ) ) );
-        }
-
-        static Stream newStream()
-        {
-            return new Stream( new ByteArrayOutputStream() );
-        }
-    }
-
-    private static byte[] toArray( ByteBuffer buffer )
-    {
-        return Arrays.copyOfRange( buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.remaining() );
-    }
-}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ConsoleOutputReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ConsoleOutputReporterTest.java
similarity index 95%
rename from maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ConsoleOutputReporterTest.java
rename to maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ConsoleOutputReporterTest.java
index 546e554..372c9a8 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ConsoleOutputReporterTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ConsoleOutputReporterTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,10 +19,12 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;
+import org.apache.maven.surefire.extensions.ConsoleOutputReporter;
 import org.apache.maven.plugin.surefire.extensions.junit5.JUnit5ConsoleOutputReporter;
 import org.apache.maven.plugin.surefire.report.ConsoleOutputFileReporter;
 import org.apache.maven.plugin.surefire.report.DirectConsoleOutput;
+import org.fest.assertions.Assertions;
 import org.junit.Test;
 
 import java.io.File;
@@ -49,7 +51,7 @@ public class ConsoleOutputReporterTest
                 .isInstanceOf( SurefireConsoleOutputReporter.class );
         assertThat( clone.toString() )
                 .isEqualTo( "SurefireConsoleOutputReporter{disable=true, encoding=ISO-8859-1}" );
-        assertThat( ( (SurefireConsoleOutputReporter) clone ).isDisable() )
+        Assertions.assertThat( ( (SurefireConsoleOutputReporter) clone ).isDisable() )
                 .isTrue();
         assertThat( ( (SurefireConsoleOutputReporter) clone ).getEncoding() )
                 .isEqualTo( "ISO-8859-1" );
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
new file mode 100644
index 0000000..cb2e382
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
@@ -0,0 +1,1183 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessEventListener;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessEventNotifier;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessExitErrorListener;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessPropertyEventListener;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessReportEventListener;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessStackTraceEventListener;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessStandardOutErrEventListener;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessStringEventListener;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+import org.apache.maven.surefire.report.SafeThrowable;
+import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.util.internal.ObjectUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import javax.annotation.Nonnull;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.Charset;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static java.nio.channels.Channels.newChannel;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Arrays.copyOfRange;
+import static org.apache.commons.codec.binary.Base64.encodeBase64String;
+import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.rules.ExpectedException.none;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test for {@link ForkedProcessEventNotifier}.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+@RunWith( Enclosed.class )
+public class ForkedProcessEventNotifierTest
+{
+    /**
+     *
+     */
+    @RunWith( PowerMockRunner.class )
+    @PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
+    public static class DecoderOperationsTest
+    {
+        @Rule
+        public final ExpectedException rule = none();
+
+        @Test
+        public void shouldBeFailSafe()
+        {
+            assertThat( EventConsumerThread.decode( null, UTF_8 ) ).isNull();
+            assertThat( EventConsumerThread.decode( "-", UTF_8 ) ).isNull();
+            assertThat( EventConsumerThread.decodeToInteger( null ) ).isNull();
+            assertThat( EventConsumerThread.decodeToInteger( "-" ) ).isNull();
+        }
+
+        @Test
+        public void shouldHaveSystemProperty() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            Map<String, String> props = ObjectUtils.systemProps();
+            encoder.sendSystemProperties( props );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            PropertyEventAssertionListener listener = new PropertyEventAssertionListener();
+            notifier.setSystemPropertiesListener( listener );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                for ( int i = 0; i < props.size(); i++ )
+                {
+                    notifier.notifyEvent( eventHandler.pullEvent() );
+                }
+            }
+
+            assertThat( listener.counter.get() )
+                .isEqualTo( props.size() );
+        }
+
+        @Test
+        public void shouldRecognizeEmptyStream4ReportEntry()
+        {
+            ReportEntry reportEntry = EventConsumerThread.decodeReportEntry( null, null, null, "", "", null, null, "",
+                    "", "", null );
+            assertThat( reportEntry ).isNull();
+
+            reportEntry = EventConsumerThread.decodeReportEntry( UTF_8, "", "", "", "", "", "", "-", "", "", "" );
+            assertThat( reportEntry ).isNotNull();
+            assertThat( reportEntry.getStackTraceWriter() ).isNotNull();
+            assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() ).isEmpty();
+            assertThat( reportEntry.getStackTraceWriter().writeTraceToString() ).isEmpty();
+            assertThat( reportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEmpty();
+            assertThat( reportEntry.getSourceName() ).isEmpty();
+            assertThat( reportEntry.getSourceText() ).isEmpty();
+            assertThat( reportEntry.getName() ).isEmpty();
+            assertThat( reportEntry.getNameText() ).isEmpty();
+            assertThat( reportEntry.getGroup() ).isEmpty();
+            assertThat( reportEntry.getNameWithGroup() ).isEmpty();
+            assertThat( reportEntry.getMessage() ).isEmpty();
+            assertThat( reportEntry.getElapsed() ).isNull();
+
+            rule.expect( NumberFormatException.class );
+            EventConsumerThread.decodeReportEntry( UTF_8, "", "", "", "", "", "", "", "", "", "" );
+        }
+
+        @Test
+        @SuppressWarnings( "checkstyle:magicnumber" )
+        public void testCreatingReportEntry()
+        {
+            final String exceptionMessage = "msg";
+            final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
+
+            final String smartStackTrace = "MyTest:86 >> Error";
+            final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
+
+            final String stackTrace = "Exception: msg\ntrace line 1\ntrace line 2";
+            final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
+
+            final String trimmedStackTrace = "trace line 1\ntrace line 2";
+            final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
+
+            SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
+            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+            when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
+            when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
+
+            ReportEntry reportEntry = mock( ReportEntry.class );
+            when( reportEntry.getElapsed() ).thenReturn( 102 );
+            when( reportEntry.getGroup() ).thenReturn( "this group" );
+            when( reportEntry.getMessage() ).thenReturn( "skipped test" );
+            when( reportEntry.getName() ).thenReturn( "my test" );
+            when( reportEntry.getNameText() ).thenReturn( "my display name" );
+            when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
+            when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+            when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
+            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
+
+            String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
+            String encodedSourceText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceText() ) ) );
+            String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
+            String encodedText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getNameText() ) ) );
+            String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
+            String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
+
+            ReportEntry decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName,
+                encodedSourceText, encodedName, encodedText, encodedGroup, encodedMessage, "-", null, null, null );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
+
+            decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName, encodedSourceText,
+                encodedName, encodedText, encodedGroup, encodedMessage, "-", encodedExceptionMsg,
+                encodedSmartStackTrace, null );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getElapsed() ).isNull();
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+                .isEqualTo( exceptionMessage );
+            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+                .isEqualTo( smartStackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
+                .isNull();
+
+            decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName, encodedSourceText,
+                encodedName, encodedText, encodedGroup, encodedMessage, "1003", encodedExceptionMsg,
+                encodedSmartStackTrace, null );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+                .isEqualTo( exceptionMessage );
+            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+                .isEqualTo( smartStackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
+                .isNull();
+
+            decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName, encodedSourceText,
+                encodedName, encodedText, encodedGroup, encodedMessage, "1003", encodedExceptionMsg,
+                encodedSmartStackTrace, encodedStackTrace );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+                    .isEqualTo( exceptionMessage );
+            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+                    .isEqualTo( smartStackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
+
+            decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName, encodedSourceText,
+                encodedName, encodedText, encodedGroup, encodedMessage, "1003", encodedExceptionMsg,
+                encodedSmartStackTrace, encodedTrimmedStackTrace );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+                    .isEqualTo( exceptionMessage );
+            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+                    .isEqualTo( smartStackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( trimmedStackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() )
+                    .isEqualTo( trimmedStackTrace );
+        }
+
+        @Test
+        public void shouldSendByeEvent() throws Exception
+        {
+            Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.bye();
+            String read = new String( out.toByteArray(), UTF_8 );
+
+            assertThat( read )
+                    .isEqualTo( ":maven-surefire-event:bye:\n" );
+
+            LineNumberReader lines = out.newReader( UTF_8 );
+
+            final String cmd = lines.readLine();
+            assertThat( cmd )
+                    .isNotNull();
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            EventAssertionListener listener = new EventAssertionListener();
+            notifier.setByeListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void shouldSendStopOnNextTestEvent() throws Exception
+        {
+            Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.stopOnNextTest();
+            String read = new String( out.toByteArray(), UTF_8 );
+
+            assertThat( read )
+                    .isEqualTo( ":maven-surefire-event:stop-on-next-test:\n" );
+
+            LineNumberReader lines = out.newReader( UTF_8 );
+
+            final String cmd = lines.readLine();
+            assertThat( cmd )
+                .isNotNull();
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            EventAssertionListener listener = new EventAssertionListener();
+            notifier.setStopOnNextTestListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void shouldCorrectlyDecodeStackTracesWithEmptyStringTraceMessages() throws Exception
+        {
+            String exceptionMessage = "";
+            String smartStackTrace = "JUnit5Test.failWithEmptyString:16";
+            String exceptionStackTrace = "org.opentest4j.AssertionFailedError: \n"
+                    + "\tat JUnit5Test.failWithEmptyString(JUnit5Test.java:16)\n";
+
+            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+            SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
+            when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( exceptionStackTrace );
+            when( stackTraceWriter.writeTraceToString() ).thenReturn( exceptionStackTrace );
+
+            ReportEntry reportEntry = mock( ReportEntry.class );
+            when( reportEntry.getElapsed() ).thenReturn( 7 );
+            when( reportEntry.getGroup() ).thenReturn( null );
+            when( reportEntry.getMessage() ).thenReturn( null );
+            when( reportEntry.getName() ).thenReturn( "failWithEmptyString" );
+            when( reportEntry.getNameWithGroup() ).thenReturn( "JUnit5Test" );
+            when( reportEntry.getSourceName() ).thenReturn( "JUnit5Test" );
+            when( reportEntry.getSourceText() ).thenReturn( null );
+            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
+
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.testFailed( reportEntry, true );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            final ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            ReportEventAssertionListener listener = new ReportEventAssertionListener( reportEntry, true );
+            notifier.setTestFailedListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void shouldSendNextTestEvent() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.acquireNextTest();
+            String read = new String( out.toByteArray(), UTF_8 );
+
+            assertThat( read )
+                    .isEqualTo( ":maven-surefire-event:next-test:\n" );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            EventAssertionListener listener = new EventAssertionListener();
+            notifier.setAcquireNextTestListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testConsole() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.consoleInfoLog( "msg" );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StringEventAssertionListener listener = new StringEventAssertionListener( "msg" );
+            notifier.setConsoleInfoListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testError() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.consoleErrorLog( "msg" );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StackTraceEventListener listener = new StackTraceEventListener( "msg", null, null );
+            notifier.setConsoleErrorListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testErrorWithException() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            Throwable throwable = new Throwable( "msg" );
+            encoder.consoleErrorLog( throwable );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            String stackTrace = ConsoleLoggerUtils.toString( throwable );
+            StackTraceEventListener listener = new StackTraceEventListener( "msg", null, stackTrace );
+            notifier.setConsoleErrorListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testErrorWithStackTraceWriter() throws Exception
+        {
+            final Stream out = Stream.newStream();
+
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "1", "2", "3" );
+            encoder.consoleErrorLog( stackTraceWriter, false );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StackTraceEventListener listener = new StackTraceEventListener( "1", "2", "3" );
+            notifier.setConsoleErrorListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testDebug() throws Exception
+        {
+            final Stream out = Stream.newStream();
+
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.consoleDebugLog( "msg" );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StringEventAssertionListener listener = new StringEventAssertionListener( "msg" );
+            notifier.setConsoleDebugListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+
+            assertThat( listener.msg )
+                .isEqualTo( "msg" );
+        }
+
+        @Test
+        public void testWarning() throws Exception
+        {
+            final Stream out = Stream.newStream();
+
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.consoleWarningLog( "msg" );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StringEventAssertionListener listener = new StringEventAssertionListener( "msg" );
+            notifier.setConsoleWarningListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testStdOutStream() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.stdOut( "msg", false );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StandardOutErrEventAssertionListener listener =
+                new StandardOutErrEventAssertionListener( NORMAL_RUN, "msg", false );
+            notifier.setStdOutListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testStdOutStreamPrint() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.stdOut( "", false );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StandardOutErrEventAssertionListener listener =
+                new StandardOutErrEventAssertionListener( NORMAL_RUN, "", false );
+            notifier.setStdOutListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testStdOutStreamPrintWithNull() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.stdOut( null, false );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StandardOutErrEventAssertionListener listener =
+                new StandardOutErrEventAssertionListener( NORMAL_RUN, null, false );
+            notifier.setStdOutListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testStdOutStreamPrintln() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.stdOut( "", true );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StandardOutErrEventAssertionListener listener =
+                new StandardOutErrEventAssertionListener( NORMAL_RUN, "", true );
+            notifier.setStdOutListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testStdOutStreamPrintlnWithNull() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.stdOut( null, true );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StandardOutErrEventAssertionListener listener =
+                new StandardOutErrEventAssertionListener( NORMAL_RUN, null, true );
+            notifier.setStdOutListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void testStdErrStream() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.stdErr( "msg", false );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            StandardOutErrEventAssertionListener listener =
+                new StandardOutErrEventAssertionListener( NORMAL_RUN, "msg", false );
+            notifier.setStdErrListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void shouldCountSameNumberOfSystemProperties() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            encoder.sendSystemProperties( ObjectUtils.systemProps() );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            PropertyEventAssertionListener listener = new PropertyEventAssertionListener();
+            notifier.setSystemPropertiesListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+
+        @Test
+        public void shouldHandleErrorAfterNullLine()
+        {
+            ForkedProcessEventNotifier decoder = new ForkedProcessEventNotifier();
+            decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
+            rule.expect( NullPointerException.class );
+            decoder.notifyEvent( null );
+        }
+
+        @Test
+        public void shouldHandleErrorAfterUnknownOperation() throws Exception
+        {
+            String cmd = ":maven-surefire-event:abnormal-run:-:\n";
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( cmd.getBytes() ) );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                countdown.awaitClosed();
+            }
+
+            verify( logger, times( 1 ) )
+                .error( eq( ":maven-surefire-event:abnormal-run:" ) );
+        }
+
+        @Test
+        public void shouldHandleExit() throws Exception
+        {
+            final Stream out = Stream.newStream();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+
+            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+            when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
+            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
+            when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
+            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
+            encoder.sendExitEvent( stackTraceWriter, false );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+            ProcessExitErrorListener listener = new ProcessExitErrorListener();
+            notifier.setExitErrorEventListener( listener );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+
+            assertThat( listener.called.get() )
+                .isTrue();
+        }
+    }
+
+    /**
+     *
+     */
+    @RunWith( Theories.class )
+    public static class ReportEntryTest
+    {
+        @DataPoints( value = "operation" )
+        @SuppressWarnings( "checkstyle:visibilitymodifier" )
+        public static String[][] operations = { { "testSetStarting", "setTestSetStartingListener" },
+                                                { "testSetCompleted", "setTestSetCompletedListener" },
+                                                { "testStarting", "setTestStartingListener" },
+                                                { "testSucceeded", "setTestSucceededListener" },
+                                                { "testFailed", "setTestFailedListener" },
+                                                { "testSkipped", "setTestSkippedListener" },
+                                                { "testError", "setTestErrorListener" },
+                                                { "testAssumptionFailure", "setTestAssumptionFailureListener" }
+        };
+
+        @DataPoints( value = "reportedMessage" )
+        @SuppressWarnings( "checkstyle:visibilitymodifier" )
+        public static String[] reportedMessage = { null, "skipped test" };
+
+        @DataPoints( value = "elapsed" )
+        @SuppressWarnings( { "checkstyle:visibilitymodifier", "checkstyle:magicnumber" } )
+        public static Integer[] elapsed = { null, 102 };
+
+        @DataPoints( value = "trim" )
+        @SuppressWarnings( "checkstyle:visibilitymodifier" )
+        public static boolean[] trim = { false, true };
+
+        @DataPoints( value = "msg" )
+        @SuppressWarnings( "checkstyle:visibilitymodifier" )
+        public static boolean[] msg = { false, true };
+
+        @DataPoints( value = "smart" )
+        @SuppressWarnings( "checkstyle:visibilitymodifier" )
+        public static boolean[] smart = { false, true };
+
+        @DataPoints( value = "trace" )
+        @SuppressWarnings( "checkstyle:visibilitymodifier" )
+        public static boolean[] trace = { false, true };
+
+        @Theory
+        public void testReportEntryOperations( @FromDataPoints( "operation" ) String[] operation,
+                                               @FromDataPoints( "reportedMessage" ) String reportedMessage,
+                                               @FromDataPoints( "elapsed" ) Integer elapsed,
+                                               @FromDataPoints( "trim" ) boolean trim,
+                                               @FromDataPoints( "msg" ) boolean msg,
+                                               @FromDataPoints( "smart" ) boolean smart,
+                                               @FromDataPoints( "trace" ) boolean trace )
+                throws Exception
+        {
+            String exceptionMessage = msg ? "msg" : null;
+            String smartStackTrace = smart ? "MyTest:86 >> Error" : null;
+            String exceptionStackTrace =
+                    trace ? ( trim ? "trace line 1\ntrace line 2" : "Exception: msg\ntrace line 1\ntrace line 2" )
+                            : null;
+
+            StackTraceWriter stackTraceWriter = null;
+            if ( exceptionStackTrace != null )
+            {
+                SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
+                stackTraceWriter = mock( StackTraceWriter.class );
+                when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+                when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+                when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( exceptionStackTrace );
+                when( stackTraceWriter.writeTraceToString() ).thenReturn( exceptionStackTrace );
+            }
+
+            ReportEntry reportEntry = mock( ReportEntry.class );
+            when( reportEntry.getElapsed() ).thenReturn( elapsed );
+            when( reportEntry.getGroup() ).thenReturn( "this group" );
+            when( reportEntry.getMessage() ).thenReturn( reportedMessage );
+            when( reportEntry.getName() ).thenReturn( "my test" );
+            when( reportEntry.getName() ).thenReturn( "display name of test" );
+            when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
+            when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+            when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
+            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
+
+            final Stream out = Stream.newStream();
+
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+
+            LegacyMasterProcessChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
+                    .invoke( encoder, reportEntry, trim );
+
+            ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
+
+            ForkedProcessEventNotifier.class.getMethod( operation[1], ForkedProcessReportEventListener.class )
+                    .invoke( notifier, new ReportEventAssertionListener( reportEntry, stackTraceWriter != null ) );
+
+            ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
+
+            EH eventHandler = new EH();
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            ConsoleLogger logger = mock( ConsoleLogger.class );
+            try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, logger ) )
+            {
+                t.start();
+                notifier.notifyEvent( eventHandler.pullEvent() );
+            }
+        }
+    }
+
+    private static class ProcessExitErrorListener implements ForkedProcessExitErrorListener
+    {
+        final AtomicBoolean called = new AtomicBoolean();
+
+        @Override
+        public void handle( StackTraceWriter stackTrace )
+        {
+            called.set( true );
+            assertThat( stackTrace.getThrowable().getMessage() ).isEqualTo( "1" );
+            assertThat( stackTrace.smartTrimmedStackTrace() ).isEqualTo( "2" );
+            assertThat( stackTrace.writeTraceToString() ).isEqualTo( "3" );
+        }
+    }
+
+    private static class PropertyEventAssertionListener implements ForkedProcessPropertyEventListener
+    {
+        final AtomicBoolean called = new AtomicBoolean();
+        private final Map<?, ?> sysProps = System.getProperties();
+        private final AtomicInteger counter = new AtomicInteger();
+
+        public void handle( RunMode runMode, String key, String value )
+        {
+            called.set( true );
+            counter.incrementAndGet();
+            assertThat( runMode ).isEqualTo( NORMAL_RUN );
+            assertTrue( sysProps.containsKey( key ) );
+            assertThat( sysProps.get( key ) ).isEqualTo( value );
+        }
+    }
+
+    private static class EventAssertionListener implements ForkedProcessEventListener
+    {
+        final AtomicBoolean called = new AtomicBoolean();
+
+        public void handle()
+        {
+            called.set( true );
+        }
+    }
+
+    private static class StringEventAssertionListener implements ForkedProcessStringEventListener
+    {
+        final AtomicBoolean called = new AtomicBoolean();
+        private final String msg;
+
+        StringEventAssertionListener( String msg )
+        {
+            this.msg = msg;
+        }
+
+        public void handle( String msg )
+        {
+            called.set( true );
+            assertThat( msg )
+                    .isEqualTo( this.msg );
+        }
+    }
+
+    private static class StackTraceEventListener implements ForkedProcessStackTraceEventListener
+    {
+        final AtomicBoolean called = new AtomicBoolean();
+        private final String msg;
+        private final String smartStackTrace;
+        private final String stackTrace;
+
+        StackTraceEventListener( String msg, String smartStackTrace, String stackTrace )
+        {
+            this.msg = msg;
+            this.smartStackTrace = smartStackTrace;
+            this.stackTrace = stackTrace;
+        }
+
+        @Override
+        public void handle( @Nonnull StackTraceWriter stackTrace )
+        {
+            called.set( true );
+
+            assertThat( stackTrace.getThrowable().getMessage() )
+                    .isEqualTo( msg );
+
+            assertThat( stackTrace.smartTrimmedStackTrace() )
+                    .isEqualTo( smartStackTrace );
+
+            assertThat( stackTrace.writeTraceToString() )
+                    .isEqualTo( this.stackTrace );
+        }
+    }
+
+    private static class StandardOutErrEventAssertionListener implements ForkedProcessStandardOutErrEventListener
+    {
+        final AtomicBoolean called = new AtomicBoolean();
+        private final RunMode runMode;
+        private final String output;
+        private final boolean newLine;
+
+        StandardOutErrEventAssertionListener( RunMode runMode, String output, boolean newLine )
+        {
+            this.runMode = runMode;
+            this.output = output;
+            this.newLine = newLine;
+        }
+
+        public void handle( RunMode runMode, String output, boolean newLine )
+        {
+            called.set( true );
+
+            assertThat( runMode )
+                    .isEqualTo( this.runMode );
+
+            assertThat( output )
+                    .isEqualTo( this.output );
+
+            assertThat( newLine )
+                    .isEqualTo( this.newLine );
+        }
+    }
+
+    private static class ReportEventAssertionListener implements ForkedProcessReportEventListener<ReportEntry>
+    {
+        final AtomicBoolean called = new AtomicBoolean();
+        private final ReportEntry reportEntry;
+        private final boolean hasStackTrace;
+
+        ReportEventAssertionListener( ReportEntry reportEntry, boolean hasStackTrace )
+        {
+            this.reportEntry = reportEntry;
+            this.hasStackTrace = hasStackTrace;
+        }
+
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            called.set( true );
+            assertThat( reportEntry.getSourceName() ).isEqualTo( this.reportEntry.getSourceName() );
+            assertThat( reportEntry.getSourceText() ).isEqualTo( this.reportEntry.getSourceText() );
+            assertThat( reportEntry.getName() ).isEqualTo( this.reportEntry.getName() );
+            assertThat( reportEntry.getNameText() ).isEqualTo( this.reportEntry.getNameText() );
+            assertThat( reportEntry.getGroup() ).isEqualTo( this.reportEntry.getGroup() );
+            assertThat( reportEntry.getMessage() ).isEqualTo( this.reportEntry.getMessage() );
+            assertThat( reportEntry.getElapsed() ).isEqualTo( this.reportEntry.getElapsed() );
+            if ( reportEntry.getStackTraceWriter() == null )
+            {
+                assertThat( hasStackTrace ).isFalse();
+                assertThat( this.reportEntry.getStackTraceWriter() ).isNull();
+            }
+            else
+            {
+                assertThat( hasStackTrace ).isTrue();
+                assertThat( this.reportEntry.getStackTraceWriter() ).isNotNull();
+
+                assertThat( reportEntry.getStackTraceWriter().getThrowable().getMessage() )
+                        .isEqualTo( this.reportEntry.getStackTraceWriter().getThrowable().getMessage() );
+
+                assertThat( reportEntry.getStackTraceWriter().getThrowable().getLocalizedMessage() )
+                        .isEqualTo( this.reportEntry.getStackTraceWriter().getThrowable().getLocalizedMessage() );
+
+                assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+                        .isEqualTo( this.reportEntry.getStackTraceWriter().smartTrimmedStackTrace() );
+            }
+        }
+    }
+
+    private static class Stream extends PrintStream
+    {
+        private final ByteArrayOutputStream out;
+
+        Stream( ByteArrayOutputStream out )
+        {
+            super( out, true );
+            this.out = out;
+        }
+
+        byte[] toByteArray()
+        {
+            return out.toByteArray();
+        }
+
+        LineNumberReader newReader( Charset streamCharset )
+        {
+            return new LineNumberReader( new StringReader( new String( toByteArray(), streamCharset ) ) );
+        }
+
+        static Stream newStream()
+        {
+            return new Stream( new ByteArrayOutputStream() );
+        }
+    }
+
+    private static byte[] toArray( ByteBuffer buffer )
+    {
+        return copyOfRange( buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.remaining() );
+    }
+
+    private static class EH implements EventHandler<Event>
+    {
+        private final BlockingQueue<Event> cache = new LinkedTransferQueue<>();
+
+        Event pullEvent() throws InterruptedException
+        {
+            return cache.poll( 1, TimeUnit.MINUTES );
+        }
+
+        @Override
+        public void handleEvent( @Nonnull Event event )
+        {
+            cache.add( event );
+        }
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StatelessReporterTest.java
similarity index 98%
rename from maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessReporterTest.java
rename to maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StatelessReporterTest.java
index bb06eff..4aa048c 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessReporterTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StatelessReporterTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,8 +19,7 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.extensions.DefaultStatelessReportMojoConfiguration;
-import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.surefire.extensions.StatelessReportEventListener;
 import org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter;
 import org.apache.maven.plugin.surefire.report.StatelessXmlReporter;
 import org.apache.maven.plugin.surefire.report.TestSetStats;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java
new file mode 100644
index 0000000..7da6745
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java
@@ -0,0 +1,162 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.extensions.CommandReader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.util.Iterator;
+
+import static java.util.Arrays.asList;
+import static org.apache.maven.surefire.booter.Command.TEST_SET_FINISHED;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link StreamFeeder}.
+ */
+public class StreamFeederTest
+{
+    private final ByteArrayOutputStream out = new ByteArrayOutputStream();
+    private final WritableByteChannel channel = mock( WritableByteChannel.class );
+    private final CommandReader commandReader = mock( CommandReader.class );
+    private StreamFeeder streamFeeder;
+
+    @Before
+    public void setup() throws IOException
+    {
+        final Iterator<Command> it = asList( new Command( RUN_CLASS, "pkg.ATest" ), TEST_SET_FINISHED ).iterator();
+        when( commandReader.readNextCommand() )
+            .thenAnswer( new Answer<Command>()
+            {
+                @Override
+                public Command answer( InvocationOnMock invocation )
+                {
+                    return it.hasNext() ? it.next() : null;
+                }
+            } );
+    }
+
+    @After
+    public void close() throws IOException
+    {
+        if ( streamFeeder != null )
+        {
+            streamFeeder.disable();
+            streamFeeder.close();
+        }
+    }
+
+    @Test
+    public void shouldEncodeCommandToStream() throws Exception
+    {
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Object>()
+            {
+                @Override
+                public Object answer( InvocationOnMock invocation ) throws IOException
+                {
+                    ByteBuffer bb = invocation.getArgument( 0 );
+                    bb.flip();
+                    out.write( bb.array() );
+                    return 0;
+                }
+            } );
+
+        ConsoleLogger logger = mock( ConsoleLogger.class );
+        streamFeeder = new StreamFeeder( "t", channel, commandReader, logger );
+        streamFeeder.start();
+
+        streamFeeder.join();
+        String commands = out.toString();
+
+        assertThat( commands )
+            .isEqualTo( ":maven-surefire-command:run-testclass:pkg.ATest::maven-surefire-command:testset-finished:" );
+
+        verify( channel, times( 1 ) )
+            .close();
+
+        assertThat( streamFeeder.getException() )
+            .isNull();
+
+        verifyZeroInteractions( logger );
+    }
+
+    @Test
+    public void shouldFailThread() throws Exception
+    {
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Object>()
+            {
+                @Override
+                public Object answer( InvocationOnMock invocation ) throws IOException
+                {
+                    throw new IOException();
+                }
+            } );
+
+        ConsoleLogger logger = mock( ConsoleLogger.class );
+        streamFeeder = new StreamFeeder( "t", channel, commandReader, logger );
+        streamFeeder.start();
+
+        streamFeeder.join();
+
+        assertThat( out.size() )
+            .isZero();
+
+        verify( channel, times( 1 ) )
+            .close();
+
+        assertThat( streamFeeder.getException() )
+            .isNotNull()
+            .isInstanceOf( IOException.class );
+
+        verifyZeroInteractions( logger );
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailWithoutData()
+    {
+        StreamFeeder.encode( RUN_CLASS );
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailWithData()
+    {
+        StreamFeeder.encode( NOOP, "" );
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index b1e3b22..a232544 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -28,7 +28,6 @@ import org.apache.maven.plugin.surefire.AbstractSurefireMojoTest;
 import org.apache.maven.plugin.surefire.CommonReflectorTest;
 import org.apache.maven.plugin.surefire.MojoMocklessTest;
 import org.apache.maven.plugin.surefire.SurefireHelperTest;
-import org.apache.maven.plugin.surefire.SurefireReflectorTest;
 import org.apache.maven.plugin.surefire.SurefirePropertiesTest;
 import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerProviderConfigurationTest;
 import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerStartupConfigurationTest;
@@ -41,7 +40,10 @@ import org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfigu
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClientTest;
-import org.apache.maven.plugin.surefire.booterclient.output.ForkedChannelDecoderTest;
+import org.apache.maven.plugin.surefire.extensions.ConsoleOutputReporterTest;
+import org.apache.maven.plugin.surefire.extensions.ForkedProcessEventNotifierTest;
+import org.apache.maven.plugin.surefire.extensions.StatelessReporterTest;
+import org.apache.maven.plugin.surefire.extensions.StreamFeederTest;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactoryTest;
 import org.apache.maven.plugin.surefire.report.StatelessXmlReporterTest;
 import org.apache.maven.plugin.surefire.report.TestSetStatsTest;
@@ -51,8 +53,7 @@ import org.apache.maven.plugin.surefire.util.DependenciesScannerTest;
 import org.apache.maven.plugin.surefire.util.DirectoryScannerTest;
 import org.apache.maven.plugin.surefire.util.ScannerUtilTest;
 import org.apache.maven.plugin.surefire.util.SpecificFileFilterTest;
-import org.apache.maven.surefire.extensions.ConsoleOutputReporterTest;
-import org.apache.maven.surefire.extensions.StatelessReporterTest;
+import org.apache.maven.surefire.extensions.ForkChannelTest;
 import org.apache.maven.surefire.extensions.StatelessTestsetInfoReporterTest;
 import org.apache.maven.surefire.report.FileReporterTest;
 import org.apache.maven.surefire.report.RunStatisticsTest;
@@ -89,7 +90,6 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( TestProvidingInputStreamTest.class ) );
         suite.addTest( new JUnit4TestAdapter( TestLessInputStreamBuilderTest.class ) );
         suite.addTest( new JUnit4TestAdapter( SPITest.class ) );
-        suite.addTest( new JUnit4TestAdapter( SurefireReflectorTest.class ) );
         suite.addTest( new JUnit4TestAdapter( SurefireHelperTest.class ) );
         suite.addTest( new JUnit4TestAdapter( AbstractSurefireMojoTest.class ) );
         suite.addTest( new JUnit4TestAdapter( DefaultForkConfigurationTest.class ) );
@@ -99,13 +99,15 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( ScannerUtilTest.class ) );
         suite.addTest( new JUnit4TestAdapter( MojoMocklessTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkClientTest.class ) );
-        suite.addTest( new JUnit4TestAdapter( ForkedChannelDecoderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( ForkedProcessEventNotifierTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ConsoleOutputReporterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( StatelessReporterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( TestSetStatsTest.class ) );
         suite.addTest( new JUnit4TestAdapter( StatelessTestsetInfoReporterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( CommonReflectorTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkStarterTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( ForkChannelTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( StreamFeederTest.class ) );
         return suite;
     }
 }
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
new file mode 100644
index 0000000..68f2324
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
@@ -0,0 +1,166 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.plugin.surefire.booterclient.MockReporter;
+import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
+import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
+import org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory;
+import org.apache.maven.surefire.eventapi.ControlByeEvent;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.URI;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ *
+ */
+public class ForkChannelTest
+{
+    private static final long TESTCASE_TIMEOUT = 30_000L;
+
+    private final AtomicBoolean hasError = new AtomicBoolean();
+
+    @Test( timeout = TESTCASE_TIMEOUT )
+    public void shouldRequestReplyMessagesViaTCP() throws Exception
+    {
+        ForkNodeFactory factory = new SurefireForkNodeFactory();
+        try ( ForkChannel channel = factory.createForkChannel( 1, new MockReporter() ) )
+        {
+            assertThat( channel.getForkChannelId() )
+                .isEqualTo( 1 );
+
+            assertThat( channel.useStdOut() )
+                .isFalse();
+
+            assertThat( channel.getForkNodeConnectionString() )
+                .startsWith( "tcp://127.0.0.1:" )
+                .isNotEqualTo( "tcp://127.0.0.1:" );
+
+            URI uri = new URI( channel.getForkNodeConnectionString() );
+
+            assertThat( uri.getPort() )
+                .isPositive();
+
+            Consumer consumer = new Consumer();
+
+            Client client = new Client( uri.getPort() );
+            client.start();
+
+            channel.connectToClient();
+            SECONDS.sleep( 3L );
+
+            TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
+            TestLessInputStream commandReader = builder.build();
+
+            channel.bindCommandReader( commandReader, null ).start();
+
+            final AtomicBoolean isCloseableCalled = new AtomicBoolean();
+            Closeable closeable = new Closeable()
+            {
+                @Override
+                public void close()
+                {
+                    isCloseableCalled.set( true );
+                }
+            };
+            CountdownCloseable cc = new CountdownCloseable( closeable, 1 );
+            channel.bindEventHandler( consumer, cc, null ).start();
+
+            SECONDS.sleep( 3L );
+
+            commandReader.noop();
+
+            SECONDS.sleep( 3L );
+
+            client.join( TESTCASE_TIMEOUT );
+
+            assertThat( hasError.get() )
+                .isFalse();
+
+            assertThat( isCloseableCalled.get() )
+                .isTrue();
+
+            assertThat( consumer.lines )
+                .hasSize( 1 );
+
+            assertThat( consumer.lines.element() )
+                .isInstanceOf( ControlByeEvent.class );
+        }
+    }
+
+    private static class Consumer implements EventHandler<Event>
+    {
+        final Queue<Event> lines = new ConcurrentLinkedQueue<>();
+
+        @Override
+        public void handleEvent( @Nonnull Event s )
+        {
+            lines.add( s );
+        }
+    }
+
+    private final class Client extends Thread
+    {
+        private final int port;
+
+        private Client( int port )
+        {
+            this.port = port;
+        }
+
+        @Override
+        public void run()
+        {
+            try ( Socket socket = new Socket( "127.0.0.1", port ) )
+            {
+                byte[] data = new byte[128];
+                int readLength = socket.getInputStream().read( data );
+                String token = new String( data, 0, readLength, US_ASCII );
+                assertThat( token ).isEqualTo( ":maven-surefire-command:noop:" );
+                socket.getOutputStream().write( ":maven-surefire-event:bye:".getBytes( US_ASCII ) );
+            }
+            catch ( IOException e )
+            {
+                hasError.set( true );
+                e.printStackTrace();
+                throw new IllegalStateException( e );
+            }
+            catch ( RuntimeException e )
+            {
+                hasError.set( true );
+                e.printStackTrace();
+                throw e;
+            }
+        }
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporterTest.java
index a626180..577bb91 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporterTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporterTest.java
@@ -26,8 +26,8 @@ import org.apache.maven.plugin.surefire.report.ConsoleReporter;
 import org.apache.maven.plugin.surefire.report.FileReporter;
 import org.apache.maven.plugin.surefire.report.TestSetStats;
 import org.apache.maven.plugin.surefire.report.WrappedReportEntry;
-import org.apache.maven.surefire.shared.utils.logging.MessageUtils;
 import org.apache.maven.surefire.report.TestSetReportEntry;
+import org.apache.maven.surefire.shared.utils.logging.MessageUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
diff --git a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
index b39181a..5f6edea 100644
--- a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
+++ b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
@@ -29,6 +29,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 
 import static org.apache.maven.plugin.surefire.SurefireHelper.reportExecution;
@@ -365,6 +366,9 @@ public class SurefirePlugin
     @Parameter( property = "surefire.useModulePath", defaultValue = "true" )
     private boolean useModulePath;
 
+    @Parameter( property = "surefire.forkNode" )
+    private ForkNodeFactory forkNode;
+
     /**
      * You can selectively exclude individual environment variables by enumerating their keys.
      * <br>
@@ -831,4 +835,10 @@ public class SurefirePlugin
     {
         return enableProcessChecker;
     }
+
+    @Override
+    protected final ForkNodeFactory getForkNode()
+    {
+        return forkNode;
+    }
 }
diff --git a/pom.xml b/pom.xml
index f4f992e..ccf7c9e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,7 @@
     <module>surefire-logger-api</module>
     <module>surefire-api</module>
     <module>surefire-extensions-api</module>
+    <module>surefire-extensions-spi</module>
     <module>surefire-booter</module>
     <module>surefire-grouper</module>
     <module>surefire-providers</module>
@@ -97,7 +98,7 @@
     <plexus-java-version>1.0.5</plexus-java-version>
     <!-- maven-shared-utils:3.2.0+ another behavior - broke Surefire performance - end of subprocess notification not arrived in ForkStarter -->
     <mavenSharedUtilsVersion>3.1.0</mavenSharedUtilsVersion>
-    <powermockVersion>2.0.4</powermockVersion>
+    <powermockVersion>2.0.5</powermockVersion>
     <jacocoVersion>0.8.5</jacocoVersion>
     <maven.surefire.scm.devConnection>scm:git:https://gitbox.apache.org/repos/asf/maven-surefire.git</maven.surefire.scm.devConnection>
     <maven.site.path>surefire-archives/surefire-LATEST</maven.site.path>
@@ -474,10 +475,8 @@
               </goals>
               <configuration>
                 <includes>
-                  <include>org/apache/maven/shared/utils/logging/*.java</include>
                   <include>HelpMojo.java</include>
                   <include>**/HelpMojo.java</include>
-                  <include>org/apache/maven/plugin/failsafe/xmlsummary/*.java</include>
                 </includes>
                 <compilerArgs>
                   <!-- FIXME: maven-plugin-plugin therefore used -syntax or none due to HelpMojo -->
@@ -493,10 +492,8 @@
               </goals>
               <configuration>
                 <excludes>
-                  <exclude>org/apache/maven/shared/utils/logging/*.java</exclude>
                   <exclude>HelpMojo.java</exclude>
                   <exclude>**/HelpMojo.java</exclude>
-                  <exclude>org/apache/maven/plugin/failsafe/xmlsummary/*.java</exclude>
                 </excludes>
                 <compilerArgs>
                   <arg>-Xdoclint:all</arg>
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
index ec05580..6ac0ce2 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
@@ -20,6 +20,8 @@ package org.apache.maven.surefire.booter;
  */
 
 import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.DefaultDirectConsoleReporter;
@@ -46,15 +48,13 @@ import static java.util.Collections.emptyList;
  * @author Kristian Rosenvold
  */
 public class BaseProviderFactory
-    implements DirectoryScannerParametersAware, ReporterConfigurationAware, SurefireClassLoadersAware, TestRequestAware,
-    ProviderPropertiesAware, ProviderParameters, TestArtifactInfoAware, RunOrderParametersAware, MainCliOptionsAware,
-    FailFastAware, ShutdownAware
+    implements ProviderParameters
 {
-    private final ReporterFactory reporterFactory;
-
     private final boolean insideFork;
 
-    private ForkedChannelEncoder forkedChannelEncoder;
+    private ReporterFactory reporterFactory;
+
+    private MasterProcessChannelEncoder masterProcessChannelEncoder;
 
     private List<CommandLineOption> mainCliOptions = emptyList();
 
@@ -74,17 +74,27 @@ public class BaseProviderFactory
 
     private int skipAfterFailureCount;
 
-    private Shutdown shutdown;
-
     private Integer systemExitTimeout;
 
-    public BaseProviderFactory( ReporterFactory reporterFactory, boolean insideFork )
+    private CommandChainReader commandReader;
+
+    public BaseProviderFactory( boolean insideFork )
     {
-        this.reporterFactory = reporterFactory;
         this.insideFork = insideFork;
     }
 
     @Override
+    public CommandChainReader getCommandReader()
+    {
+        return commandReader;
+    }
+
+    public void setCommandReader( CommandChainReader commandReader )
+    {
+        this.commandReader = commandReader;
+    }
+
+    @Override
     @Deprecated
     public DirectoryScanner getDirectoryScanner()
     {
@@ -114,25 +124,27 @@ public class BaseProviderFactory
                 ? null : new DefaultRunOrderCalculator( runOrderParameters, getThreadCount() );
     }
 
+    public void setReporterFactory( ReporterFactory reporterFactory )
+    {
+        this.reporterFactory = reporterFactory;
+    }
+
     @Override
     public ReporterFactory getReporterFactory()
     {
         return reporterFactory;
     }
 
-    @Override
     public void setDirectoryScannerParameters( DirectoryScannerParameters directoryScannerParameters )
     {
         this.directoryScannerParameters = directoryScannerParameters;
     }
 
-    @Override
     public void setReporterConfiguration( ReporterConfiguration reporterConfiguration )
     {
         this.reporterConfiguration = reporterConfiguration;
     }
 
-    @Override
     public void setClassLoaders( ClassLoader testClassLoader )
     {
         this.testClassLoader = testClassLoader;
@@ -141,11 +153,11 @@ public class BaseProviderFactory
     @Override
     public ConsoleStream getConsoleLogger()
     {
-        return insideFork ? new ForkingRunListener( forkedChannelEncoder, reporterConfiguration.isTrimStackTrace() )
-                       : new DefaultDirectConsoleReporter( reporterConfiguration.getOriginalSystemOut() );
+        return insideFork
+            ? new ForkingRunListener( masterProcessChannelEncoder, reporterConfiguration.isTrimStackTrace() )
+            : new DefaultDirectConsoleReporter( reporterConfiguration.getOriginalSystemOut() );
     }
 
-    @Override
     public void setTestRequest( TestRequest testRequest )
     {
         this.testRequest = testRequest;
@@ -175,7 +187,6 @@ public class BaseProviderFactory
         return testClassLoader;
     }
 
-    @Override
     public void setProviderProperties( Map<String, String> providerProperties )
     {
         this.providerProperties = providerProperties;
@@ -193,13 +204,11 @@ public class BaseProviderFactory
         return testArtifactInfo;
     }
 
-    @Override
     public void setTestArtifactInfo( TestArtifactInfo testArtifactInfo )
     {
         this.testArtifactInfo = testArtifactInfo;
     }
 
-    @Override
     public void setRunOrderParameters( RunOrderParameters runOrderParameters )
     {
         this.runOrderParameters = runOrderParameters;
@@ -211,7 +220,11 @@ public class BaseProviderFactory
         return mainCliOptions;
     }
 
-    @Override
+    /**
+     * CLI options in plugin (main) JVM process.
+     *
+     * @param mainCliOptions options
+     */
     public void setMainCliOptions( List<CommandLineOption> mainCliOptions )
     {
         this.mainCliOptions = mainCliOptions == null ? Collections.<CommandLineOption>emptyList() : mainCliOptions;
@@ -223,7 +236,11 @@ public class BaseProviderFactory
         return skipAfterFailureCount;
     }
 
-    @Override
+    /**
+     * See the plugin configuration parameter "skipAfterFailureCount".
+     *
+     * @param skipAfterFailureCount the value in config parameter "skipAfterFailureCount"
+     */
     public void setSkipAfterFailureCount( int skipAfterFailureCount )
     {
         this.skipAfterFailureCount = skipAfterFailureCount;
@@ -236,18 +253,6 @@ public class BaseProviderFactory
     }
 
     @Override
-    public Shutdown getShutdown()
-    {
-        return shutdown;
-    }
-
-    @Override
-    public void setShutdown( Shutdown shutdown )
-    {
-        this.shutdown = shutdown;
-    }
-
-    @Override
     public Integer getSystemExitTimeout()
     {
         return systemExitTimeout;
@@ -259,13 +264,13 @@ public class BaseProviderFactory
     }
 
     @Override
-    public ForkedChannelEncoder getForkedChannelEncoder()
+    public MasterProcessChannelEncoder getForkedChannelEncoder()
     {
-        return forkedChannelEncoder;
+        return masterProcessChannelEncoder;
     }
 
-    public void setForkedChannelEncoder( ForkedChannelEncoder forkedChannelEncoder )
+    public void setForkedChannelEncoder( MasterProcessChannelEncoder masterProcessChannelEncoder )
     {
-        this.forkedChannelEncoder = forkedChannelEncoder;
+        this.masterProcessChannelEncoder = masterProcessChannelEncoder;
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
index f05c0f6..834317b 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
@@ -19,6 +19,8 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import java.util.Objects;
+
 import static java.util.Objects.requireNonNull;
 import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
@@ -47,6 +49,11 @@ public final class Command
         this.data = data;
     }
 
+    public Command( MasterProcessCommand command )
+    {
+        this( command, null );
+    }
+
     public static Command toShutdown( Shutdown shutdownType )
     {
         return new Command( SHUTDOWN, shutdownType.name() );
@@ -57,11 +64,6 @@ public final class Command
         return new Command( RUN_CLASS, runClass );
     }
 
-    public Command( MasterProcessCommand command )
-    {
-        this( command, null );
-    }
-
     public MasterProcessCommand getCommandType()
     {
         return command;
@@ -78,18 +80,13 @@ public final class Command
      */
     public Shutdown toShutdownData()
     {
-        if ( !isType( SHUTDOWN ) )
+        if ( command != SHUTDOWN )
         {
             throw new IllegalStateException( "expected MasterProcessCommand.SHUTDOWN" );
         }
         return isBlank( data ) ? DEFAULT : Shutdown.valueOf( data );
     }
 
-    public boolean isType( MasterProcessCommand command )
-    {
-        return command == this.command;
-    }
-
     @Override
     public boolean equals( Object o )
     {
@@ -105,7 +102,7 @@ public final class Command
 
         Command arg = (Command) o;
 
-        return command == arg.command && ( data == null ? arg.data == null : data.equals( arg.data ) );
+        return command == arg.command && Objects.equals( data, arg.data );
     }
 
     @Override
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEventType.java
similarity index 86%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEventType.java
index 74b9eb9..c1087ac 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEventType.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import javax.annotation.Nonnull;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -30,7 +31,7 @@ import static java.util.Collections.unmodifiableMap;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
-public enum ForkedProcessEvent
+public enum ForkedProcessEventType
 {
     BOOTERCODE_SYSPROPS( "sys-prop" ),
 
@@ -59,14 +60,14 @@ public enum ForkedProcessEvent
 
     BOOTERCODE_JVM_EXIT_ERROR( "jvm-exit-error" );
 
-    public static final String MAGIC_NUMBER = ":maven:surefire:std:out:";
+    public static final String MAGIC_NUMBER = "maven-surefire-event";
 
-    public static final Map<String, ForkedProcessEvent> EVENTS = events();
+    private static final Map<String, ForkedProcessEventType> EVENTS = events();
 
-    private static Map<String, ForkedProcessEvent> events()
+    private static Map<String, ForkedProcessEventType> events()
     {
-        Map<String, ForkedProcessEvent> events = new ConcurrentHashMap<>();
-        for ( ForkedProcessEvent event : values() )
+        Map<String, ForkedProcessEventType> events = new ConcurrentHashMap<>();
+        for ( ForkedProcessEventType event : values() )
         {
             events.put( event.getOpcode(), event );
         }
@@ -75,7 +76,7 @@ public enum ForkedProcessEvent
 
     private final String opcode;
 
-    ForkedProcessEvent( String opcode )
+    ForkedProcessEventType( String opcode )
     {
         this.opcode = opcode;
     }
@@ -129,4 +130,9 @@ public enum ForkedProcessEvent
     {
         return this == BOOTERCODE_JVM_EXIT_ERROR;
     }
+
+    public static ForkedProcessEventType byOpcode( @Nonnull String opcode )
+    {
+        return EVENTS.get( opcode );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
index 5bb16ee..1c6db50 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.suite.RunResult;
@@ -34,9 +35,9 @@ public class ForkingReporterFactory
 {
     private final boolean trimstackTrace;
 
-    private final ForkedChannelEncoder eventChannel;
+    private final MasterProcessChannelEncoder eventChannel;
 
-    public ForkingReporterFactory( boolean trimstackTrace, ForkedChannelEncoder eventChannel )
+    public ForkingReporterFactory( boolean trimstackTrace, MasterProcessChannelEncoder eventChannel )
     {
         this.trimstackTrace = trimstackTrace;
         this.eventChannel = eventChannel;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
index 528b607..6148149 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
@@ -20,6 +20,7 @@ package org.apache.maven.surefire.booter;
  */
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.ReportEntry;
@@ -50,13 +51,13 @@ import static java.util.Objects.requireNonNull;
 public class ForkingRunListener
     implements RunListener, ConsoleLogger, ConsoleOutputReceiver, ConsoleStream
 {
-    private final ForkedChannelEncoder target;
+    private final MasterProcessChannelEncoder target;
 
     private final boolean trim;
 
     private volatile RunMode runMode = NORMAL_RUN;
 
-    public ForkingRunListener( ForkedChannelEncoder target, boolean trim )
+    public ForkingRunListener( MasterProcessChannelEncoder target, boolean trim )
     {
         this.target = target;
         this.trim = trim;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
index 7c4520f..b6ae644 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
@@ -19,46 +19,34 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.util.Objects.requireNonNull;
-import static java.lang.String.format;
 
 /**
  * Commands which are sent from plugin to the forked jvm.
- * Support and methods related to the commands.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
  */
 public enum MasterProcessCommand
 {
-    RUN_CLASS( 0, String.class ),
-    TEST_SET_FINISHED( 1, Void.class ),
-    SKIP_SINCE_NEXT_TEST( 2, Void.class ),
-    SHUTDOWN( 3, String.class ),
+    RUN_CLASS( String.class ),
+    TEST_SET_FINISHED( Void.class ),
+    SKIP_SINCE_NEXT_TEST( Void.class ),
+    SHUTDOWN( String.class ),
 
     /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */
-    NOOP( 4, Void.class ),
-    BYE_ACK( 5, Void.class );
+    NOOP( Void.class ),
+    BYE_ACK( Void.class );
 
-    private final int id;
+    public static final String MAGIC_NUMBER = "maven-surefire-command";
 
     private final Class<?> dataType;
 
-    MasterProcessCommand( int id, Class<?> dataType )
+    MasterProcessCommand( Class<?> dataType )
     {
-        this.id = id;
         this.dataType = requireNonNull( dataType, "dataType cannot be null" );
     }
 
-    public int getId()
-    {
-        return id;
-    }
-
     public Class<?> getDataType()
     {
         return dataType;
@@ -68,123 +56,4 @@ public enum MasterProcessCommand
     {
         return dataType != Void.class;
     }
-
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    public byte[] encode( String data )
-    {
-        if ( !hasDataType() )
-        {
-            throw new IllegalArgumentException( "cannot use data without data type" );
-        }
-
-        if ( getDataType() != String.class )
-        {
-            throw new IllegalArgumentException( "Data type can be only " + String.class );
-        }
-
-        final byte[] dataBytes = fromDataType( data );
-        final int len = dataBytes.length;
-
-        final byte[] encoded = new byte[8 + len];
-
-        final int command = getId();
-        setCommandAndDataLength( command, len, encoded );
-        System.arraycopy( dataBytes, 0, encoded, 8, len );
-
-        return encoded;
-    }
-
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    public byte[] encode()
-    {
-        if ( getDataType() != Void.class )
-        {
-            throw new IllegalArgumentException( "Data type can be only " + getDataType() );
-        }
-        byte[] encoded = new byte[8];
-        int command = getId();
-        setCommandAndDataLength( command, 0, encoded );
-        return encoded;
-    }
-
-    public static Command decode( DataInputStream is )
-        throws IOException
-    {
-        MasterProcessCommand command = resolve( is.readInt() );
-        if ( command == null )
-        {
-            return null;
-        }
-        else
-        {
-            int dataLength = is.readInt();
-            if ( dataLength > 0 )
-            {
-                byte[] buffer = new byte[ dataLength ];
-                is.readFully( buffer );
-
-                if ( command.getDataType() == Void.class )
-                {
-                    throw new IOException( format( "Command %s unexpectedly read Void data with length %d.",
-                                                   command, dataLength ) );
-                }
-
-                String data = command.toDataTypeAsString( buffer );
-                return new Command( command, data );
-            }
-            else
-            {
-                return new Command( command );
-            }
-        }
-    }
-
... 7551 lines suppressed ...


[maven-surefire] 03/18: extended few tests with a new alternative of forkNode: TCP

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 56d12dfc986e82546443baa41e481546b86558af
Author: tibordigana <ti...@apache.org>
AuthorDate: Tue Mar 10 21:49:07 2020 +0100

    extended few tests with a new alternative of forkNode: TCP
---
 .../plugin/surefire/AbstractSurefireMojo.java      |  2 +-
 surefire-its/pom.xml                               |  2 +
 .../maven/surefire/its/AbstractFailFastIT.java     | 28 ++++++-
 .../apache/maven/surefire/its/ConsoleOutputIT.java | 97 +++++++++++++---------
 .../apache/maven/surefire/its/FailFastJUnitIT.java | 31 +++----
 .../maven/surefire/its/FailFastTestNgIT.java       | 18 ++--
 .../its/JUnit47RerunFailingTestWithCucumberIT.java | 83 +++++++++++++++---
 .../maven/surefire/its/TestMethodPatternIT.java    | 84 +++++++++++++++++--
 .../src/test/resources/consoleoutput-noisy/pom.xml | 17 ++++
 .../src/test/resources/fail-fast-junit/pom.xml     | 14 ++++
 .../src/test/resources/fail-fast-testng/pom.xml    | 17 ++++
 .../test/resources/junit44-method-pattern/pom.xml  | 17 ++++
 .../pom.xml                                        | 20 +++++
 .../test/resources/junit48-method-pattern/pom.xml  | 14 ++++
 .../resources/testng-method-pattern-after/pom.xml  | 14 ++++
 .../resources/testng-method-pattern-before/pom.xml | 14 ++++
 .../test/resources/testng-method-pattern/pom.xml   | 14 ++++
 17 files changed, 402 insertions(+), 84 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index 7ba312a..b26e981 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -2288,7 +2288,7 @@ public abstract class AbstractSurefireMojo
 
         ForkNodeFactory forkNode = getForkNodeFactory();
 
-        getConsoleLogger().debug( "Found implementation of fork node factory: " + forkNode.getClass() );
+        getConsoleLogger().debug( "Found implementation of fork node factory: " + forkNode.getClass().getName() );
 
         if ( canExecuteProviderWithModularPath( platform ) )
         {
diff --git a/surefire-its/pom.xml b/surefire-its/pom.xml
index a49244f..42e3ec5 100644
--- a/surefire-its/pom.xml
+++ b/surefire-its/pom.xml
@@ -193,6 +193,8 @@
                         <maxParallelTestThreads>3</maxParallelTestThreads>
                     </systemPropertyVariables>
                     <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                    <enableProcessChecker>native</enableProcessChecker>
+                    <shutdown>kill</shutdown>
                 </configuration>
                 <dependencies>
                     <dependency>
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/AbstractFailFastIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/AbstractFailFastIT.java
index 507a08a..69964f4 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/AbstractFailFastIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/AbstractFailFastIT.java
@@ -29,6 +29,8 @@ import org.junit.runner.RunWith;
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
 import static org.junit.runners.Parameterized.Parameter;
 
 /**
@@ -41,6 +43,12 @@ import static org.junit.runners.Parameterized.Parameter;
 public abstract class AbstractFailFastIT
     extends SurefireJUnit4IntegrationTestCase
 {
+    private static final String LEGACY_FORK_NODE =
+        "org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory";
+
+    private static final String SUREFIRE_FORK_NODE =
+        "org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory";
+
     @Parameter( 0 )
     @SuppressWarnings( "checkstyle:visibilitymodifier" )
     public String description;
@@ -69,16 +77,26 @@ public abstract class AbstractFailFastIT
     @SuppressWarnings( "checkstyle:visibilitymodifier" )
     public int skipped;
 
+    @Parameter( 7 )
+    @SuppressWarnings( "checkstyle:visibilitymodifier" )
+    public boolean useProcessPipes;
+
     protected abstract String withProvider();
 
     private OutputValidator prepare( String description, String profile, Map<String, String> properties )
     {
         MavenLauncher launcher = unpack( "/fail-fast-" + withProvider(), "_" + description )
-            .maven();
+            .maven()
+            .debugLogging();
 
         if ( profile != null )
         {
-            launcher.addGoal( "-P " + profile );
+            launcher.activateProfile( profile );
+        }
+
+        if ( !useProcessPipes )
+        {
+            launcher.activateProfile( "tcp" );
         }
 
         if ( failures != 0 || errors != 0 )
@@ -99,9 +117,11 @@ public abstract class AbstractFailFastIT
     }
 
     @Test
-    public void test()
+    public void test() throws Exception
     {
+        String cls = useProcessPipes ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
         prepare( description, profile, properties )
-            .assertTestSuiteResults( total, errors, failures, skipped );
+            .assertTestSuiteResults( total, errors, failures, skipped )
+            .assertThatLogLine( containsString( "Found implementation of fork node factory: " + cls ), equalTo( 1 ) );
     }
 }
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
index 430441b..21b92b9 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
@@ -32,6 +32,8 @@ import org.junit.runners.Parameterized.Parameters;
 import java.util.ArrayList;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
 
 /**
  * Basic suite test using all known versions of JUnit 4.x
@@ -42,6 +44,12 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 public class ConsoleOutputIT
     extends SurefireJUnit4IntegrationTestCase
 {
+    private static final String LEGACY_FORK_NODE =
+        "org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory";
+
+    private static final String SUREFIRE_FORK_NODE =
+        "org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory";
+
     @Parameters
     public static Iterable<Object[]> data()
     {
@@ -56,58 +64,47 @@ public class ConsoleOutputIT
     public String profileId;
 
     @Test
-    public void properNewlinesAndEncodingWithDefaultEncodings()
+    public void properNewlinesAndEncodingWithDefaultEncodings() throws Exception
     {
-        SurefireLauncher launcher =
-            unpack( "/consoleOutput", profileId == null ? "" : profileId )
-                .forkOnce();
-
-        if ( profileId != null )
-        {
-            launcher.activateProfile( "tcp" );
-        }
-
-        OutputValidator outputValidator = launcher.executeTest();
-
-        validate( outputValidator, profileId == null );
+        OutputValidator outputValidator = unpack().forkOnce().executeTest();
+        validate( outputValidator, profileId == null, true );
     }
 
     @Test
-    public void properNewlinesAndEncodingWithDifferentEncoding()
+    public void properNewlinesAndEncodingWithDifferentEncoding() throws Exception
     {
-        SurefireLauncher launcher =
-            unpack( "/consoleOutput", profileId == null ? "" : profileId )
+        OutputValidator outputValidator = unpack()
                 .forkOnce()
-                .argLine( "-Dfile.encoding=UTF-16" );
-
-        if ( profileId != null )
-        {
-            launcher.activateProfile( "tcp" );
-        }
-
-        OutputValidator outputValidator = launcher.executeTest();
-
-        validate( outputValidator, profileId == null );
+                .argLine( "-Dfile.encoding=UTF-16" )
+                .executeTest();
+        validate( outputValidator, profileId == null, true );
     }
 
     @Test
-    public void properNewlinesAndEncodingWithoutFork()
+    public void properNewlinesAndEncodingWithoutFork() throws Exception
+    {
+        OutputValidator outputValidator = unpack()
+                .forkNever()
+                .executeTest();
+        validate( outputValidator, false, false );
+    }
+
+    private SurefireLauncher unpack()
     {
         SurefireLauncher launcher =
-            unpack( "/consoleOutput", profileId == null ? "" : profileId )
-                .forkNever();
+            unpack( "/consoleOutput", profileId == null ? "" : "-" + profileId )
+                .debugLogging();
 
         if ( profileId != null )
         {
-            launcher.activateProfile( "tcp" );
+            launcher.activateProfile( profileId );
         }
 
-        OutputValidator outputValidator = launcher.executeTest();
-
-        validate( outputValidator, false );
+        return launcher;
     }
 
-    private void validate( final OutputValidator outputValidator, boolean includeShutdownHook )
+    private void validate( final OutputValidator outputValidator, boolean includeShutdownHook, boolean canFork )
+        throws Exception
     {
         TestFile xmlReportFile = outputValidator.getSurefireReportsXmlFile( "TEST-consoleOutput.Test1.xml" );
         xmlReportFile.assertContainsText( "SoutLine" );
@@ -125,22 +122,44 @@ public class ConsoleOutputIT
             //todo this text should be in null-output.txt
             outputFile.assertContainsText( "Printline in shutdown hook" );
         }
+
+        String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
+
+        if ( canFork )
+        {
+            outputValidator
+                .assertThatLogLine(
+                    containsString( "Found implementation of fork node factory: " + cls ),
+                    equalTo( 1 ) );
+        }
     }
 
     @Test
-    public void largerSoutThanMemory()
+    public void largerSoutThanMemory() throws Exception
     {
         SurefireLauncher launcher =
-            unpack( "consoleoutput-noisy", profileId == null ? "" : "-" + profileId )
+            unpackNoisy()
                 .setMavenOpts( "-Xmx64m" )
                 .sysProp( "thousand", "32000" );
 
+        String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
+
+        launcher.executeTest()
+            .verifyErrorFreeLog()
+            .assertThatLogLine( containsString( "Found implementation of fork node factory: " + cls ), equalTo( 1 ) );
+    }
+
+    private SurefireLauncher unpackNoisy()
+    {
+        SurefireLauncher launcher =
+            unpack( "consoleoutput-noisy", profileId == null ? "" : "-" + profileId )
+                .debugLogging();
+
         if ( profileId != null )
         {
-            launcher.activateProfile( "tcp" );
+            launcher.activateProfile( profileId );
         }
 
-        launcher.executeTest()
-            .verifyErrorFreeLog();
+        return launcher;
     }
 }
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/FailFastJUnitIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/FailFastJUnitIT.java
index 9077c02..b3be032 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/FailFastJUnitIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/FailFastJUnitIT.java
@@ -33,14 +33,14 @@ public class FailFastJUnitIT
     extends AbstractFailFastIT
 {
     @Parameters( name = "{0}" )
-    @SuppressWarnings( "checkstyle:visibilitymodifier" )
+    @SuppressWarnings( { "checkstyle:visibilitymodifier", "checkstyle:linelength" } )
     public static Iterable<Object[]> data()
     {
         /**
          * reuseForks=false is not used because of race conditions and unpredictable commands received by
          * MasterProcessReader, this feature has significant limitation.
          */
-        ArrayList<Object[]> args = new ArrayList<Object[]>();
+        ArrayList<Object[]> args = new ArrayList<>();
         //                        description
         //                                             profile
         //                                                         forkCount,
@@ -50,18 +50,21 @@ public class FailFastJUnitIT
         //                                                                                    failures
         //                                                                                            errors
         //                                                                                                  skipped
-        args.add( new Object[] { "junit4-oneFork-ff1", "junit4",   props( 1, 1, true ),  5,   0,      1,    4 } );
-        args.add( new Object[] { "junit47-oneFork-ff1", "junit47", props( 1, 1, true ),  5,   0,      1,    4 } );
-        args.add( new Object[] { "junit4-oneFork-ff2", "junit4",   props( 1, 2, true ),  5,   0,      2,    3 } );
-        args.add( new Object[] { "junit47-oneFork-ff2", "junit47", props( 1, 2, true ),  5,   0,      2,    3 } );
-        args.add( new Object[] { "junit4-twoForks-ff1", "junit4",  props( 2, 1, true ),  5,   0,      2,    3 } );
-        args.add( new Object[] { "junit47-twoForks-ff1", "junit47", props( 2, 1, true ),  5,   0,      2,    3 } );
-        args.add( new Object[] { "junit4-twoForks-ff2", "junit4",  props( 2, 2, true ),  5,   0,      2,    2 } );
-        args.add( new Object[] { "junit47-twoForks-ff2", "junit47", props( 2, 2, true ),  5,   0,      2,    2 } );
-        args.add( new Object[] { "junit4-oneFork-ff3", "junit4",   props( 1, 3, true ),  5,   0,      2,    0 } );
-        args.add( new Object[] { "junit47-oneFork-ff3", "junit47", props( 1, 3, true ),  5,   0,      2,    0 } );
-        args.add( new Object[] { "junit4-twoForks-ff3", "junit4",  props( 2, 3, true ),  5,   0,      2,    0 } );
-        args.add( new Object[] { "junit47-twoForks-ff3", "junit47", props( 2, 3, true ),  5,   0,      2,    0 } );
+        //                                                                                                        pipes
+        args.add( new Object[] { "junit4-oneFork-ff1", "junit4",   props( 1, 1, true ),  5,   0,      1,    4, true } );
+        args.add( new Object[] { "junit47-oneFork-ff1", "junit47", props( 1, 1, true ),  5,   0,      1,    4, true } );
+        args.add( new Object[] { "junit4-oneFork-ff2", "junit4",   props( 1, 2, true ),  5,   0,      2,    3, true } );
+        args.add( new Object[] { "junit47-oneFork-ff2", "junit47", props( 1, 2, true ),  5,   0,      2,    3, true } );
+        args.add( new Object[] { "junit4-twoForks-ff1", "junit4",  props( 2, 1, true ),  5,   0,      2,    3, true } );
+        args.add( new Object[] { "junit47-twoForks-ff1", "junit47", props( 2, 1, true ),  5,   0,      2,    3, true } );
+        args.add( new Object[] { "junit4-twoForks-ff2", "junit4",  props( 2, 2, true ),  5,   0,      2,    2, true } );
+        args.add( new Object[] { "junit4-twoForks-ff2-tcp", "junit4",  props( 2, 2, true ),  5,   0,      2,    2, false } );
+        args.add( new Object[] { "junit47-twoForks-ff2", "junit47", props( 2, 2, true ),  5,   0,      2,    2, true } );
+        args.add( new Object[] { "junit4-oneFork-ff3", "junit4",   props( 1, 3, true ),  5,   0,      2,    0, true } );
+        args.add( new Object[] { "junit47-oneFork-ff3", "junit47", props( 1, 3, true ),  5,   0,      2,    0, true } );
+        args.add( new Object[] { "junit4-twoForks-ff3", "junit4",  props( 2, 3, true ),  5,   0,      2,    0, true } );
+        args.add( new Object[] { "junit47-twoForks-ff3", "junit47", props( 2, 3, true ),  5,   0,      2,    0, true } );
+        args.add( new Object[] { "junit47-twoForks-ff3-tcp", "junit47", props( 2, 3, true ),  5,   0,      2,    0, false } );
         /*args.add( new Object[] { "junit4-twoForks-ff1x","junit4",  props( 2, 1, false ), 5,   0,      2,    3 } );
         args.add( new Object[] { "junit47-twoForks-ff1x","junit47",props( 2, 1, false ), 5,   0,      2,    3 } );
         args.add( new Object[] { "junit4-twoForks-ff2x","junit4",  props( 2, 2, false ), 5,   0,      2,    2 } );
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/FailFastTestNgIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/FailFastTestNgIT.java
index 0ee2606..e282a21 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/FailFastTestNgIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/FailFastTestNgIT.java
@@ -34,13 +34,14 @@ public class FailFastTestNgIT
 {
 
     @Parameters( name = "{0}" )
+    @SuppressWarnings( "checkstyle:linelength" )
     public static Iterable<Object[]> data()
     {
         /**
          * reuseForks=false is not used because of race conditions and unpredictable commands received by
          * MasterProcessReader, this feature has significant limitation.
          */
-        ArrayList<Object[]> args = new ArrayList<Object[]>();
+        ArrayList<Object[]> args = new ArrayList<>();
         //                        description
         //                                             profile
         //                                                       forkCount,
@@ -50,12 +51,15 @@ public class FailFastTestNgIT
         //                                                                                    failures
         //                                                                                            errors
         //                                                                                                  skipped
-        args.add( new Object[] { "testng-oneFork-ff1", null,     props( 1, 1, true ),    5,   1,      0,    4 } );
-        args.add( new Object[] { "testng-oneFork-ff2", null,     props( 1, 2, true ),    5,   2,      0,    3 } );
-        args.add( new Object[] { "testng-twoForks-ff1", null,    props( 2, 1, true ),    5,   2,      0,    3 } );
-        args.add( new Object[] { "testng-twoForks-ff2", null,    props( 2, 2, true ),    5,   2,      0,    2 } );
-        args.add( new Object[] { "testng-oneFork-ff3", null,     props( 1, 3, true ),    5,   2,      0,    0 } );
-        args.add( new Object[] { "testng-twoForks-ff3", null,    props( 2, 3, true ),    5,   2,      0,    0 } );
+        //                                                                                                        pipes
+        args.add( new Object[] { "testng-oneFork-ff1", null,     props( 1, 1, true ),    5,   1,      0,    4, true } );
+        args.add( new Object[] { "testng-oneFork-ff2", null,     props( 1, 2, true ),    5,   2,      0,    3, true } );
+        args.add( new Object[] { "testng-twoForks-ff1", null,    props( 2, 1, true ),    5,   2,      0,    3, true } );
+        args.add( new Object[] { "testng-twoForks-ff2", null,    props( 2, 2, true ),    5,   2,      0,    2, true } );
+        args.add( new Object[] { "testng-twoForks-ff2-tcp", null, props( 2, 2, true ),    5,   2,      0,    2, false } );
+        args.add( new Object[] { "testng-oneFork-ff3", null,     props( 1, 3, true ),    5,   2,      0,    0, true } );
+        args.add( new Object[] { "testng-twoForks-ff3", null,    props( 2, 3, true ),    5,   2,      0,    0, true } );
+        args.add( new Object[] { "testng-twoForks-ff3-tcp", null, props( 2, 3, true ),    5,   2,      0,    0, false } );
         /*args.add( new Object[] { "testng-twoForks-ff1x", null,   props( 2, 1, false ),   5,   2,      0,    3 } );
         args.add( new Object[] { "testng-twoForks-ff2x", null,   props( 2, 2, false ),   5,   2,      0,    2 } );*/
         return args;
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit47RerunFailingTestWithCucumberIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit47RerunFailingTestWithCucumberIT.java
index 6253cdd..94a33c6 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit47RerunFailingTestWithCucumberIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit47RerunFailingTestWithCucumberIT.java
@@ -19,9 +19,18 @@ package org.apache.maven.surefire.its;
  * under the License.
  */
 
+import com.googlecode.junittoolbox.ParallelParameterized;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
 
 /**
  * Tests using the JUnit 47 provider to rerun failing tests with the cucumber runner. The main problem that the junit4
@@ -30,35 +39,89 @@ import org.junit.Test;
  *
  * @author mpkorstanje
  */
+@RunWith( ParallelParameterized.class )
 public class JUnit47RerunFailingTestWithCucumberIT extends SurefireJUnit4IntegrationTestCase
 {
+    private static final String LEGACY_FORK_NODE =
+        "org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory";
+
+    private static final String SUREFIRE_FORK_NODE =
+        "org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory";
+
+    @Parameters
+    public static Iterable<Object[]> data()
+    {
+        ArrayList<Object[]> args = new ArrayList<>();
+        args.add( new Object[] { "tcp" } );
+        args.add( new Object[] { null } );
+        return args;
+    }
+
+    @Parameter
+    @SuppressWarnings( "checkstyle:visibilitymodifier" )
+    public String profileId;
 
     private SurefireLauncher unpack()
     {
-        return unpack( "junit47-rerun-failing-tests-with-cucumber" ).setJUnitVersion( "4.13" );
+        SurefireLauncher launcher =
+            unpack( "junit47-rerun-failing-tests-with-cucumber", profileId == null ? "" : "-" + profileId )
+            .setJUnitVersion( "4.13" );
+
+        if ( profileId != null )
+        {
+            launcher.activateProfile( profileId );
+        }
+
+        return launcher;
     }
 
     @Test
     public void testRerunFailingErrorTestsFalse()
+        throws Exception
     {
-        unpack().maven().addGoal(
-                "-Dsurefire.rerunFailingTestsCount=" + 0 ).withFailure().executeTest().assertTestSuiteResults( 1, 0, 1,
-                0, 0 );
+        String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
+        unpack()
+            .debugLogging()
+            .maven()
+            .sysProp( "surefire.rerunFailingTestsCount", 0 )
+            .withFailure()
+            .executeTest()
+            .assertTestSuiteResults( 1, 0, 1, 0, 0 )
+            .assertThatLogLine(
+                containsString( "Found implementation of fork node factory: " + cls ),
+                equalTo( 1 ) );
     }
 
     @Test
     public void testRerunFailingErrorTestsWithOneRetry()
+        throws Exception
     {
-        unpack().maven().addGoal(
-                "-Dsurefire.rerunFailingTestsCount=" + 1 ).withFailure().executeTest().assertTestSuiteResults( 1, 0, 1,
-                0, 0 );
+        String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
+        unpack()
+            .debugLogging()
+            .maven()
+            .sysProp( "surefire.rerunFailingTestsCount", 1 )
+            .withFailure()
+            .executeTest()
+            .assertTestSuiteResults( 1, 0, 1, 0, 0 )
+            .assertThatLogLine(
+                containsString( "Found implementation of fork node factory: " + cls ),
+                equalTo( 1 ) );
     }
 
     @Test
     public void testRerunFailingErrorTestsTwoRetry()
+        throws Exception
     {
-        unpack().maven().addGoal( "-Dsurefire.rerunFailingTestsCount=" + 2 ).executeTest().assertTestSuiteResults( 1, 0,
-                0, 0, 2 );
+        String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
+        unpack()
+            .maven()
+            .debugLogging()
+            .sysProp( "surefire.rerunFailingTestsCount", 2 )
+            .executeTest()
+            .assertTestSuiteResults( 1, 0, 0, 0, 2 )
+            .assertThatLogLine(
+                containsString( "Found implementation of fork node factory: " + cls ),
+                equalTo( 1 ) );
     }
-
 }
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/TestMethodPatternIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/TestMethodPatternIT.java
index 917ef93..35ff272 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/TestMethodPatternIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/TestMethodPatternIT.java
@@ -19,29 +19,64 @@ package org.apache.maven.surefire.its;
  * under the License.
  */
 
+import com.googlecode.junittoolbox.ParallelParameterized;
 import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+
 /**
  * Test project using -Dtest=mtClass#myMethod
  *
  * @author Olivier Lamy
  */
+@RunWith( ParallelParameterized.class )
 public class TestMethodPatternIT
     extends SurefireJUnit4IntegrationTestCase
 {
     private static final String RUNNING_WITH_PROVIDER47 = "parallel='none', perCoreThreadCount=true, threadCount=0";
 
-    public OutputValidator runMethodPattern( String projectName, Map<String, String> props, String... goals )
+    private static final String LEGACY_FORK_NODE =
+        "org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory";
+
+    private static final String SUREFIRE_FORK_NODE =
+        "org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory";
+
+    @Parameters
+    public static Iterable<Object[]> data()
+    {
+        ArrayList<Object[]> args = new ArrayList<>();
+        args.add( new Object[] { "tcp" } );
+        args.add( new Object[] { null } );
+        return args;
+    }
+
+    @Parameter
+    @SuppressWarnings( "checkstyle:visibilitymodifier" )
+    public String profileId;
+
+    private OutputValidator runMethodPattern( String projectName, Map<String, String> props, String... goals )
+        throws Exception
     {
-        SurefireLauncher launcher = unpack( projectName );
+        SurefireLauncher launcher = unpack( projectName, profileId == null ? "" : "-" + profileId );
+
+        if ( profileId != null )
+        {
+            launcher.activateProfile( profileId );
+        }
+
         for ( Entry<String, String> entry : props.entrySet() )
         {
             launcher.sysProp( entry.getKey(), entry.getValue() );
@@ -50,41 +85,60 @@ public class TestMethodPatternIT
         {
             launcher.addGoal( goal );
         }
+        String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
         return launcher.showErrorStackTraces().debugLogging()
             .executeTest()
-            .assertTestSuiteResults( 2, 0, 0, 0 );
+            .assertTestSuiteResults( 2, 0, 0, 0 )
+            .assertThatLogLine(
+                containsString( "Found implementation of fork node factory: " + cls ),
+                equalTo( 1 ) );
     }
 
     @Test
     public void testJUnit44()
+        throws Exception
     {
         runMethodPattern( "junit44-method-pattern", Collections.<String, String>emptyMap() );
     }
 
     @Test
     public void testJUnit48Provider4()
+        throws Exception
     {
         runMethodPattern( "junit48-method-pattern", Collections.<String, String>emptyMap(), "-P surefire-junit4" );
     }
 
     @Test
     public void testJUnit48Provider47()
+        throws Exception
     {
         runMethodPattern( "junit48-method-pattern", Collections.<String, String>emptyMap(), "-P surefire-junit47" )
             .verifyTextInLog( RUNNING_WITH_PROVIDER47 );
     }
 
     @Test
-    public void testJUnit48WithCategoryFilter()
+    public void testJUnit48WithCategoryFilter() throws Exception
     {
-        unpack( "junit48-method-pattern" )
+        String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
+        SurefireLauncher launcher = unpack( "junit48-method-pattern", profileId == null ? "" : "-" + profileId );
+
+        if ( profileId != null )
+        {
+            launcher.activateProfile( profileId );
+        }
+
+        launcher.debugLogging()
             .addGoal( "-Dgroups=junit4.SampleCategory" )
             .executeTest()
-            .assertTestSuiteResults( 1, 0, 0, 0 );
+            .assertTestSuiteResults( 1, 0, 0, 0 )
+            .assertThatLogLine(
+                containsString( "Found implementation of fork node factory: " + cls ),
+                equalTo( 1 ) );
     }
 
     @Test
     public void testTestNgMethodBefore()
+        throws Exception
     {
         Map<String, String> props = new HashMap<>();
         props.put( "testNgVersion", "5.7" );
@@ -94,6 +148,7 @@ public class TestMethodPatternIT
 
     @Test
     public void testTestNGMethodPattern()
+        throws Exception
     {
         Map<String, String> props = new HashMap<>();
         props.put( "testNgVersion", "5.7" );
@@ -102,14 +157,25 @@ public class TestMethodPatternIT
     }
 
     @Test
-    public void testMethodPatternAfter()
+    public void testMethodPatternAfter() throws Exception
     {
-        unpack( "testng-method-pattern-after" )
+        String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
+        SurefireLauncher launcher = unpack( "testng-method-pattern-after", profileId == null ? "" : "-" + profileId );
+
+        if ( profileId != null )
+        {
+            launcher.activateProfile( profileId );
+        }
+
+        launcher.debugLogging()
                 .sysProp( "testNgVersion", "5.7" )
                 .sysProp( "testNgClassifier", "jdk15" )
                 .executeTest()
                 .verifyErrorFree( 2 )
-                .verifyTextInLog( "Called tearDown" );
+                .verifyTextInLog( "Called tearDown" )
+                .assertThatLogLine(
+                    containsString( "Found implementation of fork node factory: " + cls ),
+                    equalTo( 1 ) );
     }
 
 }
diff --git a/surefire-its/src/test/resources/consoleoutput-noisy/pom.xml b/surefire-its/src/test/resources/consoleoutput-noisy/pom.xml
index e80b978..6fd7013 100644
--- a/surefire-its/src/test/resources/consoleoutput-noisy/pom.xml
+++ b/surefire-its/src/test/resources/consoleoutput-noisy/pom.xml
@@ -60,4 +60,21 @@
       <threadCount>4</threadCount>
     </properties>
 
+    <profiles>
+        <profile>
+            <id>tcp</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
 </project>
diff --git a/surefire-its/src/test/resources/fail-fast-junit/pom.xml b/surefire-its/src/test/resources/fail-fast-junit/pom.xml
index f46d4ea..0556f4c 100644
--- a/surefire-its/src/test/resources/fail-fast-junit/pom.xml
+++ b/surefire-its/src/test/resources/fail-fast-junit/pom.xml
@@ -100,6 +100,20 @@
         </plugins>
       </build>
     </profile>
+      <profile>
+          <id>tcp</id>
+          <build>
+              <plugins>
+                  <plugin>
+                      <groupId>org.apache.maven.plugins</groupId>
+                      <artifactId>maven-surefire-plugin</artifactId>
+                      <configuration>
+                          <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                      </configuration>
+                  </plugin>
+              </plugins>
+          </build>
+      </profile>
   </profiles>
 
 </project>
diff --git a/surefire-its/src/test/resources/fail-fast-testng/pom.xml b/surefire-its/src/test/resources/fail-fast-testng/pom.xml
index d52ad12..ed07d8a 100644
--- a/surefire-its/src/test/resources/fail-fast-testng/pom.xml
+++ b/surefire-its/src/test/resources/fail-fast-testng/pom.xml
@@ -56,4 +56,21 @@
     </plugins>
   </build>
 
+    <profiles>
+        <profile>
+            <id>tcp</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
 </project>
diff --git a/surefire-its/src/test/resources/junit44-method-pattern/pom.xml b/surefire-its/src/test/resources/junit44-method-pattern/pom.xml
index e9ac59e..e3db519 100644
--- a/surefire-its/src/test/resources/junit44-method-pattern/pom.xml
+++ b/surefire-its/src/test/resources/junit44-method-pattern/pom.xml
@@ -57,4 +57,21 @@
     </plugins>
   </build>
 
+    <profiles>
+        <profile>
+            <id>tcp</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
 </project>
diff --git a/surefire-its/src/test/resources/junit47-rerun-failing-tests-with-cucumber/pom.xml b/surefire-its/src/test/resources/junit47-rerun-failing-tests-with-cucumber/pom.xml
index 24ee294..d58223c 100644
--- a/surefire-its/src/test/resources/junit47-rerun-failing-tests-with-cucumber/pom.xml
+++ b/surefire-its/src/test/resources/junit47-rerun-failing-tests-with-cucumber/pom.xml
@@ -42,6 +42,9 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <version>${surefire.version}</version>
+                <configuration>
+                    <forkMode>once</forkMode>
+                </configuration>
                 <dependencies>
                     <dependency>
                         <groupId>org.apache.maven.surefire</groupId>
@@ -74,4 +77,21 @@
         </dependency>
     </dependencies>
 
+    <profiles>
+        <profile>
+            <id>tcp</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
 </project>
diff --git a/surefire-its/src/test/resources/junit48-method-pattern/pom.xml b/surefire-its/src/test/resources/junit48-method-pattern/pom.xml
index 8689549..40227c3 100644
--- a/surefire-its/src/test/resources/junit48-method-pattern/pom.xml
+++ b/surefire-its/src/test/resources/junit48-method-pattern/pom.xml
@@ -98,6 +98,20 @@
         </plugins>
       </build>
     </profile>
+      <profile>
+          <id>tcp</id>
+          <build>
+              <plugins>
+                  <plugin>
+                      <groupId>org.apache.maven.plugins</groupId>
+                      <artifactId>maven-surefire-plugin</artifactId>
+                      <configuration>
+                          <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                      </configuration>
+                  </plugin>
+              </plugins>
+          </build>
+      </profile>
   </profiles>
 
 </project>
diff --git a/surefire-its/src/test/resources/testng-method-pattern-after/pom.xml b/surefire-its/src/test/resources/testng-method-pattern-after/pom.xml
index 41768cd..c894f12 100644
--- a/surefire-its/src/test/resources/testng-method-pattern-after/pom.xml
+++ b/surefire-its/src/test/resources/testng-method-pattern-after/pom.xml
@@ -61,6 +61,20 @@
         </dependency>
       </dependencies>
     </profile>
+      <profile>
+          <id>tcp</id>
+          <build>
+              <plugins>
+                  <plugin>
+                      <groupId>org.apache.maven.plugins</groupId>
+                      <artifactId>maven-surefire-plugin</artifactId>
+                      <configuration>
+                          <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                      </configuration>
+                  </plugin>
+              </plugins>
+          </build>
+      </profile>
   </profiles>
   
   <build>
diff --git a/surefire-its/src/test/resources/testng-method-pattern-before/pom.xml b/surefire-its/src/test/resources/testng-method-pattern-before/pom.xml
index 41768cd..c894f12 100644
--- a/surefire-its/src/test/resources/testng-method-pattern-before/pom.xml
+++ b/surefire-its/src/test/resources/testng-method-pattern-before/pom.xml
@@ -61,6 +61,20 @@
         </dependency>
       </dependencies>
     </profile>
+      <profile>
+          <id>tcp</id>
+          <build>
+              <plugins>
+                  <plugin>
+                      <groupId>org.apache.maven.plugins</groupId>
+                      <artifactId>maven-surefire-plugin</artifactId>
+                      <configuration>
+                          <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                      </configuration>
+                  </plugin>
+              </plugins>
+          </build>
+      </profile>
   </profiles>
   
   <build>
diff --git a/surefire-its/src/test/resources/testng-method-pattern/pom.xml b/surefire-its/src/test/resources/testng-method-pattern/pom.xml
index 50b08bf..905a56c 100644
--- a/surefire-its/src/test/resources/testng-method-pattern/pom.xml
+++ b/surefire-its/src/test/resources/testng-method-pattern/pom.xml
@@ -61,6 +61,20 @@
         </dependency>
       </dependencies>
     </profile>
+      <profile>
+          <id>tcp</id>
+          <build>
+              <plugins>
+                  <plugin>
+                      <groupId>org.apache.maven.plugins</groupId>
+                      <artifactId>maven-surefire-plugin</artifactId>
+                      <configuration>
+                          <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                      </configuration>
+                  </plugin>
+              </plugins>
+          </build>
+      </profile>
   </profiles>  
   
   


[maven-surefire] 17/18: name of the thread ends with dash "-"

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 1d726debffda10ed73409b1786c4a9386ac82bac
Author: tibordigana <ti...@apache.org>
AuthorDate: Tue Apr 7 09:56:02 2020 +0200

    name of the thread ends with dash "-"
---
 .../org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java  | 2 +-
 .../apache/maven/plugin/surefire/extensions/SurefireForkChannel.java    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
index a554edf..d490693 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
@@ -78,7 +78,7 @@ final class LegacyForkChannel extends ForkChannel
                                                    @Nonnull CountdownCloseable countdownCloseable,
                                                    ReadableByteChannel stdOut )
     {
-        return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", stdOut,
+        return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread", stdOut,
             eventHandler, countdownCloseable, logger );
     }
 
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 93607fb..e741a68 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
@@ -154,7 +154,7 @@ final class SurefireForkChannel extends ForkChannel
                                                    ReadableByteChannel stdOut )
     {
         ReadableByteChannel channel = newBufferedChannel( newInputStream( worker ) );
-        return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", channel,
+        return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread", channel,
             eventHandler, countdownCloseable, logger );
     }
 


[maven-surefire] 11/18: fixed performance problem in TCP/Pipes communication (we do NOT flush every time, used buffered channels, used Async Sockets instead of blocking NIO Sockets)

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit e93e5c68e3442f4c4bc0b1284a5bec0ba87db5f2
Author: tibordigana <ti...@apache.org>
AuthorDate: Fri Apr 3 11:20:50 2020 +0200

    fixed performance problem in TCP/Pipes communication (we do NOT flush every time, used buffered channels, used Async Sockets instead of blocking NIO Sockets)
---
 .../surefire/booterclient/output/ForkClient.java   |  13 +-
 .../surefire/extensions/EventConsumerThread.java   |   4 +-
 .../surefire/extensions/SurefireForkChannel.java   |  60 ++++--
 .../booterclient/ForkingRunListenerTest.java       |  12 +-
 .../TestLessInputStreamBuilderTest.java            |   2 +-
 .../TestProvidingInputStreamTest.java              |   2 +-
 .../booterclient/output/ForkClientTest.java        |   2 +-
 .../surefire/extensions/AsyncSocketTest.java       | 228 +++++++++++++++++++++
 .../maven/plugin/surefire/extensions/E2ETest.java  | 105 +++++++---
 .../extensions/ForkedProcessEventNotifierTest.java |  76 ++++---
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   4 +
 .../maven/surefire/extensions/ForkChannelTest.java |  33 ++-
 .../maven/surefire/booter/BaseProviderFactory.java |   7 -
 .../surefire/booter/ForkingReporterFactory.java    |   1 -
 .../maven/surefire/booter/ForkingRunListener.java  |   1 -
 .../MasterProcessChannelDecoder.java               |   4 +-
 .../MasterProcessChannelEncoder.java               |   4 +-
 .../surefire/providerapi/ProviderParameters.java   |   2 -
 .../AbstractNoninterruptibleWritableChannel.java   |  28 +--
 .../maven/surefire/util/internal/Channels.java     | 188 ++++++++++++++---
 .../util/internal/WritableBufferedByteChannel.java |  36 ++++
 .../surefire/booter/ForkingRunListenerTest.java    |   1 -
 .../surefire/util/internal/ChannelsReaderTest.java |  58 +++++-
 .../surefire/util/internal/ChannelsWriterTest.java |  43 +++-
 .../maven/surefire/booter/CommandReader.java       |   2 -
 .../apache/maven/surefire/booter/ForkedBooter.java |  25 ++-
 .../maven/surefire/booter/LazyTestsToRun.java      |   1 -
 .../spi/LegacyMasterProcessChannelDecoder.java     |   2 +-
 .../spi/LegacyMasterProcessChannelEncoder.java     |  98 +++++----
 ...LegacyMasterProcessChannelProcessorFactory.java |  11 +-
 ...refireMasterProcessChannelProcessorFactory.java |  52 ++++-
 .../maven/surefire/booter/CommandReaderTest.java   |  10 +-
 .../surefire/booter/ForkedBooterMockTest.java      |   2 -
 .../maven/surefire/booter/ForkedBooterTest.java    |   3 +-
 .../spi/LegacyMasterProcessChannelEncoderTest.java |  77 ++++---
 .../extensions/util/CommandlineStreams.java        |   9 +-
 .../spi/MasterProcessChannelProcessorFactory.java  |   4 +-
 .../apache/maven/surefire/its/ConsoleOutputIT.java |  19 +-
 .../src/test/java/consoleOutput/Test1.java         |  20 +-
 .../src/test/java/consoleoutput_noisy/Test3.java   |  52 +++++
 40 files changed, 995 insertions(+), 306 deletions(-)

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 b4de014..bc76dfe 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
@@ -24,7 +24,7 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
@@ -111,6 +111,7 @@ public class ForkClient
         notifier.setStopOnNextTestListener( new StopOnNextTestListener() );
         notifier.setConsoleDebugListener( new DebugListener() );
         notifier.setConsoleWarningListener( new WarningListener() );
+        notifier.setExitErrorEventListener( new ExitErrorEventListener() );
     }
 
     private final class TestSetStartingListener
@@ -303,6 +304,16 @@ public class ForkClient
         }
     }
 
+    private final class ExitErrorEventListener implements ForkedProcessExitErrorListener
+    {
+        @Override
+        public void handle( StackTraceWriter stackTrace )
+        {
+            getOrCreateConsoleLogger()
+                .error( "System Exit has timed out in the forked process " + forkNumber );
+        }
+    }
+
     /**
      * Overridden by a subclass, see {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
      */
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
index b92a238..8b8e2f3 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -19,7 +19,6 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.commons.codec.binary.Base64;
 import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.ForkedProcessEventType;
@@ -51,6 +50,7 @@ import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.report.TestSetReportEntry;
+import org.apache.maven.surefire.shared.codec.binary.Base64;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
@@ -203,7 +203,7 @@ public class EventConsumerThread extends CloseableDaemonThread
 
     private boolean read( ByteBuffer buffer ) throws IOException
     {
-        if ( buffer.hasRemaining() )
+        if ( buffer.hasRemaining() && buffer.position() > 0 )
         {
             return true;
         }
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 3285486..93607fb 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
@@ -28,21 +28,30 @@ import org.apache.maven.surefire.extensions.ForkChannel;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 
 import javax.annotation.Nonnull;
+import java.io.Closeable;
 import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketOption;
-import java.nio.channels.Channel;
+import java.nio.channels.AsynchronousServerSocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
 import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import static java.net.StandardSocketOptions.SO_KEEPALIVE;
 import static java.net.StandardSocketOptions.SO_REUSEADDR;
 import static java.net.StandardSocketOptions.TCP_NODELAY;
-import static java.nio.channels.ServerSocketChannel.open;
+import static java.nio.channels.AsynchronousChannelGroup.withThreadPool;
+import static java.nio.channels.AsynchronousServerSocketChannel.open;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newInputStream;
+import static org.apache.maven.surefire.util.internal.Channels.newOutputStream;
+import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
 
 /**
  * The TCP/IP server accepting only one client connection. The forked JVM connects to the server using the
@@ -60,34 +69,52 @@ import static java.nio.channels.ServerSocketChannel.open;
  */
 final class SurefireForkChannel extends ForkChannel
 {
+    private static final ExecutorService THREAD_POOL = Executors.newCachedThreadPool( newDaemonThreadFactory() );
+
     private final ConsoleLogger logger;
-    private final ServerSocketChannel server;
+    private final AsynchronousServerSocketChannel server;
+    private final String localHost;
     private final int localPort;
-    private volatile SocketChannel channel;
+    private volatile AsynchronousSocketChannel worker;
 
     SurefireForkChannel( int forkChannelId, @Nonnull ConsoleLogger logger ) throws IOException
     {
         super( forkChannelId );
         this.logger = logger;
-        server = open();
+        server = open( withThreadPool( THREAD_POOL ) );
         setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
         InetAddress ip = Inet4Address.getLoopbackAddress();
         server.bind( new InetSocketAddress( ip, 0 ), 1 );
-        localPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort();
+        InetSocketAddress localAddress = (InetSocketAddress) server.getLocalAddress();
+        localHost = localAddress.getHostString();
+        localPort = localAddress.getPort();
     }
 
     @Override
     public void connectToClient() throws IOException
     {
-        if ( channel != null )
+        if ( worker != null )
         {
             throw new IllegalStateException( "already accepted TCP client connection" );
         }
-        channel = server.accept();
+
+        try
+        {
+            worker = server.accept().get();
+        }
+        catch ( InterruptedException e )
+        {
+            throw new IOException( e.getLocalizedMessage(), e );
+        }
+        catch ( ExecutionException e )
+        {
+            throw new IOException( e.getLocalizedMessage(), e.getCause() );
+        }
     }
 
     @SafeVarargs
-    private final void setTrueOptions( SocketOption<Boolean>... options ) throws IOException
+    private final void setTrueOptions( SocketOption<Boolean>... options )
+        throws IOException
     {
         for ( SocketOption<Boolean> option : options )
         {
@@ -101,7 +128,7 @@ final class SurefireForkChannel extends ForkChannel
     @Override
     public String getForkNodeConnectionString()
     {
-        return "tcp://127.0.0.1:" + localPort;
+        return "tcp://" + localHost + ":" + localPort;
     }
 
     @Override
@@ -114,6 +141,10 @@ final class SurefireForkChannel extends ForkChannel
     public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands,
                                                     WritableByteChannel stdIn )
     {
+        // dont use newBufferedChannel here - may cause the command is not sent and the JVM hangs
+        // only newChannel flushes the message
+        // newBufferedChannel does not flush
+        WritableByteChannel channel = newChannel( newOutputStream( worker ) );
         return new StreamFeeder( "commands-fork-" + getForkChannelId(), channel, commands, logger );
     }
 
@@ -122,6 +153,7 @@ final class SurefireForkChannel extends ForkChannel
                                                    @Nonnull CountdownCloseable countdownCloseable,
                                                    ReadableByteChannel stdOut )
     {
+        ReadableByteChannel channel = newBufferedChannel( newInputStream( worker ) );
         return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", channel,
             eventHandler, countdownCloseable, logger );
     }
@@ -129,8 +161,8 @@ final class SurefireForkChannel extends ForkChannel
     @Override
     public void close() throws IOException
     {
-        //noinspection EmptyTryBlock
-        try ( Channel c1 = channel; Channel c2 = server )
+        //noinspection unused,EmptyTryBlock,EmptyTryBlock
+        try ( Closeable c1 = worker; Closeable c2 = server )
         {
             // only close all channels
         }
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 4ca19ad..8aeba39 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
@@ -38,6 +38,7 @@ import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.report.TestSetReportEntry;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 
 import javax.annotation.Nonnull;
 import java.io.ByteArrayInputStream;
@@ -53,7 +54,8 @@ import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import static java.nio.channels.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
 import static org.mockito.Mockito.mock;
 
 /**
@@ -243,10 +245,11 @@ public class ForkingRunListenerTest
         ReportEntry expected = createDefaultReportEntry();
         SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
 
-        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( printStream ) ), false )
+        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newBufferedChannel( printStream ) ), false )
                 .testStarting( expected );
 
-        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( anotherPrintStream ) ), false )
+        new ForkingRunListener(
+            new LegacyMasterProcessChannelEncoder( newBufferedChannel( anotherPrintStream ) ), false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
@@ -362,7 +365,8 @@ public class ForkingRunListenerTest
 
     private RunListener createForkingRunListener()
     {
-        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( printStream ) ), false );
+        WritableBufferedByteChannel channel = (WritableBufferedByteChannel) newChannel( printStream );
+        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( channel ), false );
     }
 
     private class StandardTestRun
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
index 2aab4c2..7c89492 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.MasterProcessCommand;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
index 1d348ce..3e2023e 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.plugin.surefire.extensions.StreamFeeder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
 import org.junit.Test;
 
 import java.io.IOException;
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 60f6872..453e735 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
@@ -66,7 +66,7 @@ import java.util.concurrent.TimeUnit;
 
 import static java.nio.channels.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.codec.binary.Base64.encodeBase64String;
+import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_DEBUG;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_ERR;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_INFO;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java
new file mode 100644
index 0000000..88cba07
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java
@@ -0,0 +1,228 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.util.internal.DaemonThreadFactory;
+import org.junit.Test;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketOption;
+import java.nio.channels.AsynchronousChannelGroup;
+import java.nio.channels.AsynchronousServerSocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static java.net.StandardSocketOptions.SO_KEEPALIVE;
+import static java.net.StandardSocketOptions.SO_REUSEADDR;
+import static java.net.StandardSocketOptions.TCP_NODELAY;
+import static org.apache.maven.surefire.util.internal.Channels.newInputStream;
+import static org.apache.maven.surefire.util.internal.Channels.newOutputStream;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ *
+ */
+public class AsyncSocketTest
+{
+    private static final String LONG_STRING =
+        "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+
+    private final CountDownLatch barrier = new CountDownLatch( 1 );
+    private final AtomicLong writeTime = new AtomicLong();
+    private final AtomicLong readTime = new AtomicLong();
+
+    private volatile InetSocketAddress address;
+
+    @Test
+    public void test() throws Exception
+    {
+        int forks = 2;
+        ThreadFactory factory = DaemonThreadFactory.newDaemonThreadFactory();
+        ExecutorService executorService = Executors.newCachedThreadPool( factory );
+        if ( executorService instanceof ThreadPoolExecutor )
+        {
+            ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
+            threadPoolExecutor.setCorePoolSize( Math.min( forks, Runtime.getRuntime().availableProcessors() ) );
+            threadPoolExecutor.prestartCoreThread();
+        }
+        AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool( executorService );
+        AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open( group );
+        setTrueOptions( server, SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
+        server.bind( null, 1 );
+        address = (InetSocketAddress) server.getLocalAddress();
+
+        System.gc();
+        TimeUnit.SECONDS.sleep( 3L );
+
+        Thread tc = new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    client();
+                }
+                catch ( Exception e )
+                {
+                    e.printStackTrace();
+                }
+            }
+        };
+        tc.setDaemon( true );
+        tc.start();
+
+        Future<AsynchronousSocketChannel> acceptFuture = server.accept();
+        AsynchronousSocketChannel worker = acceptFuture.get();
+        if ( !worker.isOpen() )
+        {
+            throw new IOException( "client socket closed" );
+        }
+        final InputStream is = newInputStream( worker );
+        final OutputStream os = new BufferedOutputStream( newOutputStream( worker ), 64 * 1024 );
+
+        Thread tt = new Thread()
+        {
+            public void run()
+            {
+                try
+                {
+                    byte[] b = new byte[1024];
+                    is.read( b );
+                }
+                catch ( Exception e )
+                {
+                    //e.printStackTrace();
+                }
+
+            }
+        };
+        tt.setName( "fork-1-event-thread-" );
+        tt.setDaemon( true );
+        tt.start();
+
+        Thread t = new Thread()
+        {
+            @SuppressWarnings( "checkstyle:magicnumber" )
+            public void run()
+            {
+                try
+                {
+                    byte[] data = LONG_STRING.getBytes( StandardCharsets.US_ASCII );
+                    long t1 = System.currentTimeMillis();
+                    int i = 0;
+                    for ( ; i < 320_000; i++ )
+                    {
+                        os.write( data );
+                        long t2 = System.currentTimeMillis();
+                        long spent = t2 - t1;
+
+                        if ( i % 100_000 == 0 )
+                        {
+                            System.out.println( spent + "ms: " + i );
+                        }
+                    }
+                    os.flush();
+                    long spent = System.currentTimeMillis() - t1;
+                    writeTime.set( spent );
+                    System.out.println( spent + "ms: " + i );
+                }
+                catch ( IOException e )
+                {
+                    e.printStackTrace();
+                }
+
+            }
+        };
+        t.setName( "commands-fork-1" );
+        t.setDaemon( true );
+        t.start();
+
+        barrier.await();
+        tt.join();
+        t.join();
+        tc.join();
+        worker.close();
+        server.close();
+
+        // 160 millis on write using the asynchronous sockets
+        // 320 millis on NIO blocking sockets
+        assertThat( writeTime.get() )
+            .isLessThan( 1000L );
+
+        // 160 millis on read using the asynchronous sockets
+        // 320 millis on NIO blocking sockets
+        assertThat( readTime.get() )
+            .isLessThan( 1000L );
+    }
+
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    private void client() throws Exception
+    {
+        InetSocketAddress hostAddress = new InetSocketAddress( InetAddress.getLoopbackAddress(), address.getPort() );
+        AsynchronousSocketChannel clientSocketChannel = AsynchronousSocketChannel.open();
+        clientSocketChannel.connect( hostAddress ).get(); // Wait until connection is done.
+        InputStream is = new BufferedInputStream( newInputStream( clientSocketChannel ), 64 * 1024 );
+        List<byte[]> bytes = new ArrayList<>();
+        long t1 = System.currentTimeMillis();
+        for ( int i = 0; i < 320_000; i++ )
+        {
+            byte[] b = new byte[100];
+            is.read( b );
+            bytes.add( b );
+        }
+        long t2 = System.currentTimeMillis();
+        long spent = t2 - t1;
+        readTime.set( spent );
+        System.out.println( new String( bytes.get( bytes.size() - 1 ) ) );
+        System.out.println( "received within " + spent + "ms" );
+        clientSocketChannel.close();
+        barrier.countDown();
+    }
+
+    @SafeVarargs
+    private static void setTrueOptions( AsynchronousServerSocketChannel server, SocketOption<Boolean>... options )
+        throws IOException
+    {
+        for ( SocketOption<Boolean> option : options )
+        {
+            if ( server.supportedOptions().contains( option ) )
+            {
+                server.setOption( option, true );
+            }
+        }
+    }
+}
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 ec76a8c..c22da94 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
@@ -1,23 +1,47 @@
 package org.apache.maven.plugin.surefire.extensions;
 
+/*
+ * 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.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
-import org.apache.maven.surefire.report.ConsoleOutputCapture;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.junit.Test;
 
 import javax.annotation.Nonnull;
 import java.io.Closeable;
-import java.io.IOException;
-import java.io.PrintStream;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
+import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 
+/**
+ * Simulates the End To End use case where Maven process and Surefire process communicate using the TCP/IP protocol.
+ */
+@SuppressWarnings( "checkstyle:magicnumber" )
 public class E2ETest
 {
     private static final String LONG_STRING =
@@ -27,14 +51,20 @@ public class E2ETest
     public void test() throws Exception
     {
         ConsoleLogger logger = mock( ConsoleLogger.class );
-        SurefireForkChannel server = new SurefireForkChannel(1, logger );
+        final SurefireForkChannel server = new SurefireForkChannel( 1, logger );
 
         final String connection = server.getForkNodeConnectionString();
 
-        SurefireMasterProcessChannelProcessorFactory factory = new SurefireMasterProcessChannelProcessorFactory();
+        final SurefireMasterProcessChannelProcessorFactory factory = new SurefireMasterProcessChannelProcessorFactory();
         factory.connect( connection );
         final MasterProcessChannelEncoder encoder = factory.createEncoder();
 
+        System.gc();
+
+        TimeUnit.SECONDS.sleep( 3L );
+
+        final CountDownLatch awaitHandlerFinished = new CountDownLatch( 2 );
+
         Thread t = new Thread()
         {
             @Override
@@ -49,20 +79,25 @@ public class E2ETest
                     }
                 };
 
-                PrintStream out = System.out;
-                PrintStream err = System.err;
+                //PrintStream out = System.out;
+                //PrintStream err = System.err;
 
-                ConsoleOutputCapture.startCapture( target );
+                //ConsoleOutputCapture.startCapture( target );
 
                 try
                 {
-                    for ( int i = 0; i < 320_000; i++ )
+                    long t1 = System.currentTimeMillis();
+                    for ( int i = 0; i < 400_000; i++ )
                     {
-                        System.out.println( LONG_STRING );
+                        //System.out.println( LONG_STRING );
+                        encoder.stdOut( LONG_STRING, true );
                     }
-                    System.setOut( out );
-                    System.setErr( err );
-                    TimeUnit.MINUTES.sleep( 1L );
+                    long t2 = System.currentTimeMillis();
+                    long spent = t2 - t1;
+                    //System.setOut( out );
+                    //System.setErr( err );
+                    System.out.println( spent + "ms on write" );
+                    awaitHandlerFinished.countDown();
                 }
                 catch ( Exception e )
                 {
@@ -75,31 +110,39 @@ public class E2ETest
 
         server.connectToClient();
 
+        final AtomicLong readTime = new AtomicLong();
+
         EventHandler<Event> h = new EventHandler<Event>()
         {
-            volatile int i;
-            volatile long t1;
+            private final AtomicInteger counter = new AtomicInteger();
+            private volatile long t1;
 
             @Override
             public void handleEvent( @Nonnull Event event )
             {
                 try
                 {
-                    if ( i++ == 0 )
+                    if ( counter.getAndIncrement() == 0 )
                     {
                         t1 = System.currentTimeMillis();
                     }
 
-                    if ( i == 320_000 )
+                    long t2 = System.currentTimeMillis();
+                    long spent = t2 - t1;
+
+                    if ( counter.get() % 100_000 == 0 )
+                    {
+                        System.out.println( spent + "ms: " + counter.get() );
+                    }
+
+                    if ( counter.get() == 320_000 )
                     {
-                        long t2 = System.currentTimeMillis();
-                        TimeUnit.SECONDS.sleep( 1L );
-                        System.out.println( "Forked JVM spent "
-                            + ( t2 - t1 )
-                            + "ms on transferring all lines of the log." );
+                        readTime.set( spent );
+                        System.out.println( spent + "ms on read" );
+                        awaitHandlerFinished.countDown();
                     }
                 }
-                catch ( InterruptedException e )
+                catch ( Exception e )
                 {
                     e.printStackTrace();
                 }
@@ -109,18 +152,24 @@ public class E2ETest
         Closeable c = new Closeable()
         {
             @Override
-            public void close() throws IOException
+            public void close()
             {
-
             }
         };
 
         server.bindEventHandler( h, new CountdownCloseable( c, 1 ), null )
-        .start();
+            .start();
 
-        TimeUnit.SECONDS.sleep( 60L );
+        assertThat( awaitHandlerFinished.await( 30L, TimeUnit.SECONDS ) )
+            .isTrue();
 
         factory.close();
         server.close();
+
+        // 2 seconds while using the encoder/decoder
+        // 160 millis of sending pure data without encoder/decoder
+        assertThat( readTime.get() )
+            .isPositive()
+            .isLessThanOrEqualTo( 3_000L );
     }
 }
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 cb2e382..369abe5 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
@@ -39,6 +39,7 @@ import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.util.internal.ObjectUtils;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
@@ -68,10 +69,11 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import static java.nio.channels.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.copyOfRange;
-import static org.apache.commons.codec.binary.Base64.encodeBase64String;
+import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
 import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -114,9 +116,11 @@ public class ForkedProcessEventNotifierTest
         public void shouldHaveSystemProperty() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             Map<String, String> props = ObjectUtils.systemProps();
             encoder.sendSystemProperties( props );
+            wChannel.close();
 
             ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
             PropertyEventAssertionListener listener = new PropertyEventAssertionListener();
@@ -307,7 +311,8 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendByeEvent() throws Exception
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.bye();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -343,7 +348,8 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendStopOnNextTestEvent() throws Exception
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.stopOnNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -401,7 +407,8 @@ public class ForkedProcessEventNotifierTest
             when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.testFailed( reportEntry, true );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -427,7 +434,8 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendNextTestEvent() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.acquireNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -457,7 +465,8 @@ public class ForkedProcessEventNotifierTest
         public void testConsole() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleInfoLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -483,7 +492,8 @@ public class ForkedProcessEventNotifierTest
         public void testError() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleErrorLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -509,7 +519,8 @@ public class ForkedProcessEventNotifierTest
         public void testErrorWithException() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             Throwable throwable = new Throwable( "msg" );
             encoder.consoleErrorLog( throwable );
 
@@ -538,7 +549,8 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "1", "2", "3" );
             encoder.consoleErrorLog( stackTraceWriter, false );
 
@@ -566,7 +578,8 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleDebugLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -596,7 +609,8 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleWarningLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -622,8 +636,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStream() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( "msg", false );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -649,8 +665,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStreamPrint() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( "", false );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -676,8 +694,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStreamPrintWithNull() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( null, false );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -703,8 +723,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStreamPrintln() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( "", true );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -730,8 +752,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStreamPrintlnWithNull() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( null, true );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -757,8 +781,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdErrStream() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdErr( "msg", false );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -784,8 +810,10 @@ public class ForkedProcessEventNotifierTest
         public void shouldCountSameNumberOfSystemProperties() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.sendSystemProperties( ObjectUtils.systemProps() );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -839,14 +867,15 @@ public class ForkedProcessEventNotifierTest
         public void shouldHandleExit() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
             StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
             when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
             when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
             when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
             when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-            encoder.sendExitEvent( stackTraceWriter, false );
+            encoder.sendExitError( stackTraceWriter, false );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -950,7 +979,8 @@ public class ForkedProcessEventNotifierTest
 
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
             LegacyMasterProcessChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
                     .invoke( encoder, reportEntry, trim );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index a232544..94754a7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -40,7 +40,9 @@ import org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfigu
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClientTest;
+import org.apache.maven.plugin.surefire.extensions.AsyncSocketTest;
 import org.apache.maven.plugin.surefire.extensions.ConsoleOutputReporterTest;
+import org.apache.maven.plugin.surefire.extensions.E2ETest;
 import org.apache.maven.plugin.surefire.extensions.ForkedProcessEventNotifierTest;
 import org.apache.maven.plugin.surefire.extensions.StatelessReporterTest;
 import org.apache.maven.plugin.surefire.extensions.StreamFeederTest;
@@ -108,6 +110,8 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( ForkStarterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkChannelTest.class ) );
         suite.addTest( new JUnit4TestAdapter( StreamFeederTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( E2ETest.class ) );
+        suite.addTest( new JUnit4TestAdapter( AsyncSocketTest.class ) );
         return suite;
     }
 }
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 68f2324..c3f6a8c 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
@@ -35,10 +35,11 @@ import java.net.Socket;
 import java.net.URI;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.fest.assertions.Assertions.assertThat;
 
 /**
@@ -71,43 +72,35 @@ public class ForkChannelTest
             assertThat( uri.getPort() )
                 .isPositive();
 
-            Consumer consumer = new Consumer();
-
-            Client client = new Client( uri.getPort() );
-            client.start();
-
-            channel.connectToClient();
-            SECONDS.sleep( 3L );
-
-            TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
+            final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
             TestLessInputStream commandReader = builder.build();
-
-            channel.bindCommandReader( commandReader, null ).start();
-
-            final AtomicBoolean isCloseableCalled = new AtomicBoolean();
+            final CountDownLatch isCloseableCalled = new CountDownLatch( 1 );
             Closeable closeable = new Closeable()
             {
                 @Override
                 public void close()
                 {
-                    isCloseableCalled.set( true );
+                    isCloseableCalled.countDown();
                 }
             };
             CountdownCloseable cc = new CountdownCloseable( closeable, 1 );
-            channel.bindEventHandler( consumer, cc, null ).start();
+            Consumer consumer = new Consumer();
 
-            SECONDS.sleep( 3L );
+            Client client = new Client( uri.getPort() );
+            client.start();
 
-            commandReader.noop();
+            channel.connectToClient();
+            channel.bindCommandReader( commandReader, null ).start();
+            channel.bindEventHandler( consumer, cc, null ).start();
 
-            SECONDS.sleep( 3L );
+            commandReader.noop();
 
             client.join( TESTCASE_TIMEOUT );
 
             assertThat( hasError.get() )
                 .isFalse();
 
-            assertThat( isCloseableCalled.get() )
+            assertThat( isCloseableCalled.await( TESTCASE_TIMEOUT, MILLISECONDS ) )
                 .isTrue();
 
             assertThat( consumer.lines )
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
index 6ac0ce2..9ae9fbd 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
@@ -21,7 +21,6 @@ package org.apache.maven.surefire.booter;
 
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.providerapi.CommandChainReader;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.DefaultDirectConsoleReporter;
@@ -263,12 +262,6 @@ public class BaseProviderFactory
         this.systemExitTimeout = systemExitTimeout;
     }
 
-    @Override
-    public MasterProcessChannelEncoder getForkedChannelEncoder()
-    {
-        return masterProcessChannelEncoder;
-    }
-
     public void setForkedChannelEncoder( MasterProcessChannelEncoder masterProcessChannelEncoder )
     {
         this.masterProcessChannelEncoder = masterProcessChannelEncoder;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
index 1c6db50..c08b52e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
@@ -19,7 +19,6 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.suite.RunResult;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
index 6148149..2603e8f 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
@@ -20,7 +20,6 @@ package org.apache.maven.surefire.booter;
  */
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.ReportEntry;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelDecoder.java
similarity index 94%
rename from surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelDecoder.java
index 4dc4908..dd06590 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelDecoder.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.providerapi;
+package org.apache.maven.surefire.booter;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,8 +19,6 @@ package org.apache.maven.surefire.providerapi;
  * under the License.
  */
 
-import org.apache.maven.surefire.booter.Command;
-
 import javax.annotation.Nonnull;
 import java.io.IOException;
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
similarity index 95%
rename from surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
index b734b61..062a57c 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.providerapi;
+package org.apache.maven.surefire.booter;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -80,5 +80,5 @@ public interface MasterProcessChannelEncoder
 
     void acquireNextTest();
 
-    void sendExitEvent( StackTraceWriter stackTraceWriter, boolean trimStackTraces );
+    void sendExitError( StackTraceWriter stackTraceWriter, boolean trimStackTraces );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
index 47a6a7e..f620cbf 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
@@ -147,7 +147,5 @@ public interface ProviderParameters
 
     Integer getSystemExitTimeout();
 
-    MasterProcessChannelEncoder getForkedChannelEncoder();
-
     CommandChainReader getCommandReader();
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java
index cb08e34..4c60bce 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java
@@ -19,12 +19,10 @@ package org.apache.maven.surefire.util.internal;
  * under the License.
  */
 
-import java.io.Flushable;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonWritableChannelException;
-import java.nio.channels.WritableByteChannel;
 
 /**
  * The channel used for writes which cannot be implicitly closed after the operational Thread
@@ -32,21 +30,27 @@ import java.nio.channels.WritableByteChannel;
  *
  * @since 3.0.0-M5
  */
-abstract class AbstractNoninterruptibleWritableChannel implements WritableByteChannel, Flushable
+abstract class AbstractNoninterruptibleWritableChannel implements WritableBufferedByteChannel
 {
-    private final boolean flushable;
     private volatile boolean open = true;
 
-    AbstractNoninterruptibleWritableChannel( boolean flushable )
-    {
-        this.flushable = flushable;
-    }
-
     protected abstract void writeImpl( ByteBuffer src ) throws IOException;
     protected abstract void closeImpl() throws IOException;
+    protected abstract void flushImpl() throws IOException;
+
+    @Override
+    public final int write( ByteBuffer src ) throws IOException
+    {
+        return write( src, true );
+    }
 
     @Override
-    public final synchronized int write( ByteBuffer src ) throws IOException
+    public final void writeBuffered( ByteBuffer src ) throws IOException
+    {
+        write( src, false );
+    }
+
+    int write( ByteBuffer src, boolean flush ) throws IOException
     {
         if ( !isOpen() )
         {
@@ -70,9 +74,9 @@ abstract class AbstractNoninterruptibleWritableChannel implements WritableByteCh
             countWrittenBytes = src.remaining();
             writeImpl( src );
             src.position( src.limit() );
-            if ( flushable )
+            if ( flush )
             {
-                flush();
+                flushImpl();
             }
         }
         return countWrittenBytes;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
index 65cb9b4..a536fc6 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
@@ -19,61 +19,195 @@ package org.apache.maven.surefire.util.internal;
  * under the License.
  */
 
+import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
+import java.nio.channels.AsynchronousCloseException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutionException;
 
 import static java.util.Objects.requireNonNull;
 
 /**
- * Converts {@link OutputStream}, {@link java.io.PrintStream}, {@link InputStream}
- * to the Java {@link java.nio.channels.Channel}.
+ * Converts {@link OutputStream}, {@link java.io.PrintStream}, {@link InputStream} to the Java {@link
+ * java.nio.channels.Channel}.
  * <br>
- * We do not use the Java's utility class {@link java.nio.channels.Channels} because the utility
- * closes the stream as soon as the particular Thread is interrupted.
- * If the frameworks (Zookeeper, Netty) interrupts the thread, the communication channels become
- * closed and the JVM hangs. Therefore we developed internal utility which is safe for the Surefire.
+ * We do not use the Java's utility class {@link java.nio.channels.Channels} because the utility closes the stream as
+ * soon as the particular Thread is interrupted. If the frameworks (Zookeeper, Netty) interrupts the thread, the
+ * communication channels become closed and the JVM hangs. Therefore we developed internal utility which is safe for the
+ * Surefire.
  *
  * @since 3.0.0-M5
  */
 public final class Channels
 {
+    private static final int BUFFER_SIZE = 64 * 1024;
+
     private Channels()
     {
         throw new IllegalStateException( "no instantiable constructor" );
     }
 
-    public static WritableByteChannel newChannel( @Nonnull  OutputStream out )
+    public static WritableByteChannel newChannel( @Nonnull OutputStream out )
     {
-        return newChannel( out, false );
+        return newChannel( out, 0 );
     }
 
-    public static WritableByteChannel newFlushableChannel( @Nonnull OutputStream out )
+    public static WritableBufferedByteChannel newBufferedChannel( @Nonnull OutputStream out )
     {
-        return newChannel( out, true );
+        return newChannel( out, BUFFER_SIZE );
     }
 
     public static ReadableByteChannel newChannel( @Nonnull final InputStream is )
     {
-        requireNonNull( is, "the stream should not be null" );
+        return newChannel( is, 0 );
+    }
+
+    public static ReadableByteChannel newBufferedChannel( @Nonnull final InputStream is )
+    {
+        return newChannel( is, BUFFER_SIZE );
+    }
 
-        if ( is instanceof FileInputStream && FileInputStream.class.equals( is.getClass() ) )
+    public static OutputStream newOutputStream( final AsynchronousByteChannel channel )
+    {
+        return new OutputStream()
         {
-            return ( (FileInputStream) is ).getChannel();
-        }
+            @Override
+            public synchronized void write( byte[] b, int off, int len ) throws IOException
+            {
+                if ( off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0 )
+                {
+                    throw new IndexOutOfBoundsException(
+                        "b.length = " + b.length + ", off = " + off + ", len = " + len );
+                }
+                else if ( len > 0 )
+                {
+                    ByteBuffer bb = ByteBuffer.wrap( b, off, len );
+                    while ( bb.hasRemaining() )
+                    {
+                        try
+                        {
+                            channel.write( bb ).get();
+                        }
+                        catch ( ExecutionException e )
+                        {
+                            Throwable t = e.getCause();
+                            throw new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
+                        }
+                        catch ( Exception e )
+                        {
+                            throw new IOException( e.getLocalizedMessage(), e );
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void write( int b ) throws IOException
+            {
+                write( new byte[] {(byte) b} );
+            }
+
+            @Override
+            public synchronized void close() throws IOException
+            {
+                if ( channel.isOpen() )
+                {
+                    try
+                    {
+                        channel.close();
+                    }
+                    catch ( AsynchronousCloseException e )
+                    {
+                        // closed channel anyway
+                    }
+                }
+            }
+        };
+    }
+
+    public static InputStream newInputStream( final AsynchronousByteChannel channel )
+    {
+        return new InputStream()
+        {
+            @Override
+            public synchronized int read( byte[] b, int off, int len ) throws IOException
+            {
+                if ( off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0 )
+                {
+                    throw new IndexOutOfBoundsException(
+                        "b.length = " + b.length + ", off = " + off + ", len = " + len );
+                }
+                else if ( len == 0 )
+                {
+                    return 0;
+                }
+                ByteBuffer bb = ByteBuffer.wrap( b, off, len );
+                try
+                {
+                    return channel.read( bb ).get();
+                }
+                catch ( ExecutionException e )
+                {
+                    Throwable t = e.getCause();
+                    throw new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
+                }
+                catch ( Exception e )
+                {
+                    throw new IOException( e.getLocalizedMessage(), e );
+                }
+            }
+
+            @Override
+            public int read() throws IOException
+            {
+                int count;
+                byte[] b = new byte[1];
+                do
+                {
+                    count = read( b, 0, 1 );
+                }
+                while ( count == 0 );
+
+                return count == -1 ? -1 : b[0];
+            }
+
+            @Override
+            public synchronized void close() throws IOException
+            {
+                if ( channel.isOpen() )
+                {
+                    try
+                    {
+                        channel.close();
+                    }
+                    catch ( AsynchronousCloseException e )
+                    {
+                        // closed channel anyway
+                    }
+                }
+            }
+        };
+    }
+
+    private static ReadableByteChannel newChannel( @Nonnull InputStream is, @Nonnegative int bufferSize )
+    {
+        requireNonNull( is, "the stream should not be null" );
+        final InputStream bis = bufferSize == 0 ? is : new BufferedInputStream( is, bufferSize );
 
         return new AbstractNoninterruptibleReadableChannel()
         {
             @Override
             protected int readImpl( ByteBuffer src ) throws IOException
             {
-                int count = is.read( src.array(), src.arrayOffset() + src.position(), src.remaining() );
+                int count = bis.read( src.array(), src.arrayOffset() + src.position(), src.remaining() );
                 if ( count > 0 )
                 {
                     src.position( count + src.position() );
@@ -84,38 +218,34 @@ public final class Channels
             @Override
             protected void closeImpl() throws IOException
             {
-                is.close();
+                bis.close();
             }
         };
     }
 
-    private static WritableByteChannel newChannel( @Nonnull final OutputStream out, final boolean flushable )
+    private static WritableBufferedByteChannel newChannel( @Nonnull OutputStream out, @Nonnegative int bufferSize )
     {
         requireNonNull( out, "the stream should not be null" );
+        final OutputStream bos = bufferSize == 0 ? out : new BufferedOutputStream( out, bufferSize );
 
-        if ( out instanceof FileOutputStream && FileOutputStream.class.equals( out.getClass() ) )
-        {
-            return ( (FileOutputStream) out ).getChannel();
-        }
-
-        return new AbstractNoninterruptibleWritableChannel( flushable )
+        return new AbstractNoninterruptibleWritableChannel()
         {
             @Override
             protected void writeImpl( ByteBuffer src ) throws IOException
             {
-                out.write( src.array(), src.arrayOffset() + src.position(), src.remaining() );
+                bos.write( src.array(), src.arrayOffset() + src.position(), src.remaining() );
             }
 
             @Override
             protected void closeImpl() throws IOException
             {
-                out.close();
+                bos.close();
             }
 
             @Override
-            public void flush() throws IOException
+            protected void flushImpl() throws IOException
             {
-                out.flush();
+                bos.flush();
             }
         };
     }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/WritableBufferedByteChannel.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/WritableBufferedByteChannel.java
new file mode 100644
index 0000000..7f64549
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/WritableBufferedByteChannel.java
@@ -0,0 +1,36 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ * Extends {@link WritableByteChannel} with buffered (i.e. non-flushable) write
+ * operations, see {@link #writeBuffered(ByteBuffer)}. The messages are buffered
+ * and the channel is flushed after the buffer has overflew.
+ * <br>
+ * The method {@link #write(ByteBuffer)} flushes every written message.
+ */
+public interface WritableBufferedByteChannel extends WritableByteChannel
+{
+    void writeBuffered( ByteBuffer src ) throws IOException;
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
index b125b0c..258b165 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
@@ -20,7 +20,6 @@ package org.apache.maven.surefire.booter;
  */
 
 import junit.framework.TestCase;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.mockito.ArgumentCaptor;
 
 import static org.fest.assertions.Assertions.assertThat;
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
index 7e3a685..0b64a42 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
@@ -38,7 +38,7 @@ import static java.nio.file.Files.write;
 import static org.fest.assertions.Assertions.assertThat;
 
 /**
- * The tests for {@link Channels#newChannel(InputStream)}.
+ * The tests for {@link Channels#newChannel(InputStream)} and {@link Channels#newBufferedChannel(InputStream)}.
  */
 public class ChannelsReaderTest
 {
@@ -107,6 +107,62 @@ public class ChannelsReaderTest
     }
 
     @Test
+    public void bufferedChannel() throws Exception
+    {
+        ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
+        ReadableByteChannel channel = Channels.newBufferedChannel( is );
+        ByteBuffer bb = ByteBuffer.allocate( 4 );
+
+        int countWritten = channel.read( bb );
+
+        assertThat( countWritten )
+            .isEqualTo( 3 );
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 1 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 4 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        bb.flip();
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        assertThat( bb.array() )
+            .isEqualTo( new byte[] {1, 2, 3, 0} );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+
+        channel.close();
+
+        assertThat( channel.isOpen() )
+            .isFalse();
+    }
+
+    @Test
     public void biggerBuffer() throws Exception
     {
         ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
index a1f6a66..35c9ddd 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
@@ -38,7 +38,7 @@ import static java.nio.file.Files.readAllBytes;
 import static org.fest.assertions.Assertions.assertThat;
 
 /**
- * The tests for {@link Channels#newChannel(OutputStream)} and {@link Channels#newFlushableChannel(OutputStream)}.
+ * The tests for {@link Channels#newChannel(OutputStream)} and {@link Channels#newBufferedChannel(OutputStream)}.
  */
 public class ChannelsWriterTest
 {
@@ -63,7 +63,7 @@ public class ChannelsWriterTest
                 super.flush();
             }
         };
-        WritableByteChannel channel = Channels.newFlushableChannel( out );
+        WritableByteChannel channel = Channels.newBufferedChannel( out );
         ByteBuffer bb = ByteBuffer.wrap( new byte[] {1, 2, 3} );
         int countWritten = channel.write( bb );
         assertThat( countWritten )
@@ -119,6 +119,45 @@ public class ChannelsWriterTest
     }
 
     @Test
+    public void bufferedChannel() throws Exception
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        WritableBufferedByteChannel channel = Channels.newBufferedChannel( out );
+        ByteBuffer bb = ByteBuffer.allocate( 5 );
+        bb.put( (byte) 1 );
+        bb.put( (byte) 2 );
+        bb.put( (byte) 3 );
+
+        channel.writeBuffered( bb );
+
+        assertThat( out.toByteArray() )
+            .isEmpty();
+
+        channel.write( ByteBuffer.allocate( 0 ) );
+
+        assertThat( out.toByteArray() )
+            .isEmpty();
+
+        channel.write( ByteBuffer.wrap( new byte[] {4} ) );
+
+        assertThat( out.toByteArray() )
+            .hasSize( 4 )
+            .isEqualTo( new byte[] {1, 2, 3, 4} );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 5 );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+    }
+
+    @Test
     public void shouldFailAfterClosed() throws IOException
     {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
index e437f97..28766f5 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
@@ -22,8 +22,6 @@ package org.apache.maven.surefire.booter;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.providerapi.CommandChainReader;
 import org.apache.maven.surefire.providerapi.CommandListener;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 
 import java.io.EOFException;
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index 46d9d74..1983ae2 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -23,8 +23,6 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.providerapi.CommandListener;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
@@ -166,6 +164,15 @@ public final class ForkedBooter
         }
         finally
         {
+            //noinspection ResultOfMethodCallIgnored
+            Thread.interrupted();
+
+            if ( eventChannel.checkError() )
+            {
+                DumpErrorSingleton.getSingleton()
+                    .dumpText( "The channel (std/out or TCP/IP) failed to send a stream from this subprocess." );
+            }
+
             acknowledgedExit();
         }
     }
@@ -382,9 +389,6 @@ public final class ForkedBooter
 
     private void acknowledgedExit()
     {
-        //noinspection ResultOfMethodCallIgnored
-        Thread.interrupted();
-
         commandReader.addByeAckListener( new CommandListener()
                                           {
                                               @Override
@@ -397,7 +401,11 @@ public final class ForkedBooter
         eventChannel.bye();
         launchLastDitchDaemonShutdownThread( 0 );
         long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS );
-        acquireOnePermit( exitBarrier, timeoutMillis );
+        boolean timeoutElapsed = !acquireOnePermit( exitBarrier, timeoutMillis );
+        if ( timeoutElapsed )
+        {
+            eventChannel.sendExitError( null, false );
+        }
         cancelPingScheduler();
         commandReader.stop();
         closeForkChannel();
@@ -544,15 +552,16 @@ public final class ForkedBooter
         return pluginProcessChecker != null && pluginProcessChecker.canUse();
     }
 
-    private static void acquireOnePermit( Semaphore barrier, long timeoutMillis )
+    private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis )
     {
         try
         {
-            barrier.tryAcquire( timeoutMillis, MILLISECONDS );
+            return barrier.tryAcquire( timeoutMillis, MILLISECONDS );
         }
         catch ( InterruptedException e )
         {
             // cancel schedulers, stop the command reader and exit 0
+            return true;
         }
     }
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
index 568a2c5..47ffa01 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
@@ -22,7 +22,6 @@ package org.apache.maven.surefire.booter;
 import java.util.Collections;
 import java.util.Iterator;
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.util.CloseableIterator;
 import org.apache.maven.surefire.util.TestsToRun;
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
index e8d3efb..df100e9 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
@@ -22,7 +22,7 @@ package org.apache.maven.surefire.booter.spi;
 import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.DumpErrorSingleton;
 import org.apache.maven.surefire.booter.MasterProcessCommand;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.util.internal.ImmutableMap;
 
 import javax.annotation.Nonnull;
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
index a85e4ef..4a0c226 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
@@ -22,21 +22,22 @@ package org.apache.maven.surefire.booter.spi;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
 import org.apache.maven.surefire.booter.DumpErrorSingleton;
 import org.apache.maven.surefire.booter.ForkedProcessEventType;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.shared.codec.binary.Base64;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
-import java.nio.channels.WritableByteChannel;
 import java.nio.charset.Charset;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -75,21 +76,20 @@ import static org.apache.maven.surefire.report.RunMode.RERUN_TEST_AFTER_FAILURE;
  */
 public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
 {
-    static final String MAGIC_NUMBER_DELIMITED = ':' + MAGIC_NUMBER + ':';
     private static final Base64 BASE64 = new Base64();
     private static final Charset STREAM_ENCODING = US_ASCII;
     private static final Charset STRING_ENCODING = UTF_8;
 
-    protected final WritableByteChannel out;
+    private final WritableBufferedByteChannel out;
     private final RunMode runMode;
-    private volatile boolean trouble;
+    private final AtomicBoolean trouble = new AtomicBoolean();
 
-    public LegacyMasterProcessChannelEncoder( @Nonnull WritableByteChannel out )
+    public LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out )
     {
         this( out, NORMAL_RUN );
     }
 
-    protected LegacyMasterProcessChannelEncoder( @Nonnull WritableByteChannel out, @Nonnull RunMode runMode )
+    protected LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode )
     {
         this.out = requireNonNull( out );
         this.runMode = requireNonNull( runMode );
@@ -110,7 +110,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     @Override
     public boolean checkError()
     {
-        return trouble;
+        return trouble.get();
     }
 
     @Override
@@ -121,56 +121,56 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
             String key = entry.getKey();
             String value = entry.getValue();
             StringBuilder event = encode( BOOTERCODE_SYSPROPS, runMode, key, value );
-            encodeAndPrintEvent( event );
+            encodeAndPrintEvent( event, false );
         }
     }
 
     @Override
     public void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testStarting( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testFailed( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testError( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
@@ -191,14 +191,14 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     {
         String base64Message = toBase64( message );
         StringBuilder event = encodeMessage( eventType, runMode.geRunName(), base64Message );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, false );
     }
 
     @Override
     public void consoleInfoLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), msg );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, true );
     }
 
     @Override
@@ -206,7 +206,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     {
         StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
         encode( encoded, msg, null, null );
-        encodeAndPrintEvent( encoded );
+        encodeAndPrintEvent( encoded, true );
     }
 
     @Override
@@ -220,74 +220,75 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     {
         StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
         encode( encoded, msg, null, ConsoleLoggerUtils.toString( t ) );
-        encodeAndPrintEvent( encoded );
+        encodeAndPrintEvent( encoded, true );
     }
 
     @Override
     public void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
     {
-        error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR );
+        error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR, true );
     }
 
     @Override
     public void consoleDebugLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), msg );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, true );
     }
 
     @Override
     public void consoleWarningLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), msg );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, true );
     }
 
     @Override
     public void bye()
     {
-        encodeOpcode( BOOTERCODE_BYE );
+        encodeOpcode( BOOTERCODE_BYE, true );
     }
 
     @Override
     public void stopOnNextTest()
     {
-        encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST );
+        encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST, true );
     }
 
     @Override
     public void acquireNextTest()
     {
-        encodeOpcode( BOOTERCODE_NEXT_TEST );
+        encodeOpcode( BOOTERCODE_NEXT_TEST, true );
     }
 
     @Override
-    public void sendExitEvent( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
+    public void sendExitError( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
     {
-        error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR );
+        error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true );
     }
 
-    private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event )
+    private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event,
+                        @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
     {
         StringBuilder encoded = encodeHeader( event.getOpcode(), null );
         encode( encoded, stackTraceWriter, trimStackTraces );
-        encodeAndPrintEvent( encoded );
+        encodeAndPrintEvent( encoded, sendImmediately );
     }
 
     private void encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
-                         boolean trimStackTraces )
+                         boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
     {
         StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, sendImmediately );
     }
 
-    private void encodeOpcode( ForkedProcessEventType operation )
+    private void encodeOpcode( ForkedProcessEventType operation, boolean sendImmediately )
     {
         StringBuilder event = encodeOpcode( operation.getOpcode(), null );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, sendImmediately );
     }
 
-    private void encodeAndPrintEvent( StringBuilder event )
+    private void encodeAndPrintEvent( StringBuilder event, boolean sendImmediately )
     {
         try
         {
@@ -298,7 +299,16 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
                 .toString()
                 .getBytes( STREAM_ENCODING );
 
-            out.write( ByteBuffer.wrap( array ) );
+            ByteBuffer bb = ByteBuffer.wrap( array );
+
+            if ( sendImmediately )
+            {
+                out.write( bb );
+            }
+            else
+            {
+                out.writeBuffered( bb );
+            }
         }
         catch ( ClosedChannelException e )
         {
@@ -307,9 +317,11 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         }
         catch ( IOException e )
         {
-            DumpErrorSingleton.getSingleton()
-                .dumpException( e );
-            trouble = true;
+            if ( trouble.compareAndSet( false, true ) )
+            {
+                DumpErrorSingleton.getSingleton()
+                    .dumpException( e );
+            }
         }
     }
 
@@ -415,7 +427,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     }
 
     /**
-     * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEventType)}
+     * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEventType, boolean)}
      * and private methods extending the buffer.
      *
      * @param operation opcode
@@ -425,7 +437,9 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     static StringBuilder encodeOpcode( String operation, String runMode )
     {
         StringBuilder s = new StringBuilder( 128 )
-            .append( MAGIC_NUMBER_DELIMITED )
+            .append( ':' )
+            .append( MAGIC_NUMBER )
+            .append( ':' )
             .append( operation )
             .append( ':' );
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
index 5713b99..c09516b 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
@@ -19,15 +19,14 @@ package org.apache.maven.surefire.booter.spi;
  * under the License.
  */
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 
 import java.io.IOException;
 import java.net.MalformedURLException;
 
-import static org.apache.maven.surefire.util.internal.Channels.newChannel;
-import static org.apache.maven.surefire.util.internal.Channels.newFlushableChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
 
 /**
  * Producer of encoder and decoder for process pipes.
@@ -57,13 +56,13 @@ public class LegacyMasterProcessChannelProcessorFactory
     @Override
     public MasterProcessChannelDecoder createDecoder()
     {
-        return new LegacyMasterProcessChannelDecoder( newChannel( System.in ) );
+        return new LegacyMasterProcessChannelDecoder( newBufferedChannel( System.in ) );
     }
 
     @Override
     public MasterProcessChannelEncoder createEncoder()
     {
-        return new LegacyMasterProcessChannelEncoder( newFlushableChannel( System.out ) );
+        return new LegacyMasterProcessChannelEncoder( newBufferedChannel( System.out ) );
     }
 
     @Override
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
index 1893009..6f52756 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
@@ -19,16 +19,28 @@ package org.apache.maven.surefire.booter.spi;
  * under the License.
  */
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
+import java.net.SocketOption;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.nio.channels.SocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.util.concurrent.ExecutionException;
+
+import static java.net.StandardSocketOptions.SO_KEEPALIVE;
+import static java.net.StandardSocketOptions.SO_REUSEADDR;
+import static java.net.StandardSocketOptions.TCP_NODELAY;
+import static java.nio.channels.AsynchronousChannelGroup.withFixedThreadPool;
+import static java.nio.channels.AsynchronousSocketChannel.open;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newInputStream;
+import static org.apache.maven.surefire.util.internal.Channels.newOutputStream;
+import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
 
 /**
  * Producer of TCP/IP encoder and decoder.
@@ -40,7 +52,7 @@ import java.nio.channels.SocketChannel;
 public class SurefireMasterProcessChannelProcessorFactory
     implements MasterProcessChannelProcessorFactory
 {
-    private volatile SocketChannel channel;
+    private volatile AsynchronousSocketChannel clientSocketChannel;
 
     @Override
     public boolean canUse( String channelConfig )
@@ -59,32 +71,52 @@ public class SurefireMasterProcessChannelProcessorFactory
         try
         {
             URI uri = new URI( channelConfig );
-            channel = SocketChannel.open( new InetSocketAddress( uri.getHost(), uri.getPort() ) );
+            InetSocketAddress hostAddress = new InetSocketAddress( uri.getHost(), uri.getPort() );
+            clientSocketChannel = open( withFixedThreadPool( 2, newDaemonThreadFactory() ) );
+            setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
+            clientSocketChannel.connect( hostAddress ).get();
         }
-        catch ( URISyntaxException e )
+        catch ( URISyntaxException | InterruptedException e )
         {
             throw new IOException( e.getLocalizedMessage(), e );
         }
+        catch ( ExecutionException e )
+        {
+            throw new IOException( e.getLocalizedMessage(), e.getCause() );
+        }
     }
 
     @Override
     public MasterProcessChannelDecoder createDecoder()
     {
-        return new LegacyMasterProcessChannelDecoder( channel );
+        return new LegacyMasterProcessChannelDecoder( newBufferedChannel( newInputStream( clientSocketChannel ) ) );
     }
 
     @Override
     public MasterProcessChannelEncoder createEncoder()
     {
-        return new LegacyMasterProcessChannelEncoder( channel );
+        return new LegacyMasterProcessChannelEncoder( newBufferedChannel( newOutputStream( clientSocketChannel ) ) );
     }
 
     @Override
     public void close() throws IOException
     {
-        if ( channel != null )
+        if ( clientSocketChannel != null && clientSocketChannel.isOpen() )
+        {
+            clientSocketChannel.close();
+        }
+    }
+
+    @SafeVarargs
+    private final void setTrueOptions( SocketOption<Boolean>... options )
+        throws IOException
+    {
+        for ( SocketOption<Boolean> option : options )
         {
-            channel.close();
+            if ( clientSocketChannel.supportedOptions().contains( option ) )
+            {
+                clientSocketChannel.setOption( option, true );
+            }
         }
     }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index edaae08..1c85048 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -23,8 +23,8 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.testset.TestSetFailedException;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -34,7 +34,6 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
-import java.nio.channels.WritableByteChannel;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.concurrent.BlockingQueue;
@@ -44,7 +43,8 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import static java.nio.channels.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -262,8 +262,8 @@ public class CommandReaderTest
         }
     }
 
-    private static WritableByteChannel nul()
+    private static WritableBufferedByteChannel nul()
     {
-        return newChannel( new PrintStream( new ByteArrayOutputStream() ) );
+        return newBufferedChannel( new PrintStream( new ByteArrayOutputStream() ) );
     }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
index fa8011d..5604fd1 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
@@ -23,8 +23,6 @@ import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import org.junit.Rule;
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
index 3f6d148..aac78f7 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
@@ -151,8 +151,9 @@ public class ForkedBooterTest
     public void testBarrier() throws Exception
     {
         Semaphore semaphore = new Semaphore( 2 );
-        invokeMethod( ForkedBooter.class, "acquireOnePermit", semaphore, 30_000L );
+        boolean acquiredOnePermit = invokeMethod( ForkedBooter.class, "acquireOnePermit", semaphore, 30_000L );
 
+        assertThat( acquiredOnePermit ).isTrue();
         assertThat( semaphore.availablePermits() ).isEqualTo( 1 );
     }
 
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
index acfcac3..761e30f 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
@@ -23,6 +23,7 @@ import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.util.internal.ObjectUtils;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
@@ -34,17 +35,17 @@ import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.Map;
 
-import static java.nio.channels.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.copyOfRange;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.MAGIC_NUMBER_DELIMITED;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.toBase64;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
 import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encode;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeHeader;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeMessage;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeOpcode;
+import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.toBase64;
 import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.MAGIC_NUMBER;
 import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -71,7 +72,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void shouldHaveSystemProperty()
     {
         StringBuilder actualEncoded = encode( BOOTERCODE_SYSPROPS, NORMAL_RUN, "arg1", "arg2" );
-        String expected = MAGIC_NUMBER_DELIMITED + BOOTERCODE_SYSPROPS.getOpcode()
+        String expected = ':' + MAGIC_NUMBER + ':' + BOOTERCODE_SYSPROPS.getOpcode()
             + ":normal-run:UTF-8:YXJnMQ==:YXJnMg==:";
 
         assertThat( actualEncoded.toString() )
@@ -235,7 +236,7 @@ public class LegacyMasterProcessChannelEncoderTest
                 );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetStarting( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -266,7 +267,7 @@ public class LegacyMasterProcessChannelEncoderTest
         assertThat( printedLines.readLine() ).isNull();
 
         out = Stream.newStream();
-        encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetStarting( reportEntry, false );
         printedLines = out.newReader( UTF_8 );
@@ -334,7 +335,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetCompleted( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -402,7 +403,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testStarting( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -470,7 +471,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSucceeded( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -538,7 +539,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testFailed( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -605,7 +606,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSkipped( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -671,7 +672,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testError( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -735,7 +736,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testAssumptionFailure( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -770,7 +771,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testBye() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.bye();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -783,7 +784,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStopOnNextTest() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.stopOnNextTest();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -796,7 +797,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testAcquireNextTest() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.acquireNextTest();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -821,7 +822,7 @@ public class LegacyMasterProcessChannelEncoderTest
                 .isEqualTo( ":maven-surefire-event:some-opcode:normal-run:UTF-8:msg:" );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
         encoded = encoder.print( "some-opcode", "msg" );
         assertThat( encoded.toString() )
                 .isEqualTo( ":maven-surefire-event:some-opcode:UTF-8:bXNn:" );
@@ -835,7 +836,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleInfo()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleInfoLog( "msg" );
 
@@ -854,7 +855,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleError()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleErrorLog( "msg" );
 
@@ -872,7 +873,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog1() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleErrorLog( new Exception( "msg" ) );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -885,7 +886,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog2() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleErrorLog( "msg2", new Exception( "msg" ) );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -898,7 +899,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog3() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
@@ -917,7 +918,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleDebug()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleDebugLog( "msg" );
 
@@ -936,7 +937,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleWarning()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleWarningLog( "msg" );
 
@@ -955,9 +956,11 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdOutStream() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         encoder.stdOut( "msg", false );
+        channel.close();
 
         String expected = ":maven-surefire-event:std-out-stream:normal-run:UTF-8:bXNn:";
 
@@ -972,9 +975,11 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdOutStreamLn() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         encoder.stdOut( "msg", true );
+        channel.close();
 
         String expected = ":maven-surefire-event:std-out-stream-new-line:normal-run:UTF-8:bXNn:";
 
@@ -989,9 +994,11 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdErrStream() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         encoder.stdErr( "msg", false );
+        channel.close();
 
         String expected = ":maven-surefire-event:std-err-stream:normal-run:UTF-8:bXNn:";
 
@@ -1006,9 +1013,11 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdErrStreamLn() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         encoder.stdErr( "msg", true );
+        channel.close();
 
         String expected = ":maven-surefire-event:std-err-stream-new-line:normal-run:UTF-8:bXNn:";
 
@@ -1024,11 +1033,13 @@ public class LegacyMasterProcessChannelEncoderTest
     public void shouldCountSameNumberOfSystemProperties() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         Map<String, String> sysProps = ObjectUtils.systemProps();
         int expectedSize = sysProps.size();
         encoder.sendSystemProperties( sysProps );
+        channel.close();
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
 
@@ -1048,13 +1059,13 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-        encoder.sendExitEvent( stackTraceWriter, false );
+        encoder.sendExitError( stackTraceWriter, false );
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
@@ -1066,13 +1077,13 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-        encoder.sendExitEvent( stackTraceWriter, true );
+        encoder.sendExitError( stackTraceWriter, true );
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
index cb57b9c..ddc43ea 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
@@ -28,8 +28,7 @@ import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 
-import static org.apache.maven.surefire.util.internal.Channels.newChannel;
-import static org.apache.maven.surefire.util.internal.Channels.newFlushableChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
 
 /**
  *
@@ -44,12 +43,12 @@ public final class CommandlineStreams implements Closeable
     public CommandlineStreams( @Nonnull Process process )
     {
         InputStream stdOutStream = process.getInputStream();
-        stdOutChannel = newChannel( stdOutStream );
+        stdOutChannel = newBufferedChannel( stdOutStream );
 
         InputStream stdErrStream = process.getErrorStream();
-        stdErrChannel = newChannel( stdErrStream );
+        stdErrChannel = newBufferedChannel( stdErrStream );
 
-        stdInChannel = newFlushableChannel( process.getOutputStream() );
+        stdInChannel = newBufferedChannel( process.getOutputStream() );
     }
 
     public ReadableByteChannel getStdOutChannel()
diff --git a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
index 989a52d..c1c5c3c 100644
--- a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
+++ b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
@@ -19,8 +19,8 @@ package org.apache.maven.surefire.spi;
  * under the License.
  */
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 
 import java.io.Closeable;
 import java.io.IOException;
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
index 21b92b9..1fd03a5 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
@@ -19,13 +19,13 @@ package org.apache.maven.surefire.its;
  * under the License.
  */
 
-import com.googlecode.junittoolbox.ParallelParameterized;
 import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.apache.maven.surefire.its.fixture.TestFile;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
@@ -40,7 +40,7 @@ import static org.hamcrest.Matchers.equalTo;
  *
  * @author Kristian Rosenvold
  */
-@RunWith( ParallelParameterized.class )
+@RunWith( Parameterized.class )
 public class ConsoleOutputIT
     extends SurefireJUnit4IntegrationTestCase
 {
@@ -67,7 +67,7 @@ public class ConsoleOutputIT
     public void properNewlinesAndEncodingWithDefaultEncodings() throws Exception
     {
         OutputValidator outputValidator = unpack().forkOnce().executeTest();
-        validate( outputValidator, profileId == null, true );
+        validate( outputValidator, true );
     }
 
     @Test
@@ -77,7 +77,7 @@ public class ConsoleOutputIT
                 .forkOnce()
                 .argLine( "-Dfile.encoding=UTF-16" )
                 .executeTest();
-        validate( outputValidator, profileId == null, true );
+        validate( outputValidator, true );
     }
 
     @Test
@@ -86,7 +86,7 @@ public class ConsoleOutputIT
         OutputValidator outputValidator = unpack()
                 .forkNever()
                 .executeTest();
-        validate( outputValidator, false, false );
+        validate( outputValidator, false );
     }
 
     private SurefireLauncher unpack()
@@ -103,7 +103,7 @@ public class ConsoleOutputIT
         return launcher;
     }
 
-    private void validate( final OutputValidator outputValidator, boolean includeShutdownHook, boolean canFork )
+    private void validate( final OutputValidator outputValidator, boolean canFork )
         throws Exception
     {
         TestFile xmlReportFile = outputValidator.getSurefireReportsXmlFile( "TEST-consoleOutput.Test1.xml" );
@@ -116,13 +116,6 @@ public class ConsoleOutputIT
         outputFile.assertContainsText( "SoutLine" );
         outputFile.assertContainsText( "äöüß" );
 
-        if ( includeShutdownHook )
-        {
-            //todo it should not be reported in the last test which is completed
-            //todo this text should be in null-output.txt
-            outputFile.assertContainsText( "Printline in shutdown hook" );
-        }
-
         String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
 
         if ( canFork )
diff --git a/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java b/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
index 93e6a83..db74acf 100644
--- a/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
+++ b/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
@@ -29,26 +29,8 @@ import static org.junit.Assert.fail;
 
 public class Test1
 {
-    static
+    public Test1()
     {
-       System.out.println("Printline in static block");
-        Runtime.getRuntime().addShutdownHook(  new Thread( ){
-            @Override
-            public void run()
-            {
-                System.out.println( "Printline in shutdown hook" );
-            }
-        });
-    }
-
-    @Override
-    protected void finalize()
-        throws Throwable
-    {
-        System.out.println( "Printline in finalizer" );
-    }
-
-    public Test1(){
        System.out.println("In constructor");
     }
 
diff --git a/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test3.java b/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test3.java
new file mode 100644
index 0000000..84c6198
--- /dev/null
+++ b/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test3.java
@@ -0,0 +1,52 @@
+package consoleoutput_noisy;
+
+/*
+ * 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.junit.Test;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import java.io.File;
+
+/**
+ *
+ */
+public class Test3
+{
+    @Test
+    public void test() throws Exception
+    {
+        long t1 = System.currentTimeMillis();
+        System.out.println( "t1 = " + t1 );
+        for ( int i = 0; i < 320_000; i++ )
+        {
+            System.out.println( "01234567890123456789012345678901234567890123456789"
+                + "01234567890123456789012345678901234567890123456789" );
+        }
+        long t2 = System.currentTimeMillis();
+        System.out.println( "t2 = " + t2 );
+
+        File target = new File( System.getProperty( "user.dir" ) );
+        new File( target, ( t2 - t1 ) + "" )
+            .createNewFile();
+    }
+}


[maven-surefire] 09/18: investigated tests

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 43621a025090c3fe8d198d24789b9d806bc340f9
Author: tibordigana <ti...@apache.org>
AuthorDate: Sun Mar 22 22:20:58 2020 +0100

    investigated tests
---
 .../consoleOutput/src/test/java/consoleOutput/Test1.java   | 14 ++++++++++++++
 .../src/test/java/consoleoutput_noisy/Test1.java           |  2 ++
 2 files changed, 16 insertions(+)

diff --git a/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java b/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
index 8994251..93e6a83 100644
--- a/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
+++ b/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
@@ -22,6 +22,8 @@ package consoleOutput;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.Before;
+import org.junit.After;
 
 import static org.junit.Assert.fail;
 
@@ -50,6 +52,18 @@ public class Test1
        System.out.println("In constructor");
     }
 
+    @Before
+    public void t1()
+    {
+        System.out.println( "t1 = " + System.currentTimeMillis() );
+    }
+
+    @After
+    public void t2()
+    {
+        System.out.println( "t2 = " + System.currentTimeMillis() );
+    }
+
     @Test
     public void testStdOut()
     {
diff --git a/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test1.java b/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test1.java
index 888cd22..c7e562e 100644
--- a/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test1.java
+++ b/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test1.java
@@ -33,10 +33,12 @@ public class Test1
     @Test
     public void test1MillionBytes()
     {
+        System.out.println( "t1 = " + System.currentTimeMillis() );
         for ( int i = 0; i < ( 10 * thousand ); i++ )
         {
             System.out.println( "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" );
         }
+        System.out.println( "t2 = " + System.currentTimeMillis() );
     }
 
     @Test


[maven-surefire] 06/18: fixed the IT 735

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 93dfd0ef984192c6cfe5cc29c1d93bc0180c5200
Author: tibordigana <ti...@apache.org>
AuthorDate: Sun Mar 22 04:37:20 2020 +0100

    fixed the IT 735
---
 .../apache/maven/surefire/extensions/util/CommandlineStreams.java    | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
index 18dae45..cb57b9c 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
@@ -24,6 +24,7 @@ import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.channels.Channel;
+import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 
@@ -80,5 +81,9 @@ public final class CommandlineStreams implements Closeable
         {
             closed = true;
         }
+        catch ( ClosedChannelException e )
+        {
+            // already closed externally
+        }
     }
 }


[maven-surefire] 13/18: fixed Surefire817SystemExitIT

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 0986f5ab8b3fc6bf100cdb0d1b2613158232e32b
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Apr 4 00:08:43 2020 +0200

    fixed Surefire817SystemExitIT
---
 .../booter/MasterProcessChannelEncoder.java         |  2 ++
 .../apache/maven/surefire/booter/ForkedBooter.java  | 21 +++++++++++++++++++++
 .../spi/LegacyMasterProcessChannelEncoder.java      |  6 ++++++
 3 files changed, 29 insertions(+)

diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
index 062a57c..75c9e17 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
@@ -38,6 +38,8 @@ public interface MasterProcessChannelEncoder
 
     boolean checkError();
 
+    void onJvmExit();
+
     void sendSystemProperties( Map<String, String> sysProps );
 
     void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces );
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index 8b24d90..7b6d09e 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -27,6 +27,7 @@ import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 
@@ -119,6 +120,8 @@ public final class ForkedBooter
         eventChannel = channelProcessorFactory.createEncoder();
         MasterProcessChannelDecoder decoder = channelProcessorFactory.createDecoder();
 
+        flushEventChannelOnExit();
+
         forkingReporterFactory = createForkingReporterFactory();
         ConsoleLogger logger = (ConsoleLogger) forkingReporterFactory.createReporter();
         commandReader = new CommandReader( decoder, providerConfiguration.getShutdown(), logger );
@@ -481,6 +484,24 @@ public final class ForkedBooter
         return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
     }
 
+    /**
+     * Necessary for the Surefire817SystemExitIT.
+     */
+    private void flushEventChannelOnExit()
+    {
+        Runnable target = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                eventChannel.onJvmExit();
+            }
+        };
+        Thread t = new Thread( target );
+        t.setDaemon( true );
+        ShutdownHookUtils.addShutDownHook( t );
+    }
+
     private static MasterProcessChannelProcessorFactory lookupDecoderFactory( String channelConfig )
     {
         MasterProcessChannelProcessorFactory defaultFactory = null;
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
index 4a0c226..91d9d1b 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
@@ -114,6 +114,12 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     }
 
     @Override
+    public void onJvmExit()
+    {
+        encodeAndPrintEvent( new StringBuilder( "\n" ), true );
+    }
+
+    @Override
     public void sendSystemProperties( Map<String, String> sysProps )
     {
         for ( Entry<String, String> entry : sysProps.entrySet() )


[maven-surefire] 10/18: E2E test for TCP

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 2d8751499ed94a3920d22609bd67614c4c661533
Author: tibordigana <ti...@apache.org>
AuthorDate: Wed Mar 25 10:41:32 2020 +0100

    E2E test for TCP
---
 .../surefire/extensions/SurefireForkChannel.java   |   2 +-
 .../maven/plugin/surefire/extensions/E2ETest.java  | 126 +++++++++++++++++++++
 2 files changed, 127 insertions(+), 1 deletion(-)

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 e11b03f..3285486 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
@@ -65,7 +65,7 @@ final class SurefireForkChannel extends ForkChannel
     private final int localPort;
     private volatile SocketChannel channel;
 
-    SurefireForkChannel( int forkChannelId, ConsoleLogger logger ) throws IOException
+    SurefireForkChannel( int forkChannelId, @Nonnull ConsoleLogger logger ) throws IOException
     {
         super( forkChannelId );
         this.logger = logger;
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
new file mode 100644
index 0000000..ec76a8c
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
@@ -0,0 +1,126 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.report.ConsoleOutputCapture;
+import org.apache.maven.surefire.report.ConsoleOutputReceiver;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Mockito.mock;
+
+public class E2ETest
+{
+    private static final String LONG_STRING =
+        "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+
+    @Test
+    public void test() throws Exception
+    {
+        ConsoleLogger logger = mock( ConsoleLogger.class );
+        SurefireForkChannel server = new SurefireForkChannel(1, logger );
+
+        final String connection = server.getForkNodeConnectionString();
+
+        SurefireMasterProcessChannelProcessorFactory factory = new SurefireMasterProcessChannelProcessorFactory();
+        factory.connect( connection );
+        final MasterProcessChannelEncoder encoder = factory.createEncoder();
+
+        Thread t = new Thread()
+        {
+            @Override
+            public void run()
+            {
+                ConsoleOutputReceiver target = new ConsoleOutputReceiver()
+                {
+                    @Override
+                    public void writeTestOutput( String output, boolean newLine, boolean stdout )
+                    {
+                        encoder.stdOut( output, true );
+                    }
+                };
+
+                PrintStream out = System.out;
+                PrintStream err = System.err;
+
+                ConsoleOutputCapture.startCapture( target );
+
+                try
+                {
+                    for ( int i = 0; i < 320_000; i++ )
+                    {
+                        System.out.println( LONG_STRING );
+                    }
+                    System.setOut( out );
+                    System.setErr( err );
+                    TimeUnit.MINUTES.sleep( 1L );
+                }
+                catch ( Exception e )
+                {
+                    e.printStackTrace();
+                }
+            }
+        };
+        t.setDaemon( true );
+        t.start();
+
+        server.connectToClient();
+
+        EventHandler<Event> h = new EventHandler<Event>()
+        {
+            volatile int i;
+            volatile long t1;
+
+            @Override
+            public void handleEvent( @Nonnull Event event )
+            {
+                try
+                {
+                    if ( i++ == 0 )
+                    {
+                        t1 = System.currentTimeMillis();
+                    }
+
+                    if ( i == 320_000 )
+                    {
+                        long t2 = System.currentTimeMillis();
+                        TimeUnit.SECONDS.sleep( 1L );
+                        System.out.println( "Forked JVM spent "
+                            + ( t2 - t1 )
+                            + "ms on transferring all lines of the log." );
+                    }
+                }
+                catch ( InterruptedException e )
+                {
+                    e.printStackTrace();
+                }
+            }
+        };
+
+        Closeable c = new Closeable()
+        {
+            @Override
+            public void close() throws IOException
+            {
+
+            }
+        };
+
+        server.bindEventHandler( h, new CountdownCloseable( c, 1 ), null )
+        .start();
+
+        TimeUnit.SECONDS.sleep( 60L );
+
+        factory.close();
+        server.close();
+    }
+}


[maven-surefire] 05/18: fix after Enrico's findings in external project

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit ff5b7c6926c302598b49488a0a727fdfbdb7949d
Author: tibordigana <ti...@apache.org>
AuthorDate: Sun Mar 22 01:47:34 2020 +0100

    fix after Enrico's findings in external project
---
 .../plugin/surefire/booterclient/ForkStarter.java  |   4 +-
 .../AbstractNoninterruptibleReadableChannel.java   |  69 ++++++
 .../AbstractNoninterruptibleWritableChannel.java   |  93 ++++++++
 .../maven/surefire/util/internal/Channels.java     | 122 ++++++++++
 .../java/org/apache/maven/JUnit4SuiteTest.java     |   6 +-
 .../surefire/util/internal/ChannelsReaderTest.java | 250 +++++++++++++++++++++
 .../surefire/util/internal/ChannelsWriterTest.java | 171 ++++++++++++++
 .../apache/maven/surefire/booter/ForkedBooter.java |   3 +
 .../spi/LegacyMasterProcessChannelEncoder.java     |  36 +--
 ...LegacyMasterProcessChannelProcessorFactory.java |   5 +-
 .../extensions/util/CommandlineStreams.java        |   6 +-
 .../util/FlushableWritableByteChannel.java         |  68 ------
 12 files changed, 742 insertions(+), 91 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 a41384a..b27dacb 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
@@ -637,9 +637,9 @@ public class ForkStarter
             out = forkChannel.bindEventHandler( eventConsumer, countdownCloseable, streams.getStdOutChannel() );
             out.start();
 
-            EventHandler<String> stdErrConsumer = new NativeStdErrStreamConsumer( reporter );
+            EventHandler<String> errConsumer = new NativeStdErrStreamConsumer( reporter );
             err = new LineConsumerThread( "fork-" + forkNumber + "-err-thread-", streams.getStdErrChannel(),
-                stdErrConsumer, countdownCloseable );
+                errConsumer, countdownCloseable );
             err.start();
 
             result = exec.awaitExit();
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleReadableChannel.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleReadableChannel.java
new file mode 100644
index 0000000..1a75972
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleReadableChannel.java
@@ -0,0 +1,69 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NonReadableChannelException;
+import java.nio.channels.ReadableByteChannel;
+
+/**
+ * The channel used for reads which cannot be implicitly closed after the operational Thread
+ * is {@link Thread#isInterrupted() interrupted}.
+ *
+ * @since 3.0.0-M5
+ */
+abstract class AbstractNoninterruptibleReadableChannel implements ReadableByteChannel
+{
+    private volatile boolean open = true;
+
+    protected abstract int readImpl( ByteBuffer src ) throws IOException;
+    protected abstract void closeImpl() throws IOException;
+
+    @Override
+    public final int read( ByteBuffer src ) throws IOException
+    {
+        if ( !isOpen() )
+        {
+            throw new ClosedChannelException();
+        }
+
+        if ( !src.hasArray() || src.isReadOnly() )
+        {
+            throw new NonReadableChannelException();
+        }
+
+        return src.hasRemaining() ? readImpl( src ) : 0;
+    }
+
+    @Override
+    public final boolean isOpen()
+    {
+        return open;
+    }
+
+    @Override
+    public final void close() throws IOException
+    {
+        open = false;
+        closeImpl();
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java
new file mode 100644
index 0000000..cb08e34
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java
@@ -0,0 +1,93 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * 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 java.io.Flushable;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ * The channel used for writes which cannot be implicitly closed after the operational Thread
+ * is {@link Thread#isInterrupted() interrupted}.
+ *
+ * @since 3.0.0-M5
+ */
+abstract class AbstractNoninterruptibleWritableChannel implements WritableByteChannel, Flushable
+{
+    private final boolean flushable;
+    private volatile boolean open = true;
+
+    AbstractNoninterruptibleWritableChannel( boolean flushable )
+    {
+        this.flushable = flushable;
+    }
+
+    protected abstract void writeImpl( ByteBuffer src ) throws IOException;
+    protected abstract void closeImpl() throws IOException;
+
+    @Override
+    public final synchronized int write( ByteBuffer src ) throws IOException
+    {
+        if ( !isOpen() )
+        {
+            throw new ClosedChannelException();
+        }
+
+        if ( !src.hasArray() || src.isReadOnly() )
+        {
+            throw new NonWritableChannelException();
+        }
+
+        if ( src.remaining() != src.capacity() )
+        {
+            src.flip();
+        }
+
+        int countWrittenBytes = 0;
+
+        if ( src.hasRemaining() )
+        {
+            countWrittenBytes = src.remaining();
+            writeImpl( src );
+            src.position( src.limit() );
+            if ( flushable )
+            {
+                flush();
+            }
+        }
+        return countWrittenBytes;
+    }
+
+    @Override
+    public final boolean isOpen()
+    {
+        return open;
+    }
+
+    @Override
+    public final void close() throws IOException
+    {
+        open = false;
+        closeImpl();
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
new file mode 100644
index 0000000..65cb9b4
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
@@ -0,0 +1,122 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * 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 javax.annotation.Nonnull;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Converts {@link OutputStream}, {@link java.io.PrintStream}, {@link InputStream}
+ * to the Java {@link java.nio.channels.Channel}.
+ * <br>
+ * We do not use the Java's utility class {@link java.nio.channels.Channels} because the utility
+ * closes the stream as soon as the particular Thread is interrupted.
+ * If the frameworks (Zookeeper, Netty) interrupts the thread, the communication channels become
+ * closed and the JVM hangs. Therefore we developed internal utility which is safe for the Surefire.
+ *
+ * @since 3.0.0-M5
+ */
+public final class Channels
+{
+    private Channels()
+    {
+        throw new IllegalStateException( "no instantiable constructor" );
+    }
+
+    public static WritableByteChannel newChannel( @Nonnull  OutputStream out )
+    {
+        return newChannel( out, false );
+    }
+
+    public static WritableByteChannel newFlushableChannel( @Nonnull OutputStream out )
+    {
+        return newChannel( out, true );
+    }
+
+    public static ReadableByteChannel newChannel( @Nonnull final InputStream is )
+    {
+        requireNonNull( is, "the stream should not be null" );
+
+        if ( is instanceof FileInputStream && FileInputStream.class.equals( is.getClass() ) )
+        {
+            return ( (FileInputStream) is ).getChannel();
+        }
+
+        return new AbstractNoninterruptibleReadableChannel()
+        {
+            @Override
+            protected int readImpl( ByteBuffer src ) throws IOException
+            {
+                int count = is.read( src.array(), src.arrayOffset() + src.position(), src.remaining() );
+                if ( count > 0 )
+                {
+                    src.position( count + src.position() );
+                }
+                return count;
+            }
+
+            @Override
+            protected void closeImpl() throws IOException
+            {
+                is.close();
+            }
+        };
+    }
+
+    private static WritableByteChannel newChannel( @Nonnull final OutputStream out, final boolean flushable )
+    {
+        requireNonNull( out, "the stream should not be null" );
+
+        if ( out instanceof FileOutputStream && FileOutputStream.class.equals( out.getClass() ) )
+        {
+            return ( (FileOutputStream) out ).getChannel();
+        }
+
+        return new AbstractNoninterruptibleWritableChannel( flushable )
+        {
+            @Override
+            protected void writeImpl( ByteBuffer src ) throws IOException
+            {
+                out.write( src.array(), src.arrayOffset() + src.position(), src.remaining() );
+            }
+
+            @Override
+            protected void closeImpl() throws IOException
+            {
+                out.close();
+            }
+
+            @Override
+            public void flush() throws IOException
+            {
+                out.flush();
+            }
+        };
+    }
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
index 66a95a6..650807f 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -35,6 +35,8 @@ import org.apache.maven.surefire.util.RunOrderCalculatorTest;
 import org.apache.maven.surefire.util.RunOrderTest;
 import org.apache.maven.surefire.util.ScanResultTest;
 import org.apache.maven.surefire.util.TestsToRunTest;
+import org.apache.maven.surefire.util.internal.ChannelsReaderTest;
+import org.apache.maven.surefire.util.internal.ChannelsWriterTest;
 import org.apache.maven.surefire.util.internal.ConcurrencyUtilsTest;
 import org.apache.maven.surefire.util.internal.ImmutableMapTest;
 import org.junit.runner.RunWith;
@@ -62,7 +64,9 @@ import org.junit.runners.Suite;
     SpecificTestClassFilterTest.class,
     FundamentalFilterTest.class,
     ImmutableMapTest.class,
-    ReflectionUtilsTest.class
+    ReflectionUtilsTest.class,
+    ChannelsReaderTest.class,
+    ChannelsWriterTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
new file mode 100644
index 0000000..7e3a685
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
@@ -0,0 +1,250 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * 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.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NonReadableChannelException;
+import java.nio.channels.ReadableByteChannel;
+
+import static java.nio.file.Files.write;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * The tests for {@link Channels#newChannel(InputStream)}.
+ */
+public class ChannelsReaderTest
+{
+    @Rule
+    public final ExpectedException ee = ExpectedException.none();
+
+    @Rule
+    public final TemporaryFolder tmp = TemporaryFolder.builder()
+        .assureDeletion()
+        .build();
+
+    @Test
+    public void exactBufferSize() throws Exception
+    {
+        ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
+        ReadableByteChannel channel = Channels.newChannel( is );
+        ByteBuffer bb = ByteBuffer.allocate( 3 );
+
+        int countWritten = channel.read( bb );
+
+        assertThat( countWritten )
+            .isEqualTo( 3 );
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 3 );
+
+        bb.flip();
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.array() )
+            .isEqualTo( new byte[] {1, 2, 3} );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+
+        channel.close();
+
+        assertThat( channel.isOpen() )
+            .isFalse();
+    }
+
+    @Test
+    public void biggerBuffer() throws Exception
+    {
+        ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
+        ReadableByteChannel channel = Channels.newChannel( is );
+        ByteBuffer bb = ByteBuffer.allocate( 4 );
+
+        int countWritten = channel.read( bb );
+
+        assertThat( countWritten )
+            .isEqualTo( 3 );
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 1 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 4 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        bb.flip();
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        assertThat( bb.array() )
+            .isEqualTo( new byte[] {1, 2, 3, 0} );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+
+        channel.close();
+
+        assertThat( channel.isOpen() )
+            .isFalse();
+    }
+
+    @Test
+    public void shouldFailAfterClosed() throws IOException
+    {
+        ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
+        ReadableByteChannel channel = Channels.newChannel( is );
+        channel.close();
+        assertThat( channel.isOpen() ).isFalse();
+        ee.expect( ClosedChannelException.class );
+        channel.read( ByteBuffer.allocate( 0 ) );
+    }
+
+    @Test
+    public void shouldFailIfNotReadable() throws IOException
+    {
+        ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
+        ReadableByteChannel channel = Channels.newChannel( is );
+        ee.expect( NonReadableChannelException.class );
+        channel.read( ByteBuffer.allocate( 0 ).asReadOnlyBuffer() );
+    }
+
+    @Test
+    public void shouldFailIOnDirectBuffer() throws IOException
+    {
+        ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
+        ReadableByteChannel channel = Channels.newChannel( is );
+        ee.expect( NonReadableChannelException.class );
+        channel.read( ByteBuffer.allocateDirect( 0 ) );
+    }
+
+    @Test
+    public void shouldUseFileChannel() throws IOException
+    {
+        File f = tmp.newFile();
+        write( f.toPath(), new byte[] {1, 2, 3} );
+        FileInputStream is = new FileInputStream( f );
+        ReadableByteChannel channel = Channels.newChannel( is );
+        ByteBuffer bb = ByteBuffer.allocate( 4 );
+        int countWritten = channel.read( bb );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+
+        channel.close();
+
+        assertThat( channel.isOpen() )
+            .isFalse();
+
+        assertThat( countWritten )
+            .isEqualTo( 3 );
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 1 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 4 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        bb.flip();
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        assertThat( bb.array() )
+            .isEqualTo( new byte[] {1, 2, 3, 0} );
+    }
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
new file mode 100644
index 0000000..a1f6a66
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
@@ -0,0 +1,171 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * 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.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.WritableByteChannel;
+
+import static java.nio.file.Files.readAllBytes;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * The tests for {@link Channels#newChannel(OutputStream)} and {@link Channels#newFlushableChannel(OutputStream)}.
+ */
+public class ChannelsWriterTest
+{
+    @Rule
+    public final ExpectedException ee = ExpectedException.none();
+
+    @Rule
+    public final TemporaryFolder tmp = TemporaryFolder.builder()
+        .assureDeletion()
+        .build();
+
+    @Test
+    public void wrappedBuffer() throws Exception
+    {
+        final boolean[] isFlush = {false};
+        ByteArrayOutputStream out = new ByteArrayOutputStream()
+        {
+            @Override
+            public void flush() throws IOException
+            {
+                isFlush[0] = true;
+                super.flush();
+            }
+        };
+        WritableByteChannel channel = Channels.newFlushableChannel( out );
+        ByteBuffer bb = ByteBuffer.wrap( new byte[] {1, 2, 3} );
+        int countWritten = channel.write( bb );
+        assertThat( countWritten )
+            .isEqualTo( 3 );
+
+        assertThat( out.toByteArray() )
+            .hasSize( 3 )
+            .isEqualTo( new byte[] {1, 2, 3} );
+
+        assertThat( isFlush )
+            .hasSize( 1 )
+            .containsOnly( true );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 3 );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+    }
+
+    @Test
+    public void bigBuffer() throws Exception
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        WritableByteChannel channel = Channels.newChannel( out );
+        ByteBuffer bb = ByteBuffer.allocate( 4 );
+        bb.put( (byte) 1 );
+        bb.put( (byte) 2 );
+        bb.put( (byte) 3 );
+        int countWritten = channel.write( bb );
+        assertThat( countWritten ).isEqualTo( 3 );
+        assertThat( out.toByteArray() )
+            .hasSize( 3 )
+            .isEqualTo( new byte[] {1, 2, 3} );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+    }
+
+    @Test
+    public void shouldFailAfterClosed() throws IOException
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        WritableByteChannel channel = Channels.newChannel( out );
+        channel.close();
+        assertThat( channel.isOpen() ).isFalse();
+        ee.expect( ClosedChannelException.class );
+        channel.write( ByteBuffer.allocate( 0 ) );
+    }
+
+    @Test
+    public void shouldFailIfNotReadable() throws IOException
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        WritableByteChannel channel = Channels.newChannel( out );
+        ee.expect( NonWritableChannelException.class );
+        channel.write( ByteBuffer.allocate( 0 ).asReadOnlyBuffer() );
+    }
+
+    @Test
+    public void shouldFailIOnDirectBuffer() throws IOException
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        WritableByteChannel channel = Channels.newChannel( out );
+        ee.expect( NonWritableChannelException.class );
+        channel.write( ByteBuffer.allocateDirect( 0 ) );
+    }
+
+    @Test
+    public void shouldUseFileChannel() throws IOException
+    {
+        File f = tmp.newFile();
+        FileOutputStream os = new FileOutputStream( f );
+        WritableByteChannel channel = Channels.newChannel( os );
+        ByteBuffer bb = ByteBuffer.wrap( new byte[] {1, 2, 3} );
+        channel.write( bb );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+
+        channel.close();
+
+        assertThat( channel.isOpen() )
+            .isFalse();
+
+        assertThat( readAllBytes( f.toPath() ) )
+            .hasSize( 3 )
+            .isEqualTo( new byte[] {1, 2, 3} );
+    }
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index 34b752e..46d9d74 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -382,6 +382,9 @@ public final class ForkedBooter
 
     private void acknowledgedExit()
     {
+        //noinspection ResultOfMethodCallIgnored
+        Thread.interrupted();
+
         commandReader.addByeAckListener( new CommandListener()
                                           {
                                               @Override
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
index a9bd41a..a85e4ef 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
@@ -289,23 +289,27 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
 
     private void encodeAndPrintEvent( StringBuilder event )
     {
-        byte[] array = event.append( '\n' ).toString().getBytes( STREAM_ENCODING );
-        synchronized ( out )
+        try
         {
-            try
-            {
-                out.write( ByteBuffer.wrap( array ) );
-            }
-            catch ( ClosedChannelException e )
-            {
-                DumpErrorSingleton.getSingleton()
-                    .dumpText( "Channel closed while writing the event '" + event + "'." );
-            }
-            catch ( IOException e )
-            {
-                DumpErrorSingleton.getSingleton().dumpException( e );
-                trouble = true;
-            }
+            //noinspection ResultOfMethodCallIgnored
+            Thread.interrupted();
+
+            byte[] array = event.append( '\n' )
+                .toString()
+                .getBytes( STREAM_ENCODING );
+
+            out.write( ByteBuffer.wrap( array ) );
+        }
+        catch ( ClosedChannelException e )
+        {
+            DumpErrorSingleton.getSingleton()
+                .dumpException( e, "Channel closed while writing the event '" + event + "'." );
+        }
+        catch ( IOException e )
+        {
+            DumpErrorSingleton.getSingleton()
+                .dumpException( e );
+            trouble = true;
         }
     }
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
index c6e11da..5713b99 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
@@ -26,7 +26,8 @@ import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import java.io.IOException;
 import java.net.MalformedURLException;
 
-import static java.nio.channels.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newFlushableChannel;
 
 /**
  * Producer of encoder and decoder for process pipes.
@@ -62,7 +63,7 @@ public class LegacyMasterProcessChannelProcessorFactory
     @Override
     public MasterProcessChannelEncoder createEncoder()
     {
-        return new LegacyMasterProcessChannelEncoder( newChannel( System.out ) );
+        return new LegacyMasterProcessChannelEncoder( newFlushableChannel( System.out ) );
     }
 
     @Override
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
index 43ec328..18dae45 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
@@ -27,8 +27,8 @@ import java.nio.channels.Channel;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 
-import static java.nio.channels.Channels.newChannel;
-import static org.apache.maven.surefire.extensions.util.FlushableWritableByteChannel.newFlushableChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newFlushableChannel;
 
 /**
  *
@@ -44,8 +44,10 @@ public final class CommandlineStreams implements Closeable
     {
         InputStream stdOutStream = process.getInputStream();
         stdOutChannel = newChannel( stdOutStream );
+
         InputStream stdErrStream = process.getErrorStream();
         stdErrChannel = newChannel( stdErrStream );
+
         stdInChannel = newFlushableChannel( process.getOutputStream() );
     }
 
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/FlushableWritableByteChannel.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/FlushableWritableByteChannel.java
deleted file mode 100644
index e4112f2..0000000
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/FlushableWritableByteChannel.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.apache.maven.surefire.extensions.util;
-
-/*
- * 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 javax.annotation.Nonnull;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.WritableByteChannel;
-
-import static java.nio.channels.Channels.newChannel;
-
-/**
- *
- */
-final class FlushableWritableByteChannel implements WritableByteChannel
-{
-    private final OutputStream os;
-    private final WritableByteChannel channel;
-
-    private FlushableWritableByteChannel( @Nonnull OutputStream os )
-    {
-        this.os = os;
-        this.channel = newChannel( os );
-    }
-
-    static synchronized WritableByteChannel newFlushableChannel( OutputStream os )
-    {
-        return new FlushableWritableByteChannel( os );
-    }
-
-    @Override
-    public int write( ByteBuffer src ) throws IOException
-    {
-        int countWrittenBytes = channel.write( src );
-        os.flush();
-        return countWrittenBytes;
-    }
-
-    @Override
-    public boolean isOpen()
-    {
-        return channel.isOpen();
-    }
-
-    @Override
-    public void close() throws IOException
-    {
-        channel.close();
-    }
-}


[maven-surefire] 07/18: fixed the IT 735

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 02ab1373bc526033d5b6cbf203059b9c627d5d3e
Author: tibordigana <ti...@apache.org>
AuthorDate: Sun Mar 22 10:53:47 2020 +0100

    fixed the IT 735
---
 .../its/jiras/Surefire735ForkFailWithRedirectConsoleOutputIT.java      | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire735ForkFailWithRedirectConsoleOutputIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire735ForkFailWithRedirectConsoleOutputIT.java
index ff5d14a..efbb0f7 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire735ForkFailWithRedirectConsoleOutputIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire735ForkFailWithRedirectConsoleOutputIT.java
@@ -63,7 +63,8 @@ public class Surefire735ForkFailWithRedirectConsoleOutputIT
                                                  @Override
                                                  public boolean accept( File dir, String name )
                                                  {
-                                                     return name.endsWith( ".dumpstream" );
+                                                     return name.endsWith( ".dumpstream" )
+                                                         && !name.contains( "-jvmRun1" );
                                                  }
                                              }
         );


[maven-surefire] 04/18: documentation and Javadoc

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 0328f178c9cdd2becb243915a0ce98eb926e59e3
Author: tibordigana <ti...@apache.org>
AuthorDate: Sun Mar 15 14:21:43 2020 +0100

    documentation and Javadoc
---
 .../maven/plugin/failsafe/IntegrationTestMojo.java |  12 ++
 .../maven/plugin/surefire/SurefirePlugin.java      |  12 ++
 .../src/site/apt/examples/process-communication.vm | 153 +++++++++++++++++++++
 maven-surefire-plugin/src/site/site.xml            |   1 +
 4 files changed, 178 insertions(+)

diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
index a6771bc..0e6e26d 100644
--- a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
+++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
@@ -385,6 +385,18 @@ public class IntegrationTestMojo
     @Parameter( property = "failsafe.useModulePath", defaultValue = "true" )
     private boolean useModulePath;
 
+    /**
+     * This parameter configures the forked node. Currently, you can select the communication protocol, i.e. process
+     * pipes or TCP/IP sockets.
+     * The plugin uses process pipes by default which will be turned to TCP/IP in the version 3.0.0.
+     * Alternatively, you can implement your own factory and SPI.
+     * <br>
+     * See the documentation for more details:<br>
+     * <a href="https://maven.apache.org/plugins/maven-surefire-plugin/examples/process-communication.html">
+     *     https://maven.apache.org/plugins/maven-surefire-plugin/examples/process-communication.html</a>
+     *
+     * @since 3.0.0-M5
+     */
     @Parameter( property = "failsafe.forkNode" )
     private ForkNodeFactory forkNode;
 
diff --git a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
index 5f6edea..b958f92 100644
--- a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
+++ b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
@@ -366,6 +366,18 @@ public class SurefirePlugin
     @Parameter( property = "surefire.useModulePath", defaultValue = "true" )
     private boolean useModulePath;
 
+    /**
+     * This parameter configures the forked node. Currently, you can select the communication protocol, i.e. process
+     * pipes or TCP/IP sockets.
+     * The plugin uses process pipes by default which will be turned to TCP/IP in the version 3.0.0.
+     * Alternatively, you can implement your own factory and SPI.
+     * <br>
+     * See the documentation for more details:<br>
+     * <a href="https://maven.apache.org/plugins/maven-surefire-plugin/examples/process-communication.html">
+     *     https://maven.apache.org/plugins/maven-surefire-plugin/examples/process-communication.html</a>
+     *
+     * @since 3.0.0-M5
+     */
     @Parameter( property = "surefire.forkNode" )
     private ForkNodeFactory forkNode;
 
diff --git a/maven-surefire-plugin/src/site/apt/examples/process-communication.vm b/maven-surefire-plugin/src/site/apt/examples/process-communication.vm
new file mode 100644
index 0000000..75e553e
--- /dev/null
+++ b/maven-surefire-plugin/src/site/apt/examples/process-communication.vm
@@ -0,0 +1,153 @@
+------
+Using JUnit 5 Platform
+------
+Communication Channels in Surefire <us...@maven.apache.org>
+------
+2020-03-15
+------
+
+~~ 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.
+
+~~ NOTE: For help with the syntax of this file, see:
+~~ http://maven.apache.org/doxia/references/apt-format.html
+
+Communication Channels used between the Maven Process and Surefire Process
+
+  There is one way to change the communication channel and two types of connection in the ${thisPlugin}.
+  The communication channel can be changed even by the user in the user's POM.
+  The physical layer of the channel can be switched from Process Pipes to TCP/IP.
+  The user select the default implementations in the POM or the user can implement his own channel without asking
+  the Apache Maven development team for a new support. This chapter will show you how this can be accomplished.
+
+* forkNode
+
+  The configuration is done by using only one configuration parameter <<<forkMode>>>.
+
+* The TCP/IP communication channel
+
+  The ${thisPlugin} plugin uses process pipes by default. The implementation class for default configuration
+  is <<<org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory>>> and it does not have to be specified.
+  The TCP/IP channel can be selected as follows and the implementation class has to be specified:
+
++---+
+<project>
+    [...]
+    <build>
+        <plugins>
+            [...]
+            <plugin>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>${project.artifactId}</artifactId>
+                <version>${project.version}</version>
+                <configuration>
+                    <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                </configuration>
+            </plugin>
+            [...]
+        </plugins>
+    </build>
+    [...]
+</project>
++---+
+
+* Custom implementation
+
+  The custom implementation involves two implementations. The first is used by the Maven process and there you
+  should implement the interface <<<org.apache.maven.surefire.extensions.ForkNodeFactory>>>, use the implementation
+  in a dependency <<<your-extension-api-impl-artifactid>>> declared below the plugin and configuration should
+  specify fully qualified class name of your custom <<<ForkNodeFactory>>> in the attribute <<<implementation>>>.
+
+  The second implementation is SPI which is utilized by the forked JVM. Implement the Java SPI interface
+  <<<org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory>>> in the artifact
+  <<<your-extension-spi-impl-artifactid>>>.
+
+
++---+
+<project>
+    [...]
+    <dependencies>
+        <dependency>
+            <groupId>your-extension-spi-impl-groupid</groupId>
+            <artifactId>your-extension-spi-impl-artifactid</artifactId>
+            <version>your-extension-spi-impl-version</version>
+        </dependency>
+    </dependencies>
+    [...]
+    <build>
+        <plugins>
+            [...]
+            <plugin>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>${project.artifactId}</artifactId>
+                <version>${project.version}</version>
+                <dependencies>
+                    <dependency>
+                        <groupId>your-extension-api-impl-groupid</groupId>
+                        <artifactId>your-extension-api-impl-artifactid</artifactId>
+                        <version>your-extension-api-impl-version</version>
+                    </dependency>
+                </dependencies>
+                <configuration>
+                    <forkNode implementation="your.extention.api.impl.CustomForkNodeFactory"/>
+                </configuration>
+            </plugin>
+            [...]
+        </plugins>
+    </build>
+    [...]
+</project>
++---+
+
+  The project <<<your-extension-api-impl-artifactid>>> should have the following dependency as mandatory:
+
++---+
+<project>
+    [...]
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>surefire-extensions-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+    [...]
+</project>
++---+
+
+  The project <<<your-extension-spi-impl-artifactid>>> should have the following dependency as mandatory:
+
++---+
+<project>
+    [...]
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>surefire-extensions-spi</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+    [...]
+</project>
++---+
+
+  Your SPI implementation appears in
+  <<<src/main/resources/META-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory>>>
+  and contains one implementation class. Your POM already specified it, see
+  <<<your.extention.api.impl.CustomForkNodeFactory>>>.
diff --git a/maven-surefire-plugin/src/site/site.xml b/maven-surefire-plugin/src/site/site.xml
index 87efd78..2cec467 100644
--- a/maven-surefire-plugin/src/site/site.xml
+++ b/maven-surefire-plugin/src/site/site.xml
@@ -38,6 +38,7 @@
       <item name="Download" href="../download.html"/>
     </menu>
     <menu name="Examples">
+      <item name="TCP/IP Communication between Forks" href="examples/process-communication.html"/>
       <item name="Using TestNG" href="examples/testng.html"/>
       <item name="Using JUnit" href="examples/junit.html"/>
       <item name="Using JUnit 5 Platform" href="examples/junit-platform.html"/>


[maven-surefire] 02/18: added TCP alternative in ConsoleOutputIT

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 7a3cdef95d66267ec3991e4475546bd2ea1f977a
Author: tibordigana <ti...@apache.org>
AuthorDate: Mon Mar 9 08:43:00 2020 +0100

    added TCP alternative in ConsoleOutputIT
---
 .../spi/LegacyMasterProcessChannelEncoder.java     | 12 +++-
 .../apache/maven/surefire/its/ConsoleOutputIT.java | 78 +++++++++++++++++++---
 .../src/test/resources/consoleOutput/pom.xml       | 17 +++++
 3 files changed, 93 insertions(+), 14 deletions(-)

diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
index 57c84d4..a9bd41a 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
@@ -32,6 +32,7 @@ import org.apache.maven.surefire.shared.codec.binary.Base64;
 import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
 import java.nio.channels.WritableByteChannel;
 import java.nio.charset.Charset;
 import java.util.Map;
@@ -286,15 +287,20 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         encodeAndPrintEvent( event );
     }
 
-    private void encodeAndPrintEvent( StringBuilder command )
+    private void encodeAndPrintEvent( StringBuilder event )
     {
-        byte[] array = command.append( '\n' ).toString().getBytes( STREAM_ENCODING );
+        byte[] array = event.append( '\n' ).toString().getBytes( STREAM_ENCODING );
         synchronized ( out )
         {
             try
             {
                 out.write( ByteBuffer.wrap( array ) );
             }
+            catch ( ClosedChannelException e )
+            {
+                DumpErrorSingleton.getSingleton()
+                    .dumpText( "Channel closed while writing the event '" + event + "'." );
+            }
             catch ( IOException e )
             {
                 DumpErrorSingleton.getSingleton().dumpException( e );
@@ -410,7 +416,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
      *
      * @param operation opcode
      * @param runMode   run mode
-     * @return encoded command
+     * @return encoded event
      */
     static StringBuilder encodeOpcode( String operation, String runMode )
     {
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
index 6f0802c..430441b 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
@@ -19,10 +19,17 @@ package org.apache.maven.surefire.its;
  * under the License.
  */
 
+import com.googlecode.junittoolbox.ParallelParameterized;
 import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.apache.maven.surefire.its.fixture.TestFile;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -31,32 +38,71 @@ import static java.nio.charset.StandardCharsets.UTF_8;
  *
  * @author Kristian Rosenvold
  */
+@RunWith( ParallelParameterized.class )
 public class ConsoleOutputIT
     extends SurefireJUnit4IntegrationTestCase
 {
+    @Parameters
+    public static Iterable<Object[]> data()
+    {
+        ArrayList<Object[]> args = new ArrayList<>();
+        args.add( new Object[] { "tcp" } );
+        args.add( new Object[] { null } );
+        return args;
+    }
+
+    @Parameter
+    @SuppressWarnings( "checkstyle:visibilitymodifier" )
+    public String profileId;
+
     @Test
     public void properNewlinesAndEncodingWithDefaultEncodings()
     {
-        final OutputValidator outputValidator =
-            unpack( "/consoleOutput" ).forkOnce().executeTest();
+        SurefireLauncher launcher =
+            unpack( "/consoleOutput", profileId == null ? "" : profileId )
+                .forkOnce();
 
-        validate( outputValidator, true );
+        if ( profileId != null )
+        {
+            launcher.activateProfile( "tcp" );
+        }
+
+        OutputValidator outputValidator = launcher.executeTest();
+
+        validate( outputValidator, profileId == null );
     }
 
     @Test
     public void properNewlinesAndEncodingWithDifferentEncoding()
     {
-        final OutputValidator outputValidator =
-            unpack( "/consoleOutput" ).forkOnce().argLine( "-Dfile.encoding=UTF-16" ).executeTest();
+        SurefireLauncher launcher =
+            unpack( "/consoleOutput", profileId == null ? "" : profileId )
+                .forkOnce()
+                .argLine( "-Dfile.encoding=UTF-16" );
 
-        validate( outputValidator, true );
+        if ( profileId != null )
+        {
+            launcher.activateProfile( "tcp" );
+        }
+
+        OutputValidator outputValidator = launcher.executeTest();
+
+        validate( outputValidator, profileId == null );
     }
 
     @Test
     public void properNewlinesAndEncodingWithoutFork()
     {
-        final OutputValidator outputValidator =
-            unpack( "/consoleOutput" ).forkNever().executeTest();
+        SurefireLauncher launcher =
+            unpack( "/consoleOutput", profileId == null ? "" : profileId )
+                .forkNever();
+
+        if ( profileId != null )
+        {
+            launcher.activateProfile( "tcp" );
+        }
+
+        OutputValidator outputValidator = launcher.executeTest();
 
         validate( outputValidator, false );
     }
@@ -75,6 +121,8 @@ public class ConsoleOutputIT
 
         if ( includeShutdownHook )
         {
+            //todo it should not be reported in the last test which is completed
+            //todo this text should be in null-output.txt
             outputFile.assertContainsText( "Printline in shutdown hook" );
         }
     }
@@ -82,9 +130,17 @@ public class ConsoleOutputIT
     @Test
     public void largerSoutThanMemory()
     {
-        unpack( "consoleoutput-noisy" )
+        SurefireLauncher launcher =
+            unpack( "consoleoutput-noisy", profileId == null ? "" : "-" + profileId )
                 .setMavenOpts( "-Xmx64m" )
-                .sysProp( "thousand", "32000" )
-                .executeTest();
+                .sysProp( "thousand", "32000" );
+
+        if ( profileId != null )
+        {
+            launcher.activateProfile( "tcp" );
+        }
+
+        launcher.executeTest()
+            .verifyErrorFreeLog();
     }
 }
diff --git a/surefire-its/src/test/resources/consoleOutput/pom.xml b/surefire-its/src/test/resources/consoleOutput/pom.xml
index 407f909..2b52941 100644
--- a/surefire-its/src/test/resources/consoleOutput/pom.xml
+++ b/surefire-its/src/test/resources/consoleOutput/pom.xml
@@ -43,4 +43,21 @@
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   </properties>
+
+    <profiles>
+        <profile>
+            <id>tcp</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 </project>


[maven-surefire] 16/18: removed unused methods in CommandReader.java

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit b8de0e4fa334d2b18b72e4d709b706bc0e78155b
Author: tibordigana <ti...@apache.org>
AuthorDate: Tue Apr 7 00:52:02 2020 +0200

    removed unused methods in CommandReader.java
---
 .../surefire/providerapi/CommandChainReader.java   |  4 ---
 .../maven/surefire/booter/CommandReader.java       | 41 +++-------------------
 2 files changed, 4 insertions(+), 41 deletions(-)

diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
index 2c94e9d..1710246 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
@@ -29,11 +29,7 @@ public interface CommandChainReader
     boolean awaitStarted()
         throws TestSetFailedException;
 
-    void addTestsFinishedListener( CommandListener listener );
-
     void addSkipNextTestsListener( CommandListener listener );
 
     void addShutdownListener( CommandListener listener );
-
-    void removeListener( CommandListener listener );
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
index 28766f5..f875240 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
@@ -35,21 +35,19 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicReference;
 
-import static java.util.Objects.requireNonNull;
+import static java.lang.StrictMath.max;
 import static java.lang.Thread.State.NEW;
 import static java.lang.Thread.State.RUNNABLE;
 import static java.lang.Thread.State.TERMINATED;
-import static java.lang.StrictMath.max;
+import static java.util.Objects.requireNonNull;
 import static org.apache.maven.surefire.booter.Command.toShutdown;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED;
-import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
 import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
 import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
+import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
 
 /**
  * Reader of commands coming from plugin(master) process.
@@ -113,31 +111,13 @@ public final class CommandReader implements CommandChainReader
         }
     }
 
-    /**
-     * @param listener listener called with <b>Any</b> {@link MasterProcessCommand command type}
-     */
-    public void addListener( CommandListener listener )
-    {
-        listeners.add( new BiProperty<MasterProcessCommand, CommandListener>( null, listener ) );
-    }
-
-    public void addTestListener( CommandListener listener )
-    {
-        addListener( RUN_CLASS, listener );
-    }
-
-    @Override
-    public void addTestsFinishedListener( CommandListener listener )
-    {
-        addListener( TEST_SET_FINISHED, listener );
-    }
-
     @Override
     public void addSkipNextTestsListener( CommandListener listener )
     {
         addListener( SKIP_SINCE_NEXT_TEST, listener );
     }
 
+    @Override
     public void addShutdownListener( CommandListener listener )
     {
         addListener( SHUTDOWN, listener );
@@ -158,19 +138,6 @@ public final class CommandReader implements CommandChainReader
         listeners.add( new BiProperty<>( cmd, listener ) );
     }
 
-    @Override
-    public void removeListener( CommandListener listener )
-    {
-        for ( Iterator<BiProperty<MasterProcessCommand, CommandListener>> it = listeners.iterator(); it.hasNext(); )
-        {
-            BiProperty<MasterProcessCommand, CommandListener> listenerWrapper = it.next();
-            if ( listener == listenerWrapper.getP2() )
-            {
-                it.remove();
-            }
-        }
-    }
-
     /**
      * @return test classes which have been retrieved by
      * {@link CommandReader#getIterableClasses(MasterProcessChannelEncoder)}.


[maven-surefire] 14/18: E2ETest performance test

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit e1b8dd45e23d7a2665364304f06ebb4e5acfbc43
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Apr 4 10:22:55 2020 +0200

    E2ETest performance test
---
 .../java/org/apache/maven/plugin/surefire/extensions/E2ETest.java     | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

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 c22da94..8f4fe5d 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
@@ -169,7 +169,9 @@ public class E2ETest
         // 2 seconds while using the encoder/decoder
         // 160 millis of sending pure data without encoder/decoder
         assertThat( readTime.get() )
+            .describedAs( "The performance test should assert 2s of read time. "
+                + "The limit 6s guarantees that the read time does not exceed this limit on overloaded CPU." )
             .isPositive()
-            .isLessThanOrEqualTo( 3_000L );
+            .isLessThanOrEqualTo( 6_000L );
     }
 }


[maven-surefire] 15/18: improved coverage in new code

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 9877f5492d21dbff21b1c679d09f96166f07a6d9
Author: tibordigana <ti...@apache.org>
AuthorDate: Sun Apr 5 02:25:41 2020 +0200

    improved coverage in new code
---
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   2 -
 .../maven/surefire/util/internal/Channels.java     |  14 +-
 .../java/org/apache/maven/JUnit4SuiteTest.java     |   4 +-
 .../surefire/util/internal}/AsyncSocketTest.java   |   3 +-
 .../surefire/util/internal/ChannelsReaderTest.java | 239 ++++++++++++++++++++
 .../surefire/util/internal/ChannelsWriterTest.java | 243 +++++++++++++++++++++
 .../surefire/booter/ForkedBooterMockTest.java      |  36 ++-
 .../maven/surefire/booter/ForkedBooterTest.java    |  20 +-
 8 files changed, 549 insertions(+), 12 deletions(-)

diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index 94754a7..22bf702 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -40,7 +40,6 @@ import org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfigu
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClientTest;
-import org.apache.maven.plugin.surefire.extensions.AsyncSocketTest;
 import org.apache.maven.plugin.surefire.extensions.ConsoleOutputReporterTest;
 import org.apache.maven.plugin.surefire.extensions.E2ETest;
 import org.apache.maven.plugin.surefire.extensions.ForkedProcessEventNotifierTest;
@@ -111,7 +110,6 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( ForkChannelTest.class ) );
         suite.addTest( new JUnit4TestAdapter( StreamFeederTest.class ) );
         suite.addTest( new JUnit4TestAdapter( E2ETest.class ) );
-        suite.addTest( new JUnit4TestAdapter( AsyncSocketTest.class ) );
         return suite;
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
index a536fc6..b15e6f6 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
@@ -28,7 +28,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousByteChannel;
-import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 import java.util.concurrent.ExecutionException;
@@ -99,7 +99,9 @@ public final class Channels
                         catch ( ExecutionException e )
                         {
                             Throwable t = e.getCause();
-                            throw new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
+                            throw t instanceof IOException
+                                ? (IOException) t
+                                : new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
                         }
                         catch ( Exception e )
                         {
@@ -124,7 +126,7 @@ public final class Channels
                     {
                         channel.close();
                     }
-                    catch ( AsynchronousCloseException e )
+                    catch ( ClosedChannelException e )
                     {
                         // closed channel anyway
                     }
@@ -157,7 +159,9 @@ public final class Channels
                 catch ( ExecutionException e )
                 {
                     Throwable t = e.getCause();
-                    throw new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
+                    throw t instanceof IOException
+                        ? (IOException) t
+                        : new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
                 }
                 catch ( Exception e )
                 {
@@ -188,7 +192,7 @@ public final class Channels
                     {
                         channel.close();
                     }
-                    catch ( AsynchronousCloseException e )
+                    catch ( ClosedChannelException e )
                     {
                         // closed channel anyway
                     }
diff --git a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
index 650807f..231ff74 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -35,6 +35,7 @@ import org.apache.maven.surefire.util.RunOrderCalculatorTest;
 import org.apache.maven.surefire.util.RunOrderTest;
 import org.apache.maven.surefire.util.ScanResultTest;
 import org.apache.maven.surefire.util.TestsToRunTest;
+import org.apache.maven.surefire.util.internal.AsyncSocketTest;
 import org.apache.maven.surefire.util.internal.ChannelsReaderTest;
 import org.apache.maven.surefire.util.internal.ChannelsWriterTest;
 import org.apache.maven.surefire.util.internal.ConcurrencyUtilsTest;
@@ -66,7 +67,8 @@ import org.junit.runners.Suite;
     ImmutableMapTest.class,
     ReflectionUtilsTest.class,
     ChannelsReaderTest.class,
-    ChannelsWriterTest.class
+    ChannelsWriterTest.class,
+    AsyncSocketTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/AsyncSocketTest.java
similarity index 98%
rename from maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java
rename to surefire-api/src/test/java/org/apache/maven/surefire/util/internal/AsyncSocketTest.java
index 88cba07..8363e29 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/AsyncSocketTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.util.internal;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,7 +19,6 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
 import org.junit.Test;
 
 import java.io.BufferedInputStream;
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
index 0b64a42..8a3b0a4 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
@@ -23,19 +23,37 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InterruptedIOException;
 import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonReadableChannelException;
 import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ShutdownChannelGroupException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 import static java.nio.file.Files.write;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 /**
  * The tests for {@link Channels#newChannel(InputStream)} and {@link Channels#newBufferedChannel(InputStream)}.
@@ -303,4 +321,225 @@ public class ChannelsReaderTest
         assertThat( bb.array() )
             .isEqualTo( new byte[] {1, 2, 3, 0} );
     }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput1() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[0], -1, 0 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput2() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[0], 0, -1 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput3() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[0], 1, 0 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput4() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[0], 0, 1 );
+    }
+
+    @Test
+    public void shouldClose() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( true );
+        InputStream is = Channels.newInputStream( channel );
+        is.close();
+        verify( channel, times( 1 ) ).close();
+    }
+
+    @Test
+    public void shouldNotClose() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( false );
+        InputStream is = Channels.newInputStream( channel );
+        is.close();
+        verify( channel, never() ).close();
+    }
+
+    @Test
+    public void shouldAlreadyClosed() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( true );
+        doThrow( ClosedChannelException.class ).when( channel ).close();
+        InputStream is = Channels.newInputStream( channel );
+        is.close();
+        verify( channel ).close();
+    }
+
+    @Test
+    public void shouldReadZeroLength() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[] { 5 }, 0, 0 );
+        verifyZeroInteractions( channel );
+    }
+
+    @Test
+    public void shouldReadArray() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Future<Integer>>()
+            {
+                @Override
+                public Future<Integer> answer( InvocationOnMock invocation ) throws Throwable
+                {
+                    ByteBuffer bb = (ByteBuffer) invocation.getArguments()[0];
+                    bb.put( (byte) 3 );
+                    bb.put( (byte) 4 );
+                    Future<Integer> future = mock( Future.class );
+                    when( future.get() ).thenReturn( 2 );
+                    return future;
+                }
+            } );
+
+        InputStream is = Channels.newInputStream( channel );
+        ArgumentCaptor<ByteBuffer> captured = ArgumentCaptor.forClass( ByteBuffer.class );
+        byte[] b = new byte[] { 1, 2, 0, 0, 5 };
+        is.read( b, 2, 2 );
+
+        verify( channel ).read( captured.capture() );
+        verifyNoMoreInteractions( channel );
+
+        assertThat( captured.getAllValues() )
+            .hasSize( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).array() )
+            .containsOnly( new byte[] { 1, 2, 3, 4, 5 } );
+
+        assertThat( captured.getAllValues().get( 0 ).arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( captured.getAllValues().get( 0 ).position() )
+            .isEqualTo( 4 );
+
+        assertThat( captured.getAllValues().get( 0 ).limit() )
+            .isEqualTo( 4 );
+
+        assertThat( captured.getAllValues().get( 0 ).capacity() )
+            .isEqualTo( 5 );
+    }
+
+    @Test
+    public void shouldRead() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Future<Integer>>()
+            {
+                @Override
+                public Future<Integer> answer( InvocationOnMock invocation ) throws Throwable
+                {
+                    ByteBuffer bb = (ByteBuffer) invocation.getArguments()[0];
+                    bb.put( (byte) 3 );
+                    Future<Integer> future = mock( Future.class );
+                    when( future.get() ).thenReturn( 1 );
+                    return future;
+                }
+            } );
+
+        InputStream is = Channels.newInputStream( channel );
+        ArgumentCaptor<ByteBuffer> captured = ArgumentCaptor.forClass( ByteBuffer.class );
+        int b = is.read();
+        assertThat( b )
+            .isEqualTo( 3 );
+
+        verify( channel ).read( captured.capture() );
+        verifyNoMoreInteractions( channel );
+
+        assertThat( captured.getAllValues() )
+            .hasSize( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).array() )
+            .containsOnly( new byte[] { 3 } );
+
+        assertThat( captured.getAllValues().get( 0 ).arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( captured.getAllValues().get( 0 ).position() )
+            .isEqualTo( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).limit() )
+            .isEqualTo( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).capacity() )
+            .isEqualTo( 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnRead() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenThrow( ShutdownChannelGroupException.class );
+        InputStream is = Channels.newInputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectCause( instanceOf( ShutdownChannelGroupException.class ) );
+        is.read( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture1() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( new InterruptedIOException() ) );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        InputStream is = Channels.newInputStream( channel );
+        ee.expect( InterruptedIOException.class );
+        is.read( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture2() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( new RuntimeException( "msg" ) ) );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        InputStream is = Channels.newInputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectCause( instanceOf( RuntimeException.class ) );
+        ee.expectMessage( "msg" );
+        is.read( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture3() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( "msg", null ) );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        InputStream is = Channels.newInputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectMessage( "msg" );
+        is.read( new byte[1], 0, 1 );
+    }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
index 35c9ddd..24ed3f3 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
@@ -23,19 +23,37 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.ShutdownChannelGroupException;
 import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 import static java.nio.file.Files.readAllBytes;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 /**
  * The tests for {@link Channels#newChannel(OutputStream)} and {@link Channels#newBufferedChannel(OutputStream)}.
@@ -207,4 +225,229 @@ public class ChannelsWriterTest
             .hasSize( 3 )
             .isEqualTo( new byte[] {1, 2, 3} );
     }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput1() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[0], -1, 0 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput2() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[0], 0, -1 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput3() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[0], 1, 0 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput4() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[0], 0, 1 );
+    }
+
+    @Test
+    public void shouldClose() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( true );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.close();
+        verify( channel, times( 1 ) ).close();
+    }
+
+    @Test
+    public void shouldNotClose() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( false );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.close();
+        verify( channel, never() ).close();
+    }
+
+    @Test
+    public void shouldAlreadyClosed() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( true );
+        doThrow( ClosedChannelException.class ).when( channel ).close();
+        OutputStream os = Channels.newOutputStream( channel );
+        os.close();
+        verify( channel ).close();
+    }
+
+    @Test
+    public void shouldWriteZeroLength() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[] { 5 }, 0, 0 );
+        verifyZeroInteractions( channel );
+    }
+
+    @Test
+    public void shouldWriteArray() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Future<Integer>>()
+            {
+                @Override
+                public Future<Integer> answer( InvocationOnMock invocation ) throws Throwable
+                {
+                    ByteBuffer bb = (ByteBuffer) invocation.getArguments()[0];
+                    int i = 0;
+                    for ( ; bb.hasRemaining(); i++ )
+                    {
+                        bb.get();
+                    }
+                    Future<Integer> future = mock( Future.class );
+                    when( future.get() ).thenReturn( i );
+                    return future;
+                }
+            } );
+
+        OutputStream os = Channels.newOutputStream( channel );
+        ArgumentCaptor<ByteBuffer> captured = ArgumentCaptor.forClass( ByteBuffer.class );
+        os.write( new byte[] { 1, 2, 3, 4, 5 }, 2, 2 );
+
+        verify( channel ).write( captured.capture() );
+        verifyNoMoreInteractions( channel );
+
+        assertThat( captured.getAllValues() )
+            .hasSize( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).array() )
+            .containsOnly( new byte[] { 1, 2, 3, 4, 5 } );
+
+        assertThat( captured.getAllValues().get( 0 ).arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( captured.getAllValues().get( 0 ).position() )
+            .isEqualTo( 4 );
+
+        assertThat( captured.getAllValues().get( 0 ).limit() )
+            .isEqualTo( 4 );
+
+        assertThat( captured.getAllValues().get( 0 ).capacity() )
+            .isEqualTo( 5 );
+    }
+
+    @Test
+    public void shouldWrite() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Future<Integer>>()
+            {
+                @Override
+                public Future<Integer> answer( InvocationOnMock invocation ) throws Throwable
+                {
+                    ByteBuffer bb = (ByteBuffer) invocation.getArguments()[0];
+                    int i = 0;
+                    for ( ; bb.hasRemaining(); i++ )
+                    {
+                        bb.get();
+                    }
+                    Future<Integer> future = mock( Future.class );
+                    when( future.get() ).thenReturn( i );
+                    return future;
+                }
+            } );
+
+        OutputStream os = Channels.newOutputStream( channel );
+        ArgumentCaptor<ByteBuffer> captured = ArgumentCaptor.forClass( ByteBuffer.class );
+        os.write( 3 );
+
+        verify( channel ).write( captured.capture() );
+        verifyNoMoreInteractions( channel );
+
+        assertThat( captured.getAllValues() )
+            .hasSize( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).array() )
+            .containsOnly( new byte[] { 3 } );
+
+        assertThat( captured.getAllValues().get( 0 ).arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( captured.getAllValues().get( 0 ).position() )
+            .isEqualTo( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).limit() )
+            .isEqualTo( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).capacity() )
+            .isEqualTo( 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnWrite() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenThrow( ShutdownChannelGroupException.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectCause( instanceOf( ShutdownChannelGroupException.class ) );
+        os.write( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture1() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( new InterruptedIOException() ) );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        OutputStream os = Channels.newOutputStream( channel );
+        ee.expect( InterruptedIOException.class );
+        os.write( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture2() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( new RuntimeException( "msg" ) ) );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        OutputStream os = Channels.newOutputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectCause( instanceOf( RuntimeException.class ) );
+        ee.expectMessage( "msg" );
+        os.write( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture3() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( "msg", null ) );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        OutputStream os = Channels.newOutputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectMessage( "msg" );
+        os.write( new byte[1], 0, 1 );
+    }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
index 5604fd1..4a3690e 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
@@ -24,6 +24,7 @@ import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import org.junit.Rule;
 import org.junit.Test;
@@ -33,6 +34,8 @@ import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
@@ -50,9 +53,11 @@ import static org.fest.assertions.Fail.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.powermock.api.mockito.PowerMockito.doAnswer;
 import static org.powermock.api.mockito.PowerMockito.doCallRealMethod;
 import static org.powermock.api.mockito.PowerMockito.doNothing;
 import static org.powermock.api.mockito.PowerMockito.doThrow;
@@ -67,7 +72,12 @@ import static org.powermock.reflect.Whitebox.setInternalState;
  * PowerMock tests for {@link ForkedBooter}.
  */
 @RunWith( PowerMockRunner.class )
-@PrepareForTest( { PpidChecker.class, ForkedBooter.class, LegacyMasterProcessChannelEncoder.class } )
+@PrepareForTest( {
+                     PpidChecker.class,
+                     ForkedBooter.class,
+                     LegacyMasterProcessChannelEncoder.class,
+                     ShutdownHookUtils.class
+} )
 @PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
 public class ForkedBooterMockTest
 {
@@ -349,4 +359,28 @@ public class ForkedBooterMockTest
             }
         }
     }
+
+    @Test
+    public void testFlushEventChannelOnExit() throws Exception
+    {
+        mockStatic( ShutdownHookUtils.class );
+
+        final MasterProcessChannelEncoder eventChannel = mock( MasterProcessChannelEncoder.class );
+        ForkedBooter booter = new ForkedBooter();
+        setInternalState( booter, "eventChannel", eventChannel );
+
+        doAnswer( new Answer<Object>()
+        {
+            @Override
+            public Object answer( InvocationOnMock invocation )
+            {
+                Thread t = invocation.getArgument( 0 );
+                assertThat( t.isDaemon() ).isTrue();
+                t.run();
+                verify( eventChannel, times( 1 ) ).onJvmExit();
+                return null;
+            }
+        } ).when( ShutdownHookUtils.class, "addShutDownHook", any( Thread.class ) );
+        invokeMethod( booter, "flushEventChannelOnExit" );
+    }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
index aac78f7..ef2ba61 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
@@ -148,7 +148,7 @@ public class ForkedBooterTest
     }
 
     @Test( timeout = 10_000 )
-    public void testBarrier() throws Exception
+    public void testBarrier1() throws Exception
     {
         Semaphore semaphore = new Semaphore( 2 );
         boolean acquiredOnePermit = invokeMethod( ForkedBooter.class, "acquireOnePermit", semaphore, 30_000L );
@@ -158,6 +158,24 @@ public class ForkedBooterTest
     }
 
     @Test
+    public void testBarrier2() throws Exception
+    {
+        Semaphore semaphore = new Semaphore( 0 );
+        Thread.currentThread().interrupt();
+        try
+        {
+            boolean acquiredOnePermit = invokeMethod( ForkedBooter.class, "acquireOnePermit", semaphore, 30_000L );
+
+            assertThat( acquiredOnePermit ).isTrue();
+            assertThat( semaphore.availablePermits() ).isEqualTo( 0 );
+        }
+        finally
+        {
+            assertThat( Thread.interrupted() ).isFalse();
+        }
+    }
+
+    @Test
     public void testScheduler() throws Exception
     {
         ScheduledThreadPoolExecutor executor = invokeMethod( ForkedBooter.class, "createPingScheduler" );


[maven-surefire] 08/18: improved performance from 320s to 54s.

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit f3aa9c295bba96380f0ce7ca85e0b3e7c5610212
Author: tibordigana <ti...@apache.org>
AuthorDate: Tue Mar 24 00:57:22 2020 +0100

    improved performance from 320s to 54s.
---
 .../surefire/extensions/EventConsumerThread.java   | 26 +++++++++++++++++-----
 .../surefire/extensions/SurefireForkChannel.java   |  4 +---
 2 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
index cc33c6c..b92a238 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -126,8 +126,9 @@ public class EventConsumerThread extends CloseableDaemonThread
         List<String> tokens = new ArrayList<>();
         StringBuilder line = new StringBuilder();
         StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
-        ByteBuffer buffer = ByteBuffer.allocate( 1 );
-        boolean endOfStream;
+        ByteBuffer buffer = ByteBuffer.allocate( 1024 );
+        buffer.position( buffer.limit() );
+        boolean streamContinues;
 
         start:
         do
@@ -136,11 +137,9 @@ public class EventConsumerThread extends CloseableDaemonThread
             tokens.clear();
             token.setLength( 0 );
             FrameCompletion completion = null;
-            for ( boolean frameStarted = false; !( endOfStream = channel.read( buffer ) == -1 ) ; completion = null )
+            for ( boolean frameStarted = false; streamContinues = read( buffer ); completion = null )
             {
-                buffer.flip();
                 char c = (char) buffer.get();
-                buffer.clear();
 
                 if ( c == '\n' || c == '\r' )
                 {
@@ -193,7 +192,7 @@ public class EventConsumerThread extends CloseableDaemonThread
                 }
             }
 
-            if ( endOfStream )
+            if ( !streamContinues )
             {
                 printExistingLine( line );
                 return;
@@ -202,6 +201,21 @@ public class EventConsumerThread extends CloseableDaemonThread
         while ( true );
     }
 
+    private boolean read( ByteBuffer buffer ) throws IOException
+    {
+        if ( buffer.hasRemaining() )
+        {
+            return true;
+        }
+        else
+        {
+            buffer.clear();
+            boolean isEndOfStream = channel.read( buffer ) == -1;
+            buffer.flip();
+            return !isEndOfStream;
+        }
+    }
+
     private void printExistingLine( StringBuilder line )
     {
         if ( line.length() != 0 )
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 0aa790c..e11b03f 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
@@ -60,8 +60,6 @@ import static java.nio.channels.ServerSocketChannel.open;
  */
 final class SurefireForkChannel extends ForkChannel
 {
-    private static final byte[] LOCAL_LOOPBACK_IP_ADDRESS = new byte[]{127, 0, 0, 1};
-
     private final ConsoleLogger logger;
     private final ServerSocketChannel server;
     private final int localPort;
@@ -73,7 +71,7 @@ final class SurefireForkChannel extends ForkChannel
         this.logger = logger;
         server = open();
         setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
-        InetAddress ip = Inet4Address.getByAddress( LOCAL_LOOPBACK_IP_ADDRESS );
+        InetAddress ip = Inet4Address.getLoopbackAddress();
         server.bind( new InetSocketAddress( ip, 0 ), 1 );
         localPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort();
     }