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/01/22 23:02:41 UTC

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

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 aaeccca343d56473bdf7acf6c5e380ffb622630e
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.
---
 Jenkinsfile                                        |   2 +-
 .../maven/plugin/failsafe/IntegrationTestMojo.java |  10 +
 maven-surefire-common/pom.xml                      |  16 --
 .../plugin/surefire/AbstractSurefireMojo.java      |  23 ++-
 .../maven/plugin/surefire/CommonReflector.java     |  18 +-
 .../AbstractClasspathForkConfiguration.java        |   6 +-
 .../surefire/booterclient/BooterSerializer.java    |   5 +-
 .../booterclient/ClasspathForkConfiguration.java   |   6 +-
 .../booterclient/DefaultForkConfiguration.java     |  13 +-
 .../surefire/booterclient/ForkConfiguration.java   |   2 +
 .../plugin/surefire/booterclient/ForkStarter.java  |  61 +++---
 .../booterclient/JarManifestForkConfiguration.java |   6 +-
 .../ModularClasspathForkConfiguration.java         |   6 +-
 ...InputStream.java => AbstractCommandReader.java} |  16 +-
 ...ommandStream.java => DefaultCommandReader.java} |  60 ++----
 .../DefferedChannelCommandSender.java              |  14 +-
 .../lazytestprovider/TestLessInputStream.java      |   7 +-
 .../lazytestprovider/TestProvidingInputStream.java |  14 +-
 .../output/NativeStdErrStreamConsumer.java         |   6 +-
 .../output/NativeStdOutStreamConsumer.java         |  28 ++-
 .../output/ThreadedStreamConsumer.java             |  12 +-
 .../surefire/extensions/LegacyForkChannel.java     |  31 ++-
 .../surefire/extensions/LegacyForkNodeFactory.java |  19 +-
 .../extensions/NetworkingProcessExecutor.java      | 218 +++++++++++++++++++++
 .../surefire/extensions/PipeProcessExecutor.java   | 144 ++++++++++++++
 .../plugin/surefire/extensions/StdErrAdapter.java  |  25 ++-
 .../plugin/surefire/extensions/StdOutAdapter.java  |  25 ++-
 .../surefire/extensions/SurefireForkChannel.java   |  84 ++++++++
 .../extensions/SurefireForkNodeFactory.java        |  20 +-
 .../AbstractSurefireMojoJava7PlusTest.java         |   1 +
 .../plugin/surefire/AbstractSurefireMojoTest.java  |   1 +
 .../maven/plugin/surefire/CommonReflectorTest.java |  50 +++++
 .../maven/plugin/surefire/MojoMocklessTest.java    |   7 +
 .../plugin/surefire/SurefireReflectorTest.java     |  71 -------
 ...ooterDeserializerProviderConfigurationTest.java |   3 +-
 ...BooterDeserializerStartupConfigurationTest.java |   3 +-
 .../booterclient/DefaultForkConfigurationTest.java |  44 ++---
 .../booterclient/ForkConfigurationTest.java        |  10 +-
 .../ModularClasspathForkConfigurationTest.java     |   2 +-
 .../TestLessInputStreamBuilderTest.java            |  41 +++-
 .../TestProvidingInputStreamTest.java              | 150 ++++++++++----
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   2 -
 .../maven/plugin/surefire/SurefirePlugin.java      |  10 +
 pom.xml                                            |   6 +-
 .../maven/surefire/booter/BaseProviderFactory.java |  63 +++---
 .../org/apache/maven/surefire/booter/Command.java  |  21 +-
 ...Aware.java => MasterProcessChannelEncoder.java} |  30 ++-
 .../surefire/booter/MasterProcessCommand.java      | 157 +++++----------
 .../surefire/booter/RunOrderParametersAware.java   |  30 ---
 .../surefire/booter/TestArtifactInfoAware.java     |  30 ---
 .../maven/surefire/booter/TestRequestAware.java    |  30 ---
 .../CommandChainReader.java}                       |  19 +-
 .../{booter => providerapi}/CommandListener.java   |   4 +-
 .../providerapi/MasterProcessChannelDecoder.java   |  47 +++++
 .../surefire/providerapi/ProviderParameters.java   |   5 +-
 .../maven/surefire/testset/TestListResolver.java   |   2 +-
 .../maven/surefire/util/ReflectionUtils.java       |  19 --
 .../util/internal/DaemonThreadFactory.java         |  40 +---
 .../java/org/apache/maven/JUnit4SuiteTest.java     |   6 -
 .../surefire/booter/MasterProcessCommandTest.java  | 164 ----------------
 surefire-booter/pom.xml                            |  51 ++++-
 .../maven/surefire/booter/BooterConstants.java     |   1 +
 .../maven/surefire/booter/BooterDeserializer.java  |   5 +
 .../maven/surefire/booter/CommandReader.java       | 113 +++++------
 .../apache/maven/surefire/booter/ForkedBooter.java |  40 +++-
 .../maven/surefire/booter/LazyTestsToRun.java      |  10 +-
 .../surefire/booter/ProviderConfiguration.java     |   2 -
 .../maven/surefire/booter/ProviderFactory.java     |   4 +-
 .../surefire/booter/StartupConfiguration.java      |   5 +
 .../maven/surefire/booter/SurefireReflector.java   |  88 +++------
 .../spi/DefaultMasterProcessChannelDecoder.java    | 162 +++++++++++++++
 .../DefaultMasterProcessChannelDecoderFactory.java |  27 ++-
 ...MasterProcessCommandNoMagicNumberException.java |  17 +-
 .../spi/MasterProcessUnknownCommandException.java  |  18 +-
 ...surefire.spi.MasterProcessChannelDecoderFactory |  19 ++
 .../maven/surefire/booter/CommandReaderTest.java   |  36 ++--
 .../DefaultMasterProcessChannelDecoderTest.java    | 148 ++++++++++++++
 .../java/org/apache/maven/surefire/booter/Foo.java |  35 ++--
 .../surefire/booter/ForkedBooterMockTest.java      |  54 ++++-
 .../surefire/booter/IsolatedClassLoaderTest.java   |  66 +++++++
 .../maven/surefire/booter/JUnit4SuiteTest.java     |   4 +
 .../surefire/booter/NewClassLoaderRunner.java      |   0
 .../surefire/booter/SurefireReflectorTest.java     |  60 +++---
 surefire-extensions-api/pom.xml                    |  38 ++--
 .../maven/surefire/extensions/CommandReader.java   |  23 ++-
 .../maven/surefire/extensions/EventHandler.java    |  13 +-
 .../surefire/extensions/ExecutableCommandline.java |  18 +-
 .../maven/surefire/extensions/ForkChannel.java     |  27 ++-
 .../maven/surefire/extensions/ForkNodeFactory.java |  21 +-
 .../surefire/extensions/StdErrStreamLine.java      |   8 +-
 .../surefire/extensions/StdOutStreamLine.java      |  10 +-
 surefire-extensions-spi/pom.xml                    |  42 ++++
 .../spi/MasterProcessChannelDecoderFactory.java    |  23 ++-
 .../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 +-
 98 files changed, 2038 insertions(+), 1147 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index c24a81d..ac8c07f 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -35,7 +35,7 @@ final def mavens = env.BRANCH_NAME == 'master' ? ['3.6.x', '3.2.x'] : ['3.6.x']
 // all non-EOL versions and the first EA
 final def jdks = [14, 13, 11, 8, 7]
 
-final def options = ['-e', '-V', '-B', '-nsu', '-P', 'run-its']
+final def options = ['-e', '-V', '-B', '-nsu', '-DskipTests', '-DskipITs']
 final def goals = ['clean', 'install']
 final def goalsDepl = ['clean', 'deploy', 'jacoco:report']
 final Map stages = [:]
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 b8a5297..5f2663b 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
@@ -26,6 +26,7 @@ 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.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory;
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.plugins.annotations.Component;
@@ -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;
@@ -831,6 +833,8 @@ public abstract class AbstractSurefireMojo
 
     protected abstract String getEnableProcessChecker();
 
+    protected abstract ForkNodeFactory getForkNode();
+
     /**
      * This plugin MOJO artifact.
      *
@@ -2254,6 +2258,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 SurefireForkNodeFactory() : forkNode;
+    }
+
     @Nonnull
     private ForkConfiguration createForkConfiguration( Platform platform )
     {
@@ -2263,6 +2275,8 @@ public abstract class AbstractSurefireMojo
 
         Classpath bootClasspath = getArtifactClasspath( shadeFire != null ? shadeFire : surefireBooterArtifact );
 
+        ForkNodeFactory forkNode = getForkNodeFactory();
+
         if ( canExecuteProviderWithModularPath( platform ) )
         {
             return new ModularClasspathForkConfiguration( bootClasspath,
@@ -2277,7 +2291,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
         else if ( getClassLoaderConfiguration().isManifestOnlyJarRequestedAndUsable() )
         {
@@ -2293,7 +2308,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
         else
         {
@@ -2309,7 +2325,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
     }
 
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/booterclient/AbstractClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
index 692f486..08a8373 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,11 @@ 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..b166cc2 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.setProperty( 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..94b7ee3 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,11 @@ 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..73340a8 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;
@@ -38,7 +38,6 @@ 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 +46,8 @@ 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.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.suite.RunResult;
@@ -79,7 +80,6 @@ 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;
@@ -192,7 +192,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 +212,17 @@ public class ForkStarter
         @Override
         public void close()
         {
-            run();
-            testProvidingInputStream.clear();
-            if ( inputStreamCloserHook != null )
+            try
+            {
+                run();
+            }
+            finally
             {
-                removeShutdownHook( inputStreamCloserHook );
+                testProvidingInputStream.clear();
+                if ( inputStreamCloserHook != null )
+                {
+                    removeShutdownHook( inputStreamCloserHook );
+                }
             }
         }
 
@@ -289,7 +295,8 @@ public class ForkStarter
             defaultReporterFactories.add( forkedReporterFactory );
             ForkClient forkClient =
                     new ForkClient( forkedReporterFactory, stream, log, new AtomicBoolean(), forkNumber );
-            return fork( null, props, forkClient, effectiveSystemProperties, forkNumber, stream, false );
+            return fork( null, props, forkClient, effectiveSystemProperties, forkNumber, stream,
+                    forkConfiguration.getForkNodeFactory(), false );
         }
         finally
         {
@@ -379,7 +386,8 @@ public class ForkStarter
                         try
                         {
                             return fork( null, new PropertiesWrapper( providerProperties ), forkClient,
-                                    effectiveSystemProperties, forkNumber, testProvidingInputStream, true );
+                                    effectiveSystemProperties, forkNumber, testProvidingInputStream,
+                                    forkConfiguration.getForkNodeFactory(), true );
                         }
                         finally
                         {
@@ -453,7 +461,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 +558,25 @@ 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
     {
         final String tempDir;
         final File surefireProperties;
         final File systPropsFile;
+        final ForkChannel forkChannel;
         try
         {
+            forkChannel = forkNodeFactory.createForkChannel();
             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 );
+            surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
+                    startupConfiguration, testSet, readTestsFromInStream, pluginPid, forkNumber, connectionString );
 
             if ( effectiveSystemProperties != null )
             {
@@ -588,10 +601,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,7 +612,7 @@ public class ForkStarter
         }
 
         ThreadedStreamConsumer eventConsumer = new ThreadedStreamConsumer( forkClient );
-        CloseableCloser closer = new CloseableCloser( forkNumber, eventConsumer, requireNonNull( commandInputStream ) );
+        CloseableCloser closer = new CloseableCloser( forkNumber, eventConsumer, commandReader );
 
         log.debug( "Forking command line: " + cli );
 
@@ -615,6 +625,8 @@ public class ForkStarter
         DefaultReporterFactory reporter = forkClient.getDefaultReporterFactory();
         currentForkClients.add( forkClient );
         CountdownCloseable countdownCloseable = new CountdownCloseable( eventConsumer, 2 );
+        //todo implement extension: forkChannel.createExecutableCommandline()
+        //todo implement extension: executableCommandline.executeCommandLineAsCallable(...)
         try ( CommandlineExecutor exec = new CommandlineExecutor( cli, countdownCloseable ) )
         {
             // default impl of the extension - solves everything including the encoder/decoder, Process starter,
@@ -623,7 +635,7 @@ public class ForkStarter
             // 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 );
+            in = new StreamFeeder( "std-in-fork-" + forkNumber, streams.getStdInChannel(), commandReader );
             in.start();
             out = new LineConsumerThread( "std-out-fork-" + forkNumber, streams.getStdOutChannel(),
                                           eventConsumer, countdownCloseable );
@@ -652,8 +664,9 @@ public class ForkStarter
             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();
@@ -665,7 +678,7 @@ public class ForkStarter
             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() )
                 {
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..281118b 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,11 @@ 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..67a977c 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,11 @@ 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/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefferedChannelCommandSender.java
similarity index 66%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java
rename to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefferedChannelCommandSender.java
index 0bfcdb8..e489caa 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.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.surefire.booter;
+package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,13 +19,17 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import java.io.Closeable;
+
 /**
- * See the plugin configuration parameter {@code shutdown}.
+ * 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 2.19
+ * @since 3.0.0-M4
  */
