You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2020/08/25 17:26:24 UTC

[maven-surefire] branch base64 updated (507453c -> 390cf0b)

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

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


 discard 507453c  300 - 400 nanos per decoding std-out (100 chars/line)
 discard f9f791e  ForkedProcessEventType with complext length computation - remove it in the next commit
     new 390cf0b  300 - 400 nanos per decoding std-out (100 chars/line)

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

 * -- * -- B -- O -- O -- O   (507453c)
            \
             N -- N -- N   refs/heads/base64 (390cf0b)

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

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

The 1 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-surefire] 01/01: 300 - 400 nanos per decoding std-out (100 chars/line)

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

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

commit 390cf0b294bf4a94fe2fb1639d7c643098e56bfa
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Aug 1 23:09:34 2020 +0200

    300 - 400 nanos per decoding std-out (100 chars/line)
---
 .../surefire/extensions/EventConsumerThread.java   | 805 +++++++++++++++------
 .../extensions/EventConsumerThreadTest.java        | 492 +++++++++++++
 .../api/booter/ForkedProcessEventType.java         | 230 +++++-
 3 files changed, 1300 insertions(+), 227 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
index 1d5686b..b74cb5e 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
@@ -44,28 +44,37 @@ 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.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-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.shared.codec.binary.Base64;
 
 import javax.annotation.Nonnull;
 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.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
-import java.util.Iterator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import static java.lang.Math.max;
+import static java.nio.charset.CodingErrorAction.REPLACE;
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.MAGIC_NUMBER;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.EOF;
+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.MAGIC_NUMBER;
+import static org.apache.maven.surefire.api.booter.Constants.STREAM_ENCODING;
 import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
 import static org.apache.maven.surefire.api.report.RunMode.MODES;
 
@@ -83,7 +92,10 @@ public class EventConsumerThread extends CloseableDaemonThread
             "java.lang.module.findexception" // JPMS errors
         };
     private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
-    private static final Base64 BASE64 = new Base64();
+    private static final byte[] MAGIC_NUMBER_BYTES = MAGIC_NUMBER.getBytes( US_ASCII );
+    private static final int DELIMINATOR_LENGTH = 1;
+    private static final int BYTE_LENGTH = 1;
+    private static final int INT_LENGTH = 4;
 
     private final ReadableByteChannel channel;
     private final EventHandler<Event> eventHandler;
