[maven-surefire] branch comm updated (138191f -> 67bc6e2)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

[maven-surefire] branch comm updated (138191f -> 67bc6e2)

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

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


    from 138191f  changes
     new 1246c59  new changes
     new 67bc6e2  fixed decoder and tests + logger of binary stream

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


Summary of changes:
 .../maven/plugin/surefire/SurefireHelper.java      |    2 +
 .../maven/plugin/surefire/SurefireProperties.java  |    5 +
 .../surefire/booterclient/BooterSerializer.java    |    2 +
 .../plugin/surefire/booterclient/ForkStarter.java  |   13 +
 .../output/InPluginProcessDumpSingleton.java       |    6 +
 .../surefire/extensions/EventConsumerThread.java   |  918 +----------------
 .../plugin/surefire/extensions/StreamFeeder.java   |  114 +--
 .../maven/surefire/stream/CommandEncoder.java      |  121 ++-
 .../apache/maven/surefire/stream/EventDecoder.java |   67 +-
 .../booterclient/ForkingRunListenerTest.java       |   20 +-
 .../plugin/surefire/booterclient/MainClass.java    |    2 +-
 .../TestLessInputStreamBuilderTest.java            |   35 +-
 .../TestProvidingInputStreamTest.java              |   46 +-
 .../booterclient/output/ForkClientTest.java        |   15 +-
 .../maven/plugin/surefire/extensions/E2ETest.java  |   14 +-
 .../extensions/EventConsumerThreadTest.java        | 1060 +-------------------
 .../extensions/ForkedProcessEventNotifierTest.java |  221 +---
 .../surefire/extensions/StreamFeederTest.java      |   39 +-
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |    2 +
 .../maven/surefire/extensions/ForkChannelTest.java |   14 +-
 .../maven/surefire/stream/EventDecoderTest.java    |  785 +++++++++++++++
 .../maven/surefire/api/booter/Constants.java       |    2 +-
 .../surefire/api/booter/DumpErrorSingleton.java    |   27 +-
 .../surefire/api/booter/ForkingRunListener.java    |    2 +-
 .../api/booter/MasterProcessChannelEncoder.java    |    6 +-
 .../surefire/api/booter/MasterProcessCommand.java  |   40 +-
 .../maven/surefire/api/fork/ForkNodeArguments.java |    4 +
 .../apache/maven/surefire/api/report/RunMode.java  |   19 +
 .../surefire/api/stream/AbstractStreamDecoder.java |   49 +-
 .../surefire/api/stream/AbstractStreamEncoder.java |   26 +-
 .../java/org/apache/maven/JUnit4SuiteTest.java     |    4 +-
 .../api/stream/AbstractStreamDecoderTest.java      |  687 +++++++++++++
 .../api/stream/AbstractStreamEncoderTest.java      |   68 +-
 .../maven/surefire/booter/BooterConstants.java     |    2 +-
 .../maven/surefire/booter/BooterDeserializer.java  |    5 +
 .../apache/maven/surefire/booter/ForkedBooter.java |   29 +-
 .../maven/surefire/booter/ForkedNodeArg.java       |   97 ++
 .../maven/surefire/booter/PropertiesWrapper.java   |    2 +-
 ...nnelDecoder.java => CommandChannelDecoder.java} |   49 +-
 ...hannelEncoder.java => EventChannelEncoder.java} |  113 ++-
 ...LegacyMasterProcessChannelProcessorFactory.java |   12 +-
 ...refireMasterProcessChannelProcessorFactory.java |   14 +-
 .../surefire/booter/stream/CommandDecoder.java     |  101 +-
 .../maven/surefire/booter/stream/EventEncoder.java |    6 +-
 .../maven/surefire/booter/CommandReaderTest.java   |   44 +-
 .../surefire/booter/ForkedBooterMockTest.java      |   33 +-
 .../maven/surefire/booter/JUnit4SuiteTest.java     |    8 +-
 .../booter/spi/CommandChannelDecoderTest.java      |  519 ++++++++++
 ...coderTest.java => EventChannelEncoderTest.java} |   70 +-
 .../spi/LegacyMasterProcessChannelDecoderTest.java |  243 -----
 .../resources/binary-commands/75171711-encoder.bin |  Bin 0 -> 851 bytes
 surefire-extensions-spi/pom.xml                    |    5 +
 .../spi/MasterProcessChannelProcessorFactory.java  |   10 +-
 .../plugin/surefire/log/api/PrintStreamLogger.java |    2 +-
 54 files changed, 3002 insertions(+), 2797 deletions(-)
 create mode 100644 maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
 create mode 100644 surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
 create mode 100644 surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
 rename surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/{LegacyMasterProcessChannelDecoder.java => CommandChannelDecoder.java} (56%)
 rename surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/{LegacyMasterProcessChannelEncoder.java => EventChannelEncoder.java} (79%)
 create mode 100644 surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
 rename surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/{LegacyMasterProcessChannelEncoderTest.java => EventChannelEncoderTest.java} (93%)
 delete mode 100644 surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java
 create mode 100644 surefire-booter/src/test/resources/binary-commands/75171711-encoder.bin

Reply | Threaded
Open this post in threaded view
|

[maven-surefire] 01/02: new changes

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

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

commit 1246c599fe18efc24ddc03506526d0ec84f5b877
Author: tibordigana <[hidden email]>
AuthorDate: Thu Jan 7 03:00:37 2021 +0100

    new changes
---
 .../maven/plugin/surefire/SurefireProperties.java  |    5 +
 .../surefire/booterclient/BooterSerializer.java    |    2 +
 .../surefire/extensions/EventConsumerThread.java   |  918 +----------------
 .../plugin/surefire/extensions/StreamFeeder.java   |  114 +--
 .../maven/surefire/stream/CommandEncoder.java      |  121 ++-
 .../apache/maven/surefire/stream/EventDecoder.java |   67 +-
 .../booterclient/ForkingRunListenerTest.java       |    8 +-
 .../plugin/surefire/booterclient/MainClass.java    |    2 +-
 .../TestLessInputStreamBuilderTest.java            |   34 +-
 .../TestProvidingInputStreamTest.java              |   45 +-
 .../booterclient/output/ForkClientTest.java        |    3 +-
 .../maven/plugin/surefire/extensions/E2ETest.java  |    2 +-
 .../extensions/EventConsumerThreadTest.java        | 1062 +-------------------
 .../extensions/ForkedProcessEventNotifierTest.java |  209 +---
 .../surefire/extensions/StreamFeederTest.java      |   39 +-
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |    2 +
 .../maven/surefire/extensions/ForkChannelTest.java |    2 +-
 .../maven/surefire/stream/EventDecoderTest.java    |  773 ++++++++++++++
 .../maven/surefire/api/booter/Constants.java       |    2 +-
 .../surefire/api/booter/DumpErrorSingleton.java    |   18 +-
 .../surefire/api/booter/ForkingRunListener.java    |    2 +-
 .../api/booter/MasterProcessChannelEncoder.java    |    6 +-
 .../surefire/api/booter/MasterProcessCommand.java  |   40 +-
 .../apache/maven/surefire/api/report/RunMode.java  |   19 +
 .../surefire/api/stream/AbstractStreamDecoder.java |   44 +-
 .../surefire/api/stream/AbstractStreamEncoder.java |   26 +-
 .../java/org/apache/maven/JUnit4SuiteTest.java     |    4 +-
 .../api/stream/AbstractStreamDecoderTest.java      |  675 +++++++++++++
 .../api/stream/AbstractStreamEncoderTest.java      |   68 +-
 .../maven/surefire/booter/BooterConstants.java     |    2 +-
 .../maven/surefire/booter/BooterDeserializer.java  |    5 +
 .../apache/maven/surefire/booter/ForkedBooter.java |   24 +-
 .../maven/surefire/booter/ForkedNodeArg.java       |   83 ++
 .../maven/surefire/booter/PropertiesWrapper.java   |    2 +-
 ...nnelDecoder.java => CommandChannelDecoder.java} |   49 +-
 ...hannelEncoder.java => EventChannelEncoder.java} |  113 ++-
 ...LegacyMasterProcessChannelProcessorFactory.java |   12 +-
 ...refireMasterProcessChannelProcessorFactory.java |   14 +-
 .../surefire/booter/stream/CommandDecoder.java     |   36 +-
 .../maven/surefire/booter/stream/EventEncoder.java |    6 +-
 .../maven/surefire/booter/CommandReaderTest.java   |   42 +-
 .../surefire/booter/ForkedBooterMockTest.java      |   33 +-
 .../maven/surefire/booter/JUnit4SuiteTest.java     |    8 +-
 ...derTest.java => CommandChannelDecoderTest.java} |  129 ++-
 ...coderTest.java => EventChannelEncoderTest.java} |   70 +-
 surefire-extensions-spi/pom.xml                    |    5 +
 .../spi/MasterProcessChannelProcessorFactory.java  |   10 +-
 .../plugin/surefire/log/api/PrintStreamLogger.java |    2 +-
 48 files changed, 2367 insertions(+), 2590 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
index a880c0d..cd2f91f 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
@@ -201,6 +201,11 @@ public class SurefireProperties
         }
     }
 