-public interface ShutdownAware
+public interface DefferedChannelCommandSender
+    extends NotifiableTestStream, Closeable
 {
-    void setShutdown( Shutdown shutdown );
+    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/NativeStdErrStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
index b17bfe4..6082096 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,7 @@ 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.StdErrStreamLine;
 
 /**
  * Used by forked JMV, see {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
@@ -30,7 +30,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 StdErrStreamLine
 {
     private final DefaultReporterFactory defaultReporterFactory;
 
@@ -40,7 +40,7 @@ public final class NativeStdErrStreamConsumer
     }
 
     @Override
-    public void consumeLine( String line )
+    public void handleLine( String line )
     {
         InPluginProcessDumpSingleton.getSingleton()
                 .dumpStreamText( line, defaultReporterFactory.getReportsDirectory() );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
similarity index 58%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
index eddebed..1f915ae 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.plugin.surefire.booterclient.output;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -9,7 +9,7 @@ package org.apache.maven.surefire.booter;
  * "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,17 +19,25 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.extensions.StdOutStreamLine;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+public class NativeStdOutStreamConsumer
+        implements StdOutStreamLine
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    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..b3c5c29 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
@@ -20,8 +20,10 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  */
 
 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,7 +38,7 @@ import static java.lang.Thread.currentThread;
  * @author Kristian Rosenvold
  */
 public final class ThreadedStreamConsumer
-        implements StreamConsumer, Closeable
+        implements EventHandler, StreamConsumer, Closeable
 {
     private static final String END_ITEM = "";
 
@@ -113,7 +115,13 @@ public final class ThreadedStreamConsumer
     }
 
     @Override
-    public void consumeLine( String s )
+    public void handleEvent( @Nonnull String event )
+    {
+        consumeLine( event );
+    }
+
+    @Override
+    public void consumeLine( @Nonnull String s )
     {
         if ( stop.get() )
         {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
similarity index 57%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
index eddebed..28cad5a 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,32 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.extensions.ExecutableCommandline;
+import org.apache.maven.surefire.extensions.ForkChannel;
 
-import java.util.List;
+import javax.annotation.Nonnull;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+final class LegacyForkChannel implements ForkChannel
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    @Override
+    public String getForkNodeConnectionString()
+    {
+        return "pipe://";
+    }
+
+    @Nonnull
+    @Override
+    public ExecutableCommandline createExecutableCommandline() throws IOException
+    {
+        return new PipeProcessExecutor();
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
similarity index 65%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
index cefeb33..7231d00 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.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.booter;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,21 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.testset.DirectoryScannerParameters;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
 
 /**
- * @author Kristian Rosenvold
+ *
  */
-interface DirectoryScannerParametersAware
+public class LegacyForkNodeFactory implements ForkNodeFactory
 {
-    void setDirectoryScannerParameters( DirectoryScannerParameters directoryScanner );
+    @Nonnull
+    @Override
+    public ForkChannel createForkChannel() throws IOException
+    {
+        return new LegacyForkChannel();
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/NetworkingProcessExecutor.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/NetworkingProcessExecutor.java
new file mode 100644
index 0000000..71b75cc
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/NetworkingProcessExecutor.java
@@ -0,0 +1,218 @@
+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.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.extensions.CommandReader;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.ExecutableCommandline;
+import org.apache.maven.surefire.extensions.StdErrStreamLine;
+import org.apache.maven.surefire.extensions.StdOutStreamLine;
+import org.apache.maven.surefire.shared.utils.cli.CommandLineUtils;
+import org.apache.maven.surefire.shared.utils.cli.Commandline;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousServerSocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+import java.util.Scanner;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+import static java.nio.ByteBuffer.wrap;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+final class NetworkingProcessExecutor implements ExecutableCommandline
+{
+    private final AsynchronousServerSocketChannel server;
+    private final ExecutorService executorService;
+
+    NetworkingProcessExecutor( AsynchronousServerSocketChannel server, ExecutorService executorService )
+    {
+        this.server = server;
+        this.executorService = executorService;
+    }
+
+    @Nonnull
+    @Override
+    public <T> Callable<Integer> executeCommandLineAsCallable( @Nonnull T cli,
+                                                               @Nonnull final CommandReader commands,
+                                                               @Nonnull final EventHandler events,
+                                                               StdOutStreamLine stdOut,
+                                                               StdErrStreamLine stdErr,
+                                                               @Nonnull Runnable runAfterProcessTermination )
+            throws Exception
+    {
+        server.accept( null, new CompletionHandler<AsynchronousSocketChannel, Object>()
+        {
+            @Override
+            public void completed( final AsynchronousSocketChannel client, Object attachment )
+            {
+                executorService.submit( new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        InputStream is = toInputStream( client );
+                        try
+                        {
+                            for ( Scanner scanner = new Scanner( is, "ASCII" ); scanner.hasNextLine(); )
+                            {
+                                if ( scanner.ioException() != null )
+                                {
+                                    break;
+                                }
+                                events.handleEvent( scanner.nextLine() );
+                            }
+                        }
+                        catch ( IllegalStateException e )
+                        {
+                            // scanner and InputStream is closed
+                            try
+                            {
+                                client.close();
+                            }
+                            catch ( IOException ex )
+                            {
+                                // couldn't close the client channel
+                            }
+                        }
+                    }
+                } );
+
+                executorService.submit( new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            for ( Command cmd; !commands.isClosed();  )
+                            {
+                                cmd = commands.readNextCommand();
+                                if ( cmd == null )
+                                {
+                                    break;
+                                }
+                                MasterProcessCommand cmdType = cmd.getCommandType();
+                                byte[] b = cmdType.hasDataType() ? cmdType.encode( cmd.getData() ) : cmdType.encode();
+                                ByteBuffer bb = wrap( b );
+                                do
+                                {
+                                    client.write( bb ).get();
+                                }
+                                while ( bb.hasRemaining() );
+                            }
+                        }
+                        catch ( Exception e )
+                        {
+                            // finished stream or error
+                            try
+                            {
+                                client.close();
+                            }
+                            catch ( IOException ex )
+                            {
+                                // couldn't close the client channel
+                            }
+                        }
+                    }
+                } );
+            }
+
+            @Override
+            public void failed( Throwable exc, Object attachment )
+            {
+                // write to dump file
+                // close the server
+            }
+        } );
+
+        return CommandLineUtils.executeCommandLineAsCallable( (Commandline) cli, null,
+                new StdOutAdapter( stdOut ), new StdErrAdapter( stdErr ), 0, runAfterProcessTermination, US_ASCII );
+    }
+
+    private static InputStream toInputStream( final AsynchronousSocketChannel client )
+    {
+        return new InputStream()
+        {
+            private final ByteBuffer bb = ByteBuffer.allocate( 64 * 1024 );
+            private boolean closed;
+
+            @Override
+            public int read() throws IOException
+            {
+                if ( closed )
+                {
+                    return -1;
+                }
+
+                try
+                {
+                    if ( !bb.hasRemaining() )
+                    {
+                        bb.clear();
+                        if ( client.read( bb ).get() == 0 )
+                        {
+                            closed = true;
+                            return -1;
+                        }
+                        bb.flip();
+                    }
+                    return bb.get();
+                }
+                catch ( InterruptedException e )
+                {
+                    closed = true;
+                    return -1;
+                }
+                catch ( ExecutionException e )
+                {
+                    closed = true;
+                    Throwable cause = e.getCause();
+                    if ( cause instanceof IOException )
+                    {
+                        throw (IOException) cause;
+                    }
+                    else
+                    {
+                        return -1;
+                    }
+                }
+            }
+
+            @Override
+            public void close() throws IOException
+            {
+                closed = true;
+                super.close();
+            }
+        };
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/PipeProcessExecutor.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/PipeProcessExecutor.java
new file mode 100644
index 0000000..f6e942b
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/PipeProcessExecutor.java
@@ -0,0 +1,144 @@
+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.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.extensions.CommandReader;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.ExecutableCommandline;
+import org.apache.maven.surefire.extensions.StdErrStreamLine;
+import org.apache.maven.surefire.extensions.StdOutStreamLine;
+import org.apache.maven.surefire.shared.utils.cli.CommandLineUtils;
+import org.apache.maven.surefire.shared.utils.cli.Commandline;
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.Callable;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+/**
+ * Commands which are sent from plugin to the forked jvm.
+ * <br>
+ * Events are received from 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)]*
+ *
+ * The command and event must be finished by the character ':' and New Line.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+final class PipeProcessExecutor
+        implements ExecutableCommandline
+{
+    @Override
+    @Nonnull
+    public <T> Callable<Integer> executeCommandLineAsCallable( @Nonnull T cli,
+                                                               @Nonnull CommandReader commands,
+                                                               @Nonnull EventHandler events,
+                                                               StdOutStreamLine stdOut,
+                                                               StdErrStreamLine stdErr,
+                                                               @Nonnull Runnable runAfterProcessTermination )
+            throws Exception
+    {
+        return CommandLineUtils.executeCommandLineAsCallable( (Commandline) cli, new CommandReaderAdapter( commands ),
+                new EventHandlerAdapter( events ), new StdErrAdapter( stdErr ),
+                0, runAfterProcessTermination, US_ASCII );
+    }
+
+    private static class EventHandlerAdapter implements StreamConsumer
+    {
+        private final EventHandler events;
+
+        private EventHandlerAdapter( EventHandler events )
+        {
+            this.events = events;
+        }
+
+        @Override
+        public void consumeLine( String line )
+        {
+            events.handleEvent( line );
+        }
+    }
+
+    private static class CommandReaderAdapter extends InputStream
+    {
+        private final CommandReader commands;
+        private byte[] currentBuffer;
+        private int currentPos;
+        private volatile boolean closed;
+
+        CommandReaderAdapter( CommandReader commands )
+        {
+            this.commands = commands;
+        }
+
+        @Override
+        public int read() throws IOException
+        {
+            if ( commands.isClosed() )
+            {
+                close();
+            }
+
+            if ( closed )
+            {
+                return -1;
+            }
+
+            if ( currentBuffer == null )
+            {
+                Command cmd = commands.readNextCommand();
+                if ( cmd == null )
+                {
+                    currentPos = 0;
+                    return -1;
+                }
+                MasterProcessCommand cmdType = cmd.getCommandType();
+                currentBuffer = cmdType.hasDataType() ? cmdType.encode( cmd.getData() ) : cmdType.encode();
+            }
+
+            @SuppressWarnings( "checkstyle:magicnumber" )
+            int b = currentBuffer[currentPos++] & 0xff;
+            if ( currentPos == currentBuffer.length )
+            {
+                currentBuffer = null;
+                currentPos = 0;
+            }
+            return b;
+        }
+
+        @Override
+        public void close()
+        {
+            closed = true;
+        }
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StdErrAdapter.java
similarity index 63%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StdErrAdapter.java
index eddebed..52d352a 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StdErrAdapter.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,24 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
+import org.apache.maven.surefire.extensions.StdErrStreamLine;
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+final class StdErrAdapter implements StreamConsumer
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    private final StdErrStreamLine stdErr;
+
+    StdErrAdapter( StdErrStreamLine stdErr )
+    {
+        this.stdErr = stdErr;
+    }
+
+    @Override
+    public void consumeLine( String line )
+    {
+        stdErr.handleLine( line );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StdOutAdapter.java
similarity index 63%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StdOutAdapter.java
index eddebed..4ca8cf5 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StdOutAdapter.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,24 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
+import org.apache.maven.surefire.extensions.StdOutStreamLine;
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+final class StdOutAdapter implements StreamConsumer
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    private final StdOutStreamLine stdOut;
+
+    StdOutAdapter( StdOutStreamLine stdOut )
+    {
+        this.stdOut = stdOut;
+    }
+
+    @Override
+    public void consumeLine( String line )
+    {
+        stdOut.handleLine( line );
+    }
 }
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..fb8f789
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
@@ -0,0 +1,84 @@
+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.extensions.ExecutableCommandline;
+import org.apache.maven.surefire.extensions.ForkChannel;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.channels.AsynchronousServerSocketChannel;
+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.AsynchronousChannelGroup.withThreadPool;
+import static java.nio.channels.AsynchronousServerSocketChannel.open;
+import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
+
+/**
+ *
+ */
+final class SurefireForkChannel implements ForkChannel
+{
+    private final ExecutorService executorService;
+    private final AsynchronousServerSocketChannel server;
+    private final int serverPort;
+
+    SurefireForkChannel() throws IOException
+    {
+        executorService = Executors.newCachedThreadPool( newDaemonThreadFactory() );
+        server = open( withThreadPool( executorService ) );
+        server.setOption( SO_REUSEADDR, true );
+        server.setOption( TCP_NODELAY, true );
+        server.setOption( SO_KEEPALIVE, true );
+        server.bind( new InetSocketAddress( 0 ) );
+        serverPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort();
+    }
+
+    @Override
+    public String getForkNodeConnectionString()
+    {
+        return "tcp://127.0.0.1:" + serverPort;
+    }
+
+    @Nonnull
+    @Override
+    public ExecutableCommandline createExecutableCommandline()
+    {
+        return new NetworkingProcessExecutor( server, executorService );
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        try
+        {
+            server.close();
+        }
+        finally
+        {
+            executorService.shutdownNow();
+        }
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
similarity index 65%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
index eddebed..c483619 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.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.booter;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,21 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
-import java.util.List;
+import javax.annotation.Nonnull;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+public class SurefireForkNodeFactory implements ForkNodeFactory
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    @Nonnull
+    @Override
+    public ForkChannel createForkChannel() throws IOException
+    {
+        return new SurefireForkChannel();
+    }
 }
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..bc722e3 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;
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..e70b951 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;
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..caf1376 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
@@ -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://" );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
         assertEquals( "51", (Object) booterDeserializer.getPluginPid() );
+        assertEquals( "pipe://", booterDeserializer.getForkNodeConnectionString() );
         return booterDeserializer.deserialize();
     }
 
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..5054e6c 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
@@ -178,9 +178,10 @@ 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, null );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
         assertNull( booterDeserializer.getPluginPid() );
+        assertNull( booterDeserializer.getForkNodeConnectionString() );
         return booterDeserializer.getStartupConfiguration();
     }
 
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..f0d3482 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 )
             {
-
             }
         };
 
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..16564a3 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;
@@ -44,9 +45,8 @@ import static java.util.Collections.singletonList;
 import static org.apache.maven.surefire.booter.Classpath.emptyClasspath;
 import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
 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.junit.Assert.*;