@@ -133,153 +145,307 @@ public class EventConsumerThread extends CloseableDaemonThread
     @SuppressWarnings( "checkstyle:innerassignment" )
     private void decode() throws IOException
     {
-        List<String> tokens = new ArrayList<>();
-        StringBuilder line = new StringBuilder();
-        StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
-        ByteBuffer buffer = ByteBuffer.allocate( 1024 );
-        buffer.position( buffer.limit() );
-        boolean streamContinues;
+        Map<Segment, ForkedProcessEventType> events = mapEventTypes();
+        Memento memento = new Memento();
+        BufferedStream line = new BufferedStream( 32 );
+        memento.bb.limit( 0 );
+        boolean streamContinues = true;
 
         start:
         do
         {
-            line.setLength( 0 );
-            tokens.clear();
-            token.setLength( 0 );
-            FrameCompletion completion = null;
-            for ( boolean frameStarted = false; streamContinues = read( buffer ); completion = null )
+            if ( !streamContinues )
             {
-                char c = (char) buffer.get();
+                printExistingLine( line );
+                return;
+            }
 
-                if ( c == '\n' || c == '\r' )
-                {
-                    printExistingLine( line );
-                    continue start;
-                }
+            line.reset();
+            memento.segment.reset();
 
-                line.append( c );
+            int readCount =
+                DELIMINATOR_LENGTH + MAGIC_NUMBER_BYTES.length + DELIMINATOR_LENGTH + BYTE_LENGTH + DELIMINATOR_LENGTH;
+            streamContinues = read( memento.bb, readCount ) != EOF;
+            if ( memento.bb.remaining() < readCount )
+            {
+                //todo throw exception - broken stream
+            }
+            checkHeader( memento.bb, memento.segment );
 
-                if ( !frameStarted )
+            memento.eventType = events.get( readSegment( memento.bb ) );
+
+            for ( SegmentType segmentType : nextSegmentType( memento ) )
+            {
+                if ( segmentType == null )
                 {
-                    if ( c == ':' )
-                    {
-                        frameStarted = true;
-                        token.setLength( 0 );
-                        tokens.clear();
-                    }
+                    break;
                 }
-                else
+
+                switch ( segmentType )
                 {
-                    if ( c == ':' )
-                    {
-                        tokens.add( token.toString() );
-                        token.setLength( 0 );
-                        completion = frameCompleteness( tokens );
-                        if ( completion == FrameCompletion.COMPLETE )
+                    case RUN_MODE:
+                        memento.runMode = MODES.get( readSegment( memento ) );
+                        break;
+                    case STRING_ENCODING:
+                        //todo handle exceptions
+                        memento.charset = Charset.forName( readSegment( memento ) );
+                        break;
+                    case BYTES_INT_COUNTER:
+                        memento.bytesCounter = readInt( memento.bb );
+                        break;
+                    case DATA_STRING:
+                        memento.cb.clear();
+                        int bytesCounter = memento.bytesCounter;
+                        if ( bytesCounter == 0 )
+                        {
+                            memento.data.add( "" );
+                        }
+                        else if ( bytesCounter == 1 )
                         {
-                            line.setLength( 0 );
-                            break;
+                            // handle the returned boolean
+                            read( memento.bb, 1 );
+                            byte oneChar = memento.bb.get();
+                            memento.data.add( oneChar == 0 ? null : String.valueOf( (char) oneChar ) );
                         }
-                        else if ( completion == FrameCompletion.MALFORMED )
+                        else
                         {
-                            printExistingLine( line );
-                            continue start;
+                            memento.data.add( readString( memento ) );
                         }
-                    }
-                    else
-                    {
-                        token.append( c );
-                    }
+                        memento.bytesCounter = 0;
+                        break;
+                    case DATA_INT:
+                        memento.data.add( readInt( memento.bb ) );
+                        break;
+                    case END_OF_FRAME:
+                        eventHandler.handleEvent( toEvent( memento ) );
+                        continue start;
+                    default:
+                        throw new IllegalArgumentException( "Unknown enum " + segmentType );
                 }
-            }
 
-            if ( completion == FrameCompletion.COMPLETE )
-            {
-                Event event = toEvent( tokens );
-                if ( !disabled && event != null )
+                memento.cb.clear();
+
+                read( memento.bb, 1 );
+                b = 0xff & memento.bb.get();
+                if ( Character.isDefined( b ) && b != ':' )
                 {
-                    eventHandler.handleEvent( event );
+                    //memento.segmentCompletion = SegmentCompletion.;
+                    //MalformedStreamException
+                    continue start;
                 }
             }
 
-            if ( !streamContinues )
+            memento.bb.flip();
+            line.write( memento.bb );
+            memento.reset();
+        }
+        while ( true );
+    }
+
+    @Nonnull
+    private static Segment readSegment( ByteBuffer bb )
+    {
+        int readCount = bb.get() & 0xff;
+        Segment segment = new Segment( bb.array(), bb.arrayOffset() + bb.position(), readCount );
+        bb.position( bb.position() + readCount );
+        checkDelimiter( bb );
+        return segment;
+    }
+
+    private static void checkHeader( ByteBuffer bb, BufferedStream segment )
+    {
+        checkDelimiter( bb );
+
+        segment.reset();
+
+        int shift = 0;
+        for ( int start = bb.arrayOffset() + bb.position(), length = MAGIC_NUMBER_BYTES.length;
+              shift < length; shift++ )
+        {
+            if ( bb.array()[shift + start] != MAGIC_NUMBER_BYTES[shift] )
             {
-                printExistingLine( line );
-                return;
+                //todo throw exception - broken stream
             }
         }
-        while ( true );
+        bb.position( bb.position() + shift );
+
+        checkDelimiter( bb );
     }
 
-    private boolean read( ByteBuffer buffer ) throws IOException
+    private static void checkDelimiter( ByteBuffer bb )
     {
-        if ( buffer.hasRemaining() && buffer.position() > 0 )
+        if ( ( 0xff & bb.get() ) != ':' )
         {
-            return true;
+            //todo throw exception - broken stream
         }
-        else
+    }
+
+    private static SegmentType[] nextSegmentType( Memento memento )
+    {
+        switch ( memento.eventType )
         {
-            buffer.clear();
-            boolean isEndOfStream = channel.read( buffer ) == -1;
-            buffer.flip();
-            return !isEndOfStream;
+            case BOOTERCODE_BYE:
+            case BOOTERCODE_STOP_ON_NEXT_TEST:
+            case BOOTERCODE_NEXT_TEST:
+                return new SegmentType[] {SegmentType.END_OF_FRAME};
+            case BOOTERCODE_CONSOLE_ERROR:
+            case BOOTERCODE_JVM_EXIT_ERROR:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            case BOOTERCODE_CONSOLE_INFO:
+            case BOOTERCODE_CONSOLE_DEBUG:
+            case BOOTERCODE_CONSOLE_WARNING:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            case BOOTERCODE_STDOUT:
+            case BOOTERCODE_STDOUT_NEW_LINE:
+            case BOOTERCODE_STDERR:
+            case BOOTERCODE_STDERR_NEW_LINE:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.RUN_MODE,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            case BOOTERCODE_SYSPROPS:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.RUN_MODE,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            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 new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.RUN_MODE,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.DATA_INT,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            default:
+                throw new IllegalArgumentException( "Unknown enum " + memento.eventType );
         }
     }
 
