[maven-surefire] branch base64 updated: the main work in this class is done

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

[maven-surefire] branch base64 updated: the main work in this class is done

Tibor Digana
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


The following commit(s) were added to refs/heads/base64 by this push:
     new a7fede2  the main work in this class is done
a7fede2 is described below

commit a7fede24ed7171b1d8c241b0cce97a9a8d12a36f
Author: tibordigana <[hidden email]>
AuthorDate: Fri Sep 11 21:03:14 2020 +0200

    the main work in this class is done
---
 .../surefire/extensions/EventConsumerThread.java   | 856 +++++++++++----------
 1 file changed, 465 insertions(+), 391 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 b74cb5e..61c0e8c 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
@@ -52,7 +52,9 @@ import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 
+import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
+import java.io.EOFException;
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -60,6 +62,7 @@ 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;
@@ -68,6 +71,7 @@ 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.EOF;
@@ -76,7 +80,6 @@ import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.St
 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;
 
 /**
  *
@@ -91,11 +94,65 @@ public class EventConsumerThread extends CloseableDaemonThread
             "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_INT,
+        SegmentType.DATA_STRING,
+        SegmentType.DATA_STRING,
+        SegmentType.DATA_STRING,
+        SegmentType.END_OF_FRAME
+    };
+
+    private static final int BUFFER_SIZE = 1024;
     private static final byte[] MAGIC_NUMBER_BYTES = MAGIC_NUMBER.getBytes( US_ASCII );
+    private static final byte[] STREAM_ENCODING_BYTES = STREAM_ENCODING.name().getBytes( US_ASCII );
     private static final int DELIMINATOR_LENGTH = 1;
     private static final int BYTE_LENGTH = 1;
     private static final int INT_LENGTH = 4;
+    public static final int NO_POSITION = -1;
 
     private final ReadableByteChannel channel;
     private final EventHandler<Event> eventHandler;
@@ -145,197 +202,228 @@ public class EventConsumerThread extends CloseableDaemonThread
     @SuppressWarnings( "checkstyle:innerassignment" )
     private void decode() throws IOException
     {
-        Map<Segment, ForkedProcessEventType> events = mapEventTypes();
+        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
+        Map<Segment, RunMode> runModes = mapRunModes();
         Memento memento = new Memento();
-        BufferedStream line = new BufferedStream( 32 );
         memento.bb.limit( 0 );
-        boolean streamContinues = true;
 
-        start:
         do
         {
-            if ( !streamContinues )
+            try
             {
-                printExistingLine( line );
-                return;
-            }
+                memento.eventType = readEventType( eventTypes, memento );
 
-            line.reset();
-            memento.segment.reset();
-
-            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
+                for ( SegmentType segmentType : nextSegmentType( memento.eventType ) )
+                {
+                    if ( segmentType == null )
+                    {
+                        break;
+                    }
+
+                    switch ( segmentType )
+                    {
+                        case RUN_MODE:
+                            memento.runMode = runModes.get( readSegment( memento ) );
+                            break;
+                        case STRING_ENCODING:
+                            memento.charset = readCharset( memento );
+                            break;
+                        case DATA_STRING:
+                            readString( memento );
+                            break;
+                        case DATA_INT:
+                            memento.data.add( readInt( memento ) );
+                            break;
+                        case END_OF_FRAME:
+                            memento.line.positionByteBuffer = memento.bb.position();
+                            eventHandler.handleEvent( toEvent( memento.eventType, memento.runMode, memento.data ) );
+                            break;
+                        default:
+                            memento.line.positionByteBuffer = NO_POSITION;
+                            arguments.dumpStreamText( "Unknown enum ("
+                                + ForkedProcessEventType.class.getSimpleName()
+                                + ") "
+                                + segmentType );
+                    }
+                }
+                memento.reset();
             }
-            checkHeader( memento.bb, memento.segment );
-
-            memento.eventType = events.get( readSegment( memento.bb ) );
-
-            for ( SegmentType segmentType : nextSegmentType( memento ) )
+            catch ( MalformedFrameException e )
             {
-                if ( segmentType == null )
+                if ( e.hasValidPositions() )
                 {
-                    break;
+                    int length = e.readTo - e.readFrom;
+                    memento.line.write( memento.bb, e.readFrom, length );
                 }
+            }
+        }
+        while ( true );
+    }
 
-                switch ( segmentType )
-                {
-                    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 )
-                        {
-                            // handle the returned boolean
-                            read( memento.bb, 1 );
-                            byte oneChar = memento.bb.get();
-                            memento.data.add( oneChar == 0 ? null : String.valueOf( (char) oneChar ) );
-                        }
-                        else
-                        {
-                            memento.data.add( readString( memento ) );
-                        }
-                        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 );
-                }
+    protected ForkedProcessEventType readEventType( Map<Segment, ForkedProcessEventType> eventTypes, Memento memento )
+        throws IOException, MalformedFrameException
+    {
+        int readCount = DELIMINATOR_LENGTH + MAGIC_NUMBER_BYTES.length + DELIMINATOR_LENGTH
+            + BYTE_LENGTH + DELIMINATOR_LENGTH;
+        if ( read( memento, readCount ) == EOF )
+        {
+            throw new EOFException();
+        }
+        checkHeader( memento );
 
-                memento.cb.clear();
+        return eventTypes.get( readSegment( memento ) );
+    }
 
-                read( memento.bb, 1 );
-                b = 0xff & memento.bb.get();
-                if ( Character.isDefined( b ) && b != ':' )
-                {
-                    //memento.segmentCompletion = SegmentCompletion.;
-                    //MalformedStreamException
-                    continue start;
-                }
-            }
+    protected void readString( Memento memento ) throws IOException, MalformedFrameException
+    {
+        memento.cb.clear();
+        if ( read( memento, INT_LENGTH + DELIMINATOR_LENGTH ) == EOF )
+        {
+            throw new EOFException();
+        }
 
-            memento.bb.flip();
-            line.write( memento.bb );
-            memento.reset();
+        int readCount = readInt( memento );
+        if ( read( memento, readCount + DELIMINATOR_LENGTH ) == EOF )
+        {
+            throw new EOFException();
         }
-        while ( true );
+
+        if ( readCount == 0 )
+        {
+            memento.data.add( "" );
+        }
+        else if ( readCount == 1 )
+        {
+            if ( read( memento, 1 ) == EOF )
+            {
+                throw new EOFException();
+            }
+            byte oneChar = memento.bb.get();
+            memento.data.add( oneChar == 0 ? null : String.valueOf( (char) oneChar ) );
+        }
+        else
+        {
+            memento.data.add( readString( memento, readCount ) );
+        }
+
+        checkDelimiter( memento );
     }
 
     @Nonnull
-    private static Segment readSegment( ByteBuffer bb )
+    protected Segment readSegment( Memento memento ) throws IOException, MalformedFrameException
     {
-        int readCount = bb.get() & 0xff;
+        if ( read( memento, BYTE_LENGTH + DELIMINATOR_LENGTH ) == EOF )
+        {
+            throw new EOFException();
+        }
+        ByteBuffer bb = memento.bb;
+        int readCount = readByte( memento ) & 0xff;
+        if ( read( memento, readCount + DELIMINATOR_LENGTH ) == EOF )
+        {
+            throw new EOFException();
+        }
         Segment segment = new Segment( bb.array(), bb.arrayOffset() + bb.position(), readCount );
         bb.position( bb.position() + readCount );
-        checkDelimiter( bb );
+        checkDelimiter( memento );
         return segment;
     }
 
-    private static void checkHeader( ByteBuffer bb, BufferedStream segment )
+    @Nonnull
+    protected Charset readCharset( Memento memento ) throws IOException, MalformedFrameException
     {
-        checkDelimiter( bb );
+        if ( read( memento, BYTE_LENGTH + DELIMINATOR_LENGTH ) == EOF )
+        {
+            throw new EOFException();
+        }
+        ByteBuffer bb = memento.bb;
+        int length = readByte( memento ) & 0xff;
+        if ( read( memento, length + DELIMINATOR_LENGTH ) == EOF )
+        {
+            throw new EOFException();
+        }
+        byte[] array = bb.array();
+        int offset = bb.arrayOffset() + bb.position();
+        bb.position( bb.position() + length );
+        boolean isDefaultEncoding = false;
+        if ( length == STREAM_ENCODING_BYTES.length )
+        {
+            isDefaultEncoding = true;
+            for ( int i = 0; i < length; i++ )
+            {
+                isDefaultEncoding &= STREAM_ENCODING_BYTES[i] == array[offset + i];
+            }
+        }
 
-        segment.reset();
+        try
+        {
+            Charset charset =
+                isDefaultEncoding ? 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;
-        for ( int start = bb.arrayOffset() + bb.position(), length = MAGIC_NUMBER_BYTES.length;
-              shift < length; shift++ )
+        try
         {
-            if ( bb.array()[shift + start] != MAGIC_NUMBER_BYTES[shift] )
+            for ( int start = bb.arrayOffset() + bb.position(), length = MAGIC_NUMBER_BYTES.length;
+                  shift < length; shift++ )
             {
-                //todo throw exception - broken stream
+                if ( bb.array()[shift + start] != MAGIC_NUMBER_BYTES[shift] )
+                {
+                    throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() + shift );
+                }
             }
         }
-        bb.position( bb.position() + shift );
+        finally
+        {
+            bb.position( bb.position() + shift );
+        }
 
-        checkDelimiter( bb );
+        checkDelimiter( memento );
     }
 
-    private static void checkDelimiter( ByteBuffer bb )
+    private static void checkDelimiter( Memento memento ) throws MalformedFrameException
     {
+        ByteBuffer bb = memento.bb;
         if ( ( 0xff & bb.get() ) != ':' )
         {
-            //todo throw exception - broken stream
+            throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() );
         }
     }
 
-    private static SegmentType[] nextSegmentType( Memento memento )
+    protected SegmentType[] nextSegmentType( ForkedProcessEventType eventType )
     {
-        switch ( memento.eventType )
+        switch ( eventType )
         {
             case BOOTERCODE_BYE:
             case BOOTERCODE_STOP_ON_NEXT_TEST:
             case BOOTERCODE_NEXT_TEST:
-                return new SegmentType[] {SegmentType.END_OF_FRAME};
+                return EVENT_WITHOUT_DATA;
             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
-                };
+                return EVENT_WITH_ERROR_TRACE;
             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
-                };
+                return EVENT_WITH_ONE_STRING;
             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
-                };
+                return EVENT_WITH_RUNMODE_AND_ONE_STRING;
             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
-                };
+                return EVENT_WITH_RUNMODE_AND_TWO_STRINGS;
             case BOOTERCODE_TESTSET_STARTING:
             case BOOTERCODE_TESTSET_COMPLETED:
             case BOOTERCODE_TEST_STARTING:
@@ -344,39 +432,15 @@ public class EventConsumerThread extends CloseableDaemonThread
             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
-                };
+                return EVENT_TEST_CONTROL;
             default:
-                throw new IllegalArgumentException( "Unknown enum " + memento.eventType );
+                throw new IllegalArgumentException( "Unknown enum " + eventType );
         }
     }
 
-    private StreamReadStatus read( ByteBuffer buffer, int recommendedCount ) throws IOException
+    protected StreamReadStatus read( Memento memento, int recommendedCount ) throws IOException
     {
+        ByteBuffer buffer = memento.bb;
         if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
         {
             return OVERFLOW;
@@ -386,6 +450,7 @@ public class EventConsumerThread extends CloseableDaemonThread
             if ( buffer.position() != 0 && recommendedCount > buffer.capacity() - buffer.position() )
             {
                 buffer.compact().flip();
+                memento.line.positionByteBuffer = 0;
             }
             int mark = buffer.position();
             buffer.position( buffer.limit() );
@@ -397,161 +462,92 @@ public class EventConsumerThread extends CloseableDaemonThread
             }
             buffer.limit( buffer.position() );
             buffer.position( mark );
-            return isEnd ? EOF : ( buffer.remaining() >= recommendedCount ? OVERFLOW : UNDERFLOW );
+            int readBytes = buffer.remaining();
+            return readBytes == 0 && isEnd ? EOF : ( readBytes >= recommendedCount ? OVERFLOW : UNDERFLOW );
         }
     }
 
-    private void printExistingLine( BufferedStream line )
+    protected Event toEvent( ForkedProcessEventType eventType, RunMode runMode, List<Object> args )
     {
-        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
+        switch ( eventType )
         {
-            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 );
-            }
+            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 Event toEvent( Memento memento )
+    @Nonnull
+    private static TestSetReportEntry toReportEntry( List<Object> args )
     {
-        ForkedProcessEventType event = memento.eventType;
-        if ( event.isControlCategory() )
-        {
-            switch ( event )
-            {
-                case BOOTERCODE_BYE:
-                    return new ControlByeEvent();
-                case BOOTERCODE_STOP_ON_NEXT_TEST:
-                    return new ControlStopOnNextTestEvent();
-                case BOOTERCODE_NEXT_TEST:
-                    return new ControlNextTestEvent();
-                default:
-                    throw new IllegalStateException( "Unknown enum " + event );
-            }
-        }
-        else if ( event.isConsoleErrorCategory() || event.isJvmExitError() )
-        {
-            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() )
-        {
-            String msg = (String) memento.data.get( 0 );
-            switch ( event )
-            {
-                case BOOTERCODE_CONSOLE_INFO:
-                    return new ConsoleInfoEvent( msg );
-                case BOOTERCODE_CONSOLE_DEBUG:
-                    return new ConsoleDebugEvent( msg );
-                case BOOTERCODE_CONSOLE_WARNING:
-                    return new ConsoleWarningEvent( msg );
-                default:
-                    throw new IllegalStateException( "Unknown enum " + event );
-            }
-        }
-        else if ( event.isStandardStreamCategory() )
-        {
-            String output = (String) memento.data.get( 0 );
-            switch ( event )
-            {
-                case BOOTERCODE_STDOUT:
-                    return new StandardStreamOutEvent( memento.runMode, output );
-                case BOOTERCODE_STDOUT_NEW_LINE:
-                    return new StandardStreamOutWithNewLineEvent( memento.runMode, output );
-                case BOOTERCODE_STDERR:
-                    return new StandardStreamErrEvent( memento.runMode, output );
-                case BOOTERCODE_STDERR_NEW_LINE:
-                    return new StandardStreamErrWithNewLineEvent( memento.runMode, output );
-                default:
-                    throw new IllegalStateException( "Unknown enum " + event );
-            }
-        }
-        else if ( event.isSysPropCategory() )
-        {
-            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() )
-        {
-            // 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( memento.runMode, reportEntry );
-                case BOOTERCODE_TESTSET_COMPLETED:
-                    return new TestsetCompletedEvent( memento.runMode, reportEntry );
-                case BOOTERCODE_TEST_STARTING:
-                    return new TestStartingEvent( memento.runMode, reportEntry );
-                case BOOTERCODE_TEST_SUCCEEDED:
-                    return new TestSucceededEvent( memento.runMode, reportEntry );
-                case BOOTERCODE_TEST_FAILED:
-                    return new TestFailedEvent( memento.runMode, reportEntry );
-                case BOOTERCODE_TEST_SKIPPED:
-                    return new TestSkippedEvent( memento.runMode, reportEntry );
-                case BOOTERCODE_TEST_ERROR:
-                    return new TestErrorEvent( memento.runMode, reportEntry );
-                case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                    return new TestAssumptionFailureEvent( memento.runMode, reportEntry );
-                default:
-                    throw new IllegalStateException( "Unknown enum " + event );
-            }
-        }
+        // 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 );
+    }
 
-        throw new IllegalStateException( "Missing a branch for the event type " + event );
+    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 newTrace( String traceMessage, String smartTrimmedStackTrace, String 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;
@@ -566,7 +562,7 @@ public class EventConsumerThread extends CloseableDaemonThread
                                               String smartTrimmedStackTrace, String stackTrace )
         throws NumberFormatException
     {
-        StackTraceWriter stackTraceWriter = newTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
+        StackTraceWriter stackTraceWriter = toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
         return reportEntry( source, sourceText, name, nameText, group, stackTraceWriter, timeElapsed, message,
             Collections.<String, String>emptyMap() );
     }
@@ -584,80 +580,84 @@ public class EventConsumerThread extends CloseableDaemonThread
         return false;
     }
 
-    private String readSegment( Memento memento ) throws IOException
+    private static int decode( @Nonnull CharsetDecoder decoder, @Nonnull ByteBuffer input, @Nonnull CharBuffer output,
+                               @Nonnegative int bytesToDecode, boolean endOfInput, @Nonnegative int errorStreamFrom )
+        throws MalformedFrameException
     {
-        int startsWithPosition = memento.bb.position();
-        boolean streamContinues;
+        int limit = input.limit();
+        input.limit( input.position() + bytesToDecode );
 
-        do
+        CoderResult result = decoder.decode( input, output, endOfInput );
+        if ( result.isError() || result.isMalformed() )
         {
-            streamContinues = read( memento.bb, 1 ) != EOF;
+            throw new MalformedFrameException( errorStreamFrom, input.position() );
         }
-        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;
+        
+        int decodedBytes = bytesToDecode - input.remaining();
+        input.limit( limit );
+        return decodedBytes;
     }
 
-    String readString( Memento memento ) throws IOException
+    String readString( @Nonnull final Memento memento, @Nonnegative final int totalBytes )
+        throws IOException, MalformedFrameException
     {
-        final CharBuffer chars = memento.cb;
-        final ByteBuffer buffer = memento.bb;
-        final int totalBytes = memento.bytesCounter;
-
+        memento.decoder.reset();
+        final CharBuffer output = memento.cb;
+        output.clear();
+        final ByteBuffer input = memento.bb;
+        final List<String> strings = new ArrayList<>();
         int countDecodedBytes = 0;
-        int countReadBytes = 0;
+        for ( boolean endOfInput = false; !endOfInput; )
+        {
+            final int bytesToRead = totalBytes - countDecodedBytes;
 
-        buffer.clear();
+            if ( read( memento, bytesToRead - input.remaining() ) == EOF )
+            {
+                throw new EOFException();
+            }
 
-        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 )
+            int bytesToDecode = min( input.remaining(), bytesToRead );
+            final boolean isLastChunk = bytesToDecode == bytesToRead;
+
+            endOfInput = countDecodedBytes + bytesToDecode >= totalBytes;
+            do
             {
-                countDecodedBytes += buffer.position() - startPosition;
-                positionChars = chars.position();
+                boolean endOfChunk = output.remaining() >= bytesToRead;
+                boolean endOfOutput = isLastChunk && endOfChunk;
+                int readInputBytes = decode( memento.decoder, input, output, bytesToDecode, endOfOutput,
+                    memento.line.positionByteBuffer );
+                bytesToDecode -= readInputBytes;
+                countDecodedBytes += readInputBytes;
             }
-            countReadBytes += buffer.limit() - startPosition;
-            if ( buffer.hasRemaining() )
+            while ( isLastChunk && bytesToDecode > 0 && output.hasRemaining() );
+
+            if ( isLastChunk || !output.hasRemaining() )
             {
-                buffer.compact();
+                strings.add( output.flip().toString() );
+                output.clear();
             }
-            strings.add( chars.flip().toString() );
-            chars.clear();
         }
-        while ( countReadBytes < totalBytes );
+
+        memento.decoder.reset();
+        output.clear();
 
         return toString( strings );
     }
 
-    int readInt( ByteBuffer bb ) throws IOException
+    protected byte readByte( Memento memento ) throws IOException, MalformedFrameException
+    {
+        read( memento, BYTE_LENGTH );
+        byte b = memento.bb.get();
+        checkDelimiter( memento );
+        return b;
+    }
+
+    protected int readInt( Memento memento ) throws IOException, MalformedFrameException
     {
-        read( bb, 4 );
-        return bb.getInt();
+        read( memento, INT_LENGTH );
+        int i = memento.bb.getInt();
+        checkDelimiter( memento );
+        return i;
     }
 
     private static String toString( List<String> strings )
@@ -666,7 +666,7 @@ public class EventConsumerThread extends CloseableDaemonThread
         {
             return strings.get( 0 );
         }
-        StringBuilder concatenated = new StringBuilder();
+        StringBuilder concatenated = new StringBuilder( strings.size() * BUFFER_SIZE );
         for ( String s : strings )
         {
             concatenated.append( s );
@@ -679,7 +679,18 @@ public class EventConsumerThread extends CloseableDaemonThread
         Map<Segment, ForkedProcessEventType> map = new HashMap<>();
         for (ForkedProcessEventType e : ForkedProcessEventType.values() )
         {
-            byte[] array = e.getOpcode().getBytes();
+            byte[] array = e.getOpcode().getBytes( US_ASCII );
+            map.put( new Segment( array, 0, array.length ), e );
+        }
+        return map;
+    }
+
+    private static Map<Segment, RunMode> mapRunModes()
+    {
+        Map<Segment, RunMode> map = new HashMap<>();
+        for ( RunMode e : RunMode.values() )
+        {
+            byte[] array = e.geRunName().getBytes( US_ASCII );
             map.put( new Segment( array, 0, array.length ), e );
         }
         return map;
@@ -706,7 +717,6 @@ public class EventConsumerThread extends CloseableDaemonThread
     {
         RUN_MODE,
         STRING_ENCODING,
-        BYTES_INT_COUNTER,
         DATA_STRING,
         DATA_INT,
         END_OF_FRAME
@@ -715,33 +725,41 @@ public class EventConsumerThread extends CloseableDaemonThread
     /**
      * This class avoids locking which gains the performance of this decoder.
      */