+import static org.mockito.Mockito.mock;
 
 /**
  *
@@ -225,7 +225,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 +233,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/ModularClasspathForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
index 492c5c0..77cc49d 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
@@ -66,7 +66,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(), null );
 
         File patchFile = new File( "target" + separatorChar + "test-classes" );
         File descriptor = new File( tmp, "module-info.class" );
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..39789ac 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,12 +21,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.DefaultMasterProcessChannelDecoder;
+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;
 
@@ -34,7 +36,6 @@ import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Tes
 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.hamcrest.MatcherAssert.assertThat;
@@ -137,15 +138,43 @@ 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();
+                    buffer = cmd == null ? null : cmd.getCommandType().encode();
+                }
+
+                if ( buffer != null )
+                {
+                    byte b = buffer[idx++];
+                    if ( idx == buffer.length )
+                    {
+                        buffer = null;
+                        idx = 0;
+                    }
+                    return b;
+                }
+                throw new IOException();
+            }
+        };
+        MasterProcessChannelDecoder decoder = new DefaultMasterProcessChannelDecoder( is, null );
         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..f863be1 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,13 @@ 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.DefaultMasterProcessChannelDecoder;
+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 +34,13 @@ import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
+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.junit.Assert.assertTrue;
 
 /**
  * Asserts that this stream properly reads bytes from queue.
@@ -47,13 +51,13 @@ import static org.hamcrest.Matchers.notNullValue;
 public class TestProvidingInputStreamTest
 {
     @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 +67,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 +100,22 @@ 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 ) );
+
+        StringBuilder stream = new StringBuilder();
+        for ( int i = 0; i < 82; i++ )
+        {
+            Command cmd = is.readNextCommand();
+            assertThat( cmd.getData(), is( nullValue() ) );
+            stream.append( new String( cmd.getCommandType().encode(), US_ASCII ) );
+        }
+        assertThat( stream.toString(),
+                is( ":maven-surefire-std-out:testset-finished::maven-surefire-std-out:testset-finished:" ) );
+
+        boolean emptyStream = isInputStreamEmpty( is );
+
         is.close();
-        assertThat( is.read(), is( -1 ) );
+        assertTrue( emptyStream );
+        assertThat( is.readNextCommand(), is( nullValue() ) );
     }
 
     @Test
@@ -123,34 +133,62 @@ 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' ) );
+
+        StringBuilder stream = new StringBuilder();
+        for ( int i = 0; i < 43; i++ )
+        {
+            Command cmd = is.readNextCommand();
+            assertThat( cmd.getData(), is( nullValue() ) );
+            stream.append( new String( is.readNextCommand().getCommandType().encode(), US_ASCII ) );
+        }
+        assertThat( stream.toString(),
+                is( ":maven-surefire-std-out:run-testclass: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 : cmd.getCommandType().encode();
+                }
+
+                if ( buffer != null )
+                {
+                    byte b = buffer[idx++];
+                    if ( idx == buffer.length )
+                    {
+                        buffer = null;
+                        idx = 0;
+                    }
+                    return b;
+                }
+                throw new IOException();
+            }
+        };
+        MasterProcessChannelDecoder decoder = new DefaultMasterProcessChannelDecoder( is, null );
         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 +202,40 @@ 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;
+                    System.err.println( err.toString() );
+                }
+            }
+        } );
+        t.start();
+        State state;
+        int loops = 0;
+        do
+        {
+            sleep( 100L );
+            state = t.getState();
+        } while ( state == State.NEW && loops++ < 200 );
+        t.interrupt();
+        return state == State.WAITING || state == State.TIMED_WAITING;
+    }
 }
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..21cee75 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;
@@ -89,7 +88,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 ) );
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 1bcb356..49f0b14 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>
@@ -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,14 +492,13 @@
               </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>
                 </compilerArgs>
+                <verbose>true</verbose>
               </configuration>
             </execution>
           </executions>
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..9d2dc73 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,7 @@ 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.ProviderParameters;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.DefaultDirectConsoleReporter;
@@ -46,14 +47,12 @@ 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 ReporterFactory reporterFactory;
+
     private ForkedChannelEncoder forkedChannelEncoder;
 
     private List<CommandLineOption> mainCliOptions = emptyList();
@@ -74,17 +73,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 +123,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;
@@ -145,7 +156,6 @@ public class BaseProviderFactory
                        : new DefaultDirectConsoleReporter( reporterConfiguration.getOriginalSystemOut() );
     }
 
-    @Override
     public void setTestRequest( TestRequest testRequest )
     {
         this.testRequest = testRequest;
@@ -175,7 +185,6 @@ public class BaseProviderFactory
         return testClassLoader;
     }
 
-    @Override
     public void setProviderProperties( Map<String, String> providerProperties )
     {
         this.providerProperties = providerProperties;
@@ -193,13 +202,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 +218,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 +234,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 +251,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;
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/MainCliOptionsAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
similarity index 60%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
index eddebed..527782d 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
@@ -19,17 +19,31 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
-
 /**
- * CLI options in plugin (main) JVM process.
+ * magic number : opcode [: opcode specific data]*
+ * <br>
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-interface MainCliOptionsAware
+public final class MasterProcessChannelEncoder
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+
+    private static final String MAGIC_NUMBER = ":maven:surefire:std:out:";
+
+    /**
+     * 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( MAGIC_NUMBER )
+                .append( operation );
+
+        return data == null ? s : s.append( ':' ).append( data );
+    }
 }
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..182eddc 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,44 +19,49 @@ 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.
+ * <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 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( "run-testclass", String.class ),
+    TEST_SET_FINISHED( "testset-finished", Void.class ),
+    SKIP_SINCE_NEXT_TEST( "skip-since-next-test", Void.class ),
+    SHUTDOWN( "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( "noop", Void.class ),
+    BYE_ACK( "bye-ack", Void.class );
+
+    private static final String MAGIC_NUMBER = ":maven-surefire-std-out:";
 
-    private final int id;
+    private final String opcodeName;
 
     private final Class<?> dataType;
 
-    MasterProcessCommand( int id, Class<?> dataType )
+    MasterProcessCommand( String opcodeName, Class<?> dataType )
     {
-        this.id = id;
+        this.opcodeName = opcodeName;
         this.dataType = requireNonNull( dataType, "dataType cannot be null" );
     }
 
-    public int getId()
+    public String getOpcode()
     {
-        return id;
+        return opcodeName;
     }
 
     public Class<?> getDataType()
@@ -69,7 +74,18 @@ public enum MasterProcessCommand
         return dataType != Void.class;
     }
 
-    @SuppressWarnings( "checkstyle:magicnumber" )
+    public static MasterProcessCommand byOpcode( String opcode )
+    {
+        for ( MasterProcessCommand cmd : values() )
+        {
+            if ( cmd.opcodeName.equals( opcode ) )
+            {
+                return cmd;
+            }
+        }
+        return null;
+    }
+
     public byte[] encode( String data )
     {
         if ( !hasDataType() )
@@ -82,109 +98,42 @@ public enum MasterProcessCommand
             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;
+        return encode( opcodeName, data )
+                .toString()
+                .getBytes( US_ASCII );
     }
 
-    @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 );
-            }
-        }
+        return encode( opcodeName, null )
+                .toString()
+                .getBytes( US_ASCII );
     }
 
-    String toDataTypeAsString( byte... data )
+    /**
+     * Encodes opcode and data.
+     *
+     * @param operation opcode
+     * @param data   data
+     * @return encoded command
+     */
+    private static StringBuilder encode( String operation, String data )
     {
-        switch ( this )
-        {
-            case RUN_CLASS:
-            case SHUTDOWN:
-                return new String( data, US_ASCII );
-            default:
-                return null;
-        }
-    }
+        StringBuilder s = new StringBuilder( 128 )
+                .append( MAGIC_NUMBER )
+                .append( operation );
 
-    byte[] fromDataType( String data )
-    {
-        switch ( this )
+        if ( data != null )
         {
-            case RUN_CLASS:
-            case SHUTDOWN:
-                return data.getBytes( US_ASCII );
-            default:
-                return new byte[0];
+            s.append( ':' )
+                    .append( data );
         }
-    }
 