-    private void printExistingLine( StringBuilder line )
+    private StreamReadStatus read( ByteBuffer buffer, int recommendedCount ) throws IOException
     {
-        if ( line.length() != 0 )
+        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+        {
+            return OVERFLOW;
+        }
+        else
         {
-            ConsoleLogger logger = arguments.getConsoleLogger();
-            String s = line.toString().trim();
-            if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+            if ( buffer.position() != 0 && recommendedCount > buffer.capacity() - buffer.position() )
             {
-                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 );
-                }
+                buffer.compact().flip();
             }
-            else
+            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() )
             {
-                if ( isJvmError( s ) )
-                {
-                    logger.error( s );
-                }
-                String msg = "Corrupted STDOUT 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() );
-
-                if ( logger.isDebugEnabled() )
-                {
-                    logger.debug( s );
-                }
+                isEnd = channel.read( buffer ) == -1;
             }
+            buffer.limit( buffer.position() );
+            buffer.position( mark );
+            return isEnd ? EOF : ( buffer.remaining() >= recommendedCount ? OVERFLOW : UNDERFLOW );
         }
     }
 
-    private Event toEvent( List<String> tokensInFrame )
+    private void printExistingLine( BufferedStream line )
     {
-        Iterator<String> tokens = tokensInFrame.iterator();
-        String header = tokens.next();
-        assert header != null;
-
-        ForkedProcessEventType event = ForkedProcessEventType.byOpcode( tokens.next() );
-
-        if ( event == null )
+        if ( line.isEmpty() )
+        {
+            return;
+        }
+        ConsoleLogger logger = arguments.getConsoleLogger();
+        String s = line.toString( STREAM_ENCODING ).trim();
+        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
         {
-            return null;
+            if ( isJvmError( s ) )
+            {
+                logger.error( s );
+            }
+            String msg = "Corrupted STDOUT 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() );
+
+            if ( logger.isDebugEnabled() )
+            {
+                logger.debug( s );
+            }
         }
+    }
 
+    private Event toEvent( Memento memento )
+    {
+        ForkedProcessEventType event = memento.eventType;
         if ( event.isControlCategory() )
         {
             switch ( event )
@@ -296,16 +462,17 @@ public class EventConsumerThread extends CloseableDaemonThread
         }
         else if ( event.isConsoleErrorCategory() || event.isJvmExitError() )
         {
-            Charset encoding = Charset.forName( tokens.next() );
-            StackTraceWriter stackTraceWriter = decodeTrace( encoding, tokens.next(), tokens.next(), tokens.next() );
+            String traceMessage = (String) memento.data.get( 0 );
+            String smartTrimmedStackTrace = (String) memento.data.get( 1 );
+            String stackTrace = (String) memento.data.get( 2 );
+            StackTraceWriter stackTraceWriter = newTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
             return event.isConsoleErrorCategory()
                 ? new ConsoleErrorEvent( stackTraceWriter )
                 : new JvmExitErrorEvent( stackTraceWriter );
         }
         else if ( event.isConsoleCategory() )
         {
-            Charset encoding = Charset.forName( tokens.next() );
-            String msg = decode( tokens.next(), encoding );
+            String msg = (String) memento.data.get( 0 );
             switch ( event )
             {
                 case BOOTERCODE_CONSOLE_INFO:
@@ -320,57 +487,62 @@ public class EventConsumerThread extends CloseableDaemonThread
         }
         else if ( event.isStandardStreamCategory() )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            String output = decode( tokens.next(), encoding );
+            String output = (String) memento.data.get( 0 );
             switch ( event )
             {
                 case BOOTERCODE_STDOUT:
-                    return new StandardStreamOutEvent( mode, output );
+                    return new StandardStreamOutEvent( memento.runMode, output );
                 case BOOTERCODE_STDOUT_NEW_LINE:
-                    return new StandardStreamOutWithNewLineEvent( mode, output );
+                    return new StandardStreamOutWithNewLineEvent( memento.runMode, output );
                 case BOOTERCODE_STDERR:
-                    return new StandardStreamErrEvent( mode, output );
+                    return new StandardStreamErrEvent( memento.runMode, output );
                 case BOOTERCODE_STDERR_NEW_LINE:
-                    return new StandardStreamErrWithNewLineEvent( mode, output );
+                    return new StandardStreamErrWithNewLineEvent( memento.runMode, output );
                 default:
                     throw new IllegalStateException( "Unknown enum " + event );
             }
         }
         else if ( event.isSysPropCategory() )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            String key = decode( tokens.next(), encoding );
-            String value = decode( tokens.next(), encoding );
-            return new SystemPropertyEvent( mode, key, value );
+            String key = (String) memento.data.get( 0 );
+            String value = (String) memento.data.get( 1 );
+            return new SystemPropertyEvent( memento.runMode, key, value );
         }
         else if ( event.isTestCategory() )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            TestSetReportEntry reportEntry =