-    private static class BufferedStream
+    private class BufferedStream
     {
         private byte[] buffer;
         private int count;
+        private int positionByteBuffer;
+        private boolean isNewLine;
 
         public BufferedStream( int capacity )
         {
             this.buffer = new byte[capacity];
         }
 
-        void write( int b )
-        {
-            ensureCapacity( 1 );
-            buffer[count++] = (byte) b;
-        }
-
-        void write( ByteBuffer bb )
+        void write( ByteBuffer bb, int position, int length )
         {
-            int size = bb.remaining();
-            if ( size > 0 )
+            ensureCapacity( length );
+            byte[] array = bb.array();
+            int pos = bb.arrayOffset() + position;
+            while ( length-- > 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 );
+                positionByteBuffer++;
+                byte b = array[pos++];
+                if ( b == '\r' || b == '\n' )
+                {
+                    if ( !isNewLine )
+                    {
+                        printExistingLine();
+                        count = 0;
+                    }
+                    isNewLine = true;
+                }
+                else
+                {
+                    buffer[count++] = b;
+                    isNewLine = false;
+                }
             }
         }
 
@@ -750,11 +768,6 @@ public class EventConsumerThread extends CloseableDaemonThread
             return count != 0;
         }
 
-        void reset()
-        {
-            count = 0;
-        }
-
         String toString( Charset charset )
         {
             return new String( buffer, 0, count, charset );
@@ -775,20 +788,62 @@ public class EventConsumerThread extends CloseableDaemonThread
                 buffer = Arrays.copyOf( buffer, max( newCapacity, exactCapacity ) );
             }
         }