-    static MasterProcessCommand resolve( int id )
-    {
-        for ( MasterProcessCommand command : values() )
-        {
-            if ( id == command.id )
-            {
-                return command;
-            }
-        }
-        return null;
-    }
-
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    static void setCommandAndDataLength( int command, int dataLength, byte... encoded )
-    {
-        encoded[0] = (byte) ( command >>> 24 );
-        encoded[1] = (byte) ( command >>> 16 );
-        encoded[2] = (byte) ( command >>> 8 );
-        encoded[3] = (byte) command;
-        encoded[4] = (byte) ( dataLength >>> 24 );
-        encoded[5] = (byte) ( dataLength >>> 16 );
-        encoded[6] = (byte) ( dataLength >>> 8 );
-        encoded[7] = (byte) dataLength;
+        return s.append( ':' );
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java
deleted file mode 100644
index 3bee07d..0000000
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.apache.maven.surefire.booter;
-
-/*
- * 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.testset.RunOrderParameters;
-
-/**
- * @author Kristian Rosenvold
- */
-interface RunOrderParametersAware
-{
-    void setRunOrderParameters( RunOrderParameters runOrderParameters );
-}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java
deleted file mode 100644
index 9898061..0000000
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.apache.maven.surefire.booter;
-
-/*
- * 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.testset.TestArtifactInfo;
-
-/**
- * @author Kristian Rosenvold
- */
-interface TestArtifactInfoAware
-{
-    void setTestArtifactInfo( TestArtifactInfo testArtifactInfo );
-}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java
deleted file mode 100644
index 3e98b92..0000000
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.apache.maven.surefire.booter;
-
-/*
- * 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.testset.TestRequest;
-
-/**
- * @author Kristian Rosenvold
- */
-interface TestRequestAware
-{
-    void setTestRequest( TestRequest testSuiteDefinition );
-}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
similarity index 62%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
index 8c65be3..2c94e9d 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.providerapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,21 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.testset.TestSetFailedException;
 
 /**
- * @author Kristian Rosenvold
+ * Hiding CommandReader instance in provider.
  */
-interface ReporterConfigurationAware
+public interface CommandChainReader
 {
-    void setReporterConfiguration( ReporterConfiguration reporterConfiguration );
+    boolean awaitStarted()
+        throws TestSetFailedException;
+
+    void addTestsFinishedListener( CommandListener listener );
+
+    void addSkipNextTestsListener( CommandListener listener );
+
+    void addShutdownListener( CommandListener listener );
+
+    void removeListener( CommandListener listener );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandListener.java
similarity index 90%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandListener.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandListener.java
index 523ca76..b0d8870 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandListener.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.providerapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,6 +19,8 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.booter.Command;
+
 /**
  * Command listener interface.
  */
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/providerapi/MasterProcessChannelDecoder.java
new file mode 100644
index 0000000..6c64b25
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java
@@ -0,0 +1,47 @@
+package org.apache.maven.surefire.providerapi;
+
+/*
+ * 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.Command;
+
+import java.io.IOException;
+
+/**
+ * An abstraction for physical decoder of commands. The commands are sent from master Maven process and
+ * received by the child forked Surefire process. The session must be open after the MasterProcessChannelDecoderFactory
+ * has created the decoder instance. The session can be closed on the decoder instance.
+ */
+public interface MasterProcessChannelDecoder
+    extends AutoCloseable
+{
+    /**
+     * Reads the bytes from a channel, waiting until the command is read completely or
+     * the channel throws {@link java.io.EOFException}.
+     * <br>
+     * This method is called in a single Thread. The constructor can be called within another thread.
+     *
+     * @return decoded command
+     * @throws IOException exception in channel
+     */
+    Command decode() throws IOException;
+
+    @Override
+    void close() throws IOException;
+}
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 0fea537..e4caae7 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
@@ -20,7 +20,6 @@ package org.apache.maven.surefire.providerapi;
  */
 
 import org.apache.maven.surefire.booter.ForkedChannelEncoder;
-import org.apache.maven.surefire.booter.Shutdown;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.ReporterConfiguration;
@@ -147,9 +146,9 @@ public interface ProviderParameters
      */
     boolean isInsideFork();
 
-    Shutdown getShutdown();
-
     Integer getSystemExitTimeout();
 
     ForkedChannelEncoder getForkedChannelEncoder();
+
+    CommandChainReader getCommandReader();
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
index c7c123a..e7ed763 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
@@ -41,7 +41,7 @@ import static org.apache.maven.surefire.testset.ResolvedTest.Type.METHOD;
  * composed of included and excluded tests.<br>
  * The methods {@link #shouldRun(String, String)} are filters easily used in JUnit filter or TestNG.
  * This class is independent of JUnit and TestNG API.<br>
- * It is accessed by Java Reflection API in {@link org.apache.maven.surefire.booter.SurefireReflector}
+ * It is accessed by Java Reflection API in {@code org.apache.maven.surefire.booter.SurefireReflector}
  * using specific ClassLoader.
  */
 public class TestListResolver
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
index 57e9ea7..2273842 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
@@ -132,25 +132,6 @@ public final class ReflectionUtils
         }
     }
 
-    public static Object instantiateTwoArgs( ClassLoader classLoader, String className, Class<?> param1Class,
-                                             Object param1, Class param2Class, Object param2 )
-    {
-        try
-        {
-            Class<?> aClass = loadClass( classLoader, className );
-            Constructor constructor = getConstructor( aClass, param1Class, param2Class );
-            return constructor.newInstance( param1, param2 );
-        }
-        catch ( InvocationTargetException e )
-        {
-            throw new SurefireReflectionException( e.getTargetException() );
-        }
-        catch ( ReflectiveOperationException e )
-        {
-            throw new SurefireReflectionException( e );
-        }
-    }
-
     public static void invokeSetter( Object o, String name, Class<?> value1clazz, Object value )
     {
         Method setter = getMethod( o, name, value1clazz );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
index 3610a4b..06ddc53 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
@@ -19,8 +19,8 @@ package org.apache.maven.surefire.util.internal;
  * under the License.
  */
 
+import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Creates new daemon Thread.
@@ -28,29 +28,16 @@ import java.util.concurrent.atomic.AtomicInteger;
 public final class DaemonThreadFactory
     implements ThreadFactory
 {
-    private static final AtomicInteger POOL_NUMBER = new AtomicInteger( 1 );
-
-    private final AtomicInteger threadNumber = new AtomicInteger( 1 );
-
-    private final ThreadGroup group;
-
-    private final String namePrefix;
+    private static final ThreadFactory DEFAULT_THREAD_FACTORY = Executors.defaultThreadFactory();
 
     private DaemonThreadFactory()
     {
-        SecurityManager s = System.getSecurityManager();
-        group = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
-        namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-";
     }
 
     @Override
     public Thread newThread( Runnable r )
     {
-        Thread t = new Thread( group, r, namePrefix + threadNumber.getAndIncrement() );
-        if ( t.getPriority() != Thread.NORM_PRIORITY )
-        {
-            t.setPriority( Thread.NORM_PRIORITY );
-        }
+        Thread t = DEFAULT_THREAD_FACTORY.newThread( r );
         t.setDaemon( true );
         return t;
     }
@@ -71,34 +58,19 @@ public final class DaemonThreadFactory
 
     public static Thread newDaemonThread( Runnable r )
     {
-        SecurityManager s = System.getSecurityManager();
-        ThreadGroup group = s == null ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
-        Thread t = new Thread( group, r );
-        if ( t.getPriority() != Thread.NORM_PRIORITY )
-        {
-            t.setPriority( Thread.NORM_PRIORITY );
-        }
-        t.setDaemon( true );
-        return t;
+        return new DaemonThreadFactory().newThread( r );
     }
 
     public static Thread newDaemonThread( Runnable r, String name )
     {
-        SecurityManager s = System.getSecurityManager();
-        ThreadGroup group = s == null ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
-        Thread t = new Thread( group, r, name );
-        if ( t.getPriority() != Thread.NORM_PRIORITY )
-        {
-            t.setPriority( Thread.NORM_PRIORITY );
-        }
-        t.setDaemon( true );
+        Thread t = new DaemonThreadFactory().newThread( r );
+        t.setName( name );
         return t;
     }
 
     private static class NamedThreadFactory
         implements ThreadFactory
     {
-
         private final String name;
 
         private NamedThreadFactory( String name )
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 38f0c48..ea27d8a 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -23,11 +23,8 @@ import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
 import org.apache.maven.plugin.surefire.runorder.ThreadedExecutionSchedulerTest;
 import org.apache.maven.surefire.SpecificTestClassFilterTest;
-import org.apache.maven.surefire.booter.CommandReaderTest;
 import org.apache.maven.surefire.booter.ForkedChannelEncoderTest;
 import org.apache.maven.surefire.booter.ForkingRunListenerTest;
-import org.apache.maven.surefire.booter.MasterProcessCommandTest;
-import org.apache.maven.surefire.booter.SurefireReflectorTest;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriterTest;
 import org.apache.maven.surefire.suite.RunResultTest;
 import org.apache.maven.surefire.testset.FundamentalFilterTest;
@@ -51,11 +48,8 @@ import org.junit.runners.Suite;
  * @since 2.19
  */
 @Suite.SuiteClasses( {
-    CommandReaderTest.class,
     ThreadedExecutionSchedulerTest.class,
     ForkingRunListenerTest.class,
-    MasterProcessCommandTest.class,
-    SurefireReflectorTest.class,
     LegacyPojoStackTraceWriterTest.class,
     RunResultTest.class,
     ResolvedTestTest.class,
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
deleted file mode 100644
index cfd4d5f..0000000
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.apache.maven.surefire.booter;
-
-/*
- * 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 junit.framework.TestCase;
-import org.junit.Rule;
-import org.junit.rules.ExpectedException;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.resolve;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.setCommandAndDataLength;
-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.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-/**
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
- */
-public class MasterProcessCommandTest
-    extends TestCase
-{
-    @Rule
-    public final ExpectedException exception = ExpectedException.none();
-
-    public void testEncodedStreamSequence()
-    {
-        byte[] streamSequence = new byte[10];
-        streamSequence[8] = (byte) 'T';
-        streamSequence[9] = (byte) 'e';
-        setCommandAndDataLength( 256, 2, streamSequence );
-        assertEquals( streamSequence[0], (byte) 0 );
-        assertEquals( streamSequence[1], (byte) 0 );
-        assertEquals( streamSequence[2], (byte) 1 );
-        assertEquals( streamSequence[3], (byte) 0 );
-        assertEquals( streamSequence[4], (byte) 0 );
-        assertEquals( streamSequence[5], (byte) 0 );
-        assertEquals( streamSequence[6], (byte) 0 );
-        assertEquals( streamSequence[7], (byte) 2 );
-        // remain unchanged
-        assertEquals( streamSequence[8], (byte) 'T' );
-        assertEquals( streamSequence[9], (byte) 'e' );
-    }
-
-    public void testResolved()
-    {
-        for ( MasterProcessCommand command : MasterProcessCommand.values() )
-        {
-            assertThat( command, is( resolve( command.getId() ) ) );
-        }
-    }
-
-    public void testDataToByteArrayAndBack()
-    {
-        String dummyData = "pkg.Test";
-        for ( MasterProcessCommand command : MasterProcessCommand.values() )
-        {
-            switch ( command )
-            {
-                case RUN_CLASS:
-                    assertEquals( String.class, command.getDataType() );
-                    byte[] encoded = command.fromDataType( dummyData );
-                    assertThat( encoded.length, is( 8 ) );
-                    assertThat( encoded[0], is( (byte) 'p' ) );
-                    assertThat( encoded[1], is( (byte) 'k' ) );
-                    assertThat( encoded[2], is( (byte) 'g' ) );
-                    assertThat( encoded[3], is( (byte) '.' ) );
-                    assertThat( encoded[4], is( (byte) 'T' ) );
-                    assertThat( encoded[5], is( (byte) 'e' ) );
-                    assertThat( encoded[6], is( (byte) 's' ) );
-                    assertThat( encoded[7], is( (byte) 't' ) );
-                    String decoded = command.toDataTypeAsString( encoded );
-                    assertThat( decoded, is( dummyData ) );
-                    break;
-                case TEST_SET_FINISHED:
-                case SKIP_SINCE_NEXT_TEST:
-                case NOOP:
-                case  BYE_ACK:
-                    assertEquals( Void.class, command.getDataType() );
-                    encoded = command.fromDataType( dummyData );
-                    assertThat( encoded.length, is( 0 ) );
-                    decoded = command.toDataTypeAsString( encoded );
-                    assertNull( decoded );
-                    break;
-                case SHUTDOWN:
-                    assertEquals( String.class, command.getDataType() );
-                    encoded = command.fromDataType( Shutdown.EXIT.name() );
-                    assertThat( encoded.length, is( 4 ) );
-                    decoded = command.toDataTypeAsString( encoded );
-                    assertThat( decoded, is( Shutdown.EXIT.name() ) );
-                    break;
-                default:
-                    fail();
-            }
-            assertThat( command, is( resolve( command.getId() ) ) );
-        }
-    }
-
-    public void testEncodedDecodedIsSameForRunClass()
-        throws IOException
-    {
-        byte[] encoded = RUN_CLASS.encode( "pkg.Test" );
-        assertThat( encoded.length, is( 16 ) );
-        assertThat( encoded[0], is( (byte) 0 ) );
-        assertThat( encoded[1], is( (byte) 0 ) );
-        assertThat( encoded[2], is( (byte) 0 ) );
-        assertThat( encoded[3], is( (byte) 0 ) );
-        assertThat( encoded[4], is( (byte) 0 ) );
-        assertThat( encoded[5], is( (byte) 0 ) );
-        assertThat( encoded[6], is( (byte) 0 ) );
-        assertThat( encoded[7], is( (byte) 8 ) );
-        assertThat( encoded[8], is( (byte) 'p' ) );
-        assertThat( encoded[9], is( (byte) 'k' ) );
-        assertThat( encoded[10], is( (byte) 'g' ) );
-        assertThat( encoded[11], is( (byte) '.' ) );
-        assertThat( encoded[12], is( (byte) 'T' ) );
-        assertThat( encoded[13], is( (byte) 'e' ) );
-        assertThat( encoded[14], is( (byte) 's' ) );
-        assertThat( encoded[15], is( (byte) 't' ) );
-        Command command = decode( new DataInputStream( new ByteArrayInputStream( encoded ) ) );
-        assertNotNull( command );
-        assertThat( command.getCommandType(), is( RUN_CLASS ) );
-        assertThat( command.getData(), is( "pkg.Test" ) );
-    }
-
-    public void testShouldDecodeTwoCommands() throws IOException
-    {
-        byte[] cmd1 = BYE_ACK.encode();
-        byte[] cmd2 = NOOP.encode();
-        byte[] stream = new byte[cmd1.length + cmd2.length];
-        System.arraycopy( cmd1, 0, stream, 0, cmd1.length );
-        System.arraycopy( cmd2, 0, stream, cmd1.length, cmd2.length );
-        DataInputStream is = new DataInputStream( new ByteArrayInputStream( stream ) );
-        Command bye = decode( is );
-        assertNotNull( bye );
-        assertThat( bye.getCommandType(), is( BYE_ACK ) );
-        Command noop = decode( is );
-        assertNotNull( noop );
-        assertThat( noop.getCommandType(), is( NOOP ) );
-    }
-}
diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml
index 1fa5045..6ba9af1 100644
--- a/surefire-booter/pom.xml
+++ b/surefire-booter/pom.xml
@@ -36,6 +36,30 @@
       <groupId>org.apache.maven.surefire</groupId>
       <artifactId>surefire-api</artifactId>
       <version>${project.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.maven.shared</groupId>
+          <artifactId>maven-shared-utils</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.surefire</groupId>
+      <artifactId>surefire-extensions-spi</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.shared</groupId>
+      <artifactId>maven-shared-utils</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
     </dependency>
     <dependency>
       <groupId>com.google.code.findbugs</groupId>
@@ -73,22 +97,35 @@
   <build>
     <plugins>
       <plugin>
-        <groupId>org.jacoco</groupId>
-        <artifactId>jacoco-maven-plugin</artifactId>
+        <artifactId>maven-dependency-plugin</artifactId>
         <executions>
           <execution>
-            <id>jacoco-instrumentation</id>
+            <id>build-test-classpath</id>
+            <phase>generate-sources</phase>
             <goals>
-              <goal>instrument</goal>
+              <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>
           <execution>
-            <id>restore-classes</id>
+            <id>jacoco-agent</id>
             <goals>
-              <goal>restore-instrumented-classes</goal>
+              <goal>prepare-agent</goal>
             </goals>
           </execution>
         </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
       </plugin>
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
@@ -100,7 +137,7 @@
           </dependency>
         </dependencies>
         <configuration>
-          <argLine>${jvm.args.tests}</argLine>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
index fc570f0..fa664be 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
@@ -58,4 +58,5 @@ public final class BooterConstants
     public static final String SYSTEM_EXIT_TIMEOUT = "systemExitTimeout";
     public static final String PLUGIN_PID = "pluginPid";
     public static final String PROCESS_CHECKER = "processChecker";
+    public static final String FORK_NODE_CONNECTION_STRING = "forkNodeConnectionString";
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
index b091679..4b31db8 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
@@ -58,6 +58,11 @@ public class BooterDeserializer
         properties = SystemPropertyManager.loadProperties( inputStream );
     }
 
+    public String getForkNodeConnectionString()
+    {
+        return properties.getProperty( FORK_NODE_CONNECTION_STRING );
+    }
+
     /**
      * @return PID of Maven process where plugin is executed; or null if PID could not be determined.
      */
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
similarity index 83%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
index b51f713..a197157 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
@@ -20,10 +20,13 @@ package org.apache.maven.surefire.booter;
  */
 
 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.MasterProcessCommandNoMagicNumberException;
+import org.apache.maven.surefire.booter.spi.MasterProcessUnknownCommandException;
+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.testset.TestSetFailedException;
 
-import java.io.DataInputStream;
 import java.io.EOFException;
 import java.io.IOException;
 import java.util.Iterator;
@@ -47,7 +50,6 @@ 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.booter.MasterProcessCommand.decode;
 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;
@@ -58,12 +60,10 @@ import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
  */
-public final class CommandReader
+public final class CommandReader implements CommandChainReader
 {
     private static final String LAST_TEST_SYMBOL = "";
 
-    private static final CommandReader READER = new CommandReader();
-
     private final Queue<BiProperty<MasterProcessCommand, CommandListener>> listeners = new ConcurrentLinkedQueue<>();
 
     private final Thread commandThread = newDaemonThread( new CommandRunnable(), "surefire-forkedjvm-command-thread" );
@@ -76,38 +76,24 @@ public final class CommandReader
 
     private final CopyOnWriteArrayList<String> testClasses = new CopyOnWriteArrayList<>();
 
-    private volatile Shutdown shutdown;
-
-    private int iteratedCount;
-
-    private volatile ConsoleLogger logger = new NullConsoleLogger();
+    private final MasterProcessChannelDecoder decoder;
 
-    private CommandReader()
-    {
-    }
+    private final Shutdown shutdown;
 
-    public static CommandReader getReader()
-    {
-        final CommandReader reader = READER;
-        if ( reader.state.compareAndSet( NEW, RUNNABLE ) )
-        {
-            reader.commandThread.start();
-        }
-        return reader;
-    }
+    private final ConsoleLogger logger;
 
-    public CommandReader setShutdown( Shutdown shutdown )
-    {
-        this.shutdown = shutdown;
-        return this;
-    }
+    private int iteratedCount;
 
-    public CommandReader setLogger( ConsoleLogger logger )
+    public CommandReader( MasterProcessChannelDecoder decoder, Shutdown shutdown, ConsoleLogger logger )
     {
+        this.decoder = requireNonNull( decoder, "null decoder" );
+        this.shutdown = requireNonNull( shutdown, "null Shutdown config" );
         this.logger = requireNonNull( logger, "null logger" );
-        return this;
+        state.set( RUNNABLE );
+        commandThread.start();
     }
 
+    @Override
     public boolean awaitStarted()
         throws TestSetFailedException
     {
@@ -143,11 +129,13 @@ public final class CommandReader
         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 );
@@ -173,6 +161,7 @@ public final class CommandReader
         listeners.add( new BiProperty<>( cmd, listener ) );
     }
 
+    @Override
     public void removeListener( CommandListener listener )
     {
         for ( Iterator<BiProperty<MasterProcessCommand, CommandListener>> it = listeners.iterator(); it.hasNext(); )
@@ -374,54 +363,43 @@ public final class CommandReader
         public void run()
         {
             CommandReader.this.startMonitor.countDown();
-            DataInputStream stdIn = new DataInputStream( System.in );
             boolean isTestSetFinished = false;
             try
             {
                 while ( CommandReader.this.state.get() == RUNNABLE )
                 {
-                    Command command = decode( stdIn );
-                    if ( command == null )
-                    {
-                        String errorMessage = "[SUREFIRE] std/in stream corrupted: first sequence not recognized";
-                        DumpErrorSingleton.getSingleton().dumpStreamText( errorMessage );
-                        logger.error( errorMessage );
-                        break;
-                    }
-                    else
+                    Command command = CommandReader.this.decoder.decode();
+                    switch ( command.getCommandType() )
                     {
-                        switch ( command.getCommandType() )
-                        {
-                            case RUN_CLASS:
-                                String test = command.getData();
-                                boolean inserted = CommandReader.this.insertToQueue( test );
-                                if ( inserted )
-                                {
-                                    CommandReader.this.wakeupIterator();
-                                    callListeners( command );
-                                }
-                                break;
-                            case TEST_SET_FINISHED:
-                                CommandReader.this.makeQueueFull();
-                                isTestSetFinished = true;
-                                CommandReader.this.wakeupIterator();
-                                callListeners( command );
-                                break;
-                            case SHUTDOWN:
-                                CommandReader.this.makeQueueFull();
+                        case RUN_CLASS:
+                            String test = command.getData();
+                            boolean inserted = CommandReader.this.insertToQueue( test );
+                            if ( inserted )
+                            {
                                 CommandReader.this.wakeupIterator();
                                 callListeners( command );
+                            }
+                            break;
+                        case TEST_SET_FINISHED:
+                            CommandReader.this.makeQueueFull();
+                            isTestSetFinished = true;
+                            CommandReader.this.wakeupIterator();
+                            callListeners( command );
+                            break;
+                        case SHUTDOWN:
+                            CommandReader.this.makeQueueFull();
+                            CommandReader.this.wakeupIterator();
+                            callListeners( command );
                                 break;
                             case BYE_ACK:
                                 callListeners( command );
-                                // After SHUTDOWN no more commands can come.
+                            // After SHUTDOWN no more commands can come.
                                 // Hence, do NOT go back to blocking in I/O.
                                 CommandReader.this.state.set( TERMINATED );
                                 break;
-                            default:
-                                callListeners( command );
-                                break;
-                        }
+                        default:
+                            callListeners( command );
+                            break;
                     }
                 }
             }
@@ -439,6 +417,11 @@ public final class CommandReader
                     // does not go to finally for non-default config: Shutdown.EXIT or Shutdown.KILL
                 }
             }
+            catch ( MasterProcessCommandNoMagicNumberException | MasterProcessUnknownCommandException e )
+            {
+                DumpErrorSingleton.getSingleton().dumpStreamException( e );
+                CommandReader.this.logger.error( e );
+            }
             catch ( IOException e )
             {
                 CommandReader.this.state.set( TERMINATED );
@@ -447,7 +430,7 @@ public final class CommandReader
                 {
                     String msg = "[SUREFIRE] std/in stream corrupted";
                     DumpErrorSingleton.getSingleton().dumpStreamException( e, msg );
-                    logger.error( msg, e );
+                    CommandReader.this.logger.error( msg, e );
                 }
             }
             finally
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 2701221..4e0bf99 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
@@ -20,9 +20,14 @@ package org.apache.maven.surefire.booter;
  */
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.spi.DefaultMasterProcessChannelDecoderFactory;
+import org.apache.maven.surefire.providerapi.CommandListener;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 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.spi.MasterProcessChannelDecoderFactory;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 
 import java.io.File;
@@ -37,6 +42,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.ServiceLoader;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.Semaphore;
@@ -73,7 +79,6 @@ public final class ForkedBooter
     private static final String LAST_DITCH_SHUTDOWN_THREAD = "surefire-forkedjvm-last-ditch-daemon-shutdown-thread-";
     private static final String PING_THREAD = "surefire-forkedjvm-ping-";
 
-    private final CommandReader commandReader = CommandReader.getReader();
     private final ForkedChannelEncoder eventChannel = new ForkedChannelEncoder( System.out );
     private final Semaphore exitBarrier = new Semaphore( 0 );
 
@@ -83,6 +88,7 @@ public final class ForkedBooter
     private ScheduledThreadPoolExecutor jvmTerminator;
     private ProviderConfiguration providerConfiguration;
     private ForkingReporterFactory forkingReporterFactory;
+    private volatile CommandReader commandReader;
     private StartupConfiguration startupConfiguration;
     private Object testSet;
 
@@ -109,6 +115,10 @@ public final class ForkedBooter
         forkingReporterFactory = createForkingReporterFactory();
 
         ConsoleLogger logger = (ConsoleLogger) forkingReporterFactory.createReporter();
+        String communicationConfig = startupConfiguration.getInterProcessChannelConfiguration();
+        MasterProcessChannelDecoder decoder = lookupDecoderFactory().createDecoder( communicationConfig, logger );
+        commandReader = new CommandReader( decoder, providerConfiguration.getShutdown(), logger );
+
         pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid(), logger );
 
         systemExitTimeoutInSeconds = providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
@@ -162,7 +172,7 @@ public final class ForkedBooter
         }
         else if ( readTestsFromCommandReader )
         {
-            return new LazyTestsToRun( eventChannel );
+            return new LazyTestsToRun( eventChannel, commandReader );
         }
         return null;
     }
@@ -418,7 +428,9 @@ public final class ForkedBooter
 
     private SurefireProvider createProviderInCurrentClassloader( ForkingReporterFactory reporterManagerFactory )
     {
-        BaseProviderFactory bpf = new BaseProviderFactory( reporterManagerFactory, true );
+        BaseProviderFactory bpf = new BaseProviderFactory( true );
+        bpf.setReporterFactory( reporterManagerFactory );
+        bpf.setCommandReader( commandReader );
         bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
         bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
         bpf.setForkedChannelEncoder( eventChannel );
@@ -430,12 +442,30 @@ public final class ForkedBooter
         bpf.setDirectoryScannerParameters( providerConfiguration.getDirScannerParams() );
         bpf.setMainCliOptions( providerConfiguration.getMainCliOptions() );
         bpf.setSkipAfterFailureCount( providerConfiguration.getSkipAfterFailureCount() );
-        bpf.setShutdown( providerConfiguration.getShutdown() );
         bpf.setSystemExitTimeout( providerConfiguration.getSystemExitTimeout() );
         String providerClass = startupConfiguration.getActualClassName();
         return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
     }
 
+    private static MasterProcessChannelDecoderFactory lookupDecoderFactory()
+    {
+        MasterProcessChannelDecoderFactory defaultDecoderFactory = null;
+        MasterProcessChannelDecoderFactory customDecoderFactory = null;
+        for ( MasterProcessChannelDecoderFactory decoderFactory : ServiceLoader.load(
+                MasterProcessChannelDecoderFactory.class ) )
+        {
+            if ( decoderFactory.getClass() == DefaultMasterProcessChannelDecoderFactory.class )
+            {
+                defaultDecoderFactory = decoderFactory;
+            }
+            else
+            {
+                customDecoderFactory = decoderFactory;
+            }
+        }
+        return defaultDecoderFactory == null ? customDecoderFactory : defaultDecoderFactory;
+    }
+
     /**
      * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
      * then calls the Surefire class' run method. <br> The system exit code will be 1 if an exception is thrown.
@@ -465,6 +495,8 @@ public final class ForkedBooter
         {
             DumpErrorSingleton.getSingleton().dumpException( t );
             t.printStackTrace();
+            StackTraceWriter stack = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t );
+            booter.eventChannel.consoleErrorLog( stack, false );
             booter.cancelPingScheduler();
             booter.exit1();
         }
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 9d0b2e0..ada3384 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
@@ -25,7 +25,6 @@ import java.util.Iterator;
 import org.apache.maven.surefire.util.CloseableIterator;
 import org.apache.maven.surefire.util.TestsToRun;
 
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.util.ReflectionUtils.loadClass;
 
 /**
@@ -44,23 +43,24 @@ final class LazyTestsToRun
     extends TestsToRun
 {
     private final ForkedChannelEncoder eventChannel;
+    private final CommandReader commandReader;
 
     /**
      * C'tor
      *
      * @param eventChannel the output stream to use when requesting new new tests
      */
-    LazyTestsToRun( ForkedChannelEncoder eventChannel )
+    LazyTestsToRun( ForkedChannelEncoder eventChannel, CommandReader commandReader )
     {
         super( Collections.<Class<?>>emptySet() );
-
         this.eventChannel = eventChannel;
+        this.commandReader = commandReader;
     }
 
     private final class BlockingIterator
         implements Iterator<Class<?>>
     {
-        private final Iterator<String> it = getReader().getIterableClasses( eventChannel ).iterator();
+        private final Iterator<String> it = commandReader.getIterableClasses( eventChannel ).iterator();
 
         @Override
         public boolean hasNext()
@@ -132,7 +132,7 @@ final class LazyTestsToRun
      */
     private Iterator<Class<?>> newWeakIterator()
     {
-        final Iterator<String> it = getReader().iterated();
+        final Iterator<String> it = commandReader.iterated();
         return new CloseableIterator<Class<?>>()
         {
             @Override
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
index c7379e4..caa26d2 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
@@ -96,7 +96,6 @@ public class ProviderConfiguration
         return reporterConfiguration;
     }
 
-
     public boolean isFailIfNoTests()
     {
         return failIfNoTests;
@@ -107,7 +106,6 @@ public class ProviderConfiguration
         return dirScannerParams.getTestClassesDirectory();
     }
 
-
     public DirectoryScannerParameters getDirScannerParams()
     {
         return dirScannerParams;
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
index 41badfd..614ff92 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
@@ -99,7 +99,8 @@ public class ProviderFactory
         final ClassLoader systemClassLoader = currentThread.getContextClassLoader();
         currentThread.setContextClassLoader( classLoader );
         // Note: Duplicated in ForkedBooter#createProviderInCurrentClassloader
-        Object o = surefireReflector.createBooterConfiguration( classLoader, reporterManagerFactory, isInsideFork );
+        Object o = surefireReflector.createBooterConfiguration( classLoader, isInsideFork );
+        surefireReflector.setReporterFactoryAware( o, reporterManagerFactory );
         surefireReflector.setTestSuiteDefinitionAware( o, providerConfiguration.getTestSuiteDefinition() );
         surefireReflector.setProviderPropertiesAware( o, providerConfiguration.getProviderProperties() );
         surefireReflector.setReporterConfigurationAware( o, providerConfiguration.getReporterConfiguration() );
@@ -109,7 +110,6 @@ public class ProviderFactory
         surefireReflector.setIfDirScannerAware( o, providerConfiguration.getDirScannerParams() );
         surefireReflector.setMainCliOptions( o, providerConfiguration.getMainCliOptions() );
         surefireReflector.setSkipAfterFailureCount( o, providerConfiguration.getSkipAfterFailureCount() );
-        surefireReflector.setShutdown( o, providerConfiguration.getShutdown() );
         if ( isInsideFork )
         {
             surefireReflector.setSystemExitTimeout( o, providerConfiguration.getSystemExitTimeout() );
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
index 0bbe988..44eb5b7 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
@@ -50,6 +50,11 @@ public class StartupConfiguration
         this.processChecker = processChecker;
     }
 
+    public String getInterProcessChannelConfiguration()
+    {
+        return "pipe:std:in";
+    }
+
     public boolean isProviderMainClass()
     {
         return providerClassName.endsWith( "#main" );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
similarity index 81%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
index 5cc1415..20ff510 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
@@ -19,8 +19,6 @@ package org.apache.maven.surefire.booter;
  * 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.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.report.ReporterConfiguration;
@@ -46,7 +44,6 @@ import static java.util.Collections.checkedList;
 import static org.apache.maven.surefire.util.ReflectionUtils.getConstructor;
 import static org.apache.maven.surefire.util.ReflectionUtils.getMethod;
 import static org.apache.maven.surefire.util.ReflectionUtils.instantiateOneArg;
-import static org.apache.maven.surefire.util.ReflectionUtils.instantiateTwoArgs;
 import static org.apache.maven.surefire.util.ReflectionUtils.invokeGetter;
 import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
 import static org.apache.maven.surefire.util.ReflectionUtils.invokeSetter;
@@ -59,7 +56,7 @@ import static org.apache.maven.surefire.util.ReflectionUtils.newInstance;
  *
  * @author Kristian Rosenvold
  */
-public class SurefireReflector
+public final class SurefireReflector
 {
     private final ClassLoader surefireClassLoader;
 
@@ -69,21 +66,11 @@ public class SurefireReflector
 
     private final Class<?> testArtifactInfo;
 
-    private final Class<?> testArtifactInfoAware;
-
     private final Class<?> directoryScannerParameters;
 
     private final Class<?> runOrderParameters;
 
-    private final Class<?> directoryScannerParametersAware;
-
-    private final Class<?> testSuiteDefinitionAware;
-
-    private final Class<?> testClassLoaderAware;
-
-    private final Class<?> reporterConfigurationAware;
-
-    private final Class<?> providerPropertiesAware;
+    private final Class<?> baseProviderFactory;
 
     private final Class<?> runResult;
 
@@ -93,12 +80,8 @@ public class SurefireReflector
 
     private final Class<?> testListResolver;
 
-    private final Class<?> mainCliOptions;
-
     private final Class<Enum> commandLineOptionsClass;
 
-    private final Class<?> shutdownAwareClass;
-
     private final Class<Enum> shutdownClass;
 
     @SuppressWarnings( "unchecked" )
@@ -110,22 +93,14 @@ public class SurefireReflector
             reporterConfiguration = surefireClassLoader.loadClass( ReporterConfiguration.class.getName() );
             testRequest = surefireClassLoader.loadClass( TestRequest.class.getName() );
             testArtifactInfo = surefireClassLoader.loadClass( TestArtifactInfo.class.getName() );
-            testArtifactInfoAware = surefireClassLoader.loadClass( TestArtifactInfoAware.class.getName() );
             directoryScannerParameters = surefireClassLoader.loadClass( DirectoryScannerParameters.class.getName() );
             runOrderParameters = surefireClassLoader.loadClass( RunOrderParameters.class.getName() );
-            directoryScannerParametersAware =
-                surefireClassLoader.loadClass( DirectoryScannerParametersAware.class.getName() );
-            testSuiteDefinitionAware = surefireClassLoader.loadClass( TestRequestAware.class.getName() );
-            testClassLoaderAware = surefireClassLoader.loadClass( SurefireClassLoadersAware.class.getName() );
-            reporterConfigurationAware = surefireClassLoader.loadClass( ReporterConfigurationAware.class.getName() );
-            providerPropertiesAware = surefireClassLoader.loadClass( ProviderPropertiesAware.class.getName() );
+            baseProviderFactory = surefireClassLoader.loadClass( BaseProviderFactory.class.getName() );
             reporterFactory = surefireClassLoader.loadClass( ReporterFactory.class.getName() );
             runResult = surefireClassLoader.loadClass( RunResult.class.getName() );
             booterParameters = surefireClassLoader.loadClass( ProviderParameters.class.getName() );
             testListResolver = surefireClassLoader.loadClass( TestListResolver.class.getName() );
-            mainCliOptions = surefireClassLoader.loadClass( MainCliOptionsAware.class.getName() );
             commandLineOptionsClass = (Class<Enum>) surefireClassLoader.loadClass( CommandLineOption.class.getName() );
-            shutdownAwareClass = surefireClassLoader.loadClass( ShutdownAware.class.getName() );
             shutdownClass = (Class<Enum>) surefireClassLoader.loadClass( Shutdown.class.getName() );
         }
         catch ( ClassNotFoundException e )
@@ -227,11 +202,9 @@ public class SurefireReflector
         return newInstance( constructor, reporterConfig.getReportsDirectory(), reporterConfig.isTrimStackTrace() );
     }
 
-    public Object createBooterConfiguration( ClassLoader surefireClassLoader, Object factoryInstance,
-                                             boolean insideFork )
+    public Object createBooterConfiguration( ClassLoader surefireClassLoader, boolean insideFork )
     {
-        return instantiateTwoArgs( surefireClassLoader, BaseProviderFactory.class.getName(),
-                                   reporterFactory, factoryInstance, boolean.class, insideFork );
+        return instantiateOneArg( surefireClassLoader, BaseProviderFactory.class.getName(), boolean.class, insideFork );
     }
 
     public Object instantiateProvider( String providerClassName, Object booterParameters )
@@ -241,7 +214,7 @@ public class SurefireReflector
 
     public void setIfDirScannerAware( Object o, DirectoryScannerParameters dirScannerParams )
     {
-        if ( directoryScannerParametersAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setDirectoryScannerParameters( o, dirScannerParams );
         }
@@ -249,7 +222,7 @@ public class SurefireReflector
 
     public void setMainCliOptions( Object o, List<CommandLineOption> options )
     {
-        if ( mainCliOptions.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             List<Enum> newOptions = checkedList( new ArrayList<Enum>( options.size() ), commandLineOptionsClass );
             Collection<Integer> ordinals = toOrdinals( options );
@@ -271,15 +244,12 @@ public class SurefireReflector
 
     public void setShutdown( Object o, Shutdown shutdown )
     {
-        if ( shutdownAwareClass.isAssignableFrom( o.getClass() ) )
+        for ( Enum e : shutdownClass.getEnumConstants() )
         {
-            for ( Enum e : shutdownClass.getEnumConstants() )
+            if ( shutdown.ordinal() == e.ordinal() )
             {
-                if ( shutdown.ordinal() == e.ordinal() )
-                {
-                    invokeSetter( o, "setShutdown", shutdownClass, e );
-                    break;
-                }
+                invokeSetter( o, "setShutdown", shutdownClass, e );
+                break;
             }
         }
     }
@@ -303,7 +273,7 @@ public class SurefireReflector
 
     public void setTestSuiteDefinitionAware( Object o, TestRequest testSuiteDefinition2 )
     {
-        if ( testSuiteDefinitionAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setTestSuiteDefinition( o, testSuiteDefinition2 );
         }
@@ -317,7 +287,7 @@ public class SurefireReflector
 
     public void setProviderPropertiesAware( Object o, Map<String, String> properties )
     {
-        if ( providerPropertiesAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setProviderProperties( o, properties );
         }
@@ -330,7 +300,7 @@ public class SurefireReflector
 
     public void setReporterConfigurationAware( Object o, ReporterConfiguration reporterConfiguration1 )
     {
-        if ( reporterConfigurationAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setReporterConfiguration( o, reporterConfiguration1 );
         }
@@ -344,7 +314,7 @@ public class SurefireReflector
 
     public void setTestClassLoaderAware( Object o, ClassLoader testClassLoader )
     {
-        if ( testClassLoaderAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setTestClassLoader( o, testClassLoader );
         }
@@ -358,7 +328,7 @@ public class SurefireReflector
 
     public void setTestArtifactInfoAware( Object o, TestArtifactInfo testArtifactInfo1 )
     {
-        if ( testArtifactInfoAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setTestArtifactInfo( o, testArtifactInfo1 );
         }
@@ -370,6 +340,19 @@ public class SurefireReflector
         invokeSetter( o, "setTestArtifactInfo", this.testArtifactInfo, param );
     }
 
+    public void setReporterFactoryAware( Object o, Object reporterFactory )
+    {
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
+        {
+            setReporterFactory( o, reporterFactory );
+        }
+    }
+
+    void setReporterFactory( Object o, Object reporterFactory )
+    {
+        invokeSetter( o, "setReporterFactory", this.reporterFactory, reporterFactory );
+    }
+
     private boolean isRunResult( Object o )
     {
         return runResult.isAssignableFrom( o.getClass() );
@@ -384,17 +367,4 @@ public class SurefireReflector
         }
         return ordinals;
     }
-
-    public 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/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/DefaultMasterProcessChannelDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/DefaultMasterProcessChannelDecoder.java
new file mode 100644
index 0000000..4b363ee
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/DefaultMasterProcessChannelDecoder.java
@@ -0,0 +1,162 @@
+package org.apache.maven.surefire.booter.spi;
+
+/*
+ * 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.providerapi.MasterProcessChannelDecoder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * magic number : opcode [: opcode specific data]*
+ * <br>
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public class DefaultMasterProcessChannelDecoder implements MasterProcessChannelDecoder
+{
+    private final InputStream is;
+    private final ConsoleLogger logger;
+
+    public DefaultMasterProcessChannelDecoder( InputStream is, ConsoleLogger logger )
+    {
+        this.is = is;
+        this.logger = logger;
+    }
+
+    protected boolean hasData( String opcode )
+    {
+        MasterProcessCommand cmd = MasterProcessCommand.byOpcode( opcode );
+        return cmd == null || cmd.hasDataType();
+    }
+
+    @SuppressWarnings( "checkstyle:innerassignment" )
+    @Override
+    public Command decode() throws IOException
+    {
+        List<String> tokens = new ArrayList<>();
+        StringBuilder frame = new StringBuilder();
+        boolean frameStarted = false;
+        boolean frameFinished = false;
+        for ( int r; ( r = is.read() ) != -1 ; )
+        {
+            char c = (char) r;
+            if ( frameFinished && c == '\n' )
+            {
+                continue;
+            }
+
+            if ( !frameStarted )
+            {
+                if ( c == ':' )
+                {
+                    frameStarted = true;
+                    frameFinished = false;
+                    frame.setLength( 0 );
+                    tokens.clear();
+                    continue;
+                }
+            }
+            else if ( !frameFinished )
+            {
+                boolean isColon = c == ':';
+                if ( isColon || c == '\n' || c == '\r' )
+                {
+                    tokens.add( frame.toString() );
+                    frame.setLength( 0 );
+                }
+                else
+                {
+                    frame.append( c );
+                }
+                boolean isFinishedFrame = isTokenComplete( tokens );
+                if ( isFinishedFrame )
+                {
+                    frameFinished = true;
+                    frameStarted = false;
+                    break;
+                }
+            }
+
+            boolean removed = removeUnsynchronizedTokens( tokens, logger );
+            if ( removed && tokens.isEmpty() )
+            {
+                frameStarted = false;
+                frameFinished = true;
+            }
+        }
+
+        if ( tokens.size() <= 1 )
+        {
+            throw new MasterProcessCommandNoMagicNumberException( frame.toString() );
+        }
+        if ( tokens.size() == 2 )
+        {
+            return new Command( MasterProcessCommand.byOpcode( tokens.get( 1 ) ) );
+        }
+        else if ( tokens.size() == 3 )
+        {
+            return new Command( MasterProcessCommand.byOpcode( tokens.get( 1 ) ), tokens.get( 2 ) );
+        }
+        else
+        {
+            throw new MasterProcessUnknownCommandException( frame.toString() );
+        }
+    }
+
+    private boolean isTokenComplete( List<String> tokens )
+    {
+        if ( tokens.size() >= 2 )
+        {
+            return hasData( tokens.get( 1 ) ) == ( tokens.size() == 3 );
+        }
+        return false;
+    }
+
+    private boolean removeUnsynchronizedTokens( Collection<String> tokens, ConsoleLogger logger )
+    {
+        boolean removed = false;
+        for ( Iterator<String> it = tokens.iterator(); it.hasNext(); )
+        {
+            String token = it.next();
+            if ( token.equals( "maven-surefire-std-out" ) )
+            {
+                break;
+            }
+            removed = true;
+            it.remove();
+            logger.error( "Forked JVM could not synchronize the '" + token + "' token with preamble sequence." );
+        }
+        return removed;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/DefaultMasterProcessChannelDecoderFactory.java
similarity index 50%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/DefaultMasterProcessChannelDecoderFactory.java
index eddebed..f7e7911 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/DefaultMasterProcessChannelDecoderFactory.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.booter.spi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,28 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.spi.MasterProcessChannelDecoderFactory;
 
-import java.util.List;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+public class DefaultMasterProcessChannelDecoderFactory
+        implements MasterProcessChannelDecoderFactory
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    @Override
+    public MasterProcessChannelDecoder createDecoder( String channelConfig, ConsoleLogger logger ) throws IOException
+    {
+        if ( "pipe:std:in".equals( channelConfig ) )
+        {
+            return new DefaultMasterProcessChannelDecoder( System.in, logger );
+        }
+        else
+        {
+            throw new IOException( "Unknown chanel configuration string " + channelConfig );
+        }
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessCommandNoMagicNumberException.java
similarity index 65%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessCommandNoMagicNumberException.java
index eddebed..261969e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessCommandNoMagicNumberException.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.booter.spi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,20 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
 
-import java.util.List;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
+ * No magic number recognized in the command line, see the JavaDoc in {@link MasterProcessCommand}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-interface MainCliOptionsAware
+public class MasterProcessCommandNoMagicNumberException extends IOException
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    MasterProcessCommandNoMagicNumberException( String line )
+    {
+        super( "No magic # recognized in the line '" + line + "'" );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessUnknownCommandException.java
similarity index 63%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessUnknownCommandException.java
index eddebed..11cab97 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessUnknownCommandException.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.booter.spi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,21 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
 
-import java.util.List;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
+ * No {@link MasterProcessCommand command} recognized according to the opcode
+ * encapsulated in the command line, see the JavaDoc in {@link MasterProcessCommand}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-interface MainCliOptionsAware
+public class MasterProcessUnknownCommandException extends IOException
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    MasterProcessUnknownCommandException( String line )
+    {
+        super( "Unrecognized command found '" + line + "'" );
+    }
 }
diff --git a/surefire-booter/src/main/resources/META-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelDecoderFactory b/surefire-booter/src/main/resources/META-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelDecoderFactory
new file mode 100644
index 0000000..2b44ee1
--- /dev/null
+++ b/surefire-booter/src/main/resources/META-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelDecoderFactory
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+org.apache.maven.surefire.booter.spi.DefaultMasterProcessChannelDecoderFactory
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
similarity index 86%
rename from surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index 40c0243..096cfb4 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -19,6 +19,10 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+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.DefaultMasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 import org.junit.After;
 import org.junit.Before;
@@ -29,7 +33,6 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
-import java.nio.ByteBuffer;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.concurrent.BlockingQueue;
@@ -39,8 +42,7 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -61,7 +63,6 @@ public class CommandReaderTest
     private static final long TEST_TIMEOUT = 15_000L;
 
     private final BlockingQueue<Byte> blockingStream = new LinkedBlockingQueue<>();
-    private InputStream realInputStream;
     private CommandReader reader;
 
     static class A
@@ -83,18 +84,19 @@ public class CommandReaderTest
     @Before
     public void init()
     {
+        //noinspection ResultOfMethodCallIgnored
         Thread.interrupted();
-        realInputStream = System.in;
+        InputStream realInputStream = new SystemInputStream();
         addTestToPipeline( getClass().getName() );
-        System.setIn( new SystemInputStream() );
-        reader = CommandReader.getReader();
+        ConsoleLogger logger = new NullConsoleLogger();
+        MasterProcessChannelDecoder decoder = new DefaultMasterProcessChannelDecoder( realInputStream, logger );
+        reader = new CommandReader( decoder, Shutdown.DEFAULT, logger );
     }
 
     @After
     public void deinit()
     {
         reader.stop();
-        System.setIn( realInputStream );
     }
 
     @Test
@@ -242,24 +244,20 @@ public class CommandReaderTest
 
     private void addTestToPipeline( String cls )
     {
-        byte[] clazz = cls.getBytes( ISO_8859_1 );
-        ByteBuffer buffer = ByteBuffer.allocate( 8 + clazz.length ).putInt(
-                MasterProcessCommand.RUN_CLASS.getId() ).putInt( clazz.length ).put( clazz );
-        buffer.rewind();
-        for ( ; buffer.hasRemaining(); )
+        String cmd = ":maven-surefire-std-out:"
+            + MasterProcessCommand.RUN_CLASS.getOpcode() + ':' + cls + '\n';
+        for ( byte cmdByte : cmd.getBytes( US_ASCII ) )
         {
-            blockingStream.add( buffer.get() );
+            blockingStream.add( cmdByte );
         }
     }
 
     private void addEndOfPipeline()
     {
-        ByteBuffer buffer = ByteBuffer.allocate( 8 ).putInt( MasterProcessCommand.TEST_SET_FINISHED.getId() ).putInt(
-                0 );
-        buffer.rewind();
-        for ( ; buffer.hasRemaining(); )
+        String cmd = ":maven-surefire-std-out:" + MasterProcessCommand.TEST_SET_FINISHED.getOpcode() + '\n';
+        for ( byte cmdByte : cmd.getBytes( US_ASCII ) )
         {
-            blockingStream.add( buffer.get() );
+            blockingStream.add( cmdByte );
         }
     }
 
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/DefaultMasterProcessChannelDecoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/DefaultMasterProcessChannelDecoderTest.java
new file mode 100644
index 0000000..debab05
--- /dev/null
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/DefaultMasterProcessChannelDecoderTest.java
@@ -0,0 +1,148 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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 junit.framework.TestCase;
+import org.apache.maven.surefire.booter.spi.DefaultMasterProcessChannelDecoder;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * Tests for {@link DefaultMasterProcessChannelDecoder}.
+ */
+public class DefaultMasterProcessChannelDecoderTest
+    extends TestCase
+{
+    public void testDataToByteArrayAndBack() throws IOException
+    {
+        for ( MasterProcessCommand commandType : MasterProcessCommand.values() )
+        {
+            switch ( commandType )
+            {
+                case RUN_CLASS:
+                    assertEquals( String.class, commandType.getDataType() );
+                    byte[] encoded = commandType.encode( "pkg.Test" );
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-std-out:run-testclass:pkg.Test:" );
+                    byte[] line = addNL( encoded, '\n' );
+                    InputStream is = new ByteArrayInputStream( line );
+                    DefaultMasterProcessChannelDecoder decoder = new DefaultMasterProcessChannelDecoder( is, null );
+                    Command command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertThat( command.getData(), is( "pkg.Test" ) );
+                    break;
+                case TEST_SET_FINISHED:
+                    assertThat( commandType ).isSameAs( Command.TEST_SET_FINISHED.getCommandType() );
+                    assertEquals( Void.class, commandType.getDataType() );
+                    encoded = commandType.encode();
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-std-out:testset-finished:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new DefaultMasterProcessChannelDecoder( is, null );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertNull( command.getData() );
+                    break;
+                case SKIP_SINCE_NEXT_TEST:
+                    assertThat( commandType ).isSameAs( Command.SKIP_SINCE_NEXT_TEST.getCommandType() );
+                    assertEquals( Void.class, commandType.getDataType() );
+                    encoded = commandType.encode();
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-std-out:skip-since-next-test:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new DefaultMasterProcessChannelDecoder( is, null );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertNull( command.getData() );
+                    break;
+                case SHUTDOWN:
+                    assertEquals( String.class, commandType.getDataType() );
+                    encoded = commandType.encode( Shutdown.EXIT.name() );
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-std-out:shutdown:EXIT:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new DefaultMasterProcessChannelDecoder( is, null );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertThat( command.getData(), is( "EXIT" ) );
+                    break;
+                case NOOP:
+                    assertThat( commandType ).isSameAs( Command.NOOP.getCommandType() );
+                    assertEquals( Void.class, commandType.getDataType() );
+                    encoded = commandType.encode();
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-std-out:noop:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new DefaultMasterProcessChannelDecoder( is, null );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertNull( command.getData() );
+                    break;
+                case BYE_ACK:
+                    assertThat( commandType ).isSameAs( Command.BYE_ACK.getCommandType() );
+                    assertEquals( Void.class, commandType.getDataType() );
+                    encoded = commandType.encode();
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-std-out:bye-ack:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new DefaultMasterProcessChannelDecoder( is, null );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertNull( command.getData() );
+                    break;
+                default:
+                    fail();
+            }
+        }
+    }
+
+    public void testShouldDecodeTwoCommands() throws IOException
+    {
+        String cmd = ":maven-surefire-std-out:bye-ack\n:maven-surefire-std-out:bye-ack:";
+        InputStream is = new ByteArrayInputStream( cmd.getBytes() );
+        DefaultMasterProcessChannelDecoder decoder = new DefaultMasterProcessChannelDecoder( is, null );
+
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isEqualTo( BYE_ACK );
+        assertThat( command.getData() ).isNull();
+
+        command = decoder.decode();
+        assertThat( command.getCommandType() ).isEqualTo( BYE_ACK );
+        assertThat( command.getData() ).isNull();
+    }
+
+    private static byte[] addNL( byte[] encoded, char... newLines )
+    {
+        byte[] line = new byte[encoded.length + newLines.length];
+        System.arraycopy( encoded, 0, line, 0, encoded.length );
+        for ( int i = encoded.length, j = 0; i < line.length; i++, j++ )
+        {
+            line[i] = (byte) newLines[j];
+        }
+        return line;
+    }
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/Foo.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
similarity index 80%
rename from surefire-api/src/test/java/org/apache/maven/surefire/booter/Foo.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
index ccb01e3..3b71b36 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/Foo.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
@@ -19,9 +19,9 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-
 import java.util.Map;
 import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.testset.DirectoryScannerParameters;
 import org.apache.maven.surefire.testset.RunOrderParameters;
 import org.apache.maven.surefire.testset.TestArtifactInfo;
@@ -30,27 +30,28 @@ import org.apache.maven.surefire.testset.TestRequest;
 /**
  * @author Kristian Rosenvold
  */
-public class Foo
-    implements DirectoryScannerParametersAware, TestRequestAware, ProviderPropertiesAware, ReporterConfigurationAware,
-    SurefireClassLoadersAware, TestArtifactInfoAware, RunOrderParametersAware
+public class Foo extends BaseProviderFactory
 {
-    DirectoryScannerParameters directoryScannerParameters;
+    private DirectoryScannerParameters directoryScannerParameters;
 
-    Map<String, String> providerProperties;
+    private Map<String, String> providerProperties;
 
-    ReporterConfiguration reporterConfiguration;
+    private ReporterConfiguration reporterConfiguration;
 
-    ClassLoader surefireClassLoader;
+    private ClassLoader testClassLoader;
 
-    ClassLoader testClassLoader;
+    private TestRequest testRequest;
 
-    TestRequest testRequest;
+    private TestArtifactInfo testArtifactInfo;
 
-    TestArtifactInfo testArtifactInfo;
+    private RunOrderParameters runOrderParameters;
 
-    RunOrderParameters runOrderParameters;
+    private boolean called;
 
-    boolean called = false;
+    Foo()
+    {
+        super( false );
+    }
 
     @Override
     public void setDirectoryScannerParameters( DirectoryScannerParameters directoryScanner )
@@ -59,7 +60,6 @@ public class Foo
         this.called = true;
     }
 
-
     /**
      * @return true if it has been called
      */
@@ -86,7 +86,6 @@ public class Foo
     public void setClassLoaders( ClassLoader testClassLoader )
     {
         this.testClassLoader = testClassLoader;
-        this.surefireClassLoader = surefireClassLoader;
         this.called = true;
     }
 
@@ -110,4 +109,10 @@ public class Foo
         this.runOrderParameters = runOrderParameters;
         this.called = true;
     }
+
+    @Override
+    public void setReporterFactory( ReporterFactory reporterFactory )
+    {
+        called = true;
+    }
 }
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 e65beb1..24a250b 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
@@ -19,11 +19,14 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.report.StackTraceWriter;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
@@ -31,18 +34,24 @@ import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.powermock.api.mockito.PowerMockito.doCallRealMethod;
+import static org.powermock.api.mockito.PowerMockito.doNothing;
 import static org.powermock.api.mockito.PowerMockito.doThrow;
 import static org.powermock.api.mockito.PowerMockito.verifyNoMoreInteractions;
 import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
 import static org.powermock.api.mockito.PowerMockito.when;
 import static org.powermock.reflect.Whitebox.invokeMethod;
+import static org.powermock.reflect.Whitebox.setInternalState;
+import static org.powermock.utils.JavaVersion.JAVA_RECENT;
+import static org.powermock.utils.JavaVersion.JAVA_12;
 
 /**
  * PowerMock tests for {@link ForkedBooter}.
  */
 @RunWith( PowerMockRunner.class )
-@PrepareForTest( { PpidChecker.class, ForkedBooter.class } )
+@PrepareForTest( { PpidChecker.class, ForkedBooter.class, ForkedChannelEncoder.class } )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
 public class ForkedBooterMockTest
 {
     @Mock
@@ -51,6 +60,21 @@ public class ForkedBooterMockTest
     @Mock
     private ForkedBooter booter;
 
+    @Mock
+    private ForkedChannelEncoder eventChannel;
+
+    @Captor
+    private ArgumentCaptor<StackTraceWriter> capturedStackTraceWriter;
+
+    @Captor
+    private ArgumentCaptor<Boolean> capturedBoolean;
+
+    @Captor
+    private ArgumentCaptor<String[]> capturedArgs;
+
+    @Captor
+    private ArgumentCaptor<ForkedBooter> capturedBooter;
+
     @Test
     public void shouldCheckNewPingMechanism() throws Exception
     {
@@ -71,8 +95,6 @@ public class ForkedBooterMockTest
     {
         PowerMockito.mockStatic( ForkedBooter.class );
 
-        ArgumentCaptor<String[]> capturedArgs = ArgumentCaptor.forClass( String[].class );
-        ArgumentCaptor<ForkedBooter> capturedBooter = ArgumentCaptor.forClass( ForkedBooter.class );
         doCallRealMethod()
                 .when( ForkedBooter.class, "run", capturedBooter.capture(), capturedArgs.capture() );
 
@@ -106,6 +128,13 @@ public class ForkedBooterMockTest
     @Test
     public void testMainWithError() throws Exception
     {
+        //does not work in PowerMock
+        //assumeFalse( JAVA_RECENT.atLeast( JAVA_12 ) );
+        if ( JAVA_RECENT.atLeast( JAVA_12 ) )
+        {
+            return;
+        }
+
         PowerMockito.mockStatic( ForkedBooter.class );
 
         doCallRealMethod()
@@ -114,6 +143,13 @@ public class ForkedBooterMockTest
         doThrow( new RuntimeException( "dummy exception" ) )
                 .when( booter, "execute" );
 
+        doNothing()
+                .when( booter, "setupBooter",
+                        any( String.class ), any( String.class ), any( String.class ), any( String.class ) );
+
+        // broken in PowerMock JDK 12
+        setInternalState( booter, "eventChannel", eventChannel );
+
         String[] args = new String[]{ "/", "dump", "surefire.properties", "surefire-effective.properties" };
         invokeMethod( ForkedBooter.class, "run", booter, args );
 
@@ -123,6 +159,18 @@ public class ForkedBooterMockTest
         verifyPrivate( booter, times( 1 ) )
                 .invoke( "execute" );
 
+        verify( eventChannel, times( 1 ) )
+                .consoleErrorLog( capturedStackTraceWriter.capture(), capturedBoolean.capture() );
+        assertThat( capturedStackTraceWriter.getValue() )
+                .isNotNull();
+        assertThat( capturedStackTraceWriter.getValue().smartTrimmedStackTrace() )
+                .isEqualTo( "test subsystem#no method RuntimeException dummy exception" );
+        assertThat( capturedStackTraceWriter.getValue().getThrowable().getTarget() )
+                .isNotNull()
+                .isInstanceOf( RuntimeException.class );
+        assertThat( capturedStackTraceWriter.getValue().getThrowable().getTarget().getMessage() )
+                .isEqualTo( "dummy exception" );
+
         verifyPrivate( booter, times( 1 ) )
                 .invoke( "cancelPingScheduler" );
 
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/IsolatedClassLoaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/IsolatedClassLoaderTest.java
new file mode 100644
index 0000000..b424526
--- /dev/null
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/IsolatedClassLoaderTest.java
@@ -0,0 +1,66 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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.shared.utils.io.FileUtils;
+import org.apache.maven.surefire.providerapi.AbstractProvider;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URL;
+
+import static java.io.File.pathSeparator;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Tests isolated CL.
+ */
+public class IsolatedClassLoaderTest
+{
+    private IsolatedClassLoader classLoader;
+
+    @Before
+    public void prepareClassLoader() throws Exception
+    {
+        classLoader = new IsolatedClassLoader( null, false, "role" );
+
+        String[] files = FileUtils.fileRead( new File( "target/test-classpath/cp.txt" ), "UTF-8" )
+                .split( pathSeparator );
+
+        for ( String file : files )
+        {
+            URL fileUrl = new File( file ).toURL();
+            classLoader.addURL( fileUrl );
+        }
+    }
+
+    @Test
+    public void shouldLoadIsolatedClass() throws Exception
+    {
+        Class<?> isolatedClass = classLoader.loadClass( AbstractProvider.class.getName() );
+        assertThat( isolatedClass, is( notNullValue() ) );
+        assertThat( isolatedClass.getName(), is( AbstractProvider.class.getName() ) );
+        assertThat( isolatedClass, is( not( (Class) AbstractProvider.class ) ) );
+    }
+}
\ No newline at end of file
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
index 75b2ef0..9ef4fce 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
@@ -35,13 +35,17 @@ public class JUnit4SuiteTest extends TestCase
     public static Test suite()
     {
         TestSuite suite = new TestSuite();
+        suite.addTest( new JUnit4TestAdapter( CommandReaderTest.class ) );
         suite.addTest( new JUnit4TestAdapter( PpidCheckerTest.class ) );
         suite.addTest( new JUnit4TestAdapter( SystemUtilsTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( IsolatedClassLoaderTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkedBooterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkedBooterMockTest.class ) );
         suite.addTest( new JUnit4TestAdapter( BooterDeserializerTest.class ) );
         suite.addTestSuite( ClasspathTest.class );
         suite.addTestSuite( PropertiesWrapperTest.class );
+        suite.addTestSuite( DefaultMasterProcessChannelDecoderTest.class );
+        suite.addTestSuite( SurefireReflectorTest.class );
         return suite;
     }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
similarity index 100%
rename from surefire-api/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
similarity index 78%
rename from surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
index 0a006bf..88a4250 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
@@ -20,7 +20,6 @@ package org.apache.maven.surefire.booter;
  */
 
 import junit.framework.TestCase;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
@@ -31,7 +30,6 @@ import org.apache.maven.surefire.testset.TestArtifactInfo;
 import org.apache.maven.surefire.testset.TestListResolver;
 import org.apache.maven.surefire.testset.TestRequest;
 import org.apache.maven.surefire.util.RunOrder;
-import org.mockito.ArgumentCaptor;
 
 import java.io.File;
 import java.lang.reflect.Method;
@@ -39,38 +37,12 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 /**
  *
  */
 public class SurefireReflectorTest
         extends TestCase
 {
-    public void testCreateConsoleLogger()
-    {
-        ClassLoader cl = Thread.currentThread().getContextClassLoader();
-        ConsoleLogger consoleLogger = mock( ConsoleLogger.class );
-        ConsoleLogger decorator = (ConsoleLogger) SurefireReflector.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" );
-    }
-
     public void testShouldCreateFactoryWithoutException()
     {
         ReporterFactory factory = new ReporterFactory()
@@ -89,10 +61,10 @@ public class SurefireReflectorTest
         };
         ClassLoader cl = Thread.currentThread().getContextClassLoader();
         SurefireReflector reflector = new SurefireReflector( cl );
-        BaseProviderFactory baseProviderFactory =
-                (BaseProviderFactory) reflector.createBooterConfiguration( cl, factory, true );
-        assertNotNull( baseProviderFactory.getReporterFactory() );
-        assertSame( factory, baseProviderFactory.getReporterFactory() );
+        BaseProviderFactory bpf = (BaseProviderFactory) reflector.createBooterConfiguration( cl, true );
+        bpf.setReporterFactory( factory );
+        assertNotNull( bpf.getReporterFactory() );
+        assertSame( factory, bpf.getReporterFactory() );
     }
 
     public void testSetDirectoryScannerParameters()
@@ -172,6 +144,30 @@ public class SurefireReflectorTest
         assertTrue( isCalled( foo ) );
     }
 
+    public void testReporterFactoryAware()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        ReporterFactory reporterFactory = new ReporterFactory()
+        {
+            @Override
+            public RunListener createReporter()
+            {
+                return null;
+            }
+
+            @Override
+            public RunResult close()
+            {
+                return null;
+            }
+        };
+
+        surefireReflector.setReporterFactory( foo, reporterFactory );
+        assertTrue( isCalled( foo ) );
+    }
+
     private SurefireReflector getReflector()
     {
         return new SurefireReflector( this.getClass().getClassLoader() );
diff --git a/surefire-extensions-api/pom.xml b/surefire-extensions-api/pom.xml
index 1e88403..87fcff4 100644
--- a/surefire-extensions-api/pom.xml
+++ b/surefire-extensions-api/pom.xml
@@ -38,11 +38,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.codehaus.plexus</groupId>
-            <artifactId>plexus-component-annotations</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
             <scope>provided</scope>
@@ -72,33 +67,20 @@
                 <artifactId>jacoco-maven-plugin</artifactId>
                 <executions>
                     <execution>
-                        <id>jacoco-instrumentation</id>
-                        <!--
-                        phase: the order of the plugins matters and maven-plugin-plugin must be before jacoco plugin
-                        -->
-                        <goals>
-                            <goal>instrument</goal>
-                        </goals>
-                    </execution>
-                    <execution>
-                        <id>restore-classes</id>
+                        <id>jacoco-agent</id>
                         <goals>
-                            <goal>restore-instrumented-classes</goal>
+                            <goal>prepare-agent</goal>
                         </goals>
                     </execution>
                 </executions>
+                <configuration>
+                    <propertyName>jacoco.agent</propertyName>
+                </configuration>
             </plugin>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <dependencies>
-                    <dependency>
-                        <groupId>org.apache.maven.surefire</groupId>
-                        <artifactId>surefire-shadefire</artifactId>
-                        <version>3.0.0-M4</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
-                    </dependency>
-                </dependencies>
                 <configuration>
-                    <argLine>${jvm.args.tests}</argLine>
+                    <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
                     <includes>
                         <include>**/JUnit4SuiteTest.java</include>
                     </includes>
@@ -106,8 +88,14 @@
                         <jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
                     </systemPropertyVariables>
                 </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.maven.surefire</groupId>
+                        <artifactId>surefire-shadefire</artifactId>
+                        <version>3.0.0-M4</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+                    </dependency>
+                </dependencies>
             </plugin>
         </plugins>
     </build>
-
 </project>
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CommandReader.java
similarity index 64%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CommandReader.java
index eddebed..8dd9d96 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CommandReader.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,26 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.booter.Command;
 
-import java.util.List;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
+ * Stream reader returns bytes which ar finally sent to the forked jvm.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-interface MainCliOptionsAware
+public interface CommandReader
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+
+    /**
+     * Waits for the next command and returns it.
+     *
+     * @return the command, or null if closed
+     */
+    Command readNextCommand() throws IOException;
+    void close();
+    boolean isClosed();
+    void tryFlush() throws IOException;
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
similarity index 68%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java
rename to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
index 994b60d..bcb6c81 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,13 +19,14 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import javax.annotation.Nonnull;
+
 /**
- * See the plugin configuration parameter {@code skipAfterFailureCount}.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface FailFastAware
+public interface EventHandler
 {
-    void setSkipAfterFailureCount( int skipAfterFailureCount );
+    // todo here should be Event object as POJO which separates physical stream representation from the independent
+    // todo Event parser in ForkClient. So the parser should be part of ForkChannel implementation.
+    void handleEvent( @Nonnull String event );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ExecutableCommandline.java
similarity index 53%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java
rename to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ExecutableCommandline.java
index cefeb33..b373002 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ExecutableCommandline.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,20 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.testset.DirectoryScannerParameters;
+import javax.annotation.Nonnull;
+import java.util.concurrent.Callable;
 
 /**
- * @author Kristian Rosenvold
+ * todo add javadoc
  */
-interface DirectoryScannerParametersAware
+public interface ExecutableCommandline
 {
-    void setDirectoryScannerParameters( DirectoryScannerParameters directoryScanner );
+    @Nonnull
+    <T> Callable<Integer> executeCommandLineAsCallable( @Nonnull T cli,
+                                                        @Nonnull CommandReader commands,
+                                                        @Nonnull EventHandler events,
+                                                        StdOutStreamLine stdOut,
+                                                        StdErrStreamLine stdErr,
+                                                        @Nonnull Runnable runAfterProcessTermination )
+            throws Exception;
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java
similarity index 52%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java
index eddebed..62ef9bf 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,24 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
+import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
- *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * The constructor prepares I/O or throws {@link IOException}. Open channel can be closed and closes all streams.
+ * <br>
+ * The forked JVM uses the {@link #createExecutableCommandline() connection string}.
+ * The executable CLI {@link #createExecutableCommandline()} is using the streams. This method and constructor should
+ * not be blocked while establishing the connection.
  */
-interface MainCliOptionsAware
+public interface ForkChannel extends Closeable
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    String getForkNodeConnectionString();
+
+    @Nonnull
+    ExecutableCommandline createExecutableCommandline() throws IOException;
+
+    @Override
+    void close() throws IOException;
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
similarity index 69%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
index eddebed..c054776 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,20 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
+import javax.annotation.Nonnull;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
- *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-interface MainCliOptionsAware
+public interface ForkNodeFactory
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    /**
+     * Opens and closes the channel.
+     *
+     * @return specific implementation of the communication channel
+     * @throws IOException if cannot open the channel
+     */
+    @Nonnull ForkChannel createForkChannel() throws IOException;
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdErrStreamLine.java
similarity index 82%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java
rename to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdErrStreamLine.java
index c2f5d99..be444ae 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdErrStreamLine.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -20,9 +20,9 @@ package org.apache.maven.surefire.booter;
  */
 
 /**
- * @author Kristian Rosenvold
+ * The line handler of forked process standard-error.
  */
-interface SurefireClassLoadersAware
+public interface StdErrStreamLine
 {
-    void setClassLoaders( ClassLoader testClassLoader );
+    void handleLine( String line );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdOutStreamLine.java
similarity index 80%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java
rename to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdOutStreamLine.java
index caedb98..f615764 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdOutStreamLine.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,10 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import java.util.Map;
-
 /**
- * @author Kristian Rosenvold
+ * The line handler of forked process standard-output.
  */
-interface ProviderPropertiesAware
+public interface StdOutStreamLine
 {
-    void setProviderProperties( Map<String, String> providerProperties );
+    void handleLine( String line );
 }
diff --git a/surefire-extensions-spi/pom.xml b/surefire-extensions-spi/pom.xml
new file mode 100644
index 0000000..95cf3d9
--- /dev/null
+++ b/surefire-extensions-spi/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.maven.surefire</groupId>
+        <artifactId>surefire</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>surefire-extensions-spi</artifactId>
+    <name>Surefire Extensions SPI</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelDecoderFactory.java
similarity index 57%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
rename to surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelDecoderFactory.java
index eddebed..5b08d4d 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelDecoderFactory.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.spi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,22 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 
-import java.util.List;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
- *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * The SPI interface, a factory of decoders.
  */
-interface MainCliOptionsAware
+public interface MasterProcessChannelDecoderFactory
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    /**
+     * Decoder factory method.
+     *
+     * @param channelConfig "pipe:std:in" or "tcp://localhost:65035"
+     * @param logger        error logger
+     * @return a new instance of decoder
+     */
+    MasterProcessChannelDecoder createDecoder( String channelConfig, ConsoleLogger logger ) throws IOException;
 }
diff --git a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
index b964933..9e5efad 100644
--- a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
+++ b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
@@ -20,8 +20,8 @@ package org.apache.maven.surefire.junit4;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.CommandListener;
-import org.apache.maven.surefire.booter.CommandReader;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.CommandListener;
 import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
 import org.apache.maven.surefire.common.junit4.JUnit4TestChecker;
 import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
@@ -52,7 +52,6 @@ import java.util.Set;
 
 import static java.lang.reflect.Modifier.isAbstract;
 import static java.lang.reflect.Modifier.isInterface;
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.createMatchAnyDescriptionFilter;
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.generateFailingTestDescriptions;
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.isFailureInsideJUnitItself;
@@ -92,14 +91,14 @@ public class JUnit4Provider
 
     private final int rerunFailingTestsCount;
 
-    private final CommandReader commandsReader;
+    private final CommandChainReader commandsReader;
 
     private TestsToRun testsToRun;
 
     public JUnit4Provider( ProviderParameters bootParams )
     {
         // don't start a thread in CommandReader while we are in in-plugin process
-        commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
+        commandsReader = bootParams.isInsideFork() ? bootParams.getCommandReader() : null;
         providerParameters = bootParams;
         testClassLoader = bootParams.getTestClassLoader();
         scanResult = bootParams.getScanResult();
diff --git a/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java b/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
index b949baa..c03ea7e 100644
--- a/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
+++ b/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
@@ -50,7 +50,7 @@ public class JUnit4ProviderTest
 
     private JUnit4Provider getJUnit4Provider()
     {
-        BaseProviderFactory providerParameters = new BaseProviderFactory( null, true );
+        BaseProviderFactory providerParameters = new BaseProviderFactory( true );
         providerParameters.setProviderProperties( new HashMap<String, String>() );
         providerParameters.setClassLoaders( getClass().getClassLoader() );
         providerParameters.setTestRequest( new TestRequest( null, null, null ) );
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
index 7c74e8b..daabb60 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
@@ -20,8 +20,8 @@ package org.apache.maven.surefire.junitcore;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.CommandListener;
-import org.apache.maven.surefire.booter.CommandReader;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.CommandListener;
 import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
 import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
 import org.apache.maven.surefire.common.junit4.Notifier;
@@ -46,7 +46,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.generateFailingTestDescriptions;
 import static org.apache.maven.surefire.common.junit4.JUnit4RunListenerFactory.createCustomListeners;
 import static org.apache.maven.surefire.common.junit4.Notifier.pureNotifier;
@@ -82,14 +81,14 @@ public class JUnitCoreProvider
 
     private final TestListResolver testResolver;
 
-    private final CommandReader commandsReader;
+    private final CommandChainReader commandsReader;
 
     private TestsToRun testsToRun;
 
     public JUnitCoreProvider( ProviderParameters bootParams )
     {
         // don't start a thread in CommandReader while we are in in-plugin process
-        commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
+        commandsReader = bootParams.isInsideFork() ? bootParams.getCommandReader() : null;
         providerParameters = bootParams;
         testClassLoader = bootParams.getTestClassLoader();
         scanResult = bootParams.getScanResult();
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
index 8859c19..66f0b85 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
@@ -110,7 +110,8 @@ public class Surefire746Test
     public void surefireIsConfused_ByMultipleIgnore_OnClassLevel() throws Exception
     {
         ReporterFactory reporterFactory = JUnitCoreTester.defaultNoXml();
-        BaseProviderFactory providerParameters = new BaseProviderFactory( reporterFactory, true );
+        BaseProviderFactory providerParameters = new BaseProviderFactory( true );
+        providerParameters.setReporterFactory( reporterFactory );
 
         providerParameters.setReporterConfiguration( new ReporterConfiguration( new File( "" ), false ) );
         Map<String, String> junitProps = new HashMap<>();
diff --git a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java
index 14e103a..a8a5fb7 100644
--- a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java
+++ b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java
@@ -20,8 +20,8 @@ package org.apache.maven.surefire.testng;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.CommandListener;
-import org.apache.maven.surefire.booter.CommandReader;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.CommandListener;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.providerapi.AbstractProvider;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
@@ -43,7 +43,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.report.ConsoleOutputCapture.startCapture;
 import static org.apache.maven.surefire.testset.TestListResolver.getEmptyTestListResolver;
 import static org.apache.maven.surefire.testset.TestListResolver.optionallyWildcardFilter;
@@ -71,14 +70,14 @@ public class TestNGProvider
 
     private final List<CommandLineOption> mainCliOptions;
 
-    private final CommandReader commandsReader;
+    private final CommandChainReader commandsReader;
 
     private TestsToRun testsToRun;
 
     public TestNGProvider( ProviderParameters bootParams )
     {
         // don't start a thread in CommandReader while we are in in-plugin process
-        commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
+        commandsReader = bootParams.isInsideFork() ? bootParams.getCommandReader() : null;
         providerParameters = bootParams;
         testClassLoader = bootParams.getTestClassLoader();
         runOrderCalculator = bootParams.getRunOrderCalculator();