-                decodeReportEntry( encoding, tokens.next(), tokens.next(), tokens.next(), tokens.next(),
-                    tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next() );
+            // ReportEntry:
+            String source = (String) memento.data.get( 0 );
+            String sourceText = (String) memento.data.get( 1 );
+            String name = (String) memento.data.get( 2 );
+            String nameText = (String) memento.data.get( 3 );
+            String group = (String) memento.data.get( 4 );
+            String message = (String) memento.data.get( 5 );
+            Integer timeElapsed = (Integer) memento.data.get( 6 );
+            // StackTraceWriter:
+            String traceMessage = (String) memento.data.get( 7 );
+            String smartTrimmedStackTrace = (String) memento.data.get( 8 );
+            String stackTrace = (String) memento.data.get( 9 );
+            TestSetReportEntry reportEntry = newReportEntry( source, sourceText, name, nameText, group, message,
+                timeElapsed, traceMessage, smartTrimmedStackTrace, stackTrace );
 
             switch ( event )
             {
                 case BOOTERCODE_TESTSET_STARTING:
-                    return new TestsetStartingEvent( mode, reportEntry );
+                    return new TestsetStartingEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TESTSET_COMPLETED:
-                    return new TestsetCompletedEvent( mode, reportEntry );
+                    return new TestsetCompletedEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_STARTING:
-                    return new TestStartingEvent( mode, reportEntry );
+                    return new TestStartingEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_SUCCEEDED:
-                    return new TestSucceededEvent( mode, reportEntry );
+                    return new TestSucceededEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_FAILED:
-                    return new TestFailedEvent( mode, reportEntry );
+                    return new TestFailedEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_SKIPPED:
-                    return new TestSkippedEvent( mode, reportEntry );
+                    return new TestSkippedEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_ERROR:
-                    return new TestErrorEvent( mode, reportEntry );
+                    return new TestErrorEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                    return new TestAssumptionFailureEvent( mode, reportEntry );
+                    return new TestAssumptionFailureEvent( memento.runMode, reportEntry );
                 default:
                     throw new IllegalStateException( "Unknown enum " + event );
             }
@@ -379,116 +551,145 @@ public class EventConsumerThread extends CloseableDaemonThread
         throw new IllegalStateException( "Missing a branch for the event type " + event );
     }
 
-    private static FrameCompletion frameCompleteness( List<String> tokens )
+    private static StackTraceWriter newTrace( String traceMessage, String smartTrimmedStackTrace, String stackTrace )
     {
-        if ( !tokens.isEmpty() && !MAGIC_NUMBER.equals( tokens.get( 0 ) ) )
-        {
-            return FrameCompletion.MALFORMED;
-        }
+        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 = newTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
+        return reportEntry( source, sourceText, name, nameText, group, stackTraceWriter, timeElapsed, message,
+            Collections.<String, String>emptyMap() );
+    }
 
-        if ( tokens.size() >= 2 )
+    private static boolean isJvmError( String line )
+    {
+        String lineLower = line.toLowerCase();
+        for ( String errorPattern : JVM_ERROR_PATTERNS )
         {
-            String opcode = tokens.get( 1 );
-            ForkedProcessEventType event = ForkedProcessEventType.byOpcode( opcode );
-            if ( event == null )
-            {
-                return FrameCompletion.MALFORMED;
-            }
-            else if ( event.isControlCategory() )
-            {
-                return FrameCompletion.COMPLETE;
-            }
-            else if ( event.isConsoleErrorCategory() )
-            {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isConsoleCategory() )
-            {
-                return tokens.size() == 4 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isStandardStreamCategory() )
-            {
-                return tokens.size() == 5 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isSysPropCategory() )
-            {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isTestCategory() )
-            {
-                return tokens.size() == 14 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isJvmExitError() )
+            if ( lineLower.contains( errorPattern ) )
             {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+                return true;
             }
         }
-        return FrameCompletion.NOT_COMPLETE;
+        return false;
     }
 
-    static String decode( String line, Charset encoding )
+    private String readSegment( Memento memento ) throws IOException
     {
-        // ForkedChannelEncoder is encoding the stream with US_ASCII
-        return line == null || "-".equals( line )
-            ? null
-            : new String( BASE64.decode( line.getBytes( US_ASCII ) ), encoding );
+        int startsWithPosition = memento.bb.position();
+        boolean streamContinues;
+
+        do
+        {
+            streamContinues = read( memento.bb, 1 ) != EOF;
+        }
+        while ( ( 0xff & memento.bb.get() ) != ':' );
+
+        int endsWithLimit = memento.bb.limit();
+        memento.bb.limit( memento.bb.position() - 1 );
+        memento.bb.position( startsWithPosition );
+
+        memento.cb.clear();
+
+        memento.decoder
+            .reset()
+            .decode( memento.bb, memento.cb, true );
+
+        memento.bb.position( memento.bb.position() + 1 );
+        memento.bb.limit( endsWithLimit );
+
+        String segment = memento.cb.flip().toString();
+        memento.cb.clear();
+        return segment;
     }
 