+    public void setProperty( String key, int value )
+    {
+        setProperty( key, String.valueOf( value ) );
+    }
+
     public void setProperty( String key, Long value )
     {
         if ( value != null )
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 1e00f16..1335a82 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
@@ -50,6 +50,7 @@ import static org.apache.maven.surefire.booter.BooterConstants.FAIL_FAST_COUNT;
 import static org.apache.maven.surefire.booter.BooterConstants.FAILIFNOTESTS;
 import static org.apache.maven.surefire.booter.BooterConstants.FORKTESTSET;
 import static org.apache.maven.surefire.booter.BooterConstants.FORKTESTSET_PREFER_TESTS_FROM_IN_STREAM;
+import static org.apache.maven.surefire.booter.BooterConstants.FORK_NUMBER;
 import static org.apache.maven.surefire.booter.BooterConstants.INCLUDES_PROPERTY_PREFIX;
 import static org.apache.maven.surefire.booter.BooterConstants.ISTRIMSTACKTRACE;
 import static org.apache.maven.surefire.booter.BooterConstants.MAIN_CLI_OPTIONS;
@@ -168,6 +169,7 @@ class BooterSerializer
         ReporterConfiguration reporterConfiguration = providerConfiguration.getReporterConfiguration();
         boolean rep = reporterConfiguration.isTrimStackTrace();
         File reportsDirectory = replaceForkThreadsInPath( reporterConfiguration.getReportsDirectory(), forkNumber );
+        properties.setProperty( FORK_NUMBER, forkNumber );
         properties.setProperty( ISTRIMSTACKTRACE, rep );
         properties.setProperty( REPORTSDIRECTORY, reportsDirectory );
         ClassLoaderConfiguration classLoaderConfig = startupConfiguration.getClassLoaderConfiguration();
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
index b1c7759..55fb4d0 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -19,146 +19,29 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
-import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
-import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
-import org.apache.maven.surefire.api.event.ConsoleInfoEvent;
-import org.apache.maven.surefire.api.event.ConsoleWarningEvent;
-import org.apache.maven.surefire.api.event.ControlByeEvent;
-import org.apache.maven.surefire.api.event.ControlNextTestEvent;
-import org.apache.maven.surefire.api.event.ControlStopOnNextTestEvent;
 import org.apache.maven.surefire.api.event.Event;
-import org.apache.maven.surefire.api.event.JvmExitErrorEvent;
-import org.apache.maven.surefire.api.event.StandardStreamErrEvent;
-import org.apache.maven.surefire.api.event.StandardStreamErrWithNewLineEvent;
-import org.apache.maven.surefire.api.event.StandardStreamOutEvent;
-import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
-import org.apache.maven.surefire.api.event.SystemPropertyEvent;
-import org.apache.maven.surefire.api.event.TestAssumptionFailureEvent;
-import org.apache.maven.surefire.api.event.TestErrorEvent;
-import org.apache.maven.surefire.api.event.TestFailedEvent;
-import org.apache.maven.surefire.api.event.TestSkippedEvent;
-import org.apache.maven.surefire.api.event.TestStartingEvent;
-import org.apache.maven.surefire.api.event.TestSucceededEvent;
-import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
-import org.apache.maven.surefire.api.event.TestsetStartingEvent;
-import org.apache.maven.surefire.api.report.RunMode;
-import org.apache.maven.surefire.api.report.StackTraceWriter;
-import org.apache.maven.surefire.api.report.TestSetReportEntry;
-import org.apache.maven.surefire.api.stream.AbstractStreamDecoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
 import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.api.stream.SegmentType;
+import org.apache.maven.surefire.stream.EventDecoder;
 
-import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
 import java.io.EOFException;
-import java.io.File;
 import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
 import java.nio.channels.ReadableByteChannel;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CoderResult;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-import static java.nio.charset.CodingErrorAction.REPLACE;
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.OVERFLOW;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.UNDERFLOW;
-import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
-import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
-import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
 
 /**
  *
  */
 public class EventConsumerThread extends CloseableDaemonThread
 {
-    private static final String[] JVM_ERROR_PATTERNS =
-        {
-            "could not create the java virtual machine",
-            "error occurred during initialization", // of VM, of boot layer
-            "error:", // general errors
-            "could not reserve enough space", "could not allocate", "unable to allocate", // memory errors
-            "java.lang.module.findexception" // JPMS errors
-        };
-
-    private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
-
-    private static final SegmentType[] EVENT_WITHOUT_DATA = new SegmentType[] {
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_WITH_ERROR_TRACE = new SegmentType[] {
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_WITH_ONE_STRING = new SegmentType[] {
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_WITH_RUNMODE_AND_ONE_STRING = new SegmentType[] {
-        SegmentType.RUN_MODE,
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_WITH_RUNMODE_AND_TWO_STRINGS = new SegmentType[] {
-        SegmentType.RUN_MODE,
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_TEST_CONTROL = new SegmentType[] {
-        SegmentType.RUN_MODE,
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_INTEGER,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final int BUFFER_SIZE = 1024;
-    private static final byte[] DEFAULT_STREAM_ENCODING_BYTES = DEFAULT_STREAM_ENCODING.name().getBytes( US_ASCII );
-    private static final int DELIMITER_LENGTH = 1;
-    private static final int BYTE_LENGTH = 1;
-    private static final int INT_LENGTH = 4;
-    private static final int NO_POSITION = -1;
-
     private final ReadableByteChannel channel;
     private final EventHandler<Event> eventHandler;
     private final CountdownCloseable countdownCloseable;
+    private final EventDecoder decoder;
     private final ForkNodeArguments arguments;
-    private final AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType> decoder;
     private volatile boolean disabled;
 
     public EventConsumerThread( @Nonnull String threadName,
@@ -168,6 +51,7 @@ public class EventConsumerThread extends CloseableDaemonThread
                                 @Nonnull ForkNodeArguments arguments )
     {
         super( threadName );
+        decoder = new EventDecoder( channel, arguments );
         this.channel = channel;
         this.eventHandler = eventHandler;
         this.countdownCloseable = countdownCloseable;
@@ -177,802 +61,42 @@ public class EventConsumerThread extends CloseableDaemonThread
     @Override
     public void run()
     {
+        Memento memento = decoder.new Memento();
         try ( ReadableByteChannel stream = channel;
               CountdownCloseable c = countdownCloseable; )
         {
-            decode();
-        }
-        catch ( IOException e )
-        {
-            // not needed
-        }
-    }
-
-    @Override
-    public void disable()
-    {
-        disabled = true;
-    }
-
-    @Override
-    public void close() throws IOException
-    {
-        channel.close();
-    }
-
-    @SuppressWarnings( "checkstyle:innerassignment" )
-    private void decode() throws IOException
-    {
-        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
-        Map<Segment, RunMode> runModes = mapRunModes();
-        Memento memento = new Memento();
-        memento.bb.limit( 0 );
-
-        do
-        {
-            try
-            {
-                ForkedProcessEventType eventType = readEventType( eventTypes, memento );
-                if ( eventType == null )
-                {
-                    throw new MalformedFrameException( memento.line.positionByteBuffer, memento.bb.position() );
-                }
-                RunMode runMode = null;
-                for ( SegmentType segmentType : nextSegmentType( eventType ) )
-                {
-                    if ( segmentType == null )
-                    {
-                        break;
-                    }
-
-                    switch ( segmentType )
-                    {
-                        case RUN_MODE:
-                            runMode = runModes.get( readSegment( memento ) );
-                            break;
-                        case STRING_ENCODING:
-                            memento.setCharset( readCharset( memento ) );
-                            break;
-                        case DATA_STRING:
-                            memento.data.add( readString( memento ) );
-                            break;
-                        case DATA_INTEGER:
-                            memento.data.add( readInteger( memento ) );
-                            break;
-                        case END_OF_FRAME:
-                            memento.line.positionByteBuffer = memento.bb.position();
-                            if ( !disabled )
-                            {
-                                eventHandler.handleEvent( toEvent( eventType, runMode, memento.data ) );
-                            }
-                            break;
-                        default:
-                            memento.line.positionByteBuffer = NO_POSITION;
-                            arguments.dumpStreamText( "Unknown enum ("
-                                + ForkedProcessEventType.class.getSimpleName()
-                                + ") "
-                                + segmentType );
-                    }
-                }
-            }
-            catch ( MalformedFrameException e )
-            {
-                if ( e.hasValidPositions() )
-                {
-                    int length = e.readTo - e.readFrom;
-                    memento.line.write( memento.bb, e.readFrom, length );
-                }
-            }
-            catch ( RuntimeException e )
-            {
-                arguments.dumpStreamException( e );
-            }
-            catch ( IOException e )
-            {
-                printRemainingStream( memento );
-                throw e;
-            }
-            finally
-            {
-                memento.reset();
-            }
-        }
-        while ( true );
-    }
-
-    protected ForkedProcessEventType readEventType( Map<Segment, ForkedProcessEventType> eventTypes, Memento memento )
-        throws IOException, MalformedFrameException
-    {
-        int readCount = DELIMITER_LENGTH + MAGIC_NUMBER_FOR_EVENTS_BYTES.length + DELIMITER_LENGTH
-            + BYTE_LENGTH + DELIMITER_LENGTH;
-        read( memento, readCount );
-        checkHeader( memento );
-        return eventTypes.get( readSegment( memento ) );
-    }
-
-    protected String readString( Memento memento ) throws IOException, MalformedFrameException
-    {
-        memento.cb.clear();
-        int readCount = readInt( memento );
-        read( memento, readCount + DELIMITER_LENGTH );
-
-        final String string;
-        if ( readCount == 0 )
-        {
-            string = "";
-        }
-        else if ( readCount == 1 )
-        {
-            read( memento, 1 );
-            byte oneChar = memento.bb.get();
-            string = oneChar == 0 ? null : String.valueOf( (char) oneChar );
-        }
-        else
-        {
-            string = readString( memento, readCount );
-        }
-
-        checkDelimiter( memento );
-        return string;
-    }
-
-    @Nonnull
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    protected Segment readSegment( Memento memento ) throws IOException, MalformedFrameException
-    {
-        int readCount = readByte( memento ) & 0xff;
-        read( memento, readCount + DELIMITER_LENGTH );
-        ByteBuffer bb = memento.bb;
-        Segment segment = new Segment( bb.array(), bb.arrayOffset() + bb.position(), readCount );
-        bb.position( bb.position() + readCount );
-        checkDelimiter( memento );
-        return segment;
-    }
-
-    @Nonnull
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    protected Charset readCharset( Memento memento ) throws IOException, MalformedFrameException
-    {
-        int length = readByte( memento ) & 0xff;
-        read( memento, length + DELIMITER_LENGTH );
-        ByteBuffer bb = memento.bb;
-        byte[] array = bb.array();
-        int offset = bb.arrayOffset() + bb.position();
-        bb.position( bb.position() + length );
-        boolean isDefaultEncoding = false;
-        if ( length == DEFAULT_STREAM_ENCODING_BYTES.length )
-        {
-            isDefaultEncoding = true;
-            for ( int i = 0; i < length; i++ )
-            {
-                isDefaultEncoding &= DEFAULT_STREAM_ENCODING_BYTES[i] == array[offset + i];
-            }
-        }
-
-        try
-        {
-            Charset charset =
-                isDefaultEncoding
-                    ? DEFAULT_STREAM_ENCODING
-                    : Charset.forName( new String( array, offset, length, US_ASCII ) );
-
-            checkDelimiter( memento );
-            return charset;
-        }
-        catch ( IllegalArgumentException e )
-        {
-            throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() );
-        }
-    }
-
-    private static void checkHeader( Memento memento ) throws MalformedFrameException
-    {
-        ByteBuffer bb = memento.bb;
-
-        checkDelimiter( memento );
-
-        int shift = 0;
-        try
-        {
-            for ( int start = bb.arrayOffset() + bb.position(), length = MAGIC_NUMBER_FOR_EVENTS_BYTES.length;
-                  shift < length; shift++ )
-            {
-                if ( bb.array()[shift + start] != MAGIC_NUMBER_FOR_EVENTS_BYTES[shift] )
-                {
-                    throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() + shift );
-                }
-            }
-        }
-        finally
-        {
-            bb.position( bb.position() + shift );
-        }
-
-        checkDelimiter( memento );
-    }
-
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    private static void checkDelimiter( Memento memento ) throws MalformedFrameException
-    {
-        ByteBuffer bb = memento.bb;
-        if ( ( 0xff & bb.get() ) != ':' )
-        {
-            throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() );
-        }
-    }
-
-    static SegmentType[] nextSegmentType( ForkedProcessEventType eventType )
-    {
-        switch ( eventType )
-        {
-            case BOOTERCODE_BYE:
-            case BOOTERCODE_STOP_ON_NEXT_TEST:
-            case BOOTERCODE_NEXT_TEST:
-                return EVENT_WITHOUT_DATA;
-            case BOOTERCODE_CONSOLE_ERROR:
-            case BOOTERCODE_JVM_EXIT_ERROR:
-                return EVENT_WITH_ERROR_TRACE;
-            case BOOTERCODE_CONSOLE_INFO:
-            case BOOTERCODE_CONSOLE_DEBUG:
-            case BOOTERCODE_CONSOLE_WARNING:
-                return EVENT_WITH_ONE_STRING;
-            case BOOTERCODE_STDOUT:
-            case BOOTERCODE_STDOUT_NEW_LINE:
-            case BOOTERCODE_STDERR:
-            case BOOTERCODE_STDERR_NEW_LINE:
-                return EVENT_WITH_RUNMODE_AND_ONE_STRING;
-            case BOOTERCODE_SYSPROPS:
-                return EVENT_WITH_RUNMODE_AND_TWO_STRINGS;
-            case BOOTERCODE_TESTSET_STARTING:
-            case BOOTERCODE_TESTSET_COMPLETED:
-            case BOOTERCODE_TEST_STARTING:
-            case BOOTERCODE_TEST_SUCCEEDED:
-            case BOOTERCODE_TEST_FAILED:
-            case BOOTERCODE_TEST_SKIPPED:
-            case BOOTERCODE_TEST_ERROR:
-            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                return EVENT_TEST_CONTROL;
-            default:
-                throw new IllegalArgumentException( "Unknown enum " + eventType );
-        }
-    }
-
-    protected StreamReadStatus read( Memento memento, int recommendedCount ) throws IOException
-    {
-        ByteBuffer buffer = memento.bb;
-        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
-        {
-            return OVERFLOW;
-        }
-        else
-        {
-            if ( buffer.position() != 0 && recommendedCount > buffer.capacity() - buffer.position() )
-            {
-                buffer.compact().flip();
-                memento.line.positionByteBuffer = 0;
-            }
-            int mark = buffer.position();
-            buffer.position( buffer.limit() );
-            buffer.limit( buffer.capacity() );
-            boolean isEnd = false;
-            while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() != buffer.limit() )
-            {
-                isEnd = channel.read( buffer ) == -1;
-            }
-
-            buffer.limit( buffer.position() );
-            buffer.position( mark );
-            int readBytes = buffer.remaining();
-
-            if ( isEnd && readBytes < recommendedCount )
-            {
-                throw new EOFException();
-            }
-            else
-            {
-                return readBytes >= recommendedCount ? OVERFLOW : UNDERFLOW;
-            }
-        }
-    }
-
-    static Event toEvent( ForkedProcessEventType eventType, RunMode runMode, List<Object> args )
-    {
-        switch ( eventType )
-        {
-            case BOOTERCODE_BYE:
-                return new ControlByeEvent();
-            case BOOTERCODE_STOP_ON_NEXT_TEST:
-                return new ControlStopOnNextTestEvent();
-            case BOOTERCODE_NEXT_TEST:
-                return new ControlNextTestEvent();
-            case BOOTERCODE_CONSOLE_ERROR:
-                return new ConsoleErrorEvent( toStackTraceWriter( args ) );
-            case BOOTERCODE_JVM_EXIT_ERROR:
-                return new JvmExitErrorEvent( toStackTraceWriter( args ) );
-            case BOOTERCODE_CONSOLE_INFO:
-                return new ConsoleInfoEvent( (String) args.get( 0 ) );
-            case BOOTERCODE_CONSOLE_DEBUG:
-                return new ConsoleDebugEvent( (String) args.get( 0 ) );
-            case BOOTERCODE_CONSOLE_WARNING:
-                return new ConsoleWarningEvent( (String) args.get( 0 ) );
-            case BOOTERCODE_STDOUT:
-                return new StandardStreamOutEvent( runMode, (String) args.get( 0 ) );
-            case BOOTERCODE_STDOUT_NEW_LINE:
-                return new StandardStreamOutWithNewLineEvent( runMode, (String) args.get( 0 ) );
-            case BOOTERCODE_STDERR:
-                return new StandardStreamErrEvent( runMode, (String) args.get( 0 ) );
-            case BOOTERCODE_STDERR_NEW_LINE:
-                return new StandardStreamErrWithNewLineEvent( runMode, (String) args.get( 0 ) );
-            case BOOTERCODE_SYSPROPS:
-                String key = (String) args.get( 0 );
-                String value = (String) args.get( 1 );
-                return new SystemPropertyEvent( runMode, key, value );
-            case BOOTERCODE_TESTSET_STARTING:
-                return new TestsetStartingEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TESTSET_COMPLETED:
-                return new TestsetCompletedEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_STARTING:
-                return new TestStartingEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_SUCCEEDED:
-                return new TestSucceededEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_FAILED:
-                return new TestFailedEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_SKIPPED:
-                return new TestSkippedEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_ERROR:
-                return new TestErrorEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                return new TestAssumptionFailureEvent( runMode, toReportEntry( args ) );
-            default:
-                throw new IllegalArgumentException( "Missing a branch for the event type " + eventType );
-        }
-    }
-
-    private static void printCorruptedStream( Memento memento )
-    {
-        ByteBuffer bb = memento.bb;
-        if ( bb.hasRemaining() )
-        {
-            int bytesToWrite = bb.remaining();
-            memento.line.write( bb, bb.position(), bytesToWrite );
-            bb.position( bb.position() + bytesToWrite );
-        }
-    }
-
-    /**
-     * Print the last string which has not been finished by a new line character.
-     *
-     * @param memento current memento object
-     */
-    private static void printRemainingStream( Memento memento )
-    {
-        printCorruptedStream( memento );
-        memento.line.printExistingLine();
-        memento.line.count = 0;
-    }
-
-    @Nonnull
-    private static TestSetReportEntry toReportEntry( List<Object> args )
-    {
-        // ReportEntry:
-        String source = (String) args.get( 0 );
-        String sourceText = (String) args.get( 1 );
-        String name = (String) args.get( 2 );
-        String nameText = (String) args.get( 3 );
-        String group = (String) args.get( 4 );
-        String message = (String) args.get( 5 );
-        Integer timeElapsed = (Integer) args.get( 6 );
-        // StackTraceWriter:
-        String traceMessage = (String) args.get( 7 );
-        String smartTrimmedStackTrace = (String) args.get( 8 );
-        String stackTrace = (String) args.get( 9 );
-        return newReportEntry( source, sourceText, name, nameText, group, message, timeElapsed,
-            traceMessage, smartTrimmedStackTrace, stackTrace );
-    }
-
-    private static StackTraceWriter toStackTraceWriter( List<Object> args )
-    {
-        String traceMessage = (String) args.get( 0 );
-        String smartTrimmedStackTrace = (String) args.get( 1 );
-        String stackTrace = (String) args.get( 2 );
-        return toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
-    }
-
-    private static StackTraceWriter toTrace( String traceMessage, String smartTrimmedStackTrace, String stackTrace )
-    {
-        boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
-        return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
-    }
-
-    static TestSetReportEntry newReportEntry( // ReportEntry:
-                                              String source, String sourceText, String name,
-                                              String nameText, String group, String message,
-                                              Integer timeElapsed,
-                                              // StackTraceWriter:
-                                              String traceMessage,
-                                              String smartTrimmedStackTrace, String stackTrace )
-        throws NumberFormatException
-    {
-        StackTraceWriter stackTraceWriter = toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
-        return reportEntry( source, sourceText, name, nameText, group, stackTraceWriter, timeElapsed, message,
-            Collections.<String, String>emptyMap() );
-    }
-
-    private static boolean isJvmError( String line )
-    {
-        String lineLower = line.toLowerCase();
-        for ( String errorPattern : JVM_ERROR_PATTERNS )
-        {
-            if ( lineLower.contains( errorPattern ) )
-            {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static int decodeString( @Nonnull CharsetDecoder decoder, @Nonnull ByteBuffer input,
-                                     @Nonnull CharBuffer output, @Nonnegative int bytesToDecode,
-                                     boolean endOfInput, @Nonnegative int errorStreamFrom )
-        throws MalformedFrameException
-    {
-        int limit = input.limit();
-        input.limit( input.position() + bytesToDecode );
-
-        CoderResult result = decoder.decode( input, output, endOfInput );
-        if ( result.isError() || result.isMalformed() )
-        {
-            throw new MalformedFrameException( errorStreamFrom, input.position() );
-        }
-        
-        int decodedBytes = bytesToDecode - input.remaining();
-        input.limit( limit );
-        return decodedBytes;
-    }
-
-    String readString( @Nonnull final Memento memento, @Nonnegative final int totalBytes )
-        throws IOException, MalformedFrameException
-    {
-        memento.getDecoder().reset();
-        final CharBuffer output = memento.cb;
-        output.clear();
-        final ByteBuffer input = memento.bb;
-        final List<String> strings = new ArrayList<>();
-        int countDecodedBytes = 0;
-        for ( boolean endOfInput = false; !endOfInput; )
-        {
-            final int bytesToRead = totalBytes - countDecodedBytes;
-            read( memento, bytesToRead - input.remaining() );
-            int bytesToDecode = min( input.remaining(), bytesToRead );
-            final boolean isLastChunk = bytesToDecode == bytesToRead;
-            endOfInput = countDecodedBytes + bytesToDecode >= totalBytes;
             do
             {
-                boolean endOfChunk = output.remaining() >= bytesToRead;
-                boolean endOfOutput = isLastChunk && endOfChunk;
-                int readInputBytes = decodeString( memento.getDecoder(), input, output, bytesToDecode, endOfOutput,
-                    memento.line.positionByteBuffer );
-                bytesToDecode -= readInputBytes;
-                countDecodedBytes += readInputBytes;
-            }
-            while ( isLastChunk && bytesToDecode > 0 && output.hasRemaining() );
-
-            if ( isLastChunk || !output.hasRemaining() )
-            {
-                strings.add( output.flip().toString() );
-                output.clear();
-            }
-        }
-
-        memento.getDecoder().reset();
-        output.clear();
-
-        return toString( strings );
-    }
-
-    protected byte readByte( Memento memento ) throws IOException, MalformedFrameException
-    {
-        read( memento, BYTE_LENGTH + DELIMITER_LENGTH );
-        byte b = memento.bb.get();
-        checkDelimiter( memento );
-        return b;
-    }
-
-    protected int readInt( Memento memento ) throws IOException, MalformedFrameException
-    {
-        read( memento, INT_LENGTH + DELIMITER_LENGTH );
-        int i = memento.bb.getInt();
-        checkDelimiter( memento );
-        return i;
-    }
-
-    protected Integer readInteger( Memento memento ) throws IOException, MalformedFrameException
-    {
-        read( memento, BYTE_LENGTH );
-        boolean isNullObject = memento.bb.get() == 0;
-        if ( isNullObject )
-        {
-            read( memento, DELIMITER_LENGTH );
-            checkDelimiter( memento );
-            return null;
-        }
-        return readInt( memento );
-    }
-
-    private static String toString( List<String> strings )
-    {
-        if ( strings.size() == 1 )
-        {
-            return strings.get( 0 );
-        }
-        StringBuilder concatenated = new StringBuilder( strings.size() * BUFFER_SIZE );
-        for ( String s : strings )
-        {
-            concatenated.append( s );
-        }
-        return concatenated.toString();
-    }
-
-    static Map<Segment, ForkedProcessEventType> mapEventTypes()
-    {
-        Map<Segment, ForkedProcessEventType> map = new HashMap<>();
-        for ( ForkedProcessEventType e : ForkedProcessEventType.values() )
-        {
-            byte[] array = e.getOpcode().getBytes( US_ASCII );
-            map.put( new Segment( array, 0, array.length ), e );
-        }
-        return map;
-    }
-
-    static Map<Segment, RunMode> mapRunModes()
-    {
-        Map<Segment, RunMode> map = new HashMap<>();
-        for ( RunMode e : RunMode.values() )
-        {
-            byte[] array = e.geRunmode().getBytes( US_ASCII );
-            map.put( new Segment( array, 0, array.length ), e );
-        }
-        return map;
-    }
-
-    enum StreamReadStatus
-    {
-        UNDERFLOW,
-        OVERFLOW,
-        EOF
-    }
-
-    /**
-     * This class avoids locking which gains the performance of this decoder.
-     */
-    private class BufferedStream
-    {
-        private byte[] buffer;
-        private int count;
-        private int positionByteBuffer;
-        private boolean isNewLine;
-
-        BufferedStream( int capacity )
-        {
-            this.buffer = new byte[capacity];
-        }
-
-        void write( ByteBuffer bb, int position, int length )
-        {
-            ensureCapacity( length );
-            byte[] array = bb.array();
-            int pos = bb.arrayOffset() + position;
-            while ( length-- > 0 )
-            {
-                positionByteBuffer++;
-                byte b = array[pos++];
-                if ( b == '\r' || b == '\n' )
+                Event event = decoder.decode( memento );
+                if ( event != null && !disabled )
                 {
-                    if ( !isNewLine )
-                    {
-                        printExistingLine();
-                        count = 0;
-                    }
-                    isNewLine = true;
-                }
-                else
-                {
-                    buffer[count++] = b;
-                    isNewLine = false;
+                    eventHandler.handleEvent( event );
                 }
             }
+            while ( true );
         }
-
-        private boolean isEmpty()
+        catch ( EOFException e )
         {
-            return count == 0;
-        }
-
-        @Override
-        public String toString()
-        {
-            return new String( buffer, 0, count, DEFAULT_STREAM_ENCODING );
-        }
-
-        private void ensureCapacity( int addCapacity )
-        {
-            int oldCapacity = buffer.length;
-            int exactCapacity = count + addCapacity;
-            if ( exactCapacity < 0 )
-            {
-                throw new OutOfMemoryError();
-            }
-
-            if ( oldCapacity < exactCapacity )
-            {
-                int newCapacity = oldCapacity << 1;
-                buffer = Arrays.copyOf( buffer, max( newCapacity, exactCapacity ) );
-            }
+            //
         }
-
-        void printExistingLine()
-        {
-            if ( isEmpty() )
-            {
-                return;
-            }
-
-            String s = toString();
-            if ( s == null )
-            {
-                return;
-            }
-
-            ConsoleLogger logger = arguments.getConsoleLogger();
-            if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
-            {
-                if ( logger.isDebugEnabled() )
-                {
-                    logger.debug( s );
-                }
-                else if ( logger.isInfoEnabled() )
-                {
-                    logger.info( s );
-                }
-                else
-                {
-                    // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
-                    System.out.println( s );
-                }
-            }
-            else
-            {
-                if ( isJvmError( s ) )
-                {
-                    logger.error( s );
-                }
-                else if ( logger.isDebugEnabled() )
-                {
-                    logger.debug( s );
-                }
-
-                String msg = "Corrupted channel by directly writing to native stream in forked JVM "
-                    + arguments.getForkChannelId() + ".";
-                File dumpFile = arguments.dumpStreamText( msg + " Stream '" + s + "'." );
-                arguments.logWarningAtEnd( msg + " See FAQ web page and the dump file "
-                    + dumpFile.getAbsolutePath() );
-            }
-        }
-    }
-
-    public final class Memento
-    {
-        private final CharsetDecoder defaultDecoder;
-        private CharsetDecoder currentDecoder;
-        private final BufferedStream line = new BufferedStream( 32 );
-        private final List<Object> data = new ArrayList<>();
-        private final CharBuffer cb = CharBuffer.allocate( BUFFER_SIZE );
-        private final ByteBuffer bb = ByteBuffer.allocate( BUFFER_SIZE );
-
-        public Memento()
-        {
-            defaultDecoder = DEFAULT_STREAM_ENCODING.newDecoder()
-                .onMalformedInput( REPLACE )
-                .onUnmappableCharacter( REPLACE );
-        }
-
-        void reset()
-        {
-            currentDecoder = null;
-            data.clear();
-        }
-
-        public CharsetDecoder getDecoder()
-        {
-            return currentDecoder == null ? defaultDecoder : currentDecoder;
-        }
-
-        void setCharset( Charset charset )
+        catch ( IOException e )
         {
-            if ( charset.name().equals( defaultDecoder.charset().name() ) )
+            if ( !( e.getCause() instanceof InterruptedException ) )
             {
-                currentDecoder = defaultDecoder;
-            }
-            else
-            {
-                currentDecoder = charset.newDecoder()
-                    .onMalformedInput( REPLACE )
-                    .onUnmappableCharacter( REPLACE );
+                arguments.dumpStreamException( e );
             }
         }
     }
 
-    static class Segment
+    @Override
+    public void disable()
     {
-        private final byte[] array;
-        private final int fromIndex;
-        private final int length;
-        private final int hashCode;
-
-        Segment( byte[] array, int fromIndex, int length )
-        {
-            this.array = array;
-            this.fromIndex = fromIndex;
-            this.length = length;
-
-            int hashCode = 0;
-            int i = fromIndex;
-            for ( int loops = length >> 1; loops-- != 0; )
-            {
-                hashCode = 31 * hashCode + array[i++];
-                hashCode = 31 * hashCode + array[i++];
-            }
-            this.hashCode = i == fromIndex + length ? hashCode : 31 * hashCode + array[i];
-        }
-
-        @Override
-        public int hashCode()
-        {
-            return hashCode;
-        }
-
-        @Override
-        public boolean equals( Object obj )
-        {
-            if ( !( obj instanceof Segment ) )
-            {
-                return false;
-            }
-
-            Segment that = (Segment) obj;
-            if ( that.length != length )
-            {
-                return false;
-            }
-
-            for ( int i = 0; i < length; i++ )
-            {
-                if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
-                {
-                    return false;
-                }
-            }
-            return true;
-        }
+        disabled = true;
     }
 
-    /**
-     *
-     */
-    static class MalformedFrameException extends Exception
+    @Override
+    public void close() throws IOException
     {
-        private final int readFrom;
-        private final int readTo;
-
-        MalformedFrameException( int readFrom, int readTo )
-        {
-            this.readFrom = readFrom;
-            this.readTo = readTo;
-        }
-
-        boolean hasValidPositions()
-        {
-            return readFrom != NO_POSITION && readTo != NO_POSITION && readTo - readFrom > 0;
-        }
+        channel.close();
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java
index 9dfe54e..0dcf5ef 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java
@@ -21,21 +21,15 @@ package org.apache.maven.plugin.surefire.extensions;
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.Command;
-import org.apache.maven.surefire.api.booter.MasterProcessCommand;
 import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.CommandReader;
+import org.apache.maven.surefire.stream.CommandEncoder;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonWritableChannelException;
 import java.nio.channels.WritableByteChannel;
-import java.util.HashMap;
-import java.util.Map;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_COMMANDS;
 
 /**
  * Commands which are sent from plugin to the forked jvm.
@@ -52,8 +46,6 @@ import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_CO
  */
 public class StreamFeeder extends CloseableDaemonThread
 {
-    private static final Map<MasterProcessCommand, String> COMMAND_TYPES = opcodesToStrings();
-
     private final WritableByteChannel channel;
     private final CommandReader commandReader;
     private final ConsoleLogger logger;
@@ -74,15 +66,35 @@ public class StreamFeeder extends CloseableDaemonThread
     @SuppressWarnings( "checkstyle:innerassignment" )
     public void run()
     {
-        try ( WritableByteChannel c = channel )
+        try ( CommandEncoder encoder = new CommandEncoder( channel ) )
         {
             for ( Command cmd; ( cmd = commandReader.readNextCommand() ) != null; )
             {
                 if ( !disabled )
                 {
-                    MasterProcessCommand cmdType = cmd.getCommandType();
-                    byte[] data = cmdType.hasDataType() ? encode( cmdType, cmd.getData() ) : encode( cmdType );
-                    c.write( ByteBuffer.wrap( data ) );
+                    switch ( cmd.getCommandType() )
+                    {
+                        case RUN_CLASS:
+                            encoder.sendRunClass( cmd.getData() );
+                            break;
+                        case TEST_SET_FINISHED:
+                            encoder.sendTestSetFinished();
+                            break;
+                        case SKIP_SINCE_NEXT_TEST:
+                            encoder.sendSkipSinceNextTest();
+                            break;
+                        case SHUTDOWN:
+                            encoder.sendShutdown( cmd.getData() );
+                            break;
+                        case NOOP:
+                            encoder.sendNoop();
+                            break;
+                        case BYE_ACK:
+                            encoder.sendByeAck();
+                            break;
+                        default:
+                            logger.error( "Unknown enum " + cmd.getCommandType().name() );
+                    }
                 }
             }
         }
@@ -115,80 +127,4 @@ public class StreamFeeder extends CloseableDaemonThread
     {
         channel.close();
     }
-
-    /**
-     * Public method for testing purposes.
-     *
-     * @param cmdType command type
-     * @param data data to encode
-     * @return command with data encoded to bytes
-     */
-    public static byte[] encode( MasterProcessCommand cmdType, String data )
-    {
-        if ( !cmdType.hasDataType() )
-        {
-            throw new IllegalArgumentException( "cannot use data without data type" );
-        }
-
-        if ( cmdType.getDataType() != String.class )
-        {
-            throw new IllegalArgumentException( "Data type can be only " + String.class );
-        }
-
-        return encode( COMMAND_TYPES.get( cmdType ), data )
-            .toString()
-            .getBytes( US_ASCII );
-    }
-
-    /**
-     * Public method for testing purposes.
-     *
-     * @param cmdType command type
-     * @return command without data encoded to bytes
-     */
-    public static byte[] encode( MasterProcessCommand cmdType )
-    {
-        if ( cmdType.getDataType() != Void.class )
-        {
-            throw new IllegalArgumentException( "Data type can be only " + cmdType.getDataType() );
-        }
-
-        return encode( COMMAND_TYPES.get( cmdType ), null )
-            .toString()
-            .getBytes( US_ASCII );
-    }
-
-    /**
-     * Encodes opcode and data.
-     *
-     * @param operation opcode
-     * @param data   data
-     * @return encoded command
-     */
-    private static StringBuilder encode( String operation, String data )
-    {
-        StringBuilder s = new StringBuilder( 128 )
-            .append( ':' )
-            .append( MAGIC_NUMBER_FOR_COMMANDS )
-            .append( ':' )
-            .append( operation );
-
-        if ( data != null )
-        {
-            s.append( ':' )
-                .append( data );
-        }
-
-        return s.append( ':' );
-    }
-
-    private static Map<MasterProcessCommand, String> opcodesToStrings()
-    {
-        Map<MasterProcessCommand, String> opcodes = new HashMap<>();
-        for ( MasterProcessCommand e : MasterProcessCommand.values() )
-        {
-            opcodes.put( e, e.toString() );
-        }
-        return opcodes;
-    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/CommandEncoder.java b/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/CommandEncoder.java
index 5018603..81f9d77 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/CommandEncoder.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/CommandEncoder.java
@@ -19,11 +19,130 @@ package org.apache.maven.surefire.stream;
  * under the License.
  */
 
+import org.apache.maven.surefire.api.booter.MasterProcessCommand;
 import org.apache.maven.surefire.api.stream.AbstractStreamEncoder;
 
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING_BYTES;
+import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_COMMANDS_BYTES;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET_FINISHED;
+import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
+
 /**
  *
  */
-public class CommandEncoder extends AbstractStreamEncoder<>
+public class CommandEncoder extends AbstractStreamEncoder<MasterProcessCommand> implements AutoCloseable
 {
+    private final WritableByteChannel out;
+
+    public CommandEncoder( WritableByteChannel out )
+    {
+        super( out );
+        this.out = out;
+    }
+
+    public void sendRunClass( String testClassName ) throws IOException
+    {
+        CharsetEncoder encoder = newCharsetEncoder();
+        int bufferMaxLength =
+            estimateBufferLength( RUN_CLASS.getOpcodeLength(), NORMAL_RUN, encoder, 0, testClassName );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encode( encoder, result, RUN_CLASS, NORMAL_RUN, testClassName );
+        write( result, true );
+    }
+
+    public void sendTestSetFinished() throws IOException
+    {
+        int bufferMaxLength = estimateBufferLength( TEST_SET_FINISHED.getOpcodeLength(), null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( result, TEST_SET_FINISHED, null );
+        write( result, true );
+    }
+
+    public void sendSkipSinceNextTest() throws IOException
+    {
+        int bufferMaxLength = estimateBufferLength( SKIP_SINCE_NEXT_TEST.getOpcodeLength(), null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( result, SKIP_SINCE_NEXT_TEST, null );
+        write( result, true );
+    }
+
+    public void sendShutdown( String shutdownData ) throws IOException
+    {
+        CharsetEncoder encoder = newCharsetEncoder();
+        int bufferMaxLength =
+            estimateBufferLength( SHUTDOWN.getOpcodeLength(), null, encoder, 0, shutdownData );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encode( encoder, result, SHUTDOWN, null, shutdownData );
+        write( result, true );
+    }
+
+    public void sendNoop() throws IOException
+    {
+        int bufferMaxLength = estimateBufferLength( NOOP.getOpcodeLength(), null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( result, NOOP, null );
+        write( result, true );
+    }
+
+    public void sendByeAck() throws IOException
+    {
+        int bufferMaxLength = estimateBufferLength( BYE_ACK.getOpcodeLength(), null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( result, BYE_ACK, null );
+        write( result, true );
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] getEncodedMagicNumber()
+    {
+        return MAGIC_NUMBER_FOR_COMMANDS_BYTES;
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] enumToByteArray( MasterProcessCommand masterProcessCommand )
+    {
+        return masterProcessCommand.getOpcodeBinary();
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] getEncodedCharsetName()
+    {
+        return DEFAULT_STREAM_ENCODING_BYTES;
+    }
+
+    @Nonnull
+    @Override
+    protected final Charset getCharset()
+    {
+        return DEFAULT_STREAM_ENCODING;
+    }
+
+    @Nonnull
+    @Override
+    protected final CharsetEncoder newCharsetEncoder()
+    {
+        return getCharset().newEncoder();
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        out.close();
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/EventDecoder.java b/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/EventDecoder.java
index aada1b6..1d7a2a4 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/EventDecoder.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/EventDecoder.java
@@ -20,7 +20,6 @@ package org.apache.maven.surefire.stream;
  */
 
 import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
 import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
 import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
@@ -127,17 +126,13 @@ public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEven
 
     private static final int NO_POSITION = -1;
 
-    private final Map<Segment, RunMode> runModes = RUN_MODES;
-
     public EventDecoder( @Nonnull ReadableByteChannel channel,
-                         @Nonnull ConsoleLogger logger,
                          @Nonnull ForkNodeArguments arguments )
     {
-        super( channel, arguments, EVENT_TYPES, logger );
+        super( channel, arguments, EVENT_TYPES );
     }
 
     @Override
-    @Nonnull
     public Event decode( @Nonnull Memento memento ) throws IOException
     {
         try
@@ -154,7 +149,7 @@ public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEven
                 switch ( segmentType )
                 {
                     case RUN_MODE:
-                        runMode = runModes.get( readSegment( memento ) );
+                        runMode = RUN_MODES.get( readSegment( memento ) );
                         break;
                     case STRING_ENCODING:
                         memento.setCharset( readCharset( memento ) );
@@ -170,10 +165,11 @@ public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEven
                         return toMessage( eventType, runMode, memento );
                     default:
                         memento.getLine().setPositionByteBuffer( NO_POSITION );
-                        getArguments().dumpStreamText( "Unknown enum ("
-                            + ForkedProcessEventType.class.getSimpleName()
-                            + ") "
-                            + segmentType );
+                        getArguments()
+                            .dumpStreamText( "Unknown enum ("
+                                + SegmentType.class.getSimpleName()
+                                + ") "
+                                + segmentType );
                 }
             }
         }
@@ -184,14 +180,19 @@ public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEven
                 int length = e.readTo() - e.readFrom();
                 memento.getLine().write( memento.getByteBuffer(), e.readFrom(), length );
             }
+            return null;
         }
         catch ( RuntimeException e )
         {
             getArguments().dumpStreamException( e );
+            return null;
         }
         catch ( IOException e )
         {
-            printRemainingStream( memento );
+            if ( !( e.getCause() instanceof InterruptedException ) )
+            {
+                printRemainingStream( memento );
+            }
             throw e;
         }
         finally
@@ -257,69 +258,69 @@ public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEven
         switch ( eventType )
         {
             case BOOTERCODE_BYE:
-                checkEventArguments( memento, 0 );
+                checkArguments( memento, 0 );
                 return new ControlByeEvent();
             case BOOTERCODE_STOP_ON_NEXT_TEST:
-                checkEventArguments( memento, 0 );
+                checkArguments( memento, 0 );
                 return new ControlStopOnNextTestEvent();
             case BOOTERCODE_NEXT_TEST:
-                checkEventArguments( memento, 0 );
+                checkArguments( memento, 0 );
                 return new ControlNextTestEvent();
             case BOOTERCODE_CONSOLE_ERROR:
-                checkEventArguments( memento, 3 );
+                checkArguments( memento, 3 );
                 return new ConsoleErrorEvent( toStackTraceWriter( memento.getData() ) );
             case BOOTERCODE_JVM_EXIT_ERROR:
-                checkEventArguments( memento, 3 );
+                checkArguments( memento, 3 );
                 return new JvmExitErrorEvent( toStackTraceWriter( memento.getData() ) );
             case BOOTERCODE_CONSOLE_INFO:
-                checkEventArguments( memento, 1 );
+                checkArguments( memento, 1 );
                 return new ConsoleInfoEvent( (String) memento.getData().get( 0 ) );
             case BOOTERCODE_CONSOLE_DEBUG:
-                checkEventArguments( memento, 1 );
+                checkArguments( memento, 1 );
                 return new ConsoleDebugEvent( (String) memento.getData().get( 0 ) );
             case BOOTERCODE_CONSOLE_WARNING:
-                checkEventArguments( memento, 1 );
+                checkArguments( memento, 1 );
                 return new ConsoleWarningEvent( (String) memento.getData().get( 0 ) );
             case BOOTERCODE_STDOUT:
-                checkEventArguments( runMode, memento, 1 );
+                checkArguments( runMode, memento, 1 );
                 return new StandardStreamOutEvent( runMode, (String) memento.getData().get( 0 ) );
             case BOOTERCODE_STDOUT_NEW_LINE:
-                checkEventArguments( runMode, memento, 1 );
+                checkArguments( runMode, memento, 1 );
                 return new StandardStreamOutWithNewLineEvent( runMode, (String) memento.getData().get( 0 ) );
             case BOOTERCODE_STDERR:
-                checkEventArguments( runMode, memento, 1 );
+                checkArguments( runMode, memento, 1 );
                 return new StandardStreamErrEvent( runMode, (String) memento.getData().get( 0 ) );
             case BOOTERCODE_STDERR_NEW_LINE:
-                checkEventArguments( runMode, memento, 1 );
+                checkArguments( runMode, memento, 1 );
                 return new StandardStreamErrWithNewLineEvent( runMode, (String) memento.getData().get( 0 ) );
             case BOOTERCODE_SYSPROPS:
-                checkEventArguments( runMode, memento, 2 );
+                checkArguments( runMode, memento, 2 );
                 String key = (String) memento.getData().get( 0 );
                 String value = (String) memento.getData().get( 1 );
                 return new SystemPropertyEvent( runMode, key, value );
             case BOOTERCODE_TESTSET_STARTING:
-                checkEventArguments( runMode, memento, 10 );
+                checkArguments( runMode, memento, 10 );
                 return new TestsetStartingEvent( runMode, toReportEntry( memento.getData() ) );
             case BOOTERCODE_TESTSET_COMPLETED:
-                checkEventArguments( runMode, memento, 10 );
+                checkArguments( runMode, memento, 10 );
                 return new TestsetCompletedEvent( runMode, toReportEntry( memento.getData() ) );
             case BOOTERCODE_TEST_STARTING:
-                checkEventArguments( runMode, memento, 10 );
+                checkArguments( runMode, memento, 10 );
                 return new TestStartingEvent( runMode, toReportEntry( memento.getData() ) );
             case BOOTERCODE_TEST_SUCCEEDED:
-                checkEventArguments( runMode, memento, 10 );
+                checkArguments( runMode, memento, 10 );
                 return new TestSucceededEvent( runMode, toReportEntry( memento.getData() ) );
             case BOOTERCODE_TEST_FAILED:
-                checkEventArguments( runMode, memento, 10 );
+                checkArguments( runMode, memento, 10 );
                 return new TestFailedEvent( runMode, toReportEntry( memento.getData() ) );
             case BOOTERCODE_TEST_SKIPPED:
-                checkEventArguments( runMode, memento, 10 );
+                checkArguments( runMode, memento, 10 );
                 return new TestSkippedEvent( runMode, toReportEntry( memento.getData() ) );
             case BOOTERCODE_TEST_ERROR:
-                checkEventArguments( runMode, memento, 10 );
+                checkArguments( runMode, memento, 10 );
                 return new TestErrorEvent( runMode, toReportEntry( memento.getData() ) );
             case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                checkEventArguments( runMode, memento, 10 );
+                checkArguments( runMode, memento, 10 );
                 return new TestAssumptionFailureEvent( runMode, toReportEntry( memento.getData() ) );
             default:
                 throw new IllegalArgumentException( "Missing a branch for the event type " + eventType );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
index 14e00f9..e5520f7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
@@ -25,7 +25,7 @@ import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
 import org.apache.maven.plugin.surefire.extensions.EventConsumerThread;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.ForkingRunListener;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
 import org.apache.maven.surefire.api.event.Event;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
@@ -258,11 +258,11 @@ public class ForkingRunListenerTest
         ReportEntry expected = createDefaultReportEntry();
         SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
 
-        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newBufferedChannel( printStream ) ), false )
+        new ForkingRunListener( new EventChannelEncoder( newBufferedChannel( printStream ) ), false )
                 .testStarting( expected );
 
         new ForkingRunListener(
-            new LegacyMasterProcessChannelEncoder( newBufferedChannel( anotherPrintStream ) ), false )
+            new EventChannelEncoder( newBufferedChannel( anotherPrintStream ) ), false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
@@ -446,7 +446,7 @@ public class ForkingRunListenerTest
     private RunListener createForkingRunListener()
     {
         WritableBufferedByteChannel channel = (WritableBufferedByteChannel) newChannel( printStream );
-        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( channel ), false );
+        return new ForkingRunListener( new EventChannelEncoder( channel ), false );
     }
 
     private class StandardTestRun
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
index 3d58ade..845897a 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
@@ -35,7 +35,7 @@ public class MainClass
         else
         {
             System.out.println( ":maven-surefire-event:\u0003:bye:" );
-            String byeAck = ":maven-surefire-command:bye-ack:";
+            String byeAck = ":maven-surefire-command:\u0007:bye-ack:";
             byte[] cmd = new byte[byeAck.length()];
             int len = System.in.read( cmd );
             if ( len != -1 && new String( cmd, 0, len ).equals( byeAck ) )
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 3c68778..41b3db3 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
@@ -20,9 +20,9 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  */
 
 import org.apache.maven.surefire.api.booter.Command;
-import org.apache.maven.surefire.api.booter.MasterProcessCommand;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.ForkedNodeArg;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoder;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -33,17 +33,19 @@ import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import static java.nio.channels.Channels.newChannel;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
 import static org.apache.maven.surefire.api.booter.Command.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
 import static org.apache.maven.surefire.api.booter.Shutdown.EXIT;
 import static org.apache.maven.surefire.api.booter.Shutdown.KILL;
-import static org.apache.maven.plugin.surefire.extensions.StreamFeeder.encode;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * Testing cached and immediate commands in {@link TestLessInputStream}.
@@ -144,6 +146,7 @@ public class TestLessInputStreamBuilderTest
         {
             private byte[] buffer;
             private int idx;
+            private boolean isLastBuffer;
 
             @Override
             public int read() throws IOException
@@ -154,8 +157,20 @@ public class TestLessInputStreamBuilderTest
                     Command cmd = pluginIs.readNextCommand();
                     if ( cmd != null )
                     {
-                        MasterProcessCommand cmdType = cmd.getCommandType();
-                        buffer = cmdType.hasDataType() ? encode( cmdType, cmd.getData() ) : encode( cmdType );
+                        if ( cmd.getCommandType() == SHUTDOWN )
+                        {
+                            buffer = ( ":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0004:"
+                                + cmd.toShutdownData().getParam() + ":" ).getBytes( UTF_8 );
+                        }
+                        else if ( cmd.getCommandType() == NOOP )
+                        {
+                            buffer = ":maven-surefire-command:\u0004:noop:".getBytes( UTF_8 );
+                            isLastBuffer = true;
+                        }
+                        else
+                        {
+                            fail();
+                        }
                     }
                 }
 
@@ -169,10 +184,15 @@ public class TestLessInputStreamBuilderTest
                     }
                     return b;
                 }
+
+                if ( isLastBuffer )
+                {
+                    return -1;
+                }
                 throw new IOException();
             }
         };
-        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        MasterProcessChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), new ForkedNodeArg( 1 ) );
         builder.getImmediateCommands().shutdown( KILL );
         builder.getImmediateCommands().noop();
         Command bye = decoder.decode();
@@ -181,7 +201,7 @@ public class TestLessInputStreamBuilderTest
         assertThat( bye.getData(), is( KILL.name() ) );
         Command noop = decoder.decode();
         assertThat( noop, is( notNullValue() ) );
-        assertThat( noop.getCommandType(), is( MasterProcessCommand.NOOP ) );
+        assertThat( noop.getCommandType(), is( NOOP ) );
     }
 
     @Test( expected = UnsupportedOperationException.class )
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 e0acb9f..bafcea5 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,9 +20,9 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  */
 
 import org.apache.maven.surefire.api.booter.Command;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
-import org.apache.maven.plugin.surefire.extensions.StreamFeeder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.ForkedNodeArg;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoder;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -36,7 +36,8 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
 import static java.nio.channels.Channels.newChannel;
-import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.surefire.api.booter.Command.TEST_SET_FINISHED;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -44,6 +45,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * Asserts that this stream properly reads bytes from queue.
@@ -93,8 +95,7 @@ public class TestProvidingInputStreamTest
     public void finishedTestsetShouldNotBlock()
         throws IOException
     {
-        Queue<String> commands = new ArrayDeque<>();
-        final TestProvidingInputStream is = new TestProvidingInputStream( commands );
+        final TestProvidingInputStream is = new TestProvidingInputStream( new ArrayDeque<String>() );
         is.testSetFinished();
         new Thread( new Runnable()
         {
@@ -105,16 +106,12 @@ public class TestProvidingInputStreamTest
             }
         } ).start();
 
-        Command cmd = is.readNextCommand();
-        assertThat( cmd.getData(), is( nullValue() ) );
-        String stream = new String( StreamFeeder.encode( cmd.getCommandType() ), US_ASCII );
-
-        cmd = is.readNextCommand();
-        assertThat( cmd.getData(), is( nullValue() ) );
-        stream += new String( StreamFeeder.encode( cmd.getCommandType() ), US_ASCII );
-
-        assertThat( stream,
-            is( ":maven-surefire-command:testset-finished::maven-surefire-command:testset-finished:" ) );
+        for ( int i = 0; i < 2; i++ )
+        {
+            Command cmd = is.readNextCommand();
+            assertThat( cmd.getData(), is( nullValue() ) );
+            assertThat( cmd, is( TEST_SET_FINISHED ) );
+        }
 
         boolean emptyStream = isInputStreamEmpty( is );
 
@@ -162,7 +159,21 @@ public class TestProvidingInputStreamTest
                 {
                     idx = 0;
                     Command cmd = pluginIs.readNextCommand();
-                    buffer = cmd == null ? null : StreamFeeder.encode( cmd.getCommandType() );
+                    if ( cmd != null )
+                    {
+                        if ( cmd.getCommandType() == BYE_ACK )
+                        {
+                            buffer = ":maven-surefire-command:\u0007:bye-ack:".getBytes( UTF_8 );
+                        }
+                        else if ( cmd.getCommandType() == NOOP )
+                        {
+                            buffer = ":maven-surefire-command:\u0004:noop:".getBytes( UTF_8 );
+                        }
+                        else
+                        {
+                            fail();
+                        }
+                    }
                 }
 
                 if ( buffer != null )
@@ -178,7 +189,7 @@ public class TestProvidingInputStreamTest
                 throw new IOException();
             }
         };
-        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        MasterProcessChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), new ForkedNodeArg( 1 ) );
         pluginIs.acknowledgeByeEventReceived();
         pluginIs.noop();
         Command bye = decoder.decode();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
index d1213ee..8cda588 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -302,7 +302,8 @@ public class ForkClientTest
         assertThat( logger.isDebugEnabledCalled )
             .isTrue();
 
-        String msg = "Corrupted channel by directly writing to native stream in forked JVM 0. Stream 'unordered error'.";
+        String msg =
+            "Corrupted channel by directly writing to native stream in forked JVM 0. Stream 'unordered error'.";
         assertThat( arguments.dumpStreamText )
             .hasSize( 1 )
             .contains( msg );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
index 358cd5c..37bc259 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
@@ -74,7 +74,7 @@ public class E2ETest
 
         final SurefireMasterProcessChannelProcessorFactory factory = new SurefireMasterProcessChannelProcessorFactory();
         factory.connect( connection );
-        final MasterProcessChannelEncoder encoder = factory.createEncoder();
+        final MasterProcessChannelEncoder encoder = factory.createEncoder( arguments );
 
         System.gc();
 
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
index 67661f9..4bd5c13 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -19,424 +19,32 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.extensions.EventConsumerThread.Memento;
-import org.apache.maven.plugin.surefire.extensions.EventConsumerThread.Segment;
-import org.apache.maven.surefire.api.stream.SegmentType;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
-import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
-import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
-import org.apache.maven.surefire.api.event.ConsoleInfoEvent;
-import org.apache.maven.surefire.api.event.ConsoleWarningEvent;
-import org.apache.maven.surefire.api.event.ControlByeEvent;
-import org.apache.maven.surefire.api.event.ControlNextTestEvent;
-import org.apache.maven.surefire.api.event.ControlStopOnNextTestEvent;
 import org.apache.maven.surefire.api.event.Event;
-import org.apache.maven.surefire.api.event.JvmExitErrorEvent;
-import org.apache.maven.surefire.api.event.StandardStreamErrEvent;
-import org.apache.maven.surefire.api.event.StandardStreamErrWithNewLineEvent;
-import org.apache.maven.surefire.api.event.StandardStreamOutEvent;
-import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
-import org.apache.maven.surefire.api.event.SystemPropertyEvent;
-import org.apache.maven.surefire.api.event.TestAssumptionFailureEvent;
-import org.apache.maven.surefire.api.event.TestErrorEvent;
-import org.apache.maven.surefire.api.event.TestFailedEvent;
-import org.apache.maven.surefire.api.event.TestSkippedEvent;
-import org.apache.maven.surefire.api.event.TestStartingEvent;
-import org.apache.maven.surefire.api.event.TestSucceededEvent;
-import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
-import org.apache.maven.surefire.api.event.TestsetStartingEvent;
-import org.apache.maven.surefire.api.report.RunMode;
-import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.fest.assertions.Condition;
 import org.junit.Test;
 
 import javax.annotation.Nonnull;
 import java.io.Closeable;
-import java.io.EOFException;
 import java.io.File;
-import java.math.BigInteger;
 import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
 import java.nio.channels.ReadableByteChannel;
-import java.nio.charset.CharsetDecoder;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static java.lang.Math.min;
-import static java.nio.charset.CodingErrorAction.REPLACE;
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Arrays.asList;
 import static java.util.Arrays.copyOfRange;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.apache.maven.surefire.api.stream.SegmentType.DATA_INTEGER;
-import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
-import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
-import static org.apache.maven.surefire.api.stream.SegmentType.RUN_MODE;
-import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.mapEventTypes;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.mapRunModes;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.nextSegmentType;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.toEvent;
-import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
-import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
-import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
+import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.fest.assertions.Assertions.assertThat;
-import static org.powermock.reflect.Whitebox.invokeMethod;
 
 /**
- * The performance of "get( Integer )" is 13.5 nano seconds on i5/2.6GHz:
- * <pre>
- *     {@code
- *     TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
- *     map.get( hash );
- *     }
- * </pre>
- *
- * <br> The performance of getting event type by Segment is 33.7 nano seconds:
- * <pre>
- *     {@code
- *     Map<Segment, ForkedProcessEventType> map = new HashMap<>();
- *     byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 );
- *     map.get( new Segment( array, 0, array.length ) );
- *     }
- * </pre>
- *
- * <br> The performance of decoder:
- * <pre>
- *     {@code
- *     CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
- *             .onMalformedInput( REPLACE )
- *             .onUnmappableCharacter( REPLACE );
- *     ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
- *     CharBuffer chars = CharBuffer.allocate( 100 );
- *     decoder.reset().decode( buffer, chars, true );
- *
- *     String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
- *
- *     buffer.clear();
- *     chars.clear();
- *
- *     ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
- *     }
- * </pre>
- *
- * <br> The performance of decoding 100 bytes via CharacterDecoder - 71 nano seconds:
- * <pre>
- *     {@code
- *     decoder.reset()
- *         .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
- *     chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
- *     }
- * </pre>
  *
- * <br> The performance of a pure string creation (instead of decoder) - 31.5 nano seconds:
- * <pre>
- *     {@code
- *     byte[] b = {};
- *     new String( b, UTF_8 );
- *     }
- * </pre>
- *
- * <br> The performance of CharsetDecoder with empty ByteBuffer:
- * <pre>
- *     {@code
- *     CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
- *     CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
- *     }
- * </pre>
  */
-@SuppressWarnings( "checkstyle:magicnumber" )
 public class EventConsumerThreadTest
 {
-    private static final CountdownCloseable COUNTDOWN_CLOSEABLE = new CountdownCloseable( new MockCloseable(), 0 );
-
-    private static final String PATTERN1 =
-        "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
-
-    private static final String PATTERN2 = "€ab©c";
-
-    private static final byte[] PATTERN2_BYTES =
-        new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
-
-    @Test
-    public void shouldDecodeHappyCase() throws Exception
-    {
-        CharsetDecoder decoder = UTF_8.newDecoder()
-            .onMalformedInput( REPLACE )
-            .onUnmappableCharacter( REPLACE );
-        ByteBuffer input = ByteBuffer.allocate( 1024 );
-        input.put( PATTERN2_BYTES )
-            .flip();
-        int bytesToDecode = PATTERN2_BYTES.length;
-        CharBuffer output = CharBuffer.allocate( 1024 );
-        int readBytes =
-            invokeMethod( EventConsumerThread.class, "decodeString", decoder, input, output, bytesToDecode, true, 0 );
-
-        assertThat( readBytes )
-            .isEqualTo( bytesToDecode );
-
-        assertThat( output.flip().toString() )
-            .isEqualTo( PATTERN2 );
-    }
-
-    @Test
-    public void shouldDecodeShifted() throws Exception
-    {
-        CharsetDecoder decoder = UTF_8.newDecoder()
-            .onMalformedInput( REPLACE )
-            .onUnmappableCharacter( REPLACE );
-        ByteBuffer input = ByteBuffer.allocate( 1024 );
-        input.put( PATTERN1.getBytes( UTF_8 ) )
-            .put( 90, (byte) 'A' )
-            .put( 91, (byte) 'B' )
-            .put( 92, (byte) 'C' )
-            .position( 90 );
-        CharBuffer output = CharBuffer.allocate( 1024 );
-        int readBytes =
-            invokeMethod( EventConsumerThread.class, "decodeString", decoder, input, output, 2, true, 0 );
-
-        assertThat( readBytes )
-            .isEqualTo( 2 );
-
-        assertThat( output.flip().toString() )
-            .isEqualTo( "AB" );
-    }
-
-    @Test( expected = IllegalArgumentException.class )
-    public void shouldNotDecode() throws Exception
-    {
-        CharsetDecoder decoder = UTF_8.newDecoder();
-        ByteBuffer input = ByteBuffer.allocate( 100 );
-        int bytesToDecode = 101;
-        CharBuffer output = CharBuffer.allocate( 1000 );
-        invokeMethod( EventConsumerThread.class, "decodeString", decoder, input, output, bytesToDecode, true, 0 );
-    }
-
-    @Test
-    public void shouldReadInt() throws Exception
-    {
-        Channel channel = new Channel( new byte[] {0x01, 0x02, 0x03, 0x04, ':'}, 1 );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        assertThat( thread.readInt( memento ) )
-            .isEqualTo( new BigInteger( new byte[] {0x01, 0x02, 0x03, 0x04} ).intValue() );
-    }
-
-    @Test
-    public void shouldReadInteger() throws Exception
-    {
-        Channel channel = new Channel( new byte[] {(byte) 0xff, 0x01, 0x02, 0x03, 0x04, ':'}, 1 );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        assertThat( thread.readInteger( memento ) )
-            .isEqualTo( new BigInteger( new byte[] {0x01, 0x02, 0x03, 0x04} ).intValue() );
-    }
-
-    @Test
-    public void shouldReadNullInteger() throws Exception
-    {
-        Channel channel = new Channel( new byte[] {(byte) 0x00, ':'}, 1 );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        assertThat( thread.readInteger( memento ) )
-            .isNull();
-    }
-
-    @Test( expected = EOFException.class )
-    public void shouldNotReadString() throws Exception
-    {
-        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
-        channel.read( ByteBuffer.allocate( 100 ) );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        thread.readString( memento, 10 );
-    }
-
-    @Test
-    public void shouldReadString() throws Exception
-    {
-        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        String s = thread.readString( memento, 10 );
-        assertThat( s )
-            .isEqualTo( "0123456789" );
-    }
-
-    @Test
-    public void shouldReadStringShiftedBuffer() throws Exception
-    {
-        StringBuilder s = new StringBuilder( 1100 );
-        for ( int i = 0; i < 11; i++ )
-        {
-            s.append( PATTERN1 );
-        }
-
-        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        // whatever position will be compacted to 0
-        memento.bb.position( 974 ).limit( 974 );
-        assertThat( thread.readString( memento, PATTERN1.length() + 3 ) )
-            .isEqualTo( PATTERN1 + "012" );
-    }
-
-    @Test
-    public void shouldReadStringShiftedInput() throws Exception
-    {
-        StringBuilder s = new StringBuilder( 1100 );
-        for ( int i = 0; i < 11; i++ )
-        {
-            s.append( PATTERN1 );
-        }
-
-        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
-        channel.read( ByteBuffer.allocate( 997 ) );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.limit( 0 );
-        assertThat( thread.readString( memento, PATTERN1.length() ) )
-            .isEqualTo( "789" + PATTERN1.substring( 0, 97 ) );
-    }
-
-    @Test
-    public void shouldReadMultipleStringsAndShiftedInput() throws Exception
-    {
-        StringBuilder s = new StringBuilder( 5000 );
-
-        for ( int i = 0; i < 50; i++ )
-        {
-            s.append( PATTERN1 );
-        }
-
-        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
-        channel.read( ByteBuffer.allocate( 1997 ) );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        // whatever position will be compacted to 0
-        memento.bb.position( 974 ).limit( 974 );
-
-        StringBuilder expected = new StringBuilder( "789" );
-        for ( int i = 0; i < 11; i++ )
-        {
-            expected.append( PATTERN1 );
-        }
-        expected.setLength( 1100 );
-        assertThat( thread.readString( memento, 1100 ) )
-            .isEqualTo( expected.toString() );
-    }
-
-    @Test
-    public void shouldDecode3BytesEncodedSymbol() throws Exception
-    {
-        byte[] encodedSymbol = new byte[] {(byte) -30, (byte) -126, (byte) -84};
-        int countSymbols = 1024;
-        byte[] input = new byte[encodedSymbol.length * countSymbols];
-        for ( int i = 0; i < countSymbols; i++ )
-        {
-            System.arraycopy( encodedSymbol, 0, input, encodedSymbol.length * i, encodedSymbol.length );
-        }
-
-        Channel channel = new Channel( input, 2 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        String decodedOutput = thread.readString( memento, input.length );
-
-        assertThat( decodedOutput )
-            .isEqualTo( new String( input, 0, input.length, UTF_8 ) );
-    }
-
-    @Test
-    public void shouldDecode100Bytes()
-    {
-        CharsetDecoder decoder = DEFAULT_STREAM_ENCODING.newDecoder()
-            .onMalformedInput( REPLACE )
-            .onUnmappableCharacter( REPLACE );
-        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
-        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
-        ByteBuffer buffer = ByteBuffer.wrap( PATTERN1.getBytes( UTF_8 ) );
-        CharBuffer chars = CharBuffer.allocate( 100 );
-        /* uncomment this section for a proper measurement of the exec time
-        TimeUnit.SECONDS.sleep( 2 );
-        System.gc();
-        TimeUnit.SECONDS.sleep( 5 );
-        */
-        String s = null;
-        long l1 = System.currentTimeMillis();
-        for ( int i = 0; i < 10_000_000; i++ )
-        {
-            decoder.reset()
-                .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
-            s = chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
-            buffer.clear();
-            chars.clear();
-        }
-        long l2 = System.currentTimeMillis();
-        System.out.println( "decoded 100 bytes within " + ( l2 - l1 ) + " millis (10 million cycles)" );
-        assertThat( s )
-            .isEqualTo( PATTERN1 );
-    }
-
+    @SuppressWarnings( "checkstyle:magicnumber" )
     @Test( timeout = 60_000L )
     public void performanceTest() throws Exception
     {
@@ -477,32 +85,36 @@ public class EventConsumerThreadTest
 
         event.flip();
         byte[] frame = copyOfRange( event.array(), event.arrayOffset(), event.arrayOffset() + event.remaining() );
-        ReadableByteChannel channel = new Channel( frame, 1024 )
+        ReadableByteChannel channel = new Channel( frame, 100 )
         {
             private int countRounds;
 
             @Override
-            public int read( ByteBuffer dst )
+            public synchronized int read( ByteBuffer dst )
             {
-                int length = super.read( dst );
-                if ( length == -1 && countRounds < totalCalls )
+                if ( countRounds == totalCalls )
+                {
+                    return -1;
+                }
+
+                if ( remaining() == 0 )
                 {
-                    i = 0;
-                    length = super.read( dst );
                     countRounds++;
+                    i = 0;
                 }
-                return length;
+
+                return super.read( dst );
             }
         };
 
         EventConsumerThread thread = new EventConsumerThread( "t", channel, handler,
             new CountdownCloseable( new MockCloseable(), 1 ), new MockForkNodeArguments() );
 
-        TimeUnit.SECONDS.sleep( 2 );
+        SECONDS.sleep( 2 );
         System.gc();
-        TimeUnit.SECONDS.sleep( 5 );
+        SECONDS.sleep( 5 );
 
-        System.out.println( "Staring the event thread..." );
+        System.out.println( "Starting the event thread..." );
 
         thread.start();
         thread.join();
@@ -510,596 +122,12 @@ public class EventConsumerThreadTest
         long execTime = finishedAt[0] - staredAt[0];
         System.out.println( execTime );
 
-        // 0.6 seconds while using the encoder/decoder
+        // 0.6 seconds while using the encoder/decoder for 10 million messages
         assertThat( execTime )
-            .describedAs( "The performance test should assert 1.0s of read time. "
-                + "The limit 3.6s guarantees that the read time does not exceed this limit on overloaded CPU." )
+            .describedAs( "The performance test should assert 0.75s of read time. "
+                + "The limit 3.65s guarantees that the read time does not exceed this limit on overloaded CPU." )
             .isPositive()
-            .isLessThanOrEqualTo( 3_600L );
-    }
-
-    @Test
-    public void shouldReadEventType() throws Exception
-    {
-        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
-        assertThat( eventTypes )
-            .hasSize( ForkedProcessEventType.values().length );
-
-        byte[] stream = ":maven-surefire-event:\u000E:std-out-stream:".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        ForkedProcessEventType eventType = thread.readEventType( eventTypes, memento );
-        assertThat( eventType )
-            .isEqualTo( BOOTERCODE_STDOUT );
-    }
-
-    @Test( expected = EOFException.class )
-    public void shouldEventTypeReachedEndOfStream() throws Exception
-    {
-        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
-        assertThat( eventTypes )
-            .hasSize( ForkedProcessEventType.values().length );
-
-        byte[] stream = ":maven-surefire-event:\u000E:xxx".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-        thread.readEventType( eventTypes, memento );
-    }
-
-    @Test( expected = EventConsumerThread.MalformedFrameException.class )
-    public void shouldEventTypeReachedMalformedHeader() throws Exception
-    {
-        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
-        assertThat( eventTypes )
-            .hasSize( ForkedProcessEventType.values().length );
-
-        byte[] stream = ":xxxxx-xxxxxxxx-xxxxx:\u000E:xxx".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-        thread.readEventType( eventTypes, memento );
-    }
-
-    @Test
-    public void shouldMapSegmentToEventType()
-    {
-        Map<Segment, RunMode> map = mapRunModes();
-
-        assertThat( map )
-            .hasSize( 2 );
-
-        byte[] stream = "normal-run".getBytes( US_ASCII );
-        Segment segment = new Segment( stream, 0, stream.length );
-        assertThat( map.get( segment ) )
-            .isEqualTo( NORMAL_RUN );
-
-        stream = "rerun-test-after-failure".getBytes( US_ASCII );
-        segment = new Segment( stream, 0, stream.length );
-        assertThat( map.get( segment ) )
-            .isEqualTo( RERUN_TEST_AFTER_FAILURE );
-    }
-
-    @Test
-    public void shouldReadEmptyString() throws Exception
-    {
-        byte[] stream = "\u0000\u0000\u0000\u0000::".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readString( memento ) )
-            .isEmpty();
-    }
-
-    @Test
-    public void shouldReadNullString() throws Exception
-    {
-        byte[] stream = "\u0000\u0000\u0000\u0001:\u0000:".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readString( memento ) )
-            .isNull();
-    }
-
-    @Test
-    public void shouldReadSingleCharString() throws Exception
-    {
-        byte[] stream = "\u0000\u0000\u0000\u0001:A:".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readString( memento ) )
-            .isEqualTo( "A" );
-    }
-
-    @Test
-    public void shouldReadThreeCharactersString() throws Exception
-    {
-        byte[] stream = "\u0000\u0000\u0000\u0003:ABC:".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readString( memento ) )
-            .isEqualTo( "ABC" );
-    }
-
-    @Test
-    public void shouldReadDefaultCharset() throws Exception
-    {
-        byte[] stream = "\u0005:UTF-8:".getBytes( US_ASCII );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readCharset( memento ) )
-            .isNotNull()
-            .isEqualTo( UTF_8 );
-    }
-
-    @Test
-    public void shouldReadNonDefaultCharset() throws Exception
-    {
-        byte[] stream = ( (char) 10 + ":ISO_8859_1:" ).getBytes( US_ASCII );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readCharset( memento ) )
-            .isNotNull()
-            .isEqualTo( ISO_8859_1 );
-    }
-
-    @Test
-    public void shouldSetNonDefaultCharset()
-    {
-        byte[] stream = {};
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-        Memento memento = thread.new Memento();
-
-        memento.setCharset( ISO_8859_1 );
-        assertThat( memento.getDecoder().charset() ).isEqualTo( ISO_8859_1 );
-
-        memento.setCharset( UTF_8 );
-        assertThat( memento.getDecoder().charset() ).isEqualTo( UTF_8 );
-
-        memento.reset();
-        assertThat( memento.getDecoder() ).isNotNull();
-        assertThat( memento.getDecoder().charset() ).isEqualTo( UTF_8 );
-    }
-
-    @Test( expected = EventConsumerThread.MalformedFrameException.class )
-    public void malformedCharset() throws Exception
-    {
-        byte[] stream = ( (char) 8 + ":ISO_8859:" ).getBytes( US_ASCII );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        thread.readCharset( memento );
-    }
-
-    @Test
-    public void shouldMapEventTypeToSegmentType()
-    {
-        SegmentType[] segmentTypes = nextSegmentType( BOOTERCODE_BYE );
-        assertThat( segmentTypes )
-            .hasSize( 1 )
-            .containsOnly( END_OF_FRAME );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_STOP_ON_NEXT_TEST );
-        assertThat( segmentTypes )
-            .hasSize( 1 )
-            .containsOnly( END_OF_FRAME );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_NEXT_TEST );
-        assertThat( segmentTypes )
-            .hasSize( 1 )
-            .containsOnly( END_OF_FRAME );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_CONSOLE_ERROR );
-        assertThat( segmentTypes )
-            .hasSize( 5 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR );
-        assertThat( segmentTypes )
-            .hasSize( 5 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_CONSOLE_INFO );
-        assertThat( segmentTypes )
-            .hasSize( 3 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG );
-        assertThat( segmentTypes )
-            .hasSize( 3 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING );
-        assertThat( segmentTypes )
-            .hasSize( 3 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_STDOUT );
-        assertThat( segmentTypes )
-            .hasSize( 4 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE );
-        assertThat( segmentTypes )
-            .hasSize( 4 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDERR );
-        assertThat( segmentTypes )
-            .hasSize( 4 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE );
-        assertThat( segmentTypes )
-            .hasSize( 4 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_SYSPROPS );
-        assertThat( segmentTypes )
-            .hasSize( 5 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_TESTSET_STARTING );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_STARTING );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_TEST_SUCCEEDED );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_TEST_FAILED );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_ERROR );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-    }
-
-    @Test
-    public void shouldCreateEvent()
-    {
-        Event event = toEvent( BOOTERCODE_BYE, NORMAL_RUN, emptyList() );
-        assertThat( event )
-            .isInstanceOf( ControlByeEvent.class );
-
-        event = toEvent( BOOTERCODE_STOP_ON_NEXT_TEST, NORMAL_RUN, emptyList() );
-        assertThat( event )
-            .isInstanceOf( ControlStopOnNextTestEvent.class );
-
-        event = toEvent( BOOTERCODE_NEXT_TEST, NORMAL_RUN, emptyList() );
-        assertThat( event )
-            .isInstanceOf( ControlNextTestEvent.class );
-
-        List data = asList( "1", "2", "3" );
-        event = toEvent( BOOTERCODE_CONSOLE_ERROR, NORMAL_RUN, data );
-        assertThat( event )
-            .isInstanceOf( ConsoleErrorEvent.class );
-        ConsoleErrorEvent consoleErrorEvent = (ConsoleErrorEvent) event;
-        assertThat( consoleErrorEvent.getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "1" );
-        assertThat( consoleErrorEvent.getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "2" );
-        assertThat( consoleErrorEvent.getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "3" );
-
-        data = asList( null, null, null );
-        event = toEvent( BOOTERCODE_CONSOLE_ERROR, NORMAL_RUN, data );
-        assertThat( event )
-            .isInstanceOf( ConsoleErrorEvent.class );
-        consoleErrorEvent = (ConsoleErrorEvent) event;
-        assertThat( consoleErrorEvent.getStackTraceWriter() )
-            .isNull();
-
-        data = asList( "1", "2", "3" );
-        event = toEvent( BOOTERCODE_JVM_EXIT_ERROR, NORMAL_RUN, data );
-        assertThat( event )
-            .isInstanceOf( JvmExitErrorEvent.class );
-        JvmExitErrorEvent jvmExitErrorEvent = (JvmExitErrorEvent) event;
-        assertThat( jvmExitErrorEvent.getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "1" );
-        assertThat( jvmExitErrorEvent.getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "2" );
-        assertThat( jvmExitErrorEvent.getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "3" );
-
-        data = asList( null, null, null );
-        event = toEvent( BOOTERCODE_JVM_EXIT_ERROR, NORMAL_RUN, data );
-        assertThat( event )
-            .isInstanceOf( JvmExitErrorEvent.class );
-        jvmExitErrorEvent = (JvmExitErrorEvent) event;
-        assertThat( jvmExitErrorEvent.getStackTraceWriter() )
-            .isNull();
-
-        data = singletonList( "m" );
-        event = toEvent( BOOTERCODE_CONSOLE_INFO, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( ConsoleInfoEvent.class );
-        assertThat( ( (ConsoleInfoEvent) event ).getMessage() ).isEqualTo( "m" );
-
-        data = singletonList( "" );
-        event = toEvent( BOOTERCODE_CONSOLE_WARNING, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( ConsoleWarningEvent.class );
-        assertThat( ( (ConsoleWarningEvent) event ).getMessage() ).isEmpty();
-
-        data = singletonList( null );
-        event = toEvent( BOOTERCODE_CONSOLE_DEBUG, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( ConsoleDebugEvent.class );
-        assertThat( ( (ConsoleDebugEvent) event ).getMessage() ).isNull();
-
-        data = singletonList( "m" );
-        event = toEvent( BOOTERCODE_STDOUT, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( StandardStreamOutEvent.class );
-        assertThat( ( (StandardStreamOutEvent) event ).getMessage() ).isEqualTo( "m" );
-        assertThat( ( (StandardStreamOutEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-
-        data = singletonList( null );
-        event = toEvent( BOOTERCODE_STDOUT_NEW_LINE, RERUN_TEST_AFTER_FAILURE, data );
-        assertThat( event ).isInstanceOf( StandardStreamOutWithNewLineEvent.class );
-        assertThat( ( (StandardStreamOutWithNewLineEvent) event ).getMessage() ).isNull();
-        assertThat( ( (StandardStreamOutWithNewLineEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
-
-        data = singletonList( null );
-        event = toEvent( BOOTERCODE_STDERR, RERUN_TEST_AFTER_FAILURE, data );
-        assertThat( event ).isInstanceOf( StandardStreamErrEvent.class );
-        assertThat( ( (StandardStreamErrEvent) event ).getMessage() ).isNull();
-        assertThat( ( (StandardStreamErrEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
-
-        data = singletonList( "abc" );
-        event = toEvent( BOOTERCODE_STDERR_NEW_LINE, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( StandardStreamErrWithNewLineEvent.class );
-        assertThat( ( (StandardStreamErrWithNewLineEvent) event ).getMessage() ).isEqualTo( "abc" );
-        assertThat( ( (StandardStreamErrWithNewLineEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-
-        data = asList( "key", "value" );
-        event = toEvent( BOOTERCODE_SYSPROPS, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( SystemPropertyEvent.class );
-        assertThat( ( (SystemPropertyEvent) event ).getKey() ).isEqualTo( "key" );
-        assertThat( ( (SystemPropertyEvent) event ).getValue() ).isEqualTo( "value" );
-        assertThat( ( (SystemPropertyEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-
-        data = asList( "source", "sourceText", "name", "nameText", "group", "message", 5,
-            "traceMessage", "smartTrimmedStackTrace", "stackTrace" );
-        event = toEvent( BOOTERCODE_TESTSET_STARTING, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestsetStartingEvent.class );
-        assertThat( ( (TestsetStartingEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestsetStartingEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "traceMessage" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "smartTrimmedStackTrace" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", "sourceText", "name", "nameText", "group", null, 5,
-            "traceMessage", "smartTrimmedStackTrace", "stackTrace" );
-        event = toEvent( BOOTERCODE_TESTSET_COMPLETED, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestsetCompletedEvent.class );
-        assertThat( ( (TestsetCompletedEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestsetCompletedEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "traceMessage" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "smartTrimmedStackTrace" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", "sourceText", "name", "nameText", "group", "message", 5,
-            null, "smartTrimmedStackTrace", "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_STARTING, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestStartingEvent.class );
-        assertThat( ( (TestStartingEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestStartingEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isNull();
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "smartTrimmedStackTrace" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", "sourceText", "name", "nameText", "group", "message", 5, null, null, null );
-        event = toEvent( BOOTERCODE_TEST_SUCCEEDED, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestSucceededEvent.class );
-        assertThat( ( (TestSucceededEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getStackTraceWriter() ).isNull();
-
-        data = asList( "source", null, "name", null, "group", null, 5,
-            "traceMessage", "smartTrimmedStackTrace", "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_FAILED, RERUN_TEST_AFTER_FAILURE, data );
-        assertThat( event ).isInstanceOf( TestFailedEvent.class );
-        assertThat( ( (TestFailedEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getSourceText() ).isNull();
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getNameText() ).isNull();
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestFailedEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "traceMessage" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "smartTrimmedStackTrace" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", null, "name", null, null, null, 5, null, null, "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_SKIPPED, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestSkippedEvent.class );
-        assertThat( ( (TestSkippedEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getSourceText() ).isNull();
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getNameText() ).isNull();
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getGroup() ).isNull();
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestSkippedEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isNull();
-        assertThat( ( (TestSkippedEvent) event )
-            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isNull();
-        assertThat( ( (TestSkippedEvent) event )
-            .getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", null, "name", "nameText", null, null, 0, null, null, "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_ERROR, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestErrorEvent.class );
-        assertThat( ( (TestErrorEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getSourceText() ).isNull();
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getGroup() ).isNull();
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getElapsed() ).isZero();
-        assertThat( ( (TestErrorEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isNull();
-        assertThat( ( (TestErrorEvent) event )
-            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isNull();
-        assertThat( ( (TestErrorEvent) event )
-            .getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", null, "name", null, "group", null, 5, null, null, "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_ASSUMPTIONFAILURE, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestAssumptionFailureEvent.class );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getSourceText() ).isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getNameText() ).isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestAssumptionFailureEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event )
-            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event )
-            .getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
+            .isLessThanOrEqualTo( 3_650L );
     }
 
     private static class Channel implements ReadableByteChannel
@@ -1117,13 +145,13 @@ public class EventConsumerThreadTest
         @Override
         public int read( ByteBuffer dst )
         {
-            if ( i == bytes.length )
+            if ( remaining() == 0 )
             {
                 return -1;
             }
             else if ( dst.hasRemaining() )
             {
-                int length = min( min( chunkSize, bytes.length - i ), dst.remaining() ) ;
+                int length = min( min( chunkSize, remaining() ), dst.remaining() ) ;
                 dst.put( bytes, i, length );
                 i += length;
                 return length;
@@ -1134,6 +162,11 @@ public class EventConsumerThreadTest
             }
         }
 
+        protected final int remaining()
+        {
+            return bytes.length - i;
+        }
+
         @Override
         public boolean isOpen()
         {
@@ -1154,14 +187,6 @@ public class EventConsumerThreadTest
         }
     }
 
-    private static class MockEventHandler<T> implements EventHandler<T>
-    {
-        @Override
-        public void handleEvent( @Nonnull T event )
-        {
-        }
-    }
-
     private static class MockForkNodeArguments implements ForkNodeArguments
     {
         @Nonnull
@@ -1203,35 +228,4 @@ public class EventConsumerThreadTest
             return null;
         }
     }
-
-    private static class InOrder extends Condition<Object[]>
-    {
-        private final SegmentType[] expected;
-
-        InOrder( SegmentType... expected )
-        {
-            this.expected = expected;
-        }
-
-        @Override
-        public boolean matches( Object[] values )
-        {
-            if ( values == null && expected == null )
-            {
-                return true;
-            }
-            else if ( values != null && expected != null && values.length == expected.length )
-            {
-                boolean matches = true;
-                for ( int i = 0; i < values.length; i++ )
-                {
-
-                    assertThat( values[i] ).isInstanceOf( SegmentType.class );
-                    matches &= values[i] == expected[i];
-                }
-                return matches;
-            }
-            return false;
-        }
-    }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
index 1b3ab59..ee0007c 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
@@ -31,15 +31,15 @@ import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessStringE
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
 import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.report.ReportEntry;
 import org.apache.maven.surefire.api.report.RunMode;
 import org.apache.maven.surefire.api.report.SafeThrowable;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
 import org.apache.maven.surefire.api.util.internal.ObjectUtils;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.junit.Rule;
 import org.junit.Test;
@@ -106,9 +106,9 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             Map<String, String> props = ObjectUtils.systemProps();
-            encoder.sendSystemProperties( props );
+            encoder.systemProperties( props );
             wChannel.close();
 
             ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
@@ -138,154 +138,10 @@ public class ForkedProcessEventNotifierTest
         }
 
         @Test
-        public void shouldRecognizeEmptyStream4ReportEntry()
-        {
-            ReportEntry reportEntry = EventConsumerThread.newReportEntry( "", "", "", "", "", "", null, "", "", "" );
-            assertThat( reportEntry ).isNotNull();
-            assertThat( reportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() ).isEmpty();
-            assertThat( reportEntry.getStackTraceWriter().writeTraceToString() ).isEmpty();
-            assertThat( reportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEmpty();
-            assertThat( reportEntry.getSourceName() ).isEmpty();
-            assertThat( reportEntry.getSourceText() ).isEmpty();
-            assertThat( reportEntry.getName() ).isEmpty();
-            assertThat( reportEntry.getNameText() ).isEmpty();
-            assertThat( reportEntry.getGroup() ).isEmpty();
-            assertThat( reportEntry.getNameWithGroup() ).isEmpty();
-            assertThat( reportEntry.getMessage() ).isEmpty();
-            assertThat( reportEntry.getElapsed() ).isNull();
-        }
-
-        @Test
-        @SuppressWarnings( "checkstyle:magicnumber" )
-        public void testCreatingReportEntry()
-        {
-            final String exceptionMessage = "msg";
-            final String smartStackTrace = "MyTest:86 >> Error";
-            final String stackTrace = "Exception: msg\ntrace line 1\ntrace line 2";
-            final String trimmedStackTrace = "trace line 1\ntrace line 2";
-
-            SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
-            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
-            when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
-            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
-            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
-            when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
-
-            ReportEntry reportEntry = mock( ReportEntry.class );
-            when( reportEntry.getElapsed() ).thenReturn( 102 );
-            when( reportEntry.getGroup() ).thenReturn( "this group" );
-            when( reportEntry.getMessage() ).thenReturn( "skipped test" );
-            when( reportEntry.getName() ).thenReturn( "my test" );
-            when( reportEntry.getNameText() ).thenReturn( "my display name" );
-            when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
-            when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
-            when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
-            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
-
-            ReportEntry decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), null, null, null, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
-
-            decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), null, exceptionMessage, smartStackTrace, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isNull();
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
-                .isNull();
-
-            decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
-                .isNull();
-
-            decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, stackTrace );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                    .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                    .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
-
-            decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, trimmedStackTrace );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                    .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                    .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( trimmedStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() )
-                    .isEqualTo( trimmedStackTrace );
-        }
-
-        @Test
         public void shouldSendByeEvent() throws Exception
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.bye();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -325,8 +181,7 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendStopOnNextTestEvent() throws Exception
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.stopOnNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -390,8 +245,7 @@ public class ForkedProcessEventNotifierTest
             when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.testFailed( reportEntry, true );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -421,8 +275,7 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendNextTestEvent() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.acquireNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -456,8 +309,7 @@ public class ForkedProcessEventNotifierTest
         public void testConsole() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleInfoLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -487,8 +339,7 @@ public class ForkedProcessEventNotifierTest
         public void testError() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleErrorLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -518,8 +369,7 @@ public class ForkedProcessEventNotifierTest
         public void testErrorWithException() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             Throwable throwable = new Throwable( "msg" );
             encoder.consoleErrorLog( throwable );
 
@@ -531,7 +381,7 @@ public class ForkedProcessEventNotifierTest
             notifier.setConsoleErrorListener( listener );
 
             EH eventHandler = new EH();
-            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
             ConsoleLoggerMock logger = new ConsoleLoggerMock( false, false, false, false );
             ForkNodeArgumentsMock arguments = new ForkNodeArgumentsMock( logger, new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
@@ -552,8 +402,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "1", "2", "3" );
             encoder.consoleErrorLog( stackTraceWriter, false );
 
@@ -585,8 +434,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleDebugLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -623,8 +471,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleWarningLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -655,7 +502,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( "msg", false );
             wChannel.close();
 
@@ -688,7 +535,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( "", false );
             wChannel.close();
 
@@ -721,7 +568,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( null, false );
             wChannel.close();
 
@@ -754,7 +601,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( "", true );
             wChannel.close();
 
@@ -787,7 +634,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( null, true );
             wChannel.close();
 
@@ -820,7 +667,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdErr( "msg", false );
             wChannel.close();
 
@@ -853,8 +700,8 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
-            encoder.sendSystemProperties( ObjectUtils.systemProps() );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
+            encoder.systemProperties( ObjectUtils.systemProps() );
             wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -934,8 +781,7 @@ public class ForkedProcessEventNotifierTest
         public void shouldHandleExit() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
             StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
             when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
@@ -1050,10 +896,9 @@ public class ForkedProcessEventNotifierTest
 
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
-            LegacyMasterProcessChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
+            EventChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
                     .invoke( encoder, reportEntry, trim );
 
             ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
@@ -1323,7 +1168,7 @@ public class ForkedProcessEventNotifierTest
         @Override
         public File dumpStreamException( @Nonnull Throwable t )
         {
-            throw new UnsupportedOperationException();
+            return dumpStreamTextFile;
         }
 
         @Override
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java
index 06cef43..adbb2a7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java
@@ -36,7 +36,6 @@ import java.util.Iterator;
 
 import static java.util.Arrays.asList;
 import static org.apache.maven.surefire.api.booter.Command.TEST_SET_FINISHED;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
@@ -92,8 +91,8 @@ public class StreamFeederTest
                 {
                     ByteBuffer bb = invocation.getArgument( 0 );
                     bb.flip();
-                    out.write( bb.array() );
-                    return 0;
+                    out.write( bb.array(), 0, bb.limit() );
+                    return bb.limit();
                 }
             } );
 
@@ -104,8 +103,28 @@ public class StreamFeederTest
         streamFeeder.join();
         String commands = out.toString();
 
+        String expected = new StringBuilder()
+            .append( ":maven-surefire-command:" )
+            .append( (char) 13 )
+            .append( ":run-testclass:" )
+            .append( (char) 10 )
+            .append( ":normal-run:" )
+            .append( (char) 5 )
+            .append( ":UTF-8:" )
+            .append( (char) 0 )
+            .append( (char) 0 )
+            .append( (char) 0 )
+            .append( (char) 9 )
+            .append( ":" )
+            .append( "pkg.ATest" )
+            .append( ":" )
+            .append( ":maven-surefire-command:" )
+            .append( (char) 16 )
+            .append( ":testset-finished:" )
+            .toString();
+
         assertThat( commands )
-            .isEqualTo( ":maven-surefire-command:run-testclass:pkg.ATest::maven-surefire-command:testset-finished:" );
+            .isEqualTo( expected );
 
         verify( channel, times( 1 ) )
             .close();
@@ -147,16 +166,4 @@ public class StreamFeederTest
 
         verifyZeroInteractions( logger );
     }
-
-    @Test( expected = IllegalArgumentException.class )
-    public void shouldFailWithoutData()
-    {
-        StreamFeeder.encode( RUN_CLASS );
-    }
-
-    @Test( expected = IllegalArgumentException.class )
-    public void shouldFailWithData()
-    {
-        StreamFeeder.encode( NOOP, "" );
-    }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index 7692a04..dd1f08d 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
@@ -62,6 +62,7 @@ import org.apache.maven.surefire.extensions.StatelessTestsetInfoReporterTest;
 import org.apache.maven.surefire.report.FileReporterTest;
 import org.apache.maven.surefire.report.RunStatisticsTest;
 import org.apache.maven.surefire.spi.SPITest;
+import org.apache.maven.surefire.stream.EventDecoderTest;
 import org.apache.maven.surefire.util.RelocatorTest;
 
 /**
@@ -115,6 +116,7 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( StreamFeederTest.class ) );
         suite.addTest( new JUnit4TestAdapter( E2ETest.class ) );
         suite.addTest( new JUnit4TestAdapter( ThreadedStreamConsumerTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( EventDecoderTest.class ) );
         suite.addTest( new JUnit4TestAdapter( EventConsumerThreadTest.class ) );
         return suite;
     }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
index 016ef60..34d3325 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
@@ -204,7 +204,7 @@ public class ForkChannelTest
                 byte[] data = new byte[128];
                 int readLength = socket.getInputStream().read( data );
                 String token = new String( data, 0, readLength, US_ASCII );
-                assertThat( token ).isEqualTo( ":maven-surefire-command:noop:" );
+                assertThat( token ).isEqualTo( ":maven-surefire-command:\u0004:noop:" );
                 socket.getOutputStream().write( ":maven-surefire-event:\u0003:bye:".getBytes( US_ASCII ) );
             }
             catch ( IOException e )
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
new file mode 100644
index 0000000..31fb51e
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
@@ -0,0 +1,773 @@
+package org.apache.maven.surefire.stream;
+
+/*
+ * 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.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
+import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
+import org.apache.maven.surefire.api.event.ConsoleInfoEvent;
+import org.apache.maven.surefire.api.event.ConsoleWarningEvent;
+import org.apache.maven.surefire.api.event.ControlByeEvent;
+import org.apache.maven.surefire.api.event.ControlNextTestEvent;
+import org.apache.maven.surefire.api.event.ControlStopOnNextTestEvent;
+import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.event.JvmExitErrorEvent;
+import org.apache.maven.surefire.api.event.StandardStreamErrEvent;
+import org.apache.maven.surefire.api.event.StandardStreamErrWithNewLineEvent;
+import org.apache.maven.surefire.api.event.StandardStreamOutEvent;
+import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
+import org.apache.maven.surefire.api.event.SystemPropertyEvent;
+import org.apache.maven.surefire.api.event.TestAssumptionFailureEvent;
+import org.apache.maven.surefire.api.event.TestErrorEvent;
+import org.apache.maven.surefire.api.event.TestFailedEvent;
+import org.apache.maven.surefire.api.event.TestSkippedEvent;
+import org.apache.maven.surefire.api.event.TestStartingEvent;
+import org.apache.maven.surefire.api.event.TestSucceededEvent;
+import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
+import org.apache.maven.surefire.api.event.TestsetStartingEvent;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.report.ReportEntry;
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.report.SafeThrowable;
+import org.apache.maven.surefire.api.report.StackTraceWriter;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
+import org.apache.maven.surefire.api.stream.SegmentType;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Map;
+
+import static java.lang.Math.min;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
+import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
+import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
+import static org.apache.maven.surefire.api.stream.SegmentType.DATA_INTEGER;
+import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
+import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
+import static org.apache.maven.surefire.api.stream.SegmentType.RUN_MODE;
+import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.when;
+import static org.powermock.reflect.Whitebox.invokeMethod;
+
+/**
+ *
+ */
+public class EventDecoderTest
+{
+    @Test
+    public void shouldMapEventTypes() throws Exception
+    {
+        Map<Segment, ForkedProcessEventType> eventTypes = invokeMethod( EventDecoder.class, "segmentsToEvents" );
+        assertThat( eventTypes )
+            .hasSize( ForkedProcessEventType.values().length );
+    }
+
+    @Test
+    public void shouldMapRunModes() throws Exception
+    {
+        Map<Segment, RunMode> map = invokeMethod( EventDecoder.class, "segmentsToRunModes" );
+
+        assertThat( map )
+            .hasSize( 2 );
+
+        byte[] stream = "normal-run".getBytes( US_ASCII );
+        Segment segment = new Segment( stream, 0, stream.length );
+        assertThat( map.get( segment ) )
+            .isEqualTo( NORMAL_RUN );
+
+        stream = "rerun-test-after-failure".getBytes( US_ASCII );
+        segment = new Segment( stream, 0, stream.length );
+        assertThat( map.get( segment ) )
+            .isEqualTo( RERUN_TEST_AFTER_FAILURE );
+    }
+
+    @Test
+    public void shouldMapEventTypeToSegmentType()
+    {
+        byte[] stream = {};
+        Channel channel = new Channel( stream, 1 );
+        EventDecoder decoder = new EventDecoder( channel, new MockForkNodeArguments() );
+
+        SegmentType[] segmentTypes = decoder.nextSegmentType( BOOTERCODE_BYE );
+        assertThat( segmentTypes )
+            .hasSize( 1 )
+            .containsOnly( END_OF_FRAME );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_STOP_ON_NEXT_TEST );
+        assertThat( segmentTypes )
+            .hasSize( 1 )
+            .containsOnly( END_OF_FRAME );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_NEXT_TEST );
+        assertThat( segmentTypes )
+            .hasSize( 1 )
+            .containsOnly( END_OF_FRAME );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_CONSOLE_ERROR );
+        assertThat( segmentTypes )
+            .hasSize( 5 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR );
+        assertThat( segmentTypes )
+            .hasSize( 5 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_CONSOLE_INFO );
+        assertThat( segmentTypes )
+            .hasSize( 3 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG );
+        assertThat( segmentTypes )
+            .hasSize( 3 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING );
+        assertThat( segmentTypes )
+            .hasSize( 3 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_STDOUT );
+        assertThat( segmentTypes )
+            .hasSize( 4 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE );
+        assertThat( segmentTypes )
+            .hasSize( 4 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDERR );
+        assertThat( segmentTypes )
+            .hasSize( 4 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE );
+        assertThat( segmentTypes )
+            .hasSize( 4 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_SYSPROPS );
+        assertThat( segmentTypes )
+            .hasSize( 5 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_TESTSET_STARTING );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] {
+                RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_STARTING );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_TEST_SUCCEEDED );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_TEST_FAILED );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_ERROR );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+    }
+
+    @Test
+    public void shouldCreateEvent() throws Exception
+    {
+        byte[] stream = {};
+        Channel channel = new Channel( stream, 1 );
+        EventDecoder decoder = new EventDecoder( channel, new MockForkNodeArguments() );
+
+        Event event = decoder.toMessage( BOOTERCODE_BYE, NORMAL_RUN, decoder.new Memento() );
+        assertThat( event )
+            .isInstanceOf( ControlByeEvent.class );
+
+        event = decoder.toMessage( BOOTERCODE_STOP_ON_NEXT_TEST, NORMAL_RUN, decoder.new Memento() );
+        assertThat( event )
+            .isInstanceOf( ControlStopOnNextTestEvent.class );
+
+        event = decoder.toMessage( BOOTERCODE_NEXT_TEST, NORMAL_RUN, decoder.new Memento() );
+        assertThat( event )
+            .isInstanceOf( ControlNextTestEvent.class );
+
+        Memento memento = decoder.new Memento();
+        memento.getData().addAll( asList( "1", "2", "3" ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_ERROR, NORMAL_RUN, memento );
+        assertThat( event )
+            .isInstanceOf( ConsoleErrorEvent.class );
+        ConsoleErrorEvent consoleErrorEvent = (ConsoleErrorEvent) event;
+        assertThat( consoleErrorEvent.getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "1" );
+        assertThat( consoleErrorEvent.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "2" );
+        assertThat( consoleErrorEvent.getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "3" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( null, null, null ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_ERROR, NORMAL_RUN, memento );
+        assertThat( event )
+            .isInstanceOf( ConsoleErrorEvent.class );
+        consoleErrorEvent = (ConsoleErrorEvent) event;
+        assertThat( consoleErrorEvent.getStackTraceWriter() )
+            .isNull();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "1", "2", "3" ) );
+        event = decoder.toMessage( BOOTERCODE_JVM_EXIT_ERROR, NORMAL_RUN, memento );
+        assertThat( event )
+            .isInstanceOf( JvmExitErrorEvent.class );
+        JvmExitErrorEvent jvmExitErrorEvent = (JvmExitErrorEvent) event;
+        assertThat( jvmExitErrorEvent.getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "1" );
+        assertThat( jvmExitErrorEvent.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "2" );
+        assertThat( jvmExitErrorEvent.getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "3" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( null, null, null ) );
+        event = decoder.toMessage( BOOTERCODE_JVM_EXIT_ERROR, NORMAL_RUN, memento );
+        assertThat( event )
+            .isInstanceOf( JvmExitErrorEvent.class );
+        jvmExitErrorEvent = (JvmExitErrorEvent) event;
+        assertThat( jvmExitErrorEvent.getStackTraceWriter() )
+            .isNull();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( "m" ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_INFO, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( ConsoleInfoEvent.class );
+        assertThat( ( (ConsoleInfoEvent) event ).getMessage() ).isEqualTo( "m" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( "" ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_WARNING, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( ConsoleWarningEvent.class );
+        assertThat( ( (ConsoleWarningEvent) event ).getMessage() ).isEmpty();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( null ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_DEBUG, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( ConsoleDebugEvent.class );
+        assertThat( ( (ConsoleDebugEvent) event ).getMessage() ).isNull();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( "m" ) );
+        event = decoder.toMessage( BOOTERCODE_STDOUT, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( StandardStreamOutEvent.class );
+        assertThat( ( (StandardStreamOutEvent) event ).getMessage() ).isEqualTo( "m" );
+        assertThat( ( (StandardStreamOutEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( null ) );
+        event = decoder.toMessage( BOOTERCODE_STDOUT_NEW_LINE, RERUN_TEST_AFTER_FAILURE, memento );
+        assertThat( event ).isInstanceOf( StandardStreamOutWithNewLineEvent.class );
+        assertThat( ( (StandardStreamOutWithNewLineEvent) event ).getMessage() ).isNull();
+        assertThat( ( (StandardStreamOutWithNewLineEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( null ) );
+        event = decoder.toMessage( BOOTERCODE_STDERR, RERUN_TEST_AFTER_FAILURE, memento );
+        assertThat( event ).isInstanceOf( StandardStreamErrEvent.class );
+        assertThat( ( (StandardStreamErrEvent) event ).getMessage() ).isNull();
+        assertThat( ( (StandardStreamErrEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( "abc" ) );
+        event = decoder.toMessage( BOOTERCODE_STDERR_NEW_LINE, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( StandardStreamErrWithNewLineEvent.class );
+        assertThat( ( (StandardStreamErrWithNewLineEvent) event ).getMessage() ).isEqualTo( "abc" );
+        assertThat( ( (StandardStreamErrWithNewLineEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "key", "value" ) );
+        event = decoder.toMessage( BOOTERCODE_SYSPROPS, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( SystemPropertyEvent.class );
+        assertThat( ( (SystemPropertyEvent) event ).getKey() ).isEqualTo( "key" );
+        assertThat( ( (SystemPropertyEvent) event ).getValue() ).isEqualTo( "value" );
+        assertThat( ( (SystemPropertyEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", "sourceText", "name", "nameText", "group", "message", 5,
+            "traceMessage", "smartTrimmedStackTrace", "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TESTSET_STARTING, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestsetStartingEvent.class );
+        assertThat( ( (TestsetStartingEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestsetStartingEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "traceMessage" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "smartTrimmedStackTrace" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", "sourceText", "name", "nameText", "group", null, 5,
+            "traceMessage", "smartTrimmedStackTrace", "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TESTSET_COMPLETED, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestsetCompletedEvent.class );
+        assertThat( ( (TestsetCompletedEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestsetCompletedEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "traceMessage" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "smartTrimmedStackTrace" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", "sourceText", "name", "nameText", "group", "message", 5,
+            null, "smartTrimmedStackTrace", "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_STARTING, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestStartingEvent.class );
+        assertThat( ( (TestStartingEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestStartingEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isNull();
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "smartTrimmedStackTrace" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData()
+            .addAll( asList( "source", "sourceText", "name", "nameText", "group", "message", 5, null, null, null ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_SUCCEEDED, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestSucceededEvent.class );
+        assertThat( ( (TestSucceededEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getStackTraceWriter() ).isNull();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", null, "name", null, "group", null, 5,
+            "traceMessage", "smartTrimmedStackTrace", "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_FAILED, RERUN_TEST_AFTER_FAILURE, memento );
+        assertThat( event ).isInstanceOf( TestFailedEvent.class );
+        assertThat( ( (TestFailedEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getSourceText() ).isNull();
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getNameText() ).isNull();
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestFailedEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "traceMessage" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "smartTrimmedStackTrace" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", null, "name", null, null, null, 5, null, null, "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_SKIPPED, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestSkippedEvent.class );
+        assertThat( ( (TestSkippedEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getSourceText() ).isNull();
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getNameText() ).isNull();
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getGroup() ).isNull();
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestSkippedEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isNull();
+        assertThat( ( (TestSkippedEvent) event )
+            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isNull();
+        assertThat( ( (TestSkippedEvent) event )
+            .getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData()
+            .addAll( asList( "source", null, "name", "nameText", null, null, 0, null, null, "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_ERROR, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestErrorEvent.class );
+        assertThat( ( (TestErrorEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getSourceText() ).isNull();
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getGroup() ).isNull();
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getElapsed() ).isZero();
+        assertThat( ( (TestErrorEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isNull();
+        assertThat( ( (TestErrorEvent) event )
+            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isNull();
+        assertThat( ( (TestErrorEvent) event )
+            .getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", null, "name", null, "group", null, 5, null, null, "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_ASSUMPTIONFAILURE, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestAssumptionFailureEvent.class );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getSourceText() ).isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getNameText() ).isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestAssumptionFailureEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event )
+            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event )
+            .getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+    }
+
+    @Test
+    public void shouldRecognizeEmptyStream4ReportEntry()
+    {
+        ReportEntry reportEntry = EventDecoder.newReportEntry( "", "", "", "", "", "", null, "", "", "" );
+        assertThat( reportEntry ).isNotNull();
+        assertThat( reportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() ).isEmpty();
+        assertThat( reportEntry.getStackTraceWriter().writeTraceToString() ).isEmpty();
+        assertThat( reportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEmpty();
+        assertThat( reportEntry.getSourceName() ).isEmpty();
+        assertThat( reportEntry.getSourceText() ).isEmpty();
+        assertThat( reportEntry.getName() ).isEmpty();
+        assertThat( reportEntry.getNameText() ).isEmpty();
+        assertThat( reportEntry.getGroup() ).isEmpty();
+        assertThat( reportEntry.getNameWithGroup() ).isEmpty();
+        assertThat( reportEntry.getMessage() ).isEmpty();
+        assertThat( reportEntry.getElapsed() ).isNull();
+    }
+
+    @Test
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    public void testCreatingReportEntry()
+    {
+        final String exceptionMessage = "msg";
+        final String smartStackTrace = "MyTest:86 >> Error";
+        final String stackTrace = "Exception: msg\ntrace line 1\ntrace line 2";
+        final String trimmedStackTrace = "trace line 1\ntrace line 2";
+
+        SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
+        StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+        when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+        when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+        when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
+        when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
+
+        ReportEntry reportEntry = mock( ReportEntry.class );
+        when( reportEntry.getElapsed() ).thenReturn( 102 );
+        when( reportEntry.getGroup() ).thenReturn( "this group" );
+        when( reportEntry.getMessage() ).thenReturn( "skipped test" );
+        when( reportEntry.getName() ).thenReturn( "my test" );
+        when( reportEntry.getNameText() ).thenReturn( "my display name" );
+        when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
+        when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+        when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
+        when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
+
+        ReportEntry decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), null, null, null, null );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
+
+        decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), null, exceptionMessage, smartStackTrace, null );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getElapsed() ).isNull();
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+            .isEqualTo( exceptionMessage );
+        assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( smartStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
+            .isNull();
+
+        decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, null );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+            .isEqualTo( exceptionMessage );
+        assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( smartStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
+            .isNull();
+
+        decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, stackTrace );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+            .isEqualTo( exceptionMessage );
+        assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( smartStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
+
+        decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, trimmedStackTrace );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+            .isEqualTo( exceptionMessage );
+        assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( smartStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( trimmedStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() )
+            .isEqualTo( trimmedStackTrace );
+    }
+
+    private static class Channel implements ReadableByteChannel
+    {
+        private final byte[] bytes;
+        private final int chunkSize;
+        protected int i;
+
+        Channel( byte[] bytes, int chunkSize )
+        {
+            this.bytes = bytes;
+            this.chunkSize = chunkSize;
+        }
+
+        @Override
+        public int read( ByteBuffer dst )
+        {
+            if ( i == bytes.length )
+            {
+                return -1;
+            }
+            else if ( dst.hasRemaining() )
+            {
+                int length = min( min( chunkSize, bytes.length - i ), dst.remaining() ) ;
+                dst.put( bytes, i, length );
+                i += length;
+                return length;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close()
+        {
+        }
+    }
+
+    private static class MockForkNodeArguments implements ForkNodeArguments
+    {
+        @Nonnull
+        @Override
+        public String getSessionId()
+        {
+            return null;
+        }
+
+        @Override
+        public int getForkChannelId()
+        {
+            return 0;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamText( @Nonnull String text )
+        {
+            return null;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamException( @Nonnull Throwable t )
+        {
+            return null;
+        }
+
+        @Override
+        public void logWarningAtEnd( @Nonnull String text )
+        {
+        }
+
+        @Nonnull
+        @Override
+        public ConsoleLogger getConsoleLogger()
+        {
+            return null;
+        }
+    }
+
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
index 3bc4329..639aba8 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
@@ -32,7 +32,7 @@ public final class Constants
     private static final String MAGIC_NUMBER_FOR_EVENTS = "maven-surefire-event";
     public static final String MAGIC_NUMBER_FOR_COMMANDS = "maven-surefire-command";
     public static final byte[] MAGIC_NUMBER_FOR_EVENTS_BYTES = MAGIC_NUMBER_FOR_EVENTS.getBytes( US_ASCII );
-    public static final byte[] MAGIC_NUMBER_FOR_COMMANDS_BYTES = MAGIC_NUMBER_FOR_EVENTS.getBytes( US_ASCII );
+    public static final byte[] MAGIC_NUMBER_FOR_COMMANDS_BYTES = MAGIC_NUMBER_FOR_COMMANDS.getBytes( US_ASCII );
     public static final Charset DEFAULT_STREAM_ENCODING = UTF_8;
     public static final byte[] DEFAULT_STREAM_ENCODING_BYTES = UTF_8.name().getBytes( US_ASCII );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
index b54bb2e..6cc7db8 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
@@ -56,34 +56,40 @@ public final class DumpErrorSingleton
         dumpStreamFile = createDumpStreamFile( reportsDir, dumpFileName );
     }
 
-    public synchronized void dumpException( Throwable t, String msg )
+    public synchronized File dumpException( Throwable t, String msg )
     {
         DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dumpFile );
+        return dumpFile;
     }
 
-    public synchronized void dumpException( Throwable t )
+    public synchronized File dumpException( Throwable t )
     {
         DumpFileUtils.dumpException( t, dumpFile );
+        return dumpFile;
     }
 
-    public synchronized void dumpText( String msg )
+    public synchronized File dumpText( String msg )
     {
         DumpFileUtils.dumpText( msg == null ? "null" : msg, dumpFile );
+        return dumpFile;
     }
 
-    public synchronized void dumpStreamException( Throwable t, String msg )
+    public synchronized File dumpStreamException( Throwable t, String msg )
     {
         DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dumpStreamFile );
+        return dumpStreamFile;
     }
 
-    public synchronized void dumpStreamException( Throwable t )
+    public synchronized File dumpStreamException( Throwable t )
     {
         DumpFileUtils.dumpException( t, dumpStreamFile );
+        return dumpStreamFile;
     }
 
-    public synchronized void dumpStreamText( String msg )
+    public synchronized File dumpStreamText( String msg )
     {
         DumpFileUtils.dumpText( msg == null ? "null" : msg, dumpStreamFile );
+        return dumpStreamFile;
     }
 
     private File createDumpFile( File reportsDir, String dumpFileName )
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkingRunListener.java
index 7cf1dea..169f358 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkingRunListener.java
@@ -71,7 +71,7 @@ public class ForkingRunListener
     @Override
     public void testSetCompleted( TestSetReportEntry report )
     {
-        target.sendSystemProperties( report.getSystemProperties() );
+        target.systemProperties( report.getSystemProperties() );
         target.testSetCompleted( report, trim );
     }
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessChannelEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessChannelEncoder.java
index e8ad45b..cb543f6 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessChannelEncoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessChannelEncoder.java
@@ -32,15 +32,11 @@ import java.util.Map;
  */
 public interface MasterProcessChannelEncoder
 {
-    MasterProcessChannelEncoder asRerunMode();
-
-    MasterProcessChannelEncoder asNormalMode();
-
     boolean checkError();
 
     void onJvmExit();
 
-    void sendSystemProperties( Map<String, String> sysProps );
+    void systemProperties( Map<String, String> sysProps );
 
     void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces );
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessCommand.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessCommand.java
index ce7649c..f492339 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessCommand.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessCommand.java
@@ -19,6 +19,12 @@ package org.apache.maven.surefire.api.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.util.Objects.requireNonNull;
 
 /**
@@ -38,15 +44,30 @@ public enum MasterProcessCommand
     NOOP( "noop", Void.class ),
     BYE_ACK( "bye-ack", Void.class );
 
-    private final String value;
+    // due to have fast and thread-safe Map
+    public static final Map<Segment, MasterProcessCommand> COMMAND_TYPES = segmentsToCmds();
+
+    private final String opcode;
+    private final byte[] opcodeBinary;
     private final Class<?> dataType;
 
-    MasterProcessCommand( String value, Class<?> dataType )
+    MasterProcessCommand( String opcode, Class<?> dataType )
     {
-        this.value = requireNonNull( value, "value cannot be null" );
+        this.opcode = requireNonNull( opcode, "value cannot be null" );
+        opcodeBinary = opcode.getBytes( US_ASCII );
         this.dataType = requireNonNull( dataType, "dataType cannot be null" );
     }
 
+    public byte[] getOpcodeBinary()
+    {
+        return opcodeBinary;
+    }
+
+    public int getOpcodeLength()
+    {
+        return opcodeBinary.length;
+    }
+
     public Class<?> getDataType()
     {
         return dataType;
@@ -60,6 +81,17 @@ public enum MasterProcessCommand
     @Override
     public String toString()
     {
-        return value;
+        return opcode;
+    }
+
+    private static Map<Segment, MasterProcessCommand> segmentsToCmds()
+    {
+        Map<Segment, MasterProcessCommand> commands = new HashMap<>();
+        for ( MasterProcessCommand command : MasterProcessCommand.values() )
+        {
+            byte[] array = command.toString().getBytes( US_ASCII );
+            commands.put( new Segment( array, 0, array.length ), command );
+        }
+        return commands;
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/report/RunMode.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/report/RunMode.java
index 7329f9c..dbdd580 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/report/RunMode.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/report/RunMode.java
@@ -19,6 +19,11 @@ package org.apache.maven.surefire.api.report;
  * under the License.
  */
 
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
+
+import java.util.HashMap;
+import java.util.Map;
+
 import static java.nio.charset.StandardCharsets.US_ASCII;
 
 /**
@@ -35,6 +40,9 @@ public enum RunMode
     RERUN_TEST_AFTER_FAILURE( "rerun-test-after-failure" );
     //todo add here RERUN_TESTSET, see https://github.com/apache/maven-surefire/pull/221
 
+    // due to have fast and thread-safe Map
+    public static final Map<Segment, RunMode> RUN_MODES = segmentsToRunModes();
+
     private final String runmode;
     private final byte[] runmodeBinary;
 
@@ -53,4 +61,15 @@ public enum RunMode
     {
         return runmodeBinary;
     }
+
+    private static Map<Segment, RunMode> segmentsToRunModes()
+    {
+        Map<Segment, RunMode> runModes = new HashMap<>();
+        for ( RunMode runMode : RunMode.values() )
+        {
+            byte[] array = runMode.getRunmodeBinary();
+            runModes.put( new Segment( array, 0, array.length ), runMode );
+        }
+        return runModes;
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
index 50ebd6f..d0daa25 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
@@ -44,7 +44,6 @@ import static java.nio.charset.CodingErrorAction.REPLACE;
 import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.util.Arrays.copyOf;
 import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
-import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
 import static org.apache.maven.surefire.api.stream.AbstractStreamDecoder.StreamReadStatus.OVERFLOW;
 import static org.apache.maven.surefire.api.stream.AbstractStreamDecoder.StreamReadStatus.UNDERFLOW;
 import static org.apache.maven.surefire.shared.lang3.StringUtils.isBlank;
@@ -79,16 +78,16 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
     private final Map<Segment, MT> messageTypes;
     private final ConsoleLogger logger;
 
-    protected AbstractStreamDecoder( @Nonnull ReadableByteChannel channel, @Nonnull ForkNodeArguments arguments,
-                                     @Nonnull Map<Segment, MT> messageTypes, @Nonnull ConsoleLogger logger )
+    protected AbstractStreamDecoder( @Nonnull ReadableByteChannel channel,
+                                     @Nonnull ForkNodeArguments arguments,
+                                     @Nonnull Map<Segment, MT> messageTypes )
     {
         this.channel = channel;
         this.arguments = arguments;
         this.messageTypes = messageTypes;
-        this.logger = logger;
+        logger = arguments.getConsoleLogger();
     }
 
-    @Nonnull
     public abstract M decode( @Nonnull Memento memento ) throws MalformedChannelException, IOException;
 
     @Nonnull
@@ -109,8 +108,8 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
 
     protected MT readMessageType( @Nonnull Memento memento ) throws IOException, MalformedFrameException
     {
-        int readCount = DELIMITER_LENGTH + MAGIC_NUMBER_FOR_EVENTS_BYTES.length + DELIMITER_LENGTH
-            + BYTE_LENGTH + DELIMITER_LENGTH;
+        byte[] header = getEncodedMagicNumber();
+        int readCount = DELIMITER_LENGTH + header.length + DELIMITER_LENGTH + BYTE_LENGTH + DELIMITER_LENGTH;
         read( memento, readCount );
         checkHeader( memento );
         return messageTypes.get( readSegment( memento ) );
@@ -169,6 +168,11 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
     {
         memento.getCharBuffer().clear();
         int readCount = readInt( memento );
+        if ( readCount < 0 )
+        {
+            throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
+                memento.getByteBuffer().position() );
+        }
         read( memento, readCount + DELIMITER_LENGTH );
 
         final String string;
@@ -186,7 +190,7 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
         {
             string = readString( memento, readCount );
         }
-
+        read( memento, 1 );
         checkDelimiter( memento );
         return string;
     }
@@ -257,18 +261,18 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
         checkDelimiter( memento );
     }
 
-    protected void checkEventArguments( RunMode runMode, Memento memento, int expectedDataElements )
+    protected void checkArguments( RunMode runMode, Memento memento, int expectedDataElements )
         throws MalformedFrameException
     {
-        if ( runMode != null )
+        if ( runMode == null )
         {
             throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
                 memento.getByteBuffer().position() );
         }
-        checkEventArguments( memento, expectedDataElements );
+        checkArguments( memento, expectedDataElements );
     }
 
-    protected void checkEventArguments( Memento memento, int expectedDataElements )
+    protected void checkArguments( Memento memento, int expectedDataElements )
         throws MalformedFrameException
     {
         if ( memento.getData().size() != expectedDataElements )
@@ -290,7 +294,7 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
         for ( boolean endOfInput = false; !endOfInput; )
         {
             final int bytesToRead = totalBytes - countDecodedBytes;
-            read( memento, bytesToRead - input.remaining() );
+            read( memento, bytesToRead );
             int bytesToDecode = min( input.remaining(), bytesToRead );
             final boolean isLastChunk = bytesToDecode == bytesToRead;
             endOfInput = countDecodedBytes + bytesToDecode >= totalBytes;
@@ -434,7 +438,7 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
     protected @Nonnull StreamReadStatus read( @Nonnull Memento memento, int recommendedCount ) throws IOException
     {
         ByteBuffer buffer = memento.getByteBuffer();
-        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+        if ( buffer.remaining() >= recommendedCount && buffer.limit() != 0 )
         {
             return OVERFLOW;
         }
@@ -447,9 +451,9 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
             }
             int mark = buffer.position();
             buffer.position( buffer.limit() );
-            buffer.limit( buffer.capacity() );
+            buffer.limit( min( buffer.position() + recommendedCount, buffer.capacity() ) );
             boolean isEnd = false;
-            while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() != buffer.limit() )
+            while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() < buffer.limit() )
             {
                 isEnd = channel.read( buffer ) == -1;
             }
@@ -486,6 +490,7 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
             defaultDecoder = DEFAULT_STREAM_ENCODING.newDecoder()
                 .onMalformedInput( REPLACE )
                 .onUnmappableCharacter( REPLACE );
+            bb.limit( 0 );
         }
 
         public void reset()
@@ -709,6 +714,13 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
         }
     }
 
+    /**
+     * Underflow - could not completely read out al bytes in one call.
+     * <br>
+     * Overflow - read all bytes or more
+     * <br>
+     * EOF - end of stream
+     */
     public enum StreamReadStatus
     {
         UNDERFLOW,
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoder.java
index b010b2c..d033958 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoder.java
@@ -19,13 +19,13 @@ package org.apache.maven.surefire.api.stream;
  * under the License.
  */
 
-import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
 import org.apache.maven.surefire.api.report.RunMode;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
 
@@ -35,6 +35,7 @@ import static java.nio.CharBuffer.wrap;
 /**
  * The base class of stream encoder.
  * The type of message is expressed by opcode where the opcode object is described by the generic type {@link E}.
+ * @param <E> type of the message
  */
 public abstract class AbstractStreamEncoder<E extends Enum<E>>
 {
@@ -42,15 +43,15 @@ public abstract class AbstractStreamEncoder<E extends Enum<E>>
     private static final byte BOOLEAN_NULL_OBJECT = (byte) 0;
     private static final byte[] INT_BINARY = new byte[] {0, 0, 0, 0};
 
-    private final WritableBufferedByteChannel out;
+    private final WritableByteChannel out;
 
-    public AbstractStreamEncoder( WritableBufferedByteChannel out )
+    public AbstractStreamEncoder( WritableByteChannel out )
     {
         this.out = out;
     }
 
     @Nonnull
-    public abstract byte[] getEncodedMagicNumber();
+    protected abstract byte[] getEncodedMagicNumber();
 
     @Nonnull
     protected abstract byte[] enumToByteArray( E e );
@@ -59,21 +60,21 @@ public abstract class AbstractStreamEncoder<E extends Enum<E>>
     protected abstract byte[] getEncodedCharsetName();
 
     @Nonnull
-    public abstract Charset getCharset();
+    protected abstract Charset getCharset();
 
     @Nonnull
-    public abstract CharsetEncoder newCharsetEncoder();
+    protected abstract CharsetEncoder newCharsetEncoder();
 
-    public void write( ByteBuffer frame, boolean sendImmediately )
+    protected void write( ByteBuffer frame, boolean sendImmediately )
         throws IOException
     {
-        if ( sendImmediately )
+        if ( !sendImmediately && out instanceof WritableBufferedByteChannel )
         {
-            out.write( frame );
+            ( (WritableBufferedByteChannel) out ).writeBuffered( frame );
         }
         else
         {
-            out.writeBuffered( frame );
+            out.write( frame );
         }
     }
 
@@ -149,15 +150,14 @@ public abstract class AbstractStreamEncoder<E extends Enum<E>>
         }
     }
 
-    public int estimateBufferLength( ForkedProcessEventType eventType, RunMode runMode, CharsetEncoder encoder,
+    public int estimateBufferLength( int opcodeLength, RunMode runMode, CharsetEncoder encoder,
                                      int integersCounter, String... strings )
     {
         assert !( encoder == null && strings.length != 0 );
 
         // one delimiter character ':' + <string> + one delimiter character ':' +
         // one byte + one delimiter character ':' + <string> + one delimiter character ':'
-        int lengthOfMetadata =
-            1 + getEncodedMagicNumber().length + 1 + 1 + 1 + eventType.getOpcode().length() + 1;
+        int lengthOfMetadata = 1 + getEncodedMagicNumber().length + 1 + 1 + 1 + opcodeLength + 1;
 
         if ( runMode != null )
         {
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 b881dce..c707ed6 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -25,6 +25,7 @@ import org.apache.maven.surefire.api.runorder.ThreadedExecutionSchedulerTest;
 import org.apache.maven.surefire.SpecificTestClassFilterTest;
 import org.apache.maven.surefire.api.booter.ForkingRunListenerTest;
 import org.apache.maven.surefire.api.report.LegacyPojoStackTraceWriterTest;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoderTest;
 import org.apache.maven.surefire.api.stream.AbstractStreamEncoderTest;
 import org.apache.maven.surefire.api.suite.RunResultTest;
 import org.apache.maven.surefire.api.testset.FundamentalFilterTest;
@@ -70,7 +71,8 @@ import org.junit.runners.Suite;
     ChannelsReaderTest.class,
     ChannelsWriterTest.class,
     AsyncSocketTest.class,
-    AbstractStreamEncoderTest.class
+    AbstractStreamEncoderTest.class,
+    AbstractStreamDecoderTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
new file mode 100644
index 0000000..fb61e4c
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
@@ -0,0 +1,675 @@
+package org.apache.maven.surefire.api.stream;
+
+/*
+ * 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.api.booter.Constants;
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.MalformedFrameException;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.EOFException;
+import java.io.File;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.CharsetDecoder;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static java.lang.Math.min;
+import static java.lang.System.arraycopy;
+import static java.nio.charset.CodingErrorAction.REPLACE;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.singletonMap;
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.reflect.Whitebox.invokeMethod;
+
+/**
+ * The performance of "get( Integer )" is 13.5 nano seconds on i5/2.6GHz:
+ * <pre>
+ *     {@code
+ *     TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
+ *     map.get( hash );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of getting event type by Segment is 33.7 nano seconds:
+ * <pre>
+ *     {@code
+ *     Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+ *     byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 );
+ *     map.get( new Segment( array, 0, array.length ) );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of decoder:
+ * <pre>
+ *     {@code
+ *     CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+ *             .onMalformedInput( REPLACE )
+ *             .onUnmappableCharacter( REPLACE );
+ *     ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
+ *     CharBuffer chars = CharBuffer.allocate( 100 );
+ *     decoder.reset().decode( buffer, chars, true );
+ *
+ *     String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
+ *
+ *     buffer.clear();
+ *     chars.clear();
+ *
+ *     ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
+ *     }
+ * </pre>
+ *
+ * <br> The performance of decoding 100 bytes via CharacterDecoder - 71 nano seconds:
+ * <pre>
+ *     {@code
+ *     decoder.reset()
+ *         .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+ *     chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
+ *     }
+ * </pre>
+ *
+ * <br> The performance of a pure string creation (instead of decoder) - 31.5 nano seconds:
+ * <pre>
+ *     {@code
+ *     byte[] b = {};
+ *     new String( b, UTF_8 );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of CharsetDecoder with empty ByteBuffer:
+ * <pre>
+ *     {@code
+ *     CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+ *     CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+ *     }
+ * </pre>
+ */
+@SuppressWarnings( "checkstyle:magicnumber" )
+public class AbstractStreamDecoderTest
+{
+    private static final Map<Segment, ForkedProcessEventType> EVENTS = new HashMap<>();
+
+    private static final String PATTERN1 =
+        "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+
+    private static final String PATTERN2 = "€ab©c";
+
+    private static final byte[] PATTERN2_BYTES =
+        new byte[] {(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
+
+    @BeforeClass
+    public static void setup()
+    {
+        for ( ForkedProcessEventType event : ForkedProcessEventType.values() )
+        {
+            byte[] array = event.getOpcodeBinary();
+            EVENTS.put( new Segment( array, 0, array.length ), event );
+        }
+    }
+
+    @Test
+    public void shouldDecodeHappyCase() throws Exception
+    {
+        CharsetDecoder decoder = UTF_8.newDecoder().onMalformedInput( REPLACE ).onUnmappableCharacter( REPLACE );
+        ByteBuffer input = ByteBuffer.allocate( 1024 );
+        input.put( PATTERN2_BYTES ).flip();
+        int bytesToDecode = PATTERN2_BYTES.length;
+        CharBuffer output = CharBuffer.allocate( 1024 );
+        int readBytes = invokeMethod( AbstractStreamDecoder.class, "decodeString", decoder, input, output,
+            bytesToDecode, true, 0 );
+
+        assertThat( readBytes )
+            .isEqualTo( bytesToDecode );
+
+        assertThat( output.flip().toString() )
+            .isEqualTo( PATTERN2 );
+    }
+
+    @Test
+    public void shouldDecodeShifted() throws Exception
+    {
+        CharsetDecoder decoder = UTF_8.newDecoder().onMalformedInput( REPLACE ).onUnmappableCharacter( REPLACE );
+        ByteBuffer input = ByteBuffer.allocate( 1024 );
+        input.put( PATTERN1.getBytes( UTF_8 ) )
+            .put( 90, (byte) 'A' )
+            .put( 91, (byte) 'B' )
+            .put( 92, (byte) 'C' )
+            .position( 90 );
+        CharBuffer output = CharBuffer.allocate( 1024 );
+        int readBytes =
+            invokeMethod( AbstractStreamDecoder.class, "decodeString", decoder, input, output, 2, true, 0 );
+
+        assertThat( readBytes ).isEqualTo( 2 );
+
+        assertThat( output.flip().toString() )
+            .isEqualTo( "AB" );
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldNotDecode() throws Exception
+    {
+        CharsetDecoder decoder = UTF_8.newDecoder();
+        ByteBuffer input = ByteBuffer.allocate( 100 );
+        int bytesToDecode = 101;
+        CharBuffer output = CharBuffer.allocate( 1000 );
+        invokeMethod( AbstractStreamDecoder.class, "decodeString", decoder, input, output, bytesToDecode, true, 0 );
+    }
+
+    @Test
+    public void shouldReadInt() throws Exception
+    {
+        Channel channel = new Channel( new byte[] {0x01, 0x02, 0x03, 0x04, ':'}, 1 );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+
+        assertThat( thread.readInt( memento ) )
+            .isEqualTo( new BigInteger( new byte[] {0x01, 0x02, 0x03, 0x04} ).intValue() );
+    }
+
+    @Test
+    public void shouldReadInteger() throws Exception
+    {
+        Channel channel = new Channel( new byte[] {(byte) 0xff, 0x01, 0x02, 0x03, 0x04, ':'}, 1 );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        assertThat( thread.readInteger( memento ) )
+            .isEqualTo( new BigInteger( new byte[] {0x01, 0x02, 0x03, 0x04} ).intValue() );
+    }
+
+    @Test
+    public void shouldReadNullInteger() throws Exception
+    {
+        Channel channel = new Channel( new byte[] {(byte) 0x00, ':'}, 1 );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        assertThat( thread.readInteger( memento ) )
+            .isNull();
+    }
+
+    @Test( expected = EOFException.class )
+    public void shouldNotReadString() throws Exception
+    {
+        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
+        channel.read( ByteBuffer.allocate( 100 ) );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        invokeMethod( thread, "readString", memento, 10 );
+    }
+
+    @Test
+    public void shouldReadString() throws Exception
+    {
+        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        String s = invokeMethod( thread, "readString", memento, 10 );
+        assertThat( s )
+            .isEqualTo( "0123456789" );
+    }
+
+    @Test
+    public void shouldReadStringShiftedBuffer() throws Exception
+    {
+        StringBuilder s = new StringBuilder( 1100 );
+        for ( int i = 0; i < 11; i++ )
+        {
+            s.append( PATTERN1 );
+        }
+
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        // whatever position will be compacted to 0
+        memento.getByteBuffer().limit( 974 ).position( 974 );
+        assertThat( invokeMethod( thread, "readString", memento, PATTERN1.length() + 3 ) )
+            .isEqualTo( PATTERN1 + "012" );
+    }
+
+    @Test
+    public void shouldReadStringShiftedInput() throws Exception
+    {
+        StringBuilder s = new StringBuilder( 1100 );
+        for ( int i = 0; i < 11; i++ )
+        {
+            s.append( PATTERN1 );
+        }
+
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
+        channel.read( ByteBuffer.allocate( 997 ) );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        assertThat( invokeMethod( thread, "readString", memento, PATTERN1.length() ) )
+            .isEqualTo( "789" + PATTERN1.substring( 0, 97 ) );
+    }
+
+    @Test
+    public void shouldReadMultipleStringsAndShiftedInput() throws Exception
+    {
+        StringBuilder s = new StringBuilder( 5000 );
+
+        for ( int i = 0; i < 50; i++ )
+        {
+            s.append( PATTERN1 );
+        }
+
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
+        channel.read( ByteBuffer.allocate( 1997 ) );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        // whatever position will be compacted to 0
+        memento.getByteBuffer().limit( 974 ).position( 974 );
+
+        StringBuilder expected = new StringBuilder( "789" );
+        for ( int i = 0; i < 11; i++ )
+        {
+            expected.append( PATTERN1 );
+        }
+        expected.setLength( 1100 );
+        assertThat( invokeMethod( thread, "readString", memento, 1100 ) )
+            .isEqualTo( expected.toString() );
+    }
+
+    @Test
+    public void shouldDecode3BytesEncodedSymbol() throws Exception
+    {
+        byte[] encodedSymbol = new byte[] {(byte) -30, (byte) -126, (byte) -84};
+        int countSymbols = 1024;
+        byte[] input = new byte[encodedSymbol.length * countSymbols];
+        for ( int i = 0; i < countSymbols; i++ )
+        {
+            arraycopy( encodedSymbol, 0, input, encodedSymbol.length * i, encodedSymbol.length );
+        }
+
+        Channel channel = new Channel( input, 64 * 1024 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+        Memento memento = thread.new Memento();
+        String decodedOutput = invokeMethod( thread, "readString", memento, input.length );
+
+        assertThat( decodedOutput )
+            .isEqualTo( new String( input, 0, input.length, UTF_8 ) );
+    }
+
+    @Test
+    public void shouldDecode100Bytes() throws Exception
+    {
+        CharsetDecoder decoder = DEFAULT_STREAM_ENCODING.newDecoder()
+            .onMalformedInput( REPLACE )
+            .onUnmappableCharacter( REPLACE );
+        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+        ByteBuffer buffer = ByteBuffer.wrap( PATTERN1.getBytes( UTF_8 ) );
+        CharBuffer chars = CharBuffer.allocate( 100 );
+        // uncomment this section for a proper measurement of the exec time
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        String s = null;
+        long l1 = System.currentTimeMillis();
+        for ( int i = 0; i < 10_000_000; i++ )
+        {
+            decoder.reset()
+                .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+            s = chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
+            buffer.clear();
+            chars.clear();
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println( "decoded 100 bytes within " + ( l2 - l1 ) + " millis (10 million cycles)" );
+        assertThat( s )
+            .isEqualTo( PATTERN1 );
+    }
+
+    @Test
+    public void shouldReadEventType() throws Exception
+    {
+        byte[] array = BOOTERCODE_STDOUT.getOpcodeBinary();
+        Map<Segment, ForkedProcessEventType> messageType =
+            singletonMap( new Segment( array, 0, array.length ), BOOTERCODE_STDOUT );
+
+        byte[] stream = ":maven-surefire-event:\u000E:std-out-stream:".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(), messageType );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        ForkedProcessEventType eventType = thread.readMessageType( memento );
+        assertThat( eventType )
+            .isEqualTo( BOOTERCODE_STDOUT );
+    }
+
+    @Test( expected = EOFException.class )
+    public void shouldEventTypeReachedEndOfStream() throws Exception
+    {
+        byte[] stream = ":maven-surefire-event:\u000E:xxx".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(), EVENTS );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+        thread.readMessageType( memento );
+    }
+
+    @Test( expected = MalformedFrameException.class )
+    public void shouldEventTypeReachedMalformedHeader() throws Exception
+    {
+        byte[] stream = ":xxxxx-xxxxxxxx-xxxxx:\u000E:xxx".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+        thread.readMessageType( memento );
+    }
+
+    @Test
+    public void shouldReadEmptyString() throws Exception
+    {
+        byte[] stream = "\u0000\u0000\u0000\u0000::".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readString( memento ) )
+            .isEmpty();
+    }
+
+    @Test
+    public void shouldReadNullString() throws Exception
+    {
+        byte[] stream = "\u0000\u0000\u0000\u0001:\u0000:".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readString( memento ) )
+            .isNull();
+    }
+
+    @Test
+    public void shouldReadSingleCharString() throws Exception
+    {
+        byte[] stream = "\u0000\u0000\u0000\u0001:A:".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readString( memento ) )
+            .isEqualTo( "A" );
+    }
+
+    @Test
+    public void shouldReadThreeCharactersString() throws Exception
+    {
+        byte[] stream = "\u0000\u0000\u0000\u0003:ABC:".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readString( memento ) )
+            .isEqualTo( "ABC" );
+    }
+
+    @Test
+    public void shouldReadDefaultCharset() throws Exception
+    {
+        byte[] stream = "\u0005:UTF-8:".getBytes( US_ASCII );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readCharset( memento ) )
+            .isNotNull()
+            .isEqualTo( UTF_8 );
+    }
+
+    @Test
+    public void shouldReadNonDefaultCharset() throws Exception
+    {
+        byte[] stream = ( (char) 10 + ":ISO_8859_1:" ).getBytes( US_ASCII );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readCharset( memento ) )
+            .isNotNull()
+            .isEqualTo( ISO_8859_1 );
+    }
+
+    @Test
+    public void shouldSetNonDefaultCharset()
+    {
+        byte[] stream = {};
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+        Memento memento = thread.new Memento();
+
+        memento.setCharset( ISO_8859_1 );
+        assertThat( memento.getDecoder().charset() ).isEqualTo( ISO_8859_1 );
+
+        memento.setCharset( UTF_8 );
+        assertThat( memento.getDecoder().charset() ).isEqualTo( UTF_8 );
+
+        memento.reset();
+        assertThat( memento.getDecoder() ).isNotNull();
+        assertThat( memento.getDecoder().charset() ).isEqualTo( UTF_8 );
+    }
+
+    @Test( expected = MalformedFrameException.class )
+    public void malformedCharset() throws Exception
+    {
+        byte[] stream = ( (char) 8 + ":ISO_8859:" ).getBytes( US_ASCII );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        thread.readCharset( memento );
+    }
+
+    private static class Channel implements ReadableByteChannel
+    {
+        private final byte[] bytes;
+        private final int chunkSize;
+        protected int i;
+
+        Channel( byte[] bytes, int chunkSize )
+        {
+            this.bytes = bytes;
+            this.chunkSize = chunkSize;
+        }
+
+        @Override
+        public int read( ByteBuffer dst )
+        {
+            if ( i == bytes.length )
+            {
+                return -1;
+            }
+            else if ( dst.hasRemaining() )
+            {
+                int length = min( min( chunkSize, bytes.length - i ), dst.remaining() ) ;
+                dst.put( bytes, i, length );
+                i += length;
+                return length;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close()
+        {
+        }
+    }
+
+    private static class MockForkNodeArguments implements ForkNodeArguments
+    {
+        @Nonnull
+        @Override
+        public String getSessionId()
+        {
+            return null;
+        }
+
+        @Override
+        public int getForkChannelId()
+        {
+            return 0;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamText( @Nonnull String text )
+        {
+            return null;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamException( @Nonnull Throwable t )
+        {
+            return null;
+        }
+
+        @Override
+        public void logWarningAtEnd( @Nonnull String text )
+        {
+        }
+
+        @Nonnull
+        @Override
+        public ConsoleLogger getConsoleLogger()
+        {
+            return null;
+        }
+    }
+
+    private static class Mock extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType>
+    {
+        protected Mock( @Nonnull ReadableByteChannel channel, @Nonnull ForkNodeArguments arguments,
+                        @Nonnull Map<Segment, ForkedProcessEventType> messageTypes )
+        {
+            super( channel, arguments, messageTypes );
+        }
+
+        @Override
+        public Event decode( @Nonnull Memento memento ) throws MalformedChannelException
+        {
+            throw new MalformedChannelException();
+        }
+
+        @Nonnull
+        @Override
+        protected byte[] getEncodedMagicNumber()
+        {
+            return Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
+        }
+
+        @Nonnull
+        @Override
+        protected SegmentType[] nextSegmentType( @Nonnull ForkedProcessEventType messageType )
+        {
+            return new SegmentType[] {END_OF_FRAME};
+        }
+
+        @Nonnull
+        @Override
+        protected Event toMessage(
+            @Nonnull ForkedProcessEventType messageType, RunMode runMode,
+            @Nonnull Memento memento ) throws MalformedFrameException
+        {
+            return null;
+        }
+    }
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoderTest.java
index 76e3a25..e36373c 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoderTest.java
@@ -56,6 +56,9 @@ import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTER
 import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 
+/**
+ *
+ */
 @SuppressWarnings( { "checkstyle:linelength", "checkstyle:magicnumber" } )
 public class AbstractStreamEncoderTest
 {
@@ -66,87 +69,108 @@ public class AbstractStreamEncoderTest
         CharsetEncoder encoder = streamEncoder.newCharsetEncoder();
 
         // :maven-surefire-event:8:sys-prop:10:normal-run:5:UTF-8:0001:kkk:0001:vvv:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_SYSPROPS, NORMAL_RUN, encoder, 0, "k", "v" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_SYSPROPS.getOpcodeBinary().length, NORMAL_RUN,
+            encoder, 0, "k", "v" ) )
             .isEqualTo( 72 );
 
         // :maven-surefire-event:16:testset-starting:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TESTSET_STARTING, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TESTSET_STARTING.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
             .isEqualTo( 149 );
 
         // :maven-surefire-event:17:testset-completed:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TESTSET_COMPLETED, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TESTSET_COMPLETED.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
             .isEqualTo( 150 );
 
         // :maven-surefire-event:13:test-starting:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_STARTING, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_STARTING.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
             .isEqualTo( 146 );
 
         // :maven-surefire-event:14:test-succeeded:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_SUCCEEDED, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_SUCCEEDED.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
             .isEqualTo( 147 );
 
         // :maven-surefire-event:11:test-failed:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_FAILED, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_FAILED.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
             .isEqualTo( 144 );
 
         // :maven-surefire-event:12:test-skipped:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_SKIPPED, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_SKIPPED.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
             .isEqualTo( 145 );
 
         // :maven-surefire-event:10:test-error:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_ERROR, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_ERROR.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
             .isEqualTo( 143 );
 
         // :maven-surefire-event:23:test-assumption-failure:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_ASSUMPTIONFAILURE, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_ASSUMPTIONFAILURE.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
             .isEqualTo( 156 );
 
         // :maven-surefire-event:14:std-out-stream:10:normal-run:5:UTF-8:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDOUT, NORMAL_RUN, encoder, 0, "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDOUT.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 0, "s" ) )
             .isEqualTo( 69 );
 
         // :maven-surefire-event:23:std-out-stream-new-line:10:normal-run:5:UTF-8:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDOUT_NEW_LINE, NORMAL_RUN, encoder, 0, "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDOUT_NEW_LINE.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 0, "s" ) )
             .isEqualTo( 78 );
 
         // :maven-surefire-event:14:std-err-stream:10:normal-run:5:UTF-8:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDERR, NORMAL_RUN, encoder, 0, "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDERR.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 0, "s" ) )
             .isEqualTo( 69 );
 
         // :maven-surefire-event:23:std-err-stream-new-line:10:normal-run:5:UTF-8:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDERR_NEW_LINE, NORMAL_RUN, encoder, 0, "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDERR_NEW_LINE.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 0, "s" ) )
             .isEqualTo( 78 );
 
         // :maven-surefire-event:16:console-info-log:5:UTF-8:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_INFO, null, encoder, 0, "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_INFO.getOpcodeBinary().length,
+            null, encoder, 0, "s" ) )
             .isEqualTo( 58 );
 
         // :maven-surefire-event:17:console-debug-log:5:UTF-8:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_DEBUG, null, encoder, 0, "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_DEBUG.getOpcodeBinary().length,
+            null, encoder, 0, "s" ) )
             .isEqualTo( 59 );
 
         // :maven-surefire-event:19:console-warning-log:5:UTF-8:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_WARNING, null, encoder, 0, "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_WARNING.getOpcodeBinary().length,
+            null, encoder, 0, "s" ) )
             .isEqualTo( 61 );
 
         // :maven-surefire-event:17:console-error-log:5:UTF-8:0001:sss:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_ERROR, null, encoder, 0, "s" ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_ERROR.getOpcodeBinary().length,
+            null, encoder, 0, "s" ) )
             .isEqualTo( 59 );
 
         // :maven-surefire-event:3:bye:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_BYE, null, null, 0 ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_BYE.getOpcodeBinary().length,
+            null, null, 0 ) )
             .isEqualTo( 28 );
 
         // :maven-surefire-event:17:stop-on-next-test:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STOP_ON_NEXT_TEST, null, null, 0 ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STOP_ON_NEXT_TEST.getOpcodeBinary().length,
+            null, null, 0 ) )
             .isEqualTo( 42 );
 
         // :maven-surefire-event:9:next-test:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_NEXT_TEST, null, null, 0 ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_NEXT_TEST.getOpcodeBinary().length,
+            null, null, 0 ) )
             .isEqualTo( 34 );
 
         // :maven-surefire-event:14:jvm-exit-error:
-        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_JVM_EXIT_ERROR, null, null, 0 ) )
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_JVM_EXIT_ERROR.getOpcodeBinary().length,
+            null, null, 0 ) )
             .isEqualTo( 39 );
     }
 
@@ -227,7 +251,7 @@ public class AbstractStreamEncoderTest
     private static class Encoder extends AbstractStreamEncoder<ForkedProcessEventType>
     {
 
-        public Encoder( WritableBufferedByteChannel out )
+        Encoder( WritableBufferedByteChannel out )
         {
             super( out );
         }
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 ae950df..dcf7361 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
@@ -42,7 +42,6 @@ public final class BooterConstants
     public static final String TESTARTIFACT_VERSION = "testFwJarVersion";
     public static final String TESTARTIFACT_CLASSIFIER = "testFwJarClassifier";
     public static final String REQUESTEDTEST = "requestedTest";
-    public static final String REQUESTEDTESTMETHOD = "requestedTestMethod";
     public static final String SOURCE_DIRECTORY = "testSuiteDefinitionTestSourceDirectory";
     public static final String TEST_CLASSES_DIRECTORY = "testClassesDirectory";
     public static final String RUN_ORDER = "runOrder";
@@ -60,4 +59,5 @@ public final class BooterConstants
     public static final String PLUGIN_PID = "pluginPid";
     public static final String PROCESS_CHECKER = "processChecker";
     public static final String FORK_NODE_CONNECTION_STRING = "forkNodeConnectionString";
+    public static final String FORK_NUMBER = "forkNumber";
 }
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 6a6cfae..ca5328e 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
@@ -62,6 +62,11 @@ public class BooterDeserializer
         properties = SystemPropertyManager.loadProperties( inputStream );
     }
 
+    public int getForkNumber()
+    {
+        return properties.getIntProperty( FORK_NUMBER );
+    }
+
     /**
      * Describes the current connection channel used by the client in the forked JVM
      * in order to connect to the plugin process.
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 d8e3bfe..f6a19e8 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -27,16 +27,17 @@ import org.apache.maven.surefire.api.booter.ForkingReporterFactory;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.api.booter.Shutdown;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.provider.CommandListener;
 import org.apache.maven.surefire.api.provider.ProviderParameters;
 import org.apache.maven.surefire.api.provider.SurefireProvider;
 import org.apache.maven.surefire.api.report.LegacyPojoStackTraceWriter;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
+import org.apache.maven.surefire.api.testset.TestSetFailedException;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
+import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.api.testset.TestSetFailedException;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -61,13 +62,13 @@ import static java.lang.Thread.currentThread;
 import static java.util.ServiceLoader.load;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.maven.surefire.api.util.ReflectionUtils.instantiateOneArg;
+import static org.apache.maven.surefire.api.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
+import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
 import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
 import static org.apache.maven.surefire.booter.ProcessCheckerType.NATIVE;
 import static org.apache.maven.surefire.booter.ProcessCheckerType.PING;
 import static org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties;
-import static org.apache.maven.surefire.api.util.ReflectionUtils.instantiateOneArg;
-import static org.apache.maven.surefire.api.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
-import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
 
 /**
  * The part of the booter that is unique to a forked vm.
@@ -113,10 +114,13 @@ public final class ForkedBooter
         DumpErrorSingleton.getSingleton()
                 .init( providerConfiguration.getReporterConfiguration().getReportsDirectory(), dumpFileName );
 
+        int forkNumber = booterDeserializer.getForkNumber();
+
         if ( isDebugging() )
         {
             DumpErrorSingleton.getSingleton()
-                    .dumpText( "Found Maven process ID " + booterDeserializer.getPluginPid() );
+                    .dumpText( "Found Maven process ID " + booterDeserializer.getPluginPid()
+                        + " for the fork " + forkNumber + "." );
         }
 
         startupConfiguration = booterDeserializer.getStartupConfiguration();
@@ -124,8 +128,9 @@ public final class ForkedBooter
         String channelConfig = booterDeserializer.getConnectionString();
         channelProcessorFactory = lookupDecoderFactory( channelConfig );
         channelProcessorFactory.connect( channelConfig );
-        eventChannel = channelProcessorFactory.createEncoder();
-        MasterProcessChannelDecoder decoder = channelProcessorFactory.createDecoder();
+        ForkNodeArguments args = new ForkedNodeArg( forkNumber );
+        eventChannel = channelProcessorFactory.createEncoder( args );
+        MasterProcessChannelDecoder decoder = channelProcessorFactory.createDecoder( args );
 
         flushEventChannelOnExit();
 
@@ -671,4 +676,5 @@ public final class ForkedBooter
         return ManagementFactory.getRuntimeMXBean()
                 .getName();
     }
+
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
new file mode 100644
index 0000000..cc375b5
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
@@ -0,0 +1,83 @@
+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.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
+import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+
+/**
+ *
+ */
+public final class ForkedNodeArg implements ForkNodeArguments
+{
+    private final int forkChannelId;
+    private final ConsoleLogger logger;
+
+    public ForkedNodeArg( int forkChannelId )
+    {
+        this.forkChannelId = forkChannelId;
+        logger = new NullConsoleLogger();
+    }
+
+    @Nonnull
+    @Override
+    public String getSessionId()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getForkChannelId()
+    {
+        return forkChannelId;
+    }
+
+    @Override
+    @Nonnull
+    public File dumpStreamText( @Nonnull String text )
+    {
+        return DumpErrorSingleton.getSingleton().dumpStreamText( text );
+    }
+
+    @Nonnull
+    @Override
+    public File dumpStreamException( @Nonnull Throwable t )
+    {
+        return DumpErrorSingleton.getSingleton().dumpStreamException( t, t.getLocalizedMessage() );
+    }
+
+    @Override
+    public void logWarningAtEnd( @Nonnull String text )
+    {
+        // do nothing - the log message of forked VM already goes to the dump file
+    }
+
+    @Nonnull
+    @Override
+    public ConsoleLogger getConsoleLogger()
+    {
+        return logger;
+    }
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
index c0004e5..b5cccf4 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
@@ -62,7 +62,7 @@ public class PropertiesWrapper
 
     public boolean getBooleanProperty( String propertyName )
     {
-        return Boolean.valueOf( properties.get( propertyName ) );
+        return Boolean.parseBoolean( properties.get( propertyName ) );
     }
 
     public int getIntProperty( String propertyName )
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoder.java
similarity index 56%
rename from surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoder.java
index 1107280..0e46be3 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoder.java
@@ -19,24 +19,16 @@ package org.apache.maven.surefire.booter.spi;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.Command;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.api.booter.MasterProcessCommand;
 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
-import org.apache.maven.surefire.api.report.RunMode;
 import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
-import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
 import org.apache.maven.surefire.api.stream.MalformedChannelException;
 import org.apache.maven.surefire.booter.stream.CommandDecoder;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.nio.channels.ReadableByteChannel;
-import java.util.HashMap;
-import java.util.Map;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
 
 /**
  * magic number : opcode [: opcode specific data]*
@@ -45,20 +37,15 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
  * @author <a href="mailto:[hidden email]">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M5
  */
-public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDecoder
+public class CommandChannelDecoder implements MasterProcessChannelDecoder
 {
-    // due to have fast and thread-safe Map
-    private static final Map<Segment, MasterProcessCommand> COMMAND_TYPES = segmentsToCmds();
-    private static final Map<Segment, RunMode> RUN_MODES = segmentsToRunModes();
-
     private final CommandDecoder decoder;
     private Memento memento;
 
-    public LegacyMasterProcessChannelDecoder( @Nonnull ReadableByteChannel channel,
-                                              @Nonnull ConsoleLogger logger,
-                                              @Nonnull ForkNodeArguments arguments )
+    public CommandChannelDecoder( @Nonnull ReadableByteChannel channel,
+                                  @Nonnull ForkNodeArguments arguments )
     {
-        decoder = new CommandDecoder( channel, COMMAND_TYPES, RUN_MODES, logger, arguments );
+        decoder = new CommandDecoder( channel, arguments );
     }
 
     @Override
@@ -77,7 +64,11 @@ public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDe
         {
             try
             {
-                return decoder.decode( memento );
+                Command command = decoder.decode( memento );
+                if ( command != null )
+                {
+                    return command;
+                }
             }
             catch ( MalformedChannelException e )
             {
@@ -91,26 +82,4 @@ public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDe
     public void close()
     {
     }
-
-    private static Map<Segment, MasterProcessCommand> segmentsToCmds()
-    {
-        Map<Segment, MasterProcessCommand> commands = new HashMap<>();
-        for ( MasterProcessCommand command : MasterProcessCommand.values() )
-        {
-            byte[] array = command.toString().getBytes( US_ASCII );
-            commands.put( new Segment( array, 0, array.length ), command );
-        }
-        return commands;
-    }
-
-    private static Map<Segment, RunMode> segmentsToRunModes()
-    {
-        Map<Segment, RunMode> runModes = new HashMap<>();
-        for ( RunMode runMode : RunMode.values() )
-        {
-            byte[] array = runMode.getRunmodeBinary();
-            runModes.put( new Segment( array, 0, array.length ), runMode );
-        }
-        return runModes;
-    }
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/EventChannelEncoder.java
similarity index 79%
rename from surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/EventChannelEncoder.java
index 56c4d9c..8f78072 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/EventChannelEncoder.java
@@ -27,7 +27,6 @@ import org.apache.maven.surefire.api.report.ReportEntry;
 import org.apache.maven.surefire.api.report.RunMode;
 import org.apache.maven.surefire.api.report.SafeThrowable;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
-import org.apache.maven.surefire.api.stream.AbstractStreamEncoder;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 import org.apache.maven.surefire.booter.stream.EventEncoder;
 
@@ -64,7 +63,6 @@ import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTER
 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
 import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
-import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
 
 /**
  * magic number : opcode : run mode [: opcode specific data]*
@@ -74,41 +72,45 @@ import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAIL
  * @since 3.0.0-M4
  */
 @SuppressWarnings( "checkstyle:linelength" )
-public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
+public class EventChannelEncoder extends EventEncoder implements MasterProcessChannelEncoder
 {
-    private final AbstractStreamEncoder<ForkedProcessEventType> streamEncoder;
     private final RunMode runMode;
     private final AtomicBoolean trouble = new AtomicBoolean();
     private volatile boolean onExit;
 
-    public LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out )
+    /**
+     * The encoder for events and normal test mode.
+     *
+     * @param out     the channel available for writing the events
+     */
+    public EventChannelEncoder( @Nonnull WritableBufferedByteChannel out )
     {
         this( out, NORMAL_RUN );
     }
 
-    protected LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode )
-    {
-        this( new EventEncoder( out ), runMode );
-    }
-
-    protected LegacyMasterProcessChannelEncoder( @Nonnull AbstractStreamEncoder<ForkedProcessEventType> streamEncoder,
-                                                 @Nonnull RunMode runMode )
+    /**
+     * The encoder for events and any test mode.
+     *
+     * @param out     the channel available for writing the events
+     * @param runMode run mode
+     */
+    public EventChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode )
     {
-        this.streamEncoder = streamEncoder;
+        super( out );
         this.runMode = requireNonNull( runMode );
     }
 
-    @Override
+    /*@Override
     public MasterProcessChannelEncoder asRerunMode() // todo apply this and rework providers
     {
-        return new LegacyMasterProcessChannelEncoder( streamEncoder, RERUN_TEST_AFTER_FAILURE );
+        return new EventChannelEncoder( streamEncoder, RERUN_TEST_AFTER_FAILURE );
     }
 
     @Override
     public MasterProcessChannelEncoder asNormalMode()
     {
-        return new LegacyMasterProcessChannelEncoder( streamEncoder, NORMAL_RUN );
-    }
+        return new EventChannelEncoder( streamEncoder, NORMAL_RUN );
+    }*/
 
     @Override
     public boolean checkError()
@@ -124,9 +126,9 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     }
 
     @Override
-    public void sendSystemProperties( Map<String, String> sysProps )
+    public void systemProperties( Map<String, String> sysProps )
     {
-        CharsetEncoder encoder = streamEncoder.newCharsetEncoder();
+        CharsetEncoder encoder = newCharsetEncoder();
         ByteBuffer result = null;
         for ( Iterator<Entry<String, String>> it = sysProps.entrySet().iterator(); it.hasNext(); )
         {
@@ -134,11 +136,12 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
             String key = entry.getKey();
             String value = entry.getValue();
 
-            int bufferLength = streamEncoder.estimateBufferLength( BOOTERCODE_SYSPROPS, runMode, encoder, 0, key, value );
+            int bufferLength =
+                estimateBufferLength( BOOTERCODE_SYSPROPS.getOpcode().length(), runMode, encoder, 0, key, value );
             result = result != null && result.capacity() >= bufferLength ? result : ByteBuffer.allocate( bufferLength );
             result.clear();
             // :maven-surefire-event:sys-prop:rerun-test-after-failure:UTF-8:<integer>:<key>:<integer>:<value>:
-            streamEncoder.encode( encoder, result, BOOTERCODE_SYSPROPS, runMode, key, value );
+            encode( encoder, result, BOOTERCODE_SYSPROPS, runMode, key, value );
             boolean sync = !it.hasNext();
             write( result, sync );
         }
@@ -234,12 +237,13 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     @Override
     public void consoleErrorLog( String message, Throwable t )
     {
-        CharsetEncoder encoder = streamEncoder.newCharsetEncoder();
+        CharsetEncoder encoder = newCharsetEncoder();
         String stackTrace = t == null ? null : ConsoleLoggerUtils.toString( t );
-        int bufferMaxLength = streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_ERROR, null, encoder, 0, message, stackTrace );
+        int bufferMaxLength = estimateBufferLength( BOOTERCODE_CONSOLE_ERROR.getOpcode().length(), null, encoder, 0,
+            message, stackTrace );
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
-        streamEncoder.encodeHeader( result, BOOTERCODE_CONSOLE_ERROR, null );
-        streamEncoder.encodeCharset( result );
+        encodeHeader( result, BOOTERCODE_CONSOLE_ERROR, null );
+        encodeCharset( result );
         encode( encoder, result, message, null, stackTrace );
         write( result, true );
     }
@@ -291,14 +295,14 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType eventType,
                         @SuppressWarnings( "SameParameterValue" ) boolean sync )
     {
-        CharsetEncoder encoder = streamEncoder.newCharsetEncoder();
+        CharsetEncoder encoder = newCharsetEncoder();
         StackTrace stackTraceWrapper = new StackTrace( stackTraceWriter, trimStackTraces );
-        int bufferMaxLength = streamEncoder.estimateBufferLength( eventType, null, encoder, 0,
+        int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), null, encoder, 0,
             stackTraceWrapper.message, stackTraceWrapper.smartTrimmedStackTrace, stackTraceWrapper.stackTrace );
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
 
-        streamEncoder.encodeHeader( result, eventType, null );
-        streamEncoder.encodeCharset( result );
+        encodeHeader( result, eventType, null );
+        encodeCharset( result );
         encode( encoder, result, stackTraceWrapper );
         write( result, sync );
     }
@@ -314,25 +318,26 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
 
     private void encodeOpcode( ForkedProcessEventType eventType, boolean sync )
     {
-        int bufferMaxLength = streamEncoder.estimateBufferLength( eventType, null, null, 0 );
+        int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), null, null, 0 );
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
-        streamEncoder.encodeHeader( result, eventType, null );
+        encodeHeader( result, eventType, null );
         write( result, sync );
     }
 
-    private void write( ByteBuffer frame, boolean sync )
+    @Override
+    protected void write( ByteBuffer frame, boolean sync )
     {
         final boolean wasInterrupted = Thread.interrupted();
         try
         {
-            streamEncoder.write( frame, sync );
+            super.write( frame, sync );
         }
         catch ( ClosedChannelException e )
         {
             if ( !onExit )
             {
                 String event = new String( frame.array(), frame.arrayOffset() + frame.position(), frame.remaining(),
-                    streamEncoder.getCharset() );
+                    getCharset() );
 
                 DumpErrorSingleton.getSingleton()
                     .dumpException( e, "Channel closed while writing the event '" + event + "'." );
@@ -363,9 +368,9 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     private void encode( CharsetEncoder encoder, ByteBuffer result,
                          String message, String smartStackTrace, String stackTrace )
     {
-        streamEncoder.encodeString( encoder, result, message );
-        streamEncoder.encodeString( encoder, result, smartStackTrace );
-        streamEncoder.encodeString( encoder, result, stackTrace );
+        encodeString( encoder, result, message );
+        encodeString( encoder, result, smartStackTrace );
+        encodeString( encoder, result, stackTrace );
     }
 
     /**
@@ -386,25 +391,25 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     {
         StackTrace stackTraceWrapper = new StackTrace( reportEntry.getStackTraceWriter(), trimStackTraces );
 
-        CharsetEncoder encoder = streamEncoder.newCharsetEncoder();
+        CharsetEncoder encoder = newCharsetEncoder();
 
-        int bufferMaxLength = streamEncoder.estimateBufferLength( operation, runMode, encoder, 1, reportEntry.getSourceName(),
-            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-            reportEntry.getMessage(), stackTraceWrapper.message, stackTraceWrapper.smartTrimmedStackTrace,
-            stackTraceWrapper.stackTrace );
+        int bufferMaxLength = estimateBufferLength( operation.getOpcode().length(), runMode, encoder, 1,
+            reportEntry.getSourceName(), reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(),
+            reportEntry.getGroup(), reportEntry.getMessage(), stackTraceWrapper.message,
+            stackTraceWrapper.smartTrimmedStackTrace, stackTraceWrapper.stackTrace );
 
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
 
-        streamEncoder.encodeHeader( result, operation, runMode );
-        streamEncoder.encodeCharset( result );
+        encodeHeader( result, operation, runMode );
+        encodeCharset( result );
 
-        streamEncoder.encodeString( encoder, result, reportEntry.getSourceName() );
-        streamEncoder.encodeString( encoder, result, reportEntry.getSourceText() );
-        streamEncoder.encodeString( encoder, result, reportEntry.getName() );
-        streamEncoder.encodeString( encoder, result, reportEntry.getNameText() );
-        streamEncoder.encodeString( encoder, result, reportEntry.getGroup() );
-        streamEncoder.encodeString( encoder, result, reportEntry.getMessage() );
-        streamEncoder.encodeInteger( result, reportEntry.getElapsed() );
+        encodeString( encoder, result, reportEntry.getSourceName() );
+        encodeString( encoder, result, reportEntry.getSourceText() );
+        encodeString( encoder, result, reportEntry.getName() );
+        encodeString( encoder, result, reportEntry.getNameText() );
+        encodeString( encoder, result, reportEntry.getGroup() );
+        encodeString( encoder, result, reportEntry.getMessage() );
+        encodeInteger( result, reportEntry.getElapsed() );
 
         encode( encoder, result, stackTraceWrapper );
 
@@ -413,10 +418,10 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
 
     ByteBuffer encodeMessage( ForkedProcessEventType eventType, RunMode runMode, String message )
     {
-        CharsetEncoder encoder = streamEncoder.newCharsetEncoder();
-        int bufferMaxLength = streamEncoder.estimateBufferLength( eventType, runMode, encoder, 0, message );
+        CharsetEncoder encoder = newCharsetEncoder();
+        int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), runMode, encoder, 0, message );
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
-        streamEncoder.encode( encoder, result, eventType, runMode, message );
+        encode( encoder, result, eventType, runMode, message );
         return result;
     }
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
index 6e28764..3d96f06 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
@@ -21,8 +21,10 @@ package org.apache.maven.surefire.booter.spi;
 
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 
+import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.net.MalformedURLException;
 
@@ -51,21 +53,21 @@ public class LegacyMasterProcessChannelProcessorFactory
     {
         if ( !canUse( channelConfig ) )
         {
-            throw new MalformedURLException( "Unknown chanel string " + channelConfig );
+            throw new MalformedURLException( "Unknown channel string " + channelConfig );
         }
     }
 
     @Override
-    public MasterProcessChannelDecoder createDecoder()
+    public MasterProcessChannelDecoder createDecoder( @Nonnull ForkNodeArguments forkingArguments )
     {
-        return new LegacyMasterProcessChannelDecoder( newBufferedChannel( System.in ) );
+        return new CommandChannelDecoder( newBufferedChannel( System.in ), forkingArguments );
     }
 
     @Override
-    public MasterProcessChannelEncoder createEncoder()
+    public MasterProcessChannelEncoder createEncoder( @Nonnull ForkNodeArguments forkingArguments )
     {
         WritableBufferedByteChannel channel = newBufferedChannel( System.out );
         schedulePeriodicFlusher( FLUSH_PERIOD_MILLIS, channel );
-        return new LegacyMasterProcessChannelEncoder( channel );
+        return new EventChannelEncoder( channel );
     }
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
index 0bebeb4..6232209 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
@@ -21,8 +21,10 @@ package org.apache.maven.surefire.booter.spi;
 
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 
+import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
@@ -31,6 +33,7 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.ReadableByteChannel;
 import java.util.StringTokenizer;
 import java.util.concurrent.ExecutionException;
 
@@ -69,7 +72,7 @@ public class SurefireMasterProcessChannelProcessorFactory
     {
         if ( !canUse( channelConfig ) )
         {
-            throw new MalformedURLException( "Unknown chanel string " + channelConfig );
+            throw new MalformedURLException( "Unknown channel string " + channelConfig );
         }
 
         try
@@ -97,17 +100,18 @@ public class SurefireMasterProcessChannelProcessorFactory
     }
 
     @Override
-    public MasterProcessChannelDecoder createDecoder()
+    public MasterProcessChannelDecoder createDecoder( @Nonnull ForkNodeArguments forkingArguments )
     {
-        return new LegacyMasterProcessChannelDecoder( newBufferedChannel( newInputStream( clientSocketChannel ) ) );
+        ReadableByteChannel bufferedChannel = newBufferedChannel( newInputStream( clientSocketChannel ) );
+        return new CommandChannelDecoder( bufferedChannel, forkingArguments );
     }
 
     @Override
-    public MasterProcessChannelEncoder createEncoder()
+    public MasterProcessChannelEncoder createEncoder( @Nonnull ForkNodeArguments forkingArguments )
     {
         WritableBufferedByteChannel channel = newBufferedChannel( newOutputStream( clientSocketChannel ) );
         schedulePeriodicFlusher( FLUSH_PERIOD_MILLIS, channel );
-        return new LegacyMasterProcessChannelEncoder( channel );
+        return new EventChannelEncoder( channel );
     }
 
     @Override
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
index 0abb07d..b7fc186 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
@@ -19,9 +19,7 @@ package org.apache.maven.surefire.booter.stream;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.Command;
-import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
 import org.apache.maven.surefire.api.booter.MasterProcessCommand;
 import org.apache.maven.surefire.api.booter.Shutdown;
 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
@@ -33,7 +31,6 @@ import org.apache.maven.surefire.api.stream.SegmentType;
 import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.nio.channels.ReadableByteChannel;
-import java.util.Map;
 
 import static org.apache.maven.surefire.api.booter.Command.BYE_ACK;
 import static org.apache.maven.surefire.api.booter.Command.NOOP;
@@ -42,6 +39,8 @@ import static org.apache.maven.surefire.api.booter.Command.TEST_SET_FINISHED;
 import static org.apache.maven.surefire.api.booter.Command.toRunClass;
 import static org.apache.maven.surefire.api.booter.Command.toShutdown;
 import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_COMMANDS_BYTES;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.COMMAND_TYPES;
+import static org.apache.maven.surefire.api.report.RunMode.RUN_MODES;
 import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
 import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
 import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
@@ -63,21 +62,15 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
         END_OF_FRAME
     };
 
-    private final Map<Segment, RunMode> runModes;
     private final ForkNodeArguments arguments;
 
     public CommandDecoder( @Nonnull ReadableByteChannel channel,
-                           @Nonnull Map<Segment, MasterProcessCommand> commandTypes,
-                           @Nonnull Map<Segment, RunMode> runModes,
-                           @Nonnull ConsoleLogger logger,
                            @Nonnull ForkNodeArguments arguments )
     {
-        super( channel, arguments, commandTypes, logger );
-        this.runModes = runModes;
+        super( channel, arguments, COMMAND_TYPES );
         this.arguments = arguments;
     }
 
-    @Nonnull
     @Override
     public Command decode( @Nonnull Memento memento ) throws IOException, MalformedChannelException
     {
@@ -95,7 +88,7 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
                 switch ( segmentType )
                 {
                     case RUN_MODE:
-                        runMode = runModes.get( readSegment( memento ) );
+                        runMode = RUN_MODES.get( readSegment( memento ) );
                         break;
                     case STRING_ENCODING:
                         memento.setCharset( readCharset( memento ) );
@@ -112,7 +105,7 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
                     default:
                         memento.getLine().setPositionByteBuffer( NO_POSITION );
                         arguments.dumpStreamText( "Unknown enum ("
-                            + ForkedProcessEventType.class.getSimpleName()
+                            + SegmentType.class.getSimpleName()
                             + ") "
                             + segmentType );
                 }
@@ -125,14 +118,19 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
                 int length = e.readTo() - e.readFrom();
                 memento.getLine().write( memento.getByteBuffer(), e.readFrom(), length );
             }
+            return null;
         }
         catch ( RuntimeException e )
         {
             getArguments().dumpStreamException( e );
+            return null;
         }
         catch ( IOException e )
         {
-            printRemainingStream( memento );
+            if ( !( e.getCause() instanceof InterruptedException ) )
+            {
+                printRemainingStream( memento );
+            }
             throw e;
         }
         finally
@@ -177,22 +175,22 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
         switch ( commandType )
         {
             case NOOP:
-                checkEventArguments( memento, 0 );
+                checkArguments( memento, 0 );
                 return NOOP;
             case BYE_ACK:
-                checkEventArguments( memento, 0 );
+                checkArguments( memento, 0 );
                 return BYE_ACK;
             case SKIP_SINCE_NEXT_TEST:
-                checkEventArguments( memento, 0 );
+                checkArguments( memento, 0 );
                 return SKIP_SINCE_NEXT_TEST;
             case TEST_SET_FINISHED:
-                checkEventArguments( memento, 0 );
+                checkArguments( memento, 0 );
                 return TEST_SET_FINISHED;
             case RUN_CLASS:
-                checkEventArguments( memento, 1 );
+                checkArguments( memento, 1 );
                 return toRunClass( (String) memento.getData().get( 0 ) );
             case SHUTDOWN:
-                checkEventArguments( memento, 1 );
+                checkArguments( memento, 1 );
                 return toShutdown( Shutdown.parameterOf( (String) memento.getData().get( 0 ) ) );
             default:
                 throw new IllegalArgumentException( "Missing a branch for the event type " + commandType );
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/EventEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/EventEncoder.java
index 9a172c5..f982017 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/EventEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/EventEncoder.java
@@ -43,7 +43,7 @@ public class EventEncoder extends AbstractStreamEncoder<ForkedProcessEventType>
 
     @Nonnull
     @Override
-    public final byte[] getEncodedMagicNumber()
+    protected final byte[] getEncodedMagicNumber()
     {
         return MAGIC_NUMBER_FOR_EVENTS_BYTES;
     }
@@ -64,14 +64,14 @@ public class EventEncoder extends AbstractStreamEncoder<ForkedProcessEventType>
 
     @Nonnull
     @Override
-    public final Charset getCharset()
+    protected final Charset getCharset()
     {
         return DEFAULT_STREAM_ENCODING;
     }
 
     @Nonnull
     @Override
-    public final CharsetEncoder newCharsetEncoder()
+    protected final CharsetEncoder newCharsetEncoder()
     {
         return getCharset().newEncoder();
     }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index 5bc5ffe..12df54d 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -23,8 +23,9 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.Shutdown;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoder;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
 import org.apache.maven.surefire.api.testset.TestSetFailedException;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 import org.junit.After;
@@ -45,6 +46,7 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static org.apache.maven.surefire.api.util.internal.Channels.newBufferedChannel;
 import static org.apache.maven.surefire.api.util.internal.Channels.newChannel;
 import static org.fest.assertions.Assertions.assertThat;
@@ -61,6 +63,7 @@ import static org.junit.Assert.fail;
  * @since 2.19
  */
 @RunWith( NewClassLoaderRunner.class )
+@SuppressWarnings( "checkstyle:magicnumber" )
 public class CommandReaderTest
 {
     private static final long DELAY = 200L;
@@ -93,7 +96,9 @@ public class CommandReaderTest
         InputStream realInputStream = new SystemInputStream();
         addTestToPipeline( getClass().getName() );
         ConsoleLogger logger = new NullConsoleLogger();
-        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( realInputStream ) );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        MasterProcessChannelDecoder decoder =
+            new CommandChannelDecoder( newChannel( realInputStream ), args );
         reader = new CommandReader( decoder, Shutdown.DEFAULT, logger );
     }
 
@@ -106,7 +111,7 @@ public class CommandReaderTest
     @Test
     public void readJustOneClass()
     {
-        Iterator<String> it = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it = reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
         assertTrue( it.hasNext() );
         assertThat( it.next(), is( getClass().getName() ) );
         reader.stop();
@@ -125,7 +130,7 @@ public class CommandReaderTest
     @Test
     public void manyClasses()
     {
-        Iterator<String> it1 = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it1 = reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
         assertThat( it1.next(), is( getClass().getName() ) );
         addTestToPipeline( A.class.getName() );
         assertThat( it1.next(), is( A.class.getName() ) );
@@ -141,7 +146,7 @@ public class CommandReaderTest
     @Test
     public void twoIterators() throws Exception
     {
-        Iterator<String> it1 = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it1 = reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
 
         assertThat( it1.next(), is( getClass().getName() ) );
         addTestToPipeline( A.class.getName() );
@@ -174,8 +179,7 @@ public class CommandReaderTest
             @Override
             public void run()
             {
-                Iterator<String> it =
-                    reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+                Iterator<String> it = reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
             }
         };
@@ -203,7 +207,7 @@ public class CommandReaderTest
             public void run()
             {
                 Iterator<String> it =
-                    reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+                    reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
                 counter.countDown();
                 assertThat( it.next(), is( Foo.class.getName() ) );
@@ -250,7 +254,23 @@ public class CommandReaderTest
 
     private void addTestToPipeline( String cls )
     {
-        for ( byte cmdByte : ( ":maven-surefire-command:run-testclass:" + cls + ":" ).getBytes() )
+        int clsLength = cls.length();
+        String cmd = new StringBuilder( 512 )
+            .append( ":maven-surefire-command:" )
+            .append( (char) 13 )
+            .append( ":run-testclass:" )
+            .append( (char) 5 )
+            .append( ":UTF-8:" )
+            .append( (char) ( clsLength >> 24 ) )
+            .append( (char) ( ( clsLength >> 16 ) & 0xff ) )
+            .append( (char) ( ( clsLength >> 8 ) & 0xff ) )
+            .append( (char) ( clsLength & 0xff ) )
+            .append( ":" )
+            .append( cls )
+            .append( ":" )
+            .toString();
+
+        for ( byte cmdByte : cmd.getBytes( US_ASCII ) )
         {
             blockingStream.add( cmdByte );
         }
@@ -258,7 +278,7 @@ public class CommandReaderTest
 
     private void addEndOfPipeline()
     {
-        for ( byte cmdByte : ":maven-surefire-command:testset-finished:".getBytes() )
+        for ( byte cmdByte : ( ":maven-surefire-command:" + (char) 16 + ":testset-finished:" ).getBytes( US_ASCII ) )
         {
             blockingStream.add( cmdByte );
         }
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 c1140c8..cb6c057 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
@@ -21,11 +21,12 @@ package org.apache.maven.surefire.booter;
 
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 import org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoder;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
@@ -44,6 +45,7 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+import javax.annotation.Nonnull;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.InetSocketAddress;
@@ -86,7 +88,7 @@ import static org.powermock.reflect.Whitebox.setInternalState;
 @PrepareForTest( {
                      PpidChecker.class,
                      ForkedBooter.class,
-                     LegacyMasterProcessChannelEncoder.class,
+                     EventChannelEncoder.class,
                      ShutdownHookUtils.class
 } )
 @PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
@@ -105,7 +107,7 @@ public class ForkedBooterMockTest
     private MasterProcessChannelProcessorFactory channelProcessorFactory;
 
     @Mock
-    private LegacyMasterProcessChannelEncoder eventChannel;
+    private EventChannelEncoder eventChannel;
 
     @Captor
     private ArgumentCaptor<StackTraceWriter> capturedStackTraceWriter;
@@ -292,10 +294,11 @@ public class ForkedBooterMockTest
 
             factory.connect( "pipe://3" );
 
-            MasterProcessChannelDecoder decoder = factory.createDecoder();
-            assertThat( decoder ).isInstanceOf( LegacyMasterProcessChannelDecoder.class );
-            MasterProcessChannelEncoder encoder = factory.createEncoder();
-            assertThat( encoder ).isInstanceOf( LegacyMasterProcessChannelEncoder.class );
+            ForkNodeArguments args = new ForkedNodeArg( 1 );
+            MasterProcessChannelDecoder decoder = factory.createDecoder( args );
+            assertThat( decoder ).isInstanceOf( CommandChannelDecoder.class );
+            MasterProcessChannelEncoder encoder = factory.createEncoder( args );
+            assertThat( encoder ).isInstanceOf( EventChannelEncoder.class );
         }
     }
 
@@ -318,13 +321,13 @@ public class ForkedBooterMockTest
             }
 
             @Override
-            public MasterProcessChannelDecoder createDecoder()
+            public MasterProcessChannelDecoder createDecoder( @Nonnull  ForkNodeArguments args )
             {
                 return null;
             }
 
             @Override
-            public MasterProcessChannelEncoder createEncoder()
+            public MasterProcessChannelEncoder createEncoder( @Nonnull ForkNodeArguments args )
             {
                 return null;
             }
@@ -411,13 +414,13 @@ public class ForkedBooterMockTest
                 } );
 
                 factory.connect( "tcp://localhost:" + serverPort );
-
-                MasterProcessChannelDecoder decoder = factory.createDecoder();
+                ForkNodeArguments args = new ForkedNodeArg( 1 );
+                MasterProcessChannelDecoder decoder = factory.createDecoder( args );
                 assertThat( decoder )
-                    .isInstanceOf( LegacyMasterProcessChannelDecoder.class );
-                MasterProcessChannelEncoder encoder = factory.createEncoder();
+                    .isInstanceOf( CommandChannelDecoder.class );
+                MasterProcessChannelEncoder encoder = factory.createEncoder( args );
                 assertThat( encoder )
-                    .isInstanceOf( LegacyMasterProcessChannelEncoder.class );
+                    .isInstanceOf( EventChannelEncoder.class );
             }
         }
     }
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 280774e..6c14e0e 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
@@ -23,8 +23,8 @@ import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoderTest;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoderTest;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoderTest;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoderTest;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
@@ -46,8 +46,8 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( BooterDeserializerTest.class ) );
         suite.addTestSuite( ClasspathTest.class );
         suite.addTestSuite( PropertiesWrapperTest.class );
-        suite.addTest( new JUnit4TestAdapter( LegacyMasterProcessChannelDecoderTest.class ) );
-        suite.addTest( new JUnit4TestAdapter( LegacyMasterProcessChannelEncoderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( CommandChannelDecoderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( EventChannelEncoderTest.class ) );
         suite.addTestSuite( SurefireReflectorTest.class );
         return suite;
     }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
similarity index 58%
rename from surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
index 4c0fb5b..33de107 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
@@ -20,15 +20,23 @@ package org.apache.maven.surefire.booter.spi;
  */
 
 import org.apache.maven.surefire.api.booter.Command;
+import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
 import org.apache.maven.surefire.api.booter.Shutdown;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.booter.ForkedNodeArg;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 
 import java.io.ByteArrayInputStream;
 import java.io.EOFException;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 
 import static java.nio.channels.Channels.newChannel;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
@@ -44,17 +52,47 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 /**
- * Tests for {@link LegacyMasterProcessChannelDecoder}.
+ * Tests for {@link CommandChannelDecoder}.
  */
-public class LegacyMasterProcessChannelDecoderTest
+public class CommandChannelDecoderTest
 {
+    @Rule
+    public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+    private File reportsDir;
+    private String dumpFileName;
+
+    @Before
+    public void initTmpFile() throws IOException
+    {
+        File tmp = tempFolder.newFile();
+        reportsDir = tmp.getParentFile();
+        dumpFileName = tmp.getName();
+    }
+
     @Test
     public void testDecoderRunClass() throws IOException
     {
         assertEquals( String.class, RUN_CLASS.getDataType() );
-        byte[] encoded = ":maven-surefire-command:run-testclass:pkg.Test:\n".getBytes();
+        byte[] encoded = new StringBuilder( 512 )
+            .append( ":maven-surefire-command:" )
+            .append( (char) 13 )
+            .append( ":run-testclass:" )
+            .append( (char) 5 )
+            .append( ":UTF-8:" )
+            .append( (char) 0 )
+            .append( (char) 0 )
+            .append( (char) 0 )
+            .append( (char) 8 )
+            .append( ":" )
+            .append( "pkg.Test" )
+            .append( ":" )
+            .toString()
+            .getBytes( UTF_8 );
         InputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( RUN_CLASS );
         assertThat( command.getData() ).isEqualTo( "pkg.Test" );
@@ -66,9 +104,11 @@ public class LegacyMasterProcessChannelDecoderTest
         Command command = Command.TEST_SET_FINISHED;
         assertThat( command.getCommandType() ).isSameAs( TEST_SET_FINISHED );
         assertEquals( Void.class, TEST_SET_FINISHED.getDataType() );
-        byte[] encoded = ":maven-surefire-command:testset-finished:".getBytes();
+        byte[] encoded = ":maven-surefire-command:\u0010:testset-finished:".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( TEST_SET_FINISHED );
         assertNull( command.getData() );
@@ -80,9 +120,11 @@ public class LegacyMasterProcessChannelDecoderTest
         Command command = Command.SKIP_SINCE_NEXT_TEST;
         assertThat( command.getCommandType() ).isSameAs( SKIP_SINCE_NEXT_TEST );
         assertEquals( Void.class, SKIP_SINCE_NEXT_TEST.getDataType() );
-        byte[] encoded = ":maven-surefire-command:skip-since-next-test:".getBytes();
+        byte[] encoded = ":maven-surefire-command:\u0014:skip-since-next-test:".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SKIP_SINCE_NEXT_TEST );
         assertNull( command.getData() );
@@ -93,9 +135,12 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         Shutdown shutdownType = EXIT;
         assertEquals( String.class, SHUTDOWN.getDataType() );
-        byte[] encoded = ( ":maven-surefire-command:shutdown:" + shutdownType + ":" ).getBytes();
+        byte[] encoded = ( ":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0004:"
+            + shutdownType.getParam() + ":" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
         assertThat( command.getData() ).isEqualTo( shutdownType.name() );
@@ -106,9 +151,12 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         Shutdown shutdownType = KILL;
         assertEquals( String.class, SHUTDOWN.getDataType() );
-        byte[] encoded = ( ":maven-surefire-command:shutdown:" + shutdownType + ":" ).getBytes();
+        byte[] encoded = ( ":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0004:"
+            + shutdownType.getParam() + ":" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
         assertThat( command.getData() ).isEqualTo( shutdownType.name() );
@@ -119,9 +167,12 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         Shutdown shutdownType = DEFAULT;
         assertEquals( String.class, SHUTDOWN.getDataType() );
-        byte[] encoded = ( ":maven-surefire-command:shutdown:" + shutdownType + ":" ).getBytes();
+        byte[] encoded = ( ":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0007:"
+            + shutdownType.getParam() + ":" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
         assertThat( command.getData() ).isEqualTo( shutdownType.name() );
@@ -132,9 +183,11 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         assertThat( NOOP ).isSameAs( Command.NOOP.getCommandType() );
         assertEquals( Void.class, NOOP.getDataType() );
-        byte[] encoded = ":maven-surefire-command:noop:".getBytes();
+        byte[] encoded = ":maven-surefire-command:\u0004:noop:".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( NOOP );
         assertNull( command.getData() );
@@ -145,10 +198,12 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         assertThat( BYE_ACK ).isSameAs( Command.BYE_ACK.getCommandType() );
         assertEquals( Void.class, BYE_ACK.getDataType() );
-        byte[] encoded = ":maven-surefire-command:bye-ack:".getBytes();
+        byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
         byte[] streamContent = ( "<something>" + new String( encoded ) + "<damaged>" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( streamContent );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
         assertNull( command.getData() );
@@ -159,10 +214,12 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         assertThat( BYE_ACK ).isSameAs( Command.BYE_ACK.getCommandType() );
         assertEquals( Void.class, BYE_ACK.getDataType() );
-        byte[] encoded = ":maven-surefire-command:bye-ack:".getBytes();
+        byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
         byte[] streamContent = ( ":<damaged>:" + new String( encoded ) ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( streamContent );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
         assertNull( command.getData() );
@@ -173,9 +230,11 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         assertThat( BYE_ACK ).isSameAs( Command.BYE_ACK.getCommandType() );
         assertEquals( Void.class, BYE_ACK.getDataType() );
-        byte[] encoded = ":maven-surefire-command:bye-ack:".getBytes();
+        byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
         assertNull( command.getData() );
@@ -184,9 +243,11 @@ public class LegacyMasterProcessChannelDecoderTest
     @Test
     public void shouldDecodeTwoCommands() throws IOException
     {
-        String cmd = ":maven-surefire-command:bye-ack:\r\n:maven-surefire-command:bye-ack:";
+        String cmd = ":maven-surefire-command:\u0007:bye-ack:\r\n:maven-surefire-command:\u0007:bye-ack:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
 
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isEqualTo( BYE_ACK );
@@ -204,7 +265,9 @@ public class LegacyMasterProcessChannelDecoderTest
     {
 
         ByteArrayInputStream is = new ByteArrayInputStream( ":maven-surefire-command:".getBytes() );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         decoder.decode();
         fail();
     }
@@ -214,7 +277,9 @@ public class LegacyMasterProcessChannelDecoderTest
     {
 
         ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {':', '\r'} );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         decoder.decode();
         fail();
     }
@@ -222,9 +287,11 @@ public class LegacyMasterProcessChannelDecoderTest
     @Test( expected = EOFException.class )
     public void shouldNotDecodeCorruptedCommand() throws IOException
     {
-        String cmd = ":maven-surefire-command:bye-ack ::maven-surefire-command:";
+        String cmd = ":maven-surefire-command:\u0007:bye-ack ::maven-surefire-command:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
 
         decoder.decode();
     }
@@ -232,9 +299,11 @@ public class LegacyMasterProcessChannelDecoderTest
     @Test
     public void shouldSkipCorruptedCommand() throws IOException
     {
-        String cmd = ":maven-surefire-command:bye-ack\r\n::maven-surefire-command:noop:";
+        String cmd = ":maven-surefire-command:\0007:bye-ack\r\n::maven-surefire-command:\u0004:noop:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
 
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( NOOP );
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/EventChannelEncoderTest.java
similarity index 93%
rename from surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/EventChannelEncoderTest.java
index 5a5e187..44abbf9 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/EventChannelEncoderTest.java
@@ -44,13 +44,13 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 /**
- * Test for {@link LegacyMasterProcessChannelEncoder}.
+ * Test for {@link EventChannelEncoder}.
  *
  * @author <a href="mailto:[hidden email]">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
 @SuppressWarnings( { "checkstyle:linelength", "checkstyle:magicnumber" } )
-public class LegacyMasterProcessChannelEncoderTest
+public class EventChannelEncoderTest
 {
     private static final int ELAPSED_TIME = 102;
     private static final byte[] ELAPSED_TIME_HEXA = new byte[] {0, 0, 0, 0x66};
@@ -80,7 +80,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         ByteBuffer encoded = encoder.encode( BOOTERCODE_TEST_ERROR, NORMAL_RUN, reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
         expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
@@ -135,7 +135,7 @@ public class LegacyMasterProcessChannelEncoderTest
             .isEqualTo( expectedFrame.toByteArray() );
 
         out = Stream.newStream();
-        encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         encoded = encoder.encode( BOOTERCODE_TEST_ERROR, NORMAL_RUN, reportEntry, true );
         expectedFrame = new ByteArrayOutputStream();
         expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
@@ -190,7 +190,7 @@ public class LegacyMasterProcessChannelEncoderTest
             .isEqualTo( expectedFrame.toByteArray() );
 
         out = Stream.newStream();
-        encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         encoder.testSetStarting( reportEntry, true );
         expectedFrame = new ByteArrayOutputStream();
         expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
@@ -243,7 +243,7 @@ public class LegacyMasterProcessChannelEncoderTest
             .isEqualTo( expectedFrame.toByteArray() );
 
         out = Stream.newStream();
-        encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetStarting( reportEntry, false );
         expectedFrame = new ByteArrayOutputStream();
@@ -322,7 +322,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetCompleted( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -401,7 +401,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testStarting( reportEntry, true );
 
@@ -481,7 +481,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSucceeded( reportEntry, true );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -560,7 +560,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testFailed( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -638,7 +638,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSkipped( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -716,7 +716,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         encoder.testError( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
         expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
@@ -793,7 +793,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testAssumptionFailure( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -850,7 +850,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testBye()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.bye();
 
@@ -864,7 +864,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStopOnNextTest()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.stopOnNextTest();
 
@@ -877,7 +877,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testAcquireNextTest()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.acquireNextTest();
 
@@ -890,18 +890,18 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testSendOpcode()
     {
         Channel channel = new Channel();
-        new LegacyMasterProcessChannelEncoder( channel ).stdOut( "msg", false );
+        new EventChannelEncoder( channel ).stdOut( "msg", false );
         assertThat( toString( channel.src ) )
                 .isEqualTo( ":maven-surefire-event:" + (char) 14 + ":std-out-stream:" + (char) 10 + ":normal-run:"
                     + (char) 5 + ":UTF-8:\u0000\u0000\u0000\u0003:msg:" );
 
         channel = new Channel();
-        new LegacyMasterProcessChannelEncoder( channel ).stdErr( null, false );
+        new EventChannelEncoder( channel ).stdErr( null, false );
         assertThat( toString( channel.src ) )
                 .isEqualTo( ":maven-surefire-event:" + (char) 14 + ":std-err-stream:" + (char) 10 + ":normal-run:"
                     + (char) 5 + ":UTF-8:\u0000\u0000\u0000\u0001:\u0000:" );
 
-        ByteBuffer result = new LegacyMasterProcessChannelEncoder( new Channel() )
+        ByteBuffer result = new EventChannelEncoder( new Channel() )
             .encodeMessage( BOOTERCODE_TEST_ERROR, NORMAL_RUN, "msg" );
         assertThat( toString( result ) )
             .isEqualTo( ":maven-surefire-event:" + (char) 10 + ":test-error:" + (char) 10 + ":normal-run:"
@@ -912,7 +912,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleInfo()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleInfoLog( "msg" );
 
@@ -928,7 +928,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleError()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleErrorLog( "msg" );
 
@@ -947,7 +947,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog1() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         Exception e = new Exception( "msg" );
         encoder.consoleErrorLog( e );
@@ -985,7 +985,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog2() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         Exception e = new Exception( "msg" );
         encoder.consoleErrorLog( "msg2", e );
@@ -1023,7 +1023,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog3()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
@@ -1041,7 +1041,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleDebug()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleDebugLog( "msg" );
 
@@ -1057,7 +1057,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleWarning()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleWarningLog( "msg" );
 
@@ -1074,7 +1074,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         encoder.stdOut( "msg", false );
         channel.close();
@@ -1091,7 +1091,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         encoder.stdOut( "msg", true );
         channel.close();
@@ -1108,7 +1108,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         encoder.stdErr( "msg", false );
         channel.close();
@@ -1125,7 +1125,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         encoder.stdErr( "msg", true );
         channel.close();
@@ -1143,10 +1143,10 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream stream = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( stream );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         Map<String, String> sysProps = ObjectUtils.systemProps();
-        encoder.sendSystemProperties( sysProps );
+        encoder.systemProperties( sysProps );
         channel.close();
 
         for ( Entry<String, String> entry : sysProps.entrySet() )
@@ -1177,7 +1177,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
@@ -1194,7 +1194,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
@@ -1211,7 +1211,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         Thread.currentThread().interrupt();
         try
diff --git a/surefire-extensions-spi/pom.xml b/surefire-extensions-spi/pom.xml
index 12bf299..43d0123 100644
--- a/surefire-extensions-spi/pom.xml
+++ b/surefire-extensions-spi/pom.xml
@@ -35,6 +35,11 @@
             <artifactId>surefire-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
index 8da6c11..486349b 100644
--- a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
+++ b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
@@ -21,7 +21,9 @@ package org.apache.maven.surefire.spi;
 
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 
+import javax.annotation.Nonnull;
 import java.io.Closeable;
 import java.io.IOException;
 
@@ -48,15 +50,15 @@ public interface MasterProcessChannelProcessorFactory extends Closeable
 
     /**
      * Decoder factory method.
-     *
+     * @param forkingArguments forking arguments
      * @return a new instance of decoder
      */
-    MasterProcessChannelDecoder createDecoder() throws IOException;
+    MasterProcessChannelDecoder createDecoder( @Nonnull ForkNodeArguments forkingArguments ) throws IOException;
 
     /**
      * Encoder factory method.
-     *
+     * @param forkingArguments forking arguments
      * @return a new instance of encoder
      */
-    MasterProcessChannelEncoder createEncoder() throws IOException;
+    MasterProcessChannelEncoder createEncoder( @Nonnull ForkNodeArguments forkingArguments ) throws IOException;
 }
diff --git a/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/PrintStreamLogger.java b/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/PrintStreamLogger.java
index b3ecb46..e1425fa 100644
--- a/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/PrintStreamLogger.java
+++ b/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/PrintStreamLogger.java
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.log.api;
 import java.io.PrintStream;
 
 /**
- * For testing purposes.
+ * Console logger for {@link PrintStream}.
  */
 public class PrintStreamLogger
     implements ConsoleLogger

Reply | Threaded
Open this post in threaded view
|

[maven-surefire] 02/02: fixed decoder and tests + logger of binary stream

Tibor Digana
In reply to this post by Tibor Digana
This is an automated email from the ASF dual-hosted git repository.

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

commit 67bc6e2e995816c9ec6f8822f78807f6eb470889
Author: tibordigana <[hidden email]>
AuthorDate: Tue Jan 12 08:51:50 2021 +0100

    fixed decoder and tests + logger of binary stream
---
 .../maven/plugin/surefire/SurefireHelper.java      |   2 +
 .../plugin/surefire/booterclient/ForkStarter.java  |  13 ++
 .../output/InPluginProcessDumpSingleton.java       |   6 +
 .../booterclient/ForkingRunListenerTest.java       |  12 ++
 .../TestLessInputStreamBuilderTest.java            |   3 +-
 .../TestProvidingInputStreamTest.java              |   3 +-
 .../booterclient/output/ForkClientTest.java        |  12 ++
 .../maven/plugin/surefire/extensions/E2ETest.java  |  12 ++
 .../extensions/EventConsumerThreadTest.java        |  12 ++
 .../extensions/ForkedProcessEventNotifierTest.java |  12 ++
 .../maven/surefire/extensions/ForkChannelTest.java |  12 ++
 .../maven/surefire/stream/EventDecoderTest.java    |  12 ++
 .../surefire/api/booter/DumpErrorSingleton.java    |   9 +
 .../maven/surefire/api/fork/ForkNodeArguments.java |   4 +
 .../surefire/api/stream/AbstractStreamDecoder.java |   5 +
 .../api/stream/AbstractStreamDecoderTest.java      |  12 ++
 .../apache/maven/surefire/booter/ForkedBooter.java |   7 +-
 .../maven/surefire/booter/ForkedNodeArg.java       |  16 +-
 .../surefire/booter/stream/CommandDecoder.java     |  65 ++++++
 .../maven/surefire/booter/CommandReaderTest.java   |   4 +-
 .../surefire/booter/ForkedBooterMockTest.java      |   4 +-
 .../booter/spi/CommandChannelDecoderTest.java      | 237 +++++++++++++++++++--
 .../resources/binary-commands/75171711-encoder.bin | Bin 0 -> 851 bytes
 23 files changed, 451 insertions(+), 23 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
index d0cac4d..9149946 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
@@ -64,6 +64,8 @@ public final class SurefireHelper
 
     public static final String DUMP_FILENAME = DUMP_FILE_DATE + DUMP_FILE_EXT;
 
+    public static final String EVENTS_BINARY_DUMP_FILENAME_FORMATTER = DUMP_FILE_DATE + "-jvmRun%d-events.bin";
+
     /**
      * The maximum path that does not require long path prefix on Windows.<br>
      * See {@code sun/nio/fs/WindowsPath} in
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 3ebc935..55647a1 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
@@ -932,5 +932,18 @@ public class ForkStarter
         {
             return log;
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return InPluginProcessDumpSingleton.getSingleton()
+                .getEventStreamBinaryFile( dumpLogDir, forkChannelId );
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java
index 372c046..aa4fa3c 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java
@@ -28,6 +28,7 @@ import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILENAME;
 import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILENAME_FORMATTER;
 import static org.apache.maven.plugin.surefire.SurefireHelper.DUMPSTREAM_FILENAME;
 import static org.apache.maven.plugin.surefire.SurefireHelper.DUMPSTREAM_FILENAME_FORMATTER;
+import static org.apache.maven.plugin.surefire.SurefireHelper.EVENTS_BINARY_DUMP_FILENAME_FORMATTER;
 
 /**
  * Reports errors to dump file.
@@ -82,6 +83,11 @@ public final class InPluginProcessDumpSingleton
         DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dump );
     }
 
+    public File getEventStreamBinaryFile( File reportsDirectory, int jvmRun )
+    {
+        return new File( reportsDirectory, format( EVENTS_BINARY_DUMP_FILENAME_FORMATTER, jvmRun ) );
+    }
+
     private File newDumpStreamFile( File reportsDirectory )
     {
         return new File( reportsDirectory, DUMPSTREAM_FILENAME );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
index e5520f7..48eb35f 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
@@ -375,6 +375,18 @@ public class ForkingRunListenerTest
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 
     private static class EH implements EventHandler<Event>
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 41b3db3..87bad45 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
@@ -192,7 +192,8 @@ public class TestLessInputStreamBuilderTest
                 throw new IOException();
             }
         };
-        MasterProcessChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), new ForkedNodeArg( 1 ) );
+        MasterProcessChannelDecoder decoder =
+            new CommandChannelDecoder( newChannel( is ), new ForkedNodeArg( 1, false ) );
         builder.getImmediateCommands().shutdown( KILL );
         builder.getImmediateCommands().noop();
         Command bye = decoder.decode();
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 bafcea5..29a5745 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
@@ -189,7 +189,8 @@ public class TestProvidingInputStreamTest
                 throw new IOException();
             }
         };
-        MasterProcessChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), new ForkedNodeArg( 1 ) );
+        MasterProcessChannelDecoder decoder =
+            new CommandChannelDecoder( newChannel( is ), new ForkedNodeArg( 1, false ) );
         pluginIs.acknowledgeByeEventReceived();
         pluginIs.noop();
         Command bye = decoder.decode();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
index 8cda588..531bafb 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -1884,6 +1884,18 @@ public class ForkClientTest
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 
     /**
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
index 37bc259..87bcca7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
@@ -333,5 +333,17 @@ public class E2ETest
         {
             return logger;
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
index 4bd5c13..9357080 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -227,5 +227,17 @@ public class EventConsumerThreadTest
         {
             return null;
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
index ee0007c..558c021 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
@@ -1188,6 +1188,18 @@ public class ForkedProcessEventNotifierTest
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 
     /**
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
index 34d3325..428ab10 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
@@ -68,6 +68,18 @@ public class ForkChannelTest
         final String sessionId = UUID.randomUUID().toString();
         ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
         {
+            @Override
+            public File getEventStreamBinaryFile()
+            {
+                return null;
+            }
+
+            @Override
+            public File getCommandStreamBinaryFile()
+            {
+                return null;
+            }
+
             @Nonnull
             @Override
             public String getSessionId()
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
index 31fb51e..ccfd3f0 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
@@ -768,6 +768,18 @@ public class EventDecoderTest
         {
             return null;
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
index 6cc7db8..381f852 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
@@ -40,6 +40,7 @@ public final class DumpErrorSingleton
 
     private File dumpFile;
     private File dumpStreamFile;
+    private File binaryDumpStreamFile;
 
     private DumpErrorSingleton()
     {
@@ -54,6 +55,9 @@ public final class DumpErrorSingleton
     {
         dumpFile = createDumpFile( reportsDir, dumpFileName );
         dumpStreamFile = createDumpStreamFile( reportsDir, dumpFileName );
+        String fileNameWithoutExtension =
+            dumpFileName.contains( "." ) ? dumpFileName.substring( 0, dumpFileName.lastIndexOf( '.' ) ) : dumpFileName;
+        binaryDumpStreamFile = createDumpStreamFile( reportsDir, fileNameWithoutExtension + "-commands.bin" );
     }
 
     public synchronized File dumpException( Throwable t, String msg )
@@ -92,6 +96,11 @@ public final class DumpErrorSingleton
         return dumpStreamFile;
     }
 
+    public File getCommandStreamBinaryFile()
+    {
+        return binaryDumpStreamFile;
+    }
+
     private File createDumpFile( File reportsDir, String dumpFileName )
     {
         return newDumpFile( reportsDir, dumpFileName + DUMP_FILE_EXT );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
index 0d8aa75..7d20e0e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
@@ -54,4 +54,8 @@ public interface ForkNodeArguments
 
     @Nonnull
     ConsoleLogger getConsoleLogger();
+
+    File getEventStreamBinaryFile();
+
+    File getCommandStreamBinaryFile();
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
index d0daa25..e989d12 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
@@ -106,6 +106,10 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
         return arguments;
     }
 
+    protected void debugStream( byte[] array, int position, int remaining )
+    {
+    }
+
     protected MT readMessageType( @Nonnull Memento memento ) throws IOException, MalformedFrameException
     {
         byte[] header = getEncodedMagicNumber();
@@ -468,6 +472,7 @@ public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends E
             }
             else
             {
+                debugStream( buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining() );
                 return readBytes >= recommendedCount ? OVERFLOW : UNDERFLOW;
             }
         }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
index fb61e4c..3f4c0bd 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
@@ -633,6 +633,18 @@ public class AbstractStreamDecoderTest
         {
             return null;
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 
     private static class Mock extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType>
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 f6a19e8..f00cb6a 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
@@ -62,6 +62,7 @@ import static java.lang.Thread.currentThread;
 import static java.util.ServiceLoader.load;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.maven.surefire.api.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
 import static org.apache.maven.surefire.api.util.ReflectionUtils.instantiateOneArg;
 import static org.apache.maven.surefire.api.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
 import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
@@ -128,7 +129,9 @@ public final class ForkedBooter
         String channelConfig = booterDeserializer.getConnectionString();
         channelProcessorFactory = lookupDecoderFactory( channelConfig );
         channelProcessorFactory.connect( channelConfig );
-        ForkNodeArguments args = new ForkedNodeArg( forkNumber );
+        boolean isDebugging = isDebugging();
+        boolean debug = isDebugging || providerConfiguration.getMainCliOptions().contains( LOGGING_LEVEL_DEBUG );
+        ForkNodeArguments args = new ForkedNodeArg( forkNumber, debug );
         eventChannel = channelProcessorFactory.createEncoder( args );
         MasterProcessChannelDecoder decoder = channelProcessorFactory.createDecoder( args );
 
@@ -138,7 +141,7 @@ public final class ForkedBooter
         ConsoleLogger logger = (ConsoleLogger) forkingReporterFactory.createReporter();
         commandReader = new CommandReader( decoder, providerConfiguration.getShutdown(), logger );
 
-        pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid(), logger );
+        pingScheduler = isDebugging ? null : listenToShutdownCommands( booterDeserializer.getPluginPid(), logger );
 
         systemExitTimeoutInSeconds = providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
index cc375b5..4bed5ca 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
@@ -34,11 +34,13 @@ public final class ForkedNodeArg implements ForkNodeArguments
 {
     private final int forkChannelId;
     private final ConsoleLogger logger;
+    private final boolean isDebug;
 
-    public ForkedNodeArg( int forkChannelId )
+    public ForkedNodeArg( int forkChannelId, boolean isDebug )
     {
         this.forkChannelId = forkChannelId;
         logger = new NullConsoleLogger();
+        this.isDebug = isDebug;
     }
 
     @Nonnull
@@ -80,4 +82,16 @@ public final class ForkedNodeArg implements ForkNodeArguments
     {
         return logger;
     }
+
+    @Override
+    public File getEventStreamBinaryFile()
+    {
+        return null;
+    }
+
+    @Override
+    public File getCommandStreamBinaryFile()
+    {
+        return isDebug ? DumpErrorSingleton.getSingleton().getCommandStreamBinaryFile() : null;
+    }
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
index b7fc186..873136f 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
@@ -29,8 +29,14 @@ import org.apache.maven.surefire.api.stream.MalformedChannelException;
 import org.apache.maven.surefire.api.stream.SegmentType;
 
 import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.nio.channels.ReadableByteChannel;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
 
 import static org.apache.maven.surefire.api.booter.Command.BYE_ACK;
 import static org.apache.maven.surefire.api.booter.Command.NOOP;
@@ -43,6 +49,7 @@ import static org.apache.maven.surefire.api.booter.MasterProcessCommand.COMMAND_
 import static org.apache.maven.surefire.api.report.RunMode.RUN_MODES;
 import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
 import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
+import static org.apache.maven.surefire.api.stream.SegmentType.RUN_MODE;
 import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
 
 /**
@@ -56,6 +63,13 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
         END_OF_FRAME
     };
 
+    private static final SegmentType[] COMMAND_WITH_RUNNABLE_STRING = new SegmentType[] {
+        RUN_MODE,
+        STRING_ENCODING,
+        DATA_STRING,
+        END_OF_FRAME
+    };
+
     private static final SegmentType[] COMMAND_WITH_ONE_STRING = new SegmentType[] {
         STRING_ENCODING,
         DATA_STRING,
@@ -63,12 +77,14 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
     };
 
     private final ForkNodeArguments arguments;
+    private final OutputStream debugSink;
 
     public CommandDecoder( @Nonnull ReadableByteChannel channel,
                            @Nonnull ForkNodeArguments arguments )
     {
         super( channel, arguments, COMMAND_TYPES );
         this.arguments = arguments;
+        debugSink = newDebugSink();
     }
 
     @Override
@@ -160,6 +176,7 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
             case TEST_SET_FINISHED:
                 return COMMAND_WITHOUT_DATA;
             case RUN_CLASS:
+                return COMMAND_WITH_RUNNABLE_STRING;
             case SHUTDOWN:
                 return COMMAND_WITH_ONE_STRING;
             default:
@@ -196,4 +213,52 @@ public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcess
                 throw new IllegalArgumentException( "Missing a branch for the event type " + commandType );
         }
     }
+
+    @Override
+    protected void debugStream( byte[] array, int position, int remaining )
+    {
+        if ( debugSink == null )
+        {
+            return;
+        }
+
+        try
+        {
+            debugSink.write( array, position, remaining );
+            debugSink.flush();
+        }
+        catch ( IOException e )
+        {
+            // logger file was deleted
+            // System.out is already used by the stream in this decoder
+        }
+    }
+
+    private OutputStream newDebugSink()
+    {
+        final File sink = arguments.getCommandStreamBinaryFile();
+        if ( sink == null )
+        {
+            return null;
+        }
+
+        try
+        {
+            final OutputStream os = new FileOutputStream( sink, true );
+            Runtime.getRuntime().addShutdownHook( new Thread( new FutureTask<>( new Callable<Void>()
+            {
+                @Override
+                public Void call() throws Exception
+                {
+                    os.close();
+                    return null;
+                }
+            } ) ) );
+            return os;
+        }
+        catch ( FileNotFoundException e )
+        {
+            return null;
+        }
+    }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index 12df54d..2cb7267 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -96,7 +96,7 @@ public class CommandReaderTest
         InputStream realInputStream = new SystemInputStream();
         addTestToPipeline( getClass().getName() );
         ConsoleLogger logger = new NullConsoleLogger();
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         MasterProcessChannelDecoder decoder =
             new CommandChannelDecoder( newChannel( realInputStream ), args );
         reader = new CommandReader( decoder, Shutdown.DEFAULT, logger );
@@ -259,6 +259,8 @@ public class CommandReaderTest
             .append( ":maven-surefire-command:" )
             .append( (char) 13 )
             .append( ":run-testclass:" )
+            .append( (char) 10 )
+            .append( ":normal-run:" )
             .append( (char) 5 )
             .append( ":UTF-8:" )
             .append( (char) ( clsLength >> 24 ) )
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 cb6c057..0f3d8db 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
@@ -294,7 +294,7 @@ public class ForkedBooterMockTest
 
             factory.connect( "pipe://3" );
 
-            ForkNodeArguments args = new ForkedNodeArg( 1 );
+            ForkNodeArguments args = new ForkedNodeArg( 1, false );
             MasterProcessChannelDecoder decoder = factory.createDecoder( args );
             assertThat( decoder ).isInstanceOf( CommandChannelDecoder.class );
             MasterProcessChannelEncoder encoder = factory.createEncoder( args );
@@ -414,7 +414,7 @@ public class ForkedBooterMockTest
                 } );
 
                 factory.connect( "tcp://localhost:" + serverPort );
-                ForkNodeArguments args = new ForkedNodeArg( 1 );
+                ForkNodeArguments args = new ForkedNodeArg( 1, false );
                 MasterProcessChannelDecoder decoder = factory.createDecoder( args );
                 assertThat( decoder )
                     .isInstanceOf( CommandChannelDecoder.class );
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
index 33de107..f31d9d8 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.booter.spi;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.Command;
 import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
 import org.apache.maven.surefire.api.booter.Shutdown;
@@ -29,11 +30,13 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+import javax.annotation.Nonnull;
 import java.io.ByteArrayInputStream;
 import java.io.EOFException;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 import static java.nio.channels.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -54,6 +57,7 @@ import static org.junit.Assert.fail;
 /**
  * Tests for {@link CommandChannelDecoder}.
  */
+@SuppressWarnings( "checkstyle:magicnumber" )
 public class CommandChannelDecoderTest
 {
     @Rule
@@ -78,6 +82,8 @@ public class CommandChannelDecoderTest
             .append( ":maven-surefire-command:" )
             .append( (char) 13 )
             .append( ":run-testclass:" )
+            .append( (char) 10 )
+            .append( ":normal-run:" )
             .append( (char) 5 )
             .append( ":UTF-8:" )
             .append( (char) 0 )
@@ -91,7 +97,7 @@ public class CommandChannelDecoderTest
             .getBytes( UTF_8 );
         InputStream is = new ByteArrayInputStream( encoded );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( RUN_CLASS );
@@ -107,7 +113,7 @@ public class CommandChannelDecoderTest
         byte[] encoded = ":maven-surefire-command:\u0010:testset-finished:".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( TEST_SET_FINISHED );
@@ -123,7 +129,7 @@ public class CommandChannelDecoderTest
         byte[] encoded = ":maven-surefire-command:\u0014:skip-since-next-test:".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SKIP_SINCE_NEXT_TEST );
@@ -139,7 +145,7 @@ public class CommandChannelDecoderTest
             + shutdownType.getParam() + ":" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
@@ -155,7 +161,7 @@ public class CommandChannelDecoderTest
             + shutdownType.getParam() + ":" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
@@ -171,7 +177,7 @@ public class CommandChannelDecoderTest
             + shutdownType.getParam() + ":" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
@@ -186,7 +192,7 @@ public class CommandChannelDecoderTest
         byte[] encoded = ":maven-surefire-command:\u0004:noop:".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( NOOP );
@@ -202,7 +208,7 @@ public class CommandChannelDecoderTest
         byte[] streamContent = ( "<something>" + new String( encoded ) + "<damaged>" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( streamContent );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
@@ -218,7 +224,7 @@ public class CommandChannelDecoderTest
         byte[] streamContent = ( ":<damaged>:" + new String( encoded ) ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( streamContent );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
@@ -233,7 +239,7 @@ public class CommandChannelDecoderTest
         byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
@@ -246,7 +252,7 @@ public class CommandChannelDecoderTest
         String cmd = ":maven-surefire-command:\u0007:bye-ack:\r\n:maven-surefire-command:\u0007:bye-ack:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
 
         Command command = decoder.decode();
@@ -266,7 +272,7 @@ public class CommandChannelDecoderTest
 
         ByteArrayInputStream is = new ByteArrayInputStream( ":maven-surefire-command:".getBytes() );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         decoder.decode();
         fail();
@@ -278,7 +284,7 @@ public class CommandChannelDecoderTest
 
         ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {':', '\r'} );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
         decoder.decode();
         fail();
@@ -290,7 +296,7 @@ public class CommandChannelDecoderTest
         String cmd = ":maven-surefire-command:\u0007:bye-ack ::maven-surefire-command:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
 
         decoder.decode();
@@ -302,11 +308,212 @@ public class CommandChannelDecoderTest
         String cmd = ":maven-surefire-command:\0007:bye-ack\r\n::maven-surefire-command:\u0004:noop:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
         DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
-        ForkNodeArguments args = new ForkedNodeArg( 1 );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
         CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
 
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( NOOP );
         assertNull( command.getData() );
     }
+
+    @Test
+    public void testBinaryCommandStream() throws Exception
+    {
+        InputStream commands = getClass().getResourceAsStream( "/binary-commands/75171711-encoder.bin" );
+        ConsoleLoggerMock logger = new ConsoleLoggerMock( true, true, true, true );
+        ForkNodeArguments args = new ForkNodeArgumentsMock( logger, new File( "" ) );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( commands ), args );
+
+        Command command = decoder.decode();
+        assertThat( command ).isNotNull();
+        assertThat( command.getCommandType() ).isEqualTo( NOOP );
+        assertThat( command.getData() ).isNull();
+
+        command = decoder.decode();
+        assertThat( command ).isNotNull();
+        assertThat( command.getCommandType() ).isEqualTo( RUN_CLASS );
+        assertThat( command.getData() ).isEqualTo( "pkg.ATest" );
+
+        for ( int i = 0; i < 24; i++ )
+        {
+            command = decoder.decode();
+            assertThat( command ).isNotNull();
+            assertThat( command.getCommandType() ).isEqualTo( NOOP );
+            assertThat( command.getData() ).isNull();
+        }
+    }
+
+    /**
+     * Threadsafe impl. Mockito and Powermock are not thread-safe.
+     */
+    private static class ForkNodeArgumentsMock implements ForkNodeArguments
+    {
+        private final ConcurrentLinkedQueue<String> dumpStreamText = new ConcurrentLinkedQueue<>();
+        private final ConcurrentLinkedQueue<String> logWarningAtEnd = new ConcurrentLinkedQueue<>();
+        private final ConsoleLogger logger;
+        private final File dumpStreamTextFile;
+
+        ForkNodeArgumentsMock( ConsoleLogger logger, File dumpStreamTextFile )
+        {
+            this.logger = logger;
+            this.dumpStreamTextFile = dumpStreamTextFile;
+        }
+
+        @Nonnull
+        @Override
+        public String getSessionId()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getForkChannelId()
+        {
+            return 0;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamText( @Nonnull String text )
+        {
+            dumpStreamText.add( text );
+            return dumpStreamTextFile;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamException( @Nonnull Throwable t )
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void logWarningAtEnd( @Nonnull String text )
+        {
+            logWarningAtEnd.add( text );
+        }
+
+        @Nonnull
+        @Override
+        public ConsoleLogger getConsoleLogger()
+        {
+            return logger;
+        }
+
+        boolean isCalled()
+        {
+            return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
+        }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Threadsafe impl. Mockito and Powermock are not thread-safe.
+     */
+    private static class ConsoleLoggerMock implements ConsoleLogger
+    {
+        final ConcurrentLinkedQueue<String> debug = new ConcurrentLinkedQueue<>();
+        final ConcurrentLinkedQueue<String> info = new ConcurrentLinkedQueue<>();
+        final ConcurrentLinkedQueue<String> error = new ConcurrentLinkedQueue<>();
+        final boolean isDebug;
+        final boolean isInfo;
+        final boolean isWarning;
+        final boolean isError;
+        boolean called;
+        boolean isDebugEnabledCalled;
+        boolean isInfoEnabledCalled;
+
+        ConsoleLoggerMock( boolean isDebug, boolean isInfo, boolean isWarning, boolean isError )
+        {
+            this.isDebug = isDebug;
+            this.isInfo = isInfo;
+            this.isWarning = isWarning;
+            this.isError = isError;
+        }
+
+        @Override
+        public boolean isDebugEnabled()
+        {
+            isDebugEnabledCalled = true;
+            called = true;
+            return isDebug;
+        }
+
+        @Override
+        public void debug( String message )
+        {
+            debug.add( message );
+            called = true;
+        }
+
+        @Override
+        public boolean isInfoEnabled()
+        {
+            isInfoEnabledCalled = true;
+            called = true;
+            return isInfo;
+        }
+
+        @Override
+        public void info( String message )
+        {
+            info.add( message );
+            called = true;
+        }
+
+        @Override
+        public boolean isWarnEnabled()
+        {
+            called = true;
+            return isWarning;
+        }
+
+        @Override
+        public void warning( String message )
+        {
+            called = true;
+        }
+
+        @Override
+        public boolean isErrorEnabled()
+        {
+            called = true;
+            return isError;
+        }
+
+        @Override
+        public void error( String message )
+        {
+            error.add( message );
+            called = true;
+        }
+
+        @Override
+        public void error( String message, Throwable t )
+        {
+            called = true;
+        }
+
+        @Override
+        public void error( Throwable t )
+        {
+            called = true;
+        }
+
+        boolean isCalled()
+        {
+            return called;
+        }
+    }
 }
diff --git a/surefire-booter/src/test/resources/binary-commands/75171711-encoder.bin b/surefire-booter/src/test/resources/binary-commands/75171711-encoder.bin
new file mode 100644
index 0000000..bbc337b
Binary files /dev/null and b/surefire-booter/src/test/resources/binary-commands/75171711-encoder.bin differ