+
+        private void printExistingLine()
+        {
+            if ( isEmpty() )
+            {
+                return;
+            }
+            ConsoleLogger logger = arguments.getConsoleLogger();
+            String s = toString( STREAM_ENCODING );
+            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 );
+                }
+                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 );
+                }
+            }
+        }
     }
 
-    static class Memento
+    class Memento
     {
         final CharsetDecoder decoder;
-        final BufferedStream segment = new BufferedStream( MAGIC_NUMBER.length() );
+        final BufferedStream line = new BufferedStream( 32 );
         final List<Object> data = new ArrayList<>();
-        final CharBuffer cb = CharBuffer.allocate( 1024 );
-        final ByteBuffer bb = ByteBuffer.allocate( 1024 );
+        final CharBuffer cb = CharBuffer.allocate( BUFFER_SIZE );
+        final ByteBuffer bb = ByteBuffer.allocate( BUFFER_SIZE );
         FrameCompletion frameCompletion;
         ForkedProcessEventType eventType;
         RunMode runMode;
         Charset charset;
-        int bytesCounter;
 
         Memento()
         {
@@ -799,8 +854,10 @@ public class EventConsumerThread extends CloseableDaemonThread
 
         void reset()
         {
-            segment.reset();
             frameCompletion = null;
+            eventType = null;
+            runMode = null;
+            charset = null;
         }
     }
 
@@ -857,4 +914,21 @@ public class EventConsumerThread extends CloseableDaemonThread
             return true;
         }
     }
+
+    public static class MalformedFrameException extends Exception
+    {
+        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;
+        }
+    }
 }