-    private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
-                                                 String encSmartTrimmedStackTrace, String encStackTrace )
+    String readString( Memento memento ) throws IOException
     {
-        String traceMessage = decode( encTraceMessage, encoding );
-        String stackTrace = decode( encStackTrace, encoding );
-        String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
-        boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
-        return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
+        final CharBuffer chars = memento.cb;
+        final ByteBuffer buffer = memento.bb;
+        final int totalBytes = memento.bytesCounter;
+
+        int countDecodedBytes = 0;
+        int countReadBytes = 0;
+
+        buffer.clear();
+
+        int positionChars = chars.position();
+        int startPosition;
+        List<String> strings = new ArrayList<>();
+        do
+        {
+            startPosition = buffer.position();
+            buffer.limit( startPosition );
+            read( buffer, totalBytes - countReadBytes );
+            memento.decoder
+                .reset()
+                .decode( buffer, chars, countDecodedBytes + buffer.remaining() >= totalBytes );
+            final boolean hasDecodedNewBytes = chars.position() != positionChars;
+            if ( hasDecodedNewBytes )
+            {
+                countDecodedBytes += buffer.position() - startPosition;
+                positionChars = chars.position();
+            }
+            countReadBytes += buffer.limit() - startPosition;
+            if ( buffer.hasRemaining() )
+            {
+                buffer.compact();
+            }
+            strings.add( chars.flip().toString() );
+            chars.clear();
+        }
+        while ( countReadBytes < totalBytes );
+
+        return toString( strings );
     }
 
-    static TestSetReportEntry decodeReportEntry( Charset encoding,
-                                                 // ReportEntry:
-                                                 String encSource, String encSourceText, String encName,
-                                                 String encNameText, String encGroup, String encMessage,
-                                                 String encTimeElapsed,
-                                                 // StackTraceWriter:
-                                                 String encTraceMessage,
-                                                 String encSmartTrimmedStackTrace, String encStackTrace )
-        throws NumberFormatException
+    int readInt( ByteBuffer bb ) throws IOException
     {
-        if ( encoding == null )
-        {
-            // corrupted or incomplete stream
-            return null;
-        }
-
-        String source = decode( encSource, encoding );
-        String sourceText = decode( encSourceText, encoding );
-        String name = decode( encName, encoding );
-        String nameText = decode( encNameText, encoding );
-        String group = decode( encGroup, encoding );
-        StackTraceWriter stackTraceWriter =
-            decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
-        Integer elapsed = decodeToInteger( encTimeElapsed );
-        String message = decode( encMessage, encoding );
-        return reportEntry( source, sourceText, name, nameText,
-            group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
+        read( bb, 4 );
+        return bb.getInt();
     }
 
-    static Integer decodeToInteger( String line )
+    private static String toString( List<String> strings )
     {
-        return line == null || "-".equals( line ) ? null : Integer.decode( line );
+        if ( strings.size() == 1 )
+        {
+            return strings.get( 0 );
+        }
+        StringBuilder concatenated = new StringBuilder();
+        for ( String s : strings )
+        {
+            concatenated.append( s );
+        }
+        return concatenated.toString();
     }
 
-    private static boolean isJvmError( String line )
+    static Map<Segment, ForkedProcessEventType> mapEventTypes()
     {
-        String lineLower = line.toLowerCase();
-        for ( String errorPattern : JVM_ERROR_PATTERNS )
+        Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+        for (ForkedProcessEventType e : ForkedProcessEventType.values() )
         {
-            if ( lineLower.contains( errorPattern ) )
-            {
-                return true;
-            }
+            byte[] array = e.getOpcode().getBytes();
+            map.put( new Segment( array, 0, array.length ), e );
         }
-        return false;
+        return map;
+    }
+
+    enum StreamReadStatus
+    {
+        UNDERFLOW,
+        OVERFLOW,
+        EOF
     }
 
     /**
@@ -500,4 +701,160 @@ public class EventConsumerThread extends CloseableDaemonThread
         COMPLETE,
         MALFORMED
     }
+
+    private enum SegmentType
+    {
+        RUN_MODE,
+        STRING_ENCODING,
+        BYTES_INT_COUNTER,
+        DATA_STRING,
+        DATA_INT,
+        END_OF_FRAME
+    }
+
+    /**
+     * This class avoids locking which gains the performance of this decoder.
+     */
+    private static class BufferedStream
+    {
+        private byte[] buffer;
+        private int count;
+
+        public BufferedStream( int capacity )
+        {
+            this.buffer = new byte[capacity];
+        }
+
+        void write( int b )
+        {
+            ensureCapacity( 1 );
+            buffer[count++] = (byte) b;
+        }
+
+        void write( ByteBuffer bb )
+        {
+            int size = bb.remaining();
+            if ( size > 0 )
+            {
+                ensureCapacity( size );
+                byte[] b = bb.array();
+                int pos = bb.arrayOffset() + bb.position();
+                System.arraycopy( b, pos, buffer, count, size );
+                count+= size;
+                bb.position( bb.position() + size );
+            }
+        }
+
+        boolean isEmpty()
+        {
+            return count != 0;
+        }
+
+        void reset()
+        {
+            count = 0;
+        }
+
+        String toString( Charset charset )
+        {
+            return new String( buffer, 0, count, charset );
+        }
+
+        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 ) );
+            }
+        }
+    }
+
+    static class Memento
+    {
+        final CharsetDecoder decoder;
+        final BufferedStream segment = new BufferedStream( MAGIC_NUMBER.length() );
+        final List<Object> data = new ArrayList<>();
+        final CharBuffer cb = CharBuffer.allocate( 1024 );
+        final ByteBuffer bb = ByteBuffer.allocate( 1024 );
+        FrameCompletion frameCompletion;
+        ForkedProcessEventType eventType;
+        RunMode runMode;
+        Charset charset;
+        int bytesCounter;
+
+        Memento()
+        {
+            decoder = STREAM_ENCODING.newDecoder()
+                .onMalformedInput( REPLACE )
+                .onUnmappableCharacter( REPLACE );
+        }
+
+        void reset()
+        {
+            segment.reset();
+            frameCompletion = null;
+        }
+    }
+
+    static class Segment
+    {
+        private final byte[] array;
+        private final int fromIndex;
+        private final int length;
+        private final int hashCode;
+
+        public 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;
+        }
+    }
 }
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
new file mode 100644
index 0000000..0979399
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -0,0 +1,492 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+import org.apache.maven.plugin.surefire.extensions.EventConsumerThread.Memento;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.fest.assertions.Assertions;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.Mockito;
+
+import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.Channel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+
+import static java.nio.charset.CodingErrorAction.REPLACE;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.EOF;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.OVERFLOW;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.UNDERFLOW;
+import static org.apache.maven.surefire.api.booter.Constants.STREAM_ENCODING;
+import static org.fest.assertions.Assertions.assertThat;
+//import static org.mockito.Mockito.mock;
+
+//@RunWith( Parameterized.class )
+public class EventConsumerThreadTest
+{
+    @Parameters
+    public static Iterable<Object> channels()
+    {
+        return Arrays.asList( (Object) complexEncodings() );
+    }
+
+    @Parameter
+    public Channel channel;
+
+    private static Channel complexEncodings()
+    {
+        byte[] bytes = new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
+        bytes = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 );
+        return new Channel( bytes, 1 );
+    }
+
+    private static Channel complexEncodings( int chunkSize )
+    {
+        byte[] bytes = new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
+        bytes = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 );
+        return new Channel( bytes, chunkSize );
+    }
+
+    @Test
+    public void test5() throws IOException, InterruptedException
+    {
+        /*final CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+            .onMalformedInput( REPLACE )
+            .onUnmappableCharacter( REPLACE );
+        final CharBuffer chars = CharBuffer.allocate(5);
+
+        final int totalBytes = 7;
+
+        int countDecodedBytes = 0;
+        int countReadBytes = 0;
+        final ByteBuffer buffer = ByteBuffer.wrap(new byte[4]);
+
+        buffer.clear();
+
+        int positionChars = chars.position();
+        int startPosition;
+        List<String> strings = new ArrayList<>();
+        do
+        {
+            startPosition = buffer.position();
+            buffer.limit( startPosition );
+            read( buffer, totalBytes - countReadBytes );
+            decoder.decode(buffer, chars, countDecodedBytes >= totalBytes );
+            final boolean hasDecodedNewBytes = chars.position() != positionChars;
+            if ( hasDecodedNewBytes )
+            {
+                countDecodedBytes += buffer.position() - startPosition;
+                positionChars = chars.position();
+            }
+            countReadBytes += buffer.limit() - startPosition;
+            buffer.compact();
+            strings.add( chars.flip().toString() );
+            chars.clear();
+        }
+        while ( countReadBytes < totalBytes );
+        decoder.reset();
+
+        String s = toString( strings );*/
+        channel = complexEncodings(100);
+
+        Closeable c = new Closeable()
+        {
+            @Override
+            public void close() throws IOException
+            {
+
+            }
+        };
+
+        EventHandler eh = new EventHandler()
+        {
+            @Override
+            public void handleEvent( @Nonnull Object event )
+            {
+
+            }
+        };
+
+        ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
+        {
+            @Nonnull
+            @Override
+            public String getSessionId()
+            {
+                return null;
+            }
+
+            @Override
+            public int getForkChannelId()
+            {
+                return 0;
+            }
+
+            @Nonnull
+            @Override
+            public File dumpStreamText( @Nonnull String text )
+            {
+                return null;
+            }
+
+            @Override
+            public void logWarningAtEnd( @Nonnull String text )
+            {
+
+            }
+
+            @Nonnull
+            @Override
+            public ConsoleLogger getConsoleLogger()
+            {
+                return null;
+            }
+        };
+
+        CountdownCloseable closeable = new CountdownCloseable( c, 0 );
+        EventConsumerThread thread = new EventConsumerThread( "t", channel, eh, closeable,
+            forkNodeArguments );
+        Memento memento = new Memento();
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            //memento.cb.clear();
+            memento.bb.position(0);
+            memento.bb.limit(memento.bb.capacity());
+            memento.bytesCounter = 100;//7
+            memento.data.clear();
+            channel.reset();
+            thread.readString( memento );
+        }
+
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        /*assertThat( s )
+            .isEqualTo( "€ab©" );*/
+    }
+
+    @Test
+    public void test6() throws InterruptedException
+    {
+        CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+            .onMalformedInput( REPLACE )
+            .onUnmappableCharacter( REPLACE );
+        // CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+        // CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+        ByteBuffer buffer = ByteBuffer.wrap( "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 ) );
+        CharBuffer chars = CharBuffer.allocate( 100 );
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        long l1 = System.currentTimeMillis();
+        for ( int i = 0; i < 10_000_000; i++ )
+        {
+            decoder
+                .reset()
+                .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+            chars.flip().toString();//CharsetDecoder + toString = 91 nanos
+            buffer.clear();
+            chars.clear();
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+    }
+
+    @Test
+    public void test7() throws Exception {
+        byte[] b = {};
+        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++) {
+            s = new String(b, UTF_8);
+        }
+
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        System.out.println(s);
+    }
+
+    @Test
+    public void test8() {
+        ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+        SortedSet<Integer> s = new TreeSet<>();
+        for (ForkedProcessEventType e : enums) {
+            if (s.contains( e.getOpcode().length() )) {
+                System.out.println("obsahuje "  + e + " s dlzkou " + e.getOpcode().length() + " s has codom " + e.getOpcode().hashCode());
+            }
+            s.add( e.getOpcode().length() );
+        }
+    }
+
+    @Test
+    public void test9() throws InterruptedException
+    {
+        ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+        TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
+        for (ForkedProcessEventType e : enums) {
+            map.put( e.getOpcode().hashCode(), e );
+        }
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        ForkedProcessEventType s = null;
+        int hash = ForkedProcessEventType.BOOTERCODE_STDOUT.hashCode();
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            s = map.get( hash );
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        System.out.println(s);
+    }
+
+    @Test
+    public void test10() throws InterruptedException
+    {
+        ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+        Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+        for (ForkedProcessEventType e : enums) {
+            byte[] array = e.getOpcode().getBytes();
+            map.put( new Segment( array, 0, array.length ), e );
+        }
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        ForkedProcessEventType s = null;
+        byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes();
+        Segment segment = new Segment( array, 0, array.length );
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            s = map.get( new Segment( array, 0, array.length ) ); // 33.7 nanos
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        System.out.println(s);
+    }
+
+    @Test
+    public void test11() throws InterruptedException
+    {
+        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 );
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        long l1 = System.currentTimeMillis();
+        for ( int i = 0; i < 10_000_000; i++ )
+        {
+            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
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+    }
+
+    @Test
+    public void test12() throws InterruptedException
+    {
+        StringBuilder builder = new StringBuilder( 256 );
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes();
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            builder.setLength( 0 );
+            for ( byte b : array )
+            {
+                char c = (char) b;
+                if ( c == ':' ) break;
+                builder.append( c );
+            }
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        System.out.println( builder.toString() );
+    }
+
+    private static class Segment {
+        private final byte[] array;
+        private final int fromIndex;
+        private final int length;
+        private final int hashCode;
+
+        public 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;
+        }
+    }
+
+
+    private static String toString( List<String> strings )
+    {
+        if ( strings.size() == 1 )
+        {
+            return strings.get( 0 );
+        }
+        StringBuilder concatenated = new StringBuilder();
+        for ( String s : strings )
+        {
+            concatenated.append( s );
+        }
+        return concatenated.toString();
+    }
+
+    private StreamReadStatus read( ByteBuffer buffer, int recommendedCount ) throws IOException
+    {
+        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+        {
+            return OVERFLOW;
+        }
+        else
+        {
+            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 );
+            return isEnd ? EOF : ( buffer.remaining() >= recommendedCount ? OVERFLOW : UNDERFLOW );
+        }
+    }
+
+    private static class Channel implements ReadableByteChannel
+    {
+        private final byte[] bytes;
+        private final int chunkSize;
+        private int i;
+
+        public Channel( byte[] bytes, int chunkSize )
+        {
+            this.bytes = bytes;
+            this.chunkSize = chunkSize;
+        }
+
+        public void reset()
+        {
+            i = 0;
+        }
+
+        @Override
+        public int read( ByteBuffer dst )
+        {
+            if ( i == bytes.length )
+            {
+                return -1;
+            }
+            else if ( dst.hasRemaining() )
+            {
+                int length = Math.min( chunkSize, bytes.length - i );
+                dst.put( bytes, i, length );
+                i += length;
+                return length;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+        }
+    }
+
+    enum StreamReadStatus
+    {
+        UNDERFLOW,
+        OVERFLOW,
+        EOF
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
index e84e5bc..ea81458 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
@@ -31,37 +31,261 @@ import static java.util.Collections.unmodifiableMap;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
+@SuppressWarnings( "checkstyle:linelength" )
 public enum ForkedProcessEventType
 {
-    BOOTERCODE_SYSPROPS( "sys-prop" ),
+    /**
+     * This is the opcode "sys-prop". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:sys-prop:RunMode:UTF-8:0xFFFFFFFF:key:0xFFFFFFFF:value:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "sys-prop"
+     * </ul>
+     */
+    BOOTERCODE_SYSPROPS( "sys-prop"  ),
 
+    /**
+     * This is the opcode "testset-starting". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:testset-starting:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "testset-starting"
+     * </ul>
+     */
     BOOTERCODE_TESTSET_STARTING( "testset-starting" ),
+
+    /**
+     * This is the opcode "testset-completed". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:testset-completed:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "testset-completed"
+     * </ul>
+     */
     BOOTERCODE_TESTSET_COMPLETED( "testset-completed" ),
+
+    /**
+     * This is the opcode "test-starting". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-starting:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-starting"
+     * </ul>
+     */
     BOOTERCODE_TEST_STARTING( "test-starting" ),
+
+    /**
+     * This is the opcode "test-succeeded". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-succeeded:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-succeeded"
+     * </ul>
+     */
     BOOTERCODE_TEST_SUCCEEDED( "test-succeeded" ),
+
+    /**
+     * This is the opcode "test-failed". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-failed:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-failed"
+     * </ul>
+     */
     BOOTERCODE_TEST_FAILED( "test-failed" ),
+
+    /**
+     * This is the opcode "test-skipped". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-skipped:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-skipped"
+     * </ul>
+     */
     BOOTERCODE_TEST_SKIPPED( "test-skipped" ),
+
+    /**
+     * This is the opcode "test-error". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-error:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-error"
+     * </ul>
+     */
     BOOTERCODE_TEST_ERROR( "test-error" ),
+
+    /**
+     * This is the opcode "test-assumption-failure". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-assumption-failure:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-assumption-failure"
+     * </ul>
+     */
     BOOTERCODE_TEST_ASSUMPTIONFAILURE( "test-assumption-failure" ),
 
+    /**
+     * This is the opcode "std-out-stream". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-out-stream:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-out-stream"
+     * </ul>
+     */
     BOOTERCODE_STDOUT( "std-out-stream" ),
+
+    /**
+     * This is the opcode "std-out-stream-new-line". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-out-stream-new-line:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-out-stream-new-line"
+     * </ul>
+     */
     BOOTERCODE_STDOUT_NEW_LINE( "std-out-stream-new-line" ),
+
+    /**
+     * This is the opcode "std-err-stream". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-err-stream:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-err-stream"
+     * </ul>
+     */
     BOOTERCODE_STDERR( "std-err-stream" ),
+
+    /**
+     * This is the opcode "std-err-stream-new-line". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-err-stream-new-line:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-err-stream-new-line"
+     * </ul>
+     */
     BOOTERCODE_STDERR_NEW_LINE( "std-err-stream-new-line" ),
 
+    /**
+     * This is the opcode "console-info-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-info-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-info-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_INFO( "console-info-log" ),
+
+    /**
+     * This is the opcode "console-debug-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-debug-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-debug-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_DEBUG( "console-debug-log" ),
+
+    /**
+     * This is the opcode "console-warning-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-warning-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-warning-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_WARNING( "console-warning-log" ),
+
+    /**
+     * This is the opcode "console-error-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-error-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-error-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_ERROR( "console-error-log" ),
 
+    /**
+     * This is the opcode "bye". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:bye:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "bye"
+     * </ul>
+     */
     BOOTERCODE_BYE( "bye" ),
+
+    /**
+     * This is the opcode "stop-on-next-test". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:stop-on-next-test:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "stop-on-next-test"
+     * </ul>
+     */
     BOOTERCODE_STOP_ON_NEXT_TEST( "stop-on-next-test" ),
+
+    /**
+     * This is the opcode "next-test". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:next-test:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "next-test"
+     * </ul>
+     */
     BOOTERCODE_NEXT_TEST( "next-test" ),
 
+    /**
+     * This is the opcode "jvm-exit-error". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:jvm-exit-error:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "jvm-exit-error"
+     * </ul>
+     */
     BOOTERCODE_JVM_EXIT_ERROR( "jvm-exit-error" );
 
-    public static final String MAGIC_NUMBER = "maven-surefire-event";
-
     private static final Map<String, ForkedProcessEventType> EVENTS = events();
 
     private static Map<String, ForkedProcessEventType> events()