[maven-surefire] branch base64 updated: unit tests cleanup and employed default and non-default charset in the algorithm

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: unit tests cleanup and employed default and non-default charset in the algorithm

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 5c9524d  unit tests cleanup and employed default and non-default charset in the algorithm
5c9524d is described below

commit 5c9524d4539df457ed9121053155e1e903f02617
Author: tibordigana <[hidden email]>
AuthorDate: Wed Sep 16 15:27:47 2020 +0200

    unit tests cleanup and employed default and non-default charset in the algorithm
---
 .../surefire/extensions/EventConsumerThread.java   |  64 ++-
 .../extensions/EventConsumerThreadTest.java        | 638 +++++++++------------
 2 files changed, 322 insertions(+), 380 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 45c6d0d..34787b8 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
@@ -78,7 +78,7 @@ import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.St
 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.booter.Constants.DEFAULT_STREAM_ENCODING;
 import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
 
 /**
@@ -148,7 +148,7 @@ public class EventConsumerThread extends CloseableDaemonThread
 
     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 byte[] DEFAULT_STREAM_ENCODING_BYTES = DEFAULT_STREAM_ENCODING.name().getBytes( US_ASCII );
     private static final int DELIMITER_LENGTH = 1;
     private static final int BYTE_LENGTH = 1;
     private static final int INT_LENGTH = 4;
@@ -227,10 +227,10 @@ public class EventConsumerThread extends CloseableDaemonThread
                             runMode = runModes.get( readSegment( memento ) );
                             break;
                         case STRING_ENCODING:
-                            memento.charset = readCharset( memento );
+                            memento.setCharset( readCharset( memento ) );
                             break;
                         case DATA_STRING:
-                            readString( memento );
+                            memento.data.add( readString( memento ) );
                             break;
                         case DATA_INT:
                             memento.data.add( readInt( memento ) );
@@ -282,7 +282,7 @@ public class EventConsumerThread extends CloseableDaemonThread
         return eventTypes.get( readSegment( memento ) );
     }
 
-    protected void readString( Memento memento ) throws IOException, MalformedFrameException
+    protected String readString( Memento memento ) throws IOException, MalformedFrameException
     {
         memento.cb.clear();
         if ( read( memento, INT_LENGTH + DELIMITER_LENGTH ) == EOF )
@@ -296,9 +296,10 @@ public class EventConsumerThread extends CloseableDaemonThread
             throw new EOFException();
         }
 
+        final String string;
         if ( readCount == 0 )
         {
-            memento.data.add( "" );
+            string = "";
         }
         else if ( readCount == 1 )
         {
@@ -307,14 +308,15 @@ public class EventConsumerThread extends CloseableDaemonThread
                 throw new EOFException();
             }
             byte oneChar = memento.bb.get();
-            memento.data.add( oneChar == 0 ? null : String.valueOf( (char) oneChar ) );
+            string = oneChar == 0 ? null : String.valueOf( (char) oneChar );
         }
         else
         {
-            memento.data.add( readString( memento, readCount ) );
+            string = readString( memento, readCount );
         }
 
         checkDelimiter( memento );
+        return string;
     }
 
     @Nonnull
@@ -353,19 +355,22 @@ public class EventConsumerThread extends CloseableDaemonThread
         int offset = bb.arrayOffset() + bb.position();
         bb.position( bb.position() + length );
         boolean isDefaultEncoding = false;
-        if ( length == STREAM_ENCODING_BYTES.length )
+        if ( length == DEFAULT_STREAM_ENCODING_BYTES.length )
         {
             isDefaultEncoding = true;
             for ( int i = 0; i < length; i++ )
             {
-                isDefaultEncoding &= STREAM_ENCODING_BYTES[i] == array[offset + i];
+                isDefaultEncoding &= DEFAULT_STREAM_ENCODING_BYTES[i] == array[offset + i];
             }
         }
 
         try
         {
             Charset charset =
-                isDefaultEncoding ? STREAM_ENCODING : Charset.forName( new String( array, offset, length, US_ASCII ) );
+                isDefaultEncoding
+                    ? DEFAULT_STREAM_ENCODING
+                    : Charset.forName( new String( array, offset, length, US_ASCII ) );
+
             checkDelimiter( memento );
             return charset;
         }
@@ -609,7 +614,7 @@ public class EventConsumerThread extends CloseableDaemonThread
     String readString( @Nonnull final Memento memento, @Nonnegative final int totalBytes )
         throws IOException, MalformedFrameException
     {
-        memento.decoder.reset();
+        memento.getDecoder().reset();
         final CharBuffer output = memento.cb;
         output.clear();
         final ByteBuffer input = memento.bb;
@@ -632,7 +637,7 @@ public class EventConsumerThread extends CloseableDaemonThread
             {
                 boolean endOfChunk = output.remaining() >= bytesToRead;
                 boolean endOfOutput = isLastChunk && endOfChunk;
-                int readInputBytes = decode( memento.decoder, input, output, bytesToDecode, endOfOutput,
+                int readInputBytes = decode( memento.getDecoder(), input, output, bytesToDecode, endOfOutput,
                     memento.line.positionByteBuffer );
                 bytesToDecode -= readInputBytes;
                 countDecodedBytes += readInputBytes;
@@ -646,7 +651,7 @@ public class EventConsumerThread extends CloseableDaemonThread
             }
         }
 
-        memento.decoder.reset();
+        memento.getDecoder().reset();
         output.clear();
 
         return toString( strings );
@@ -794,7 +799,7 @@ public class EventConsumerThread extends CloseableDaemonThread
                 return;
             }
             ConsoleLogger logger = arguments.getConsoleLogger();
-            String s = toString( STREAM_ENCODING );
+            String s = toString( DEFAULT_STREAM_ENCODING );
             if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
             {
                 if ( logger.isDebugEnabled() )
@@ -833,27 +838,46 @@ public class EventConsumerThread extends CloseableDaemonThread
 
     class Memento
     {
-        final CharsetDecoder decoder;
+        private final CharsetDecoder defaultDecoder;
+        private CharsetDecoder currentDecoder;
         final BufferedStream line = new BufferedStream( 32 );
         final List<Object> data = new ArrayList<>();
         final CharBuffer cb = CharBuffer.allocate( BUFFER_SIZE );
         final ByteBuffer bb = ByteBuffer.allocate( BUFFER_SIZE );
-        Charset charset;
 
         Memento()
         {
-            decoder = STREAM_ENCODING.newDecoder()
+            defaultDecoder = DEFAULT_STREAM_ENCODING.newDecoder()
                 .onMalformedInput( REPLACE )
                 .onUnmappableCharacter( REPLACE );
         }
 
         void reset()
         {
-            charset = null;
+            currentDecoder = null;
+        }
+
+        CharsetDecoder getDecoder()
+        {
+            return currentDecoder == null ? defaultDecoder : currentDecoder;
+        }
+
+        void setCharset( Charset charset )
+        {
+            if ( charset.name().equals( defaultDecoder.charset().name() ) )
+            {
+                currentDecoder = defaultDecoder;
+            }
+            else
+            {
+                currentDecoder = charset.newDecoder()
+                    .onMalformedInput( REPLACE )
+                    .onUnmappableCharacter( REPLACE );
+            }
         }
     }
 
-    static class Segment
+    private static class Segment
     {
         private final byte[] array;
         private final int fromIndex;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
index 0979399..a599e54 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -2,491 +2,409 @@ 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.api.event.Event;
 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.EOFException;
 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.lang.Math.min;
 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.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
 import static org.fest.assertions.Assertions.assertThat;
-//import static org.mockito.Mockito.mock;
-
-//@RunWith( Parameterized.class )
+import static org.powermock.reflect.Whitebox.invokeMethod;
+
+/**
+ * The performance of "get( Integer )" is 13.5 nano seconds on i5/2.6GHz:
+ * <pre>
+ *     {@code
+ *     TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
+ *     map.get( hash );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of getting event type by Segment is 33.7 nano seconds:
+ * <pre>
+ *     {@code
+ *     Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+ *     byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 );
+ *     map.get( new Segment( array, 0, array.length ) );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of decoder:
+ * <pre>
+ *     {@code
+ *     CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+ *             .onMalformedInput( REPLACE )
+ *             .onUnmappableCharacter( REPLACE );
+ *     ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
+ *     CharBuffer chars = CharBuffer.allocate( 100 );
+ *     decoder.reset().decode( buffer, chars, true );
+ *
+ *     String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
+ *
+ *     buffer.clear();
+ *     chars.clear();
+ *
+ *     ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
+ *     }
+ * </pre>
+ *
+ * <br> The performance of decoding 100 bytes via CharacterDecoder - 71 nano seconds:
+ * <pre>
+ *     {@code
+ *     decoder.reset()
+ *         .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+ *     chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
+ *     }
+ * </pre>
+ *
+ * <br> The performance of a pure string creation (instead of decoder) - 31.5 nano seconds:
+ * <pre>
+ *     {@code
+ *     byte[] b = {};
+ *     new String( b, UTF_8 );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of CharsetDecoder with empty ByteBuffer:
+ * <pre>
+ *     {@code
+ *     CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+ *     CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+ *     }
+ * </pre>
+ */
 public class EventConsumerThreadTest
 {
-    @Parameters
-    public static Iterable<Object> channels()
-    {
-        return Arrays.asList( (Object) complexEncodings() );
-    }
+    private static final CountdownCloseable COUNTDOWN_CLOSEABLE = new CountdownCloseable( new MockCloseable(), 0 );
 
-    @Parameter
-    public Channel channel;
+    private static final String PATTERN1 =
+        "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
 
-    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 final String PATTERN2 = "€ab©c";
+
+    private static final byte[] PATTERN2_BYTES =
+        new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
 
-    private static Channel complexEncodings( int chunkSize )
+    @Test
+    public void shouldDecodeHappyCase() throws Exception
     {
-        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 );
+        CharsetDecoder decoder = UTF_8.newDecoder()
+            .onMalformedInput( REPLACE )
+            .onUnmappableCharacter( REPLACE );
+        ByteBuffer input = ByteBuffer.allocate( 1024 );
+        input.put( PATTERN2_BYTES )
+            .flip();
+        int bytesToDecode = PATTERN2_BYTES.length;
+        CharBuffer output = CharBuffer.allocate( 1024 );
+        int readBytes =
+            invokeMethod( EventConsumerThread.class, "decode", decoder, input, output, bytesToDecode, true, 0 );
+
+        assertThat( readBytes )
+            .isEqualTo( bytesToDecode );
+
+        assertThat( output.flip().toString() )
+            .isEqualTo( PATTERN2 );
     }
 
     @Test
-    public void test5() throws IOException, InterruptedException
+    public void shouldDecodeShifted() throws Exception
     {
-        /*final CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+        CharsetDecoder decoder = UTF_8.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]);
+        ByteBuffer input = ByteBuffer.allocate( 1024 );
+        input.put( PATTERN1.getBytes( UTF_8 ) )
+            .put( 90, (byte) 'A' )
+            .put( 91, (byte) 'B' )
+            .put( 92, (byte) 'C' )
+            .position( 90 );
+        CharBuffer output = CharBuffer.allocate( 1024 );
+        int readBytes =
+            invokeMethod( EventConsumerThread.class, "decode", decoder, input, output, 2, true, 0 );
+
+        assertThat( readBytes )
+            .isEqualTo( 2 );
+
+        assertThat( output.flip().toString() )
+            .isEqualTo( "AB" );
+    }
 
-        buffer.clear();
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldNotDecode() throws Exception
+    {
+        CharsetDecoder decoder = UTF_8.newDecoder();
+        ByteBuffer input = ByteBuffer.allocate( 100 );
+        int bytesToDecode = 101;
+        CharBuffer output = CharBuffer.allocate( 1000 );
+        invokeMethod( EventConsumerThread.class, "decode", decoder, input, output, bytesToDecode, true, 0 );
+    }
 
-        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();
+    @Test( expected = EOFException.class )
+    public void shouldNotReadString() throws Exception
+    {
+        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
+        channel.read( ByteBuffer.allocate( 100 ) );
 
-        String s = toString( strings );*/
-        channel = complexEncodings(100);
+        EventConsumerThread thread = new EventConsumerThread( "t", channel,
+            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
 
-        Closeable c = new Closeable()
-        {
-            @Override
-            public void close() throws IOException
-            {
+        Memento memento = thread.new Memento();
+        memento.bb.position( 0 ).limit( 0 );
+        thread.readString( memento, 10 );
+    }
 
-            }
-        };
+    @Test
+    public void shouldReadString() throws Exception
+    {
+        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
 
-        EventHandler eh = new EventHandler()
-        {
-            @Override
-            public void handleEvent( @Nonnull Object event )
-            {
+        EventConsumerThread thread = new EventConsumerThread( "t", channel,
+            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
 
-            }
-        };
+        Memento memento = thread.new Memento();
+        memento.bb.position( 0 ).limit( 0 );
+        String s = thread.readString( memento, 10 );
+        assertThat( s )
+            .isEqualTo( "0123456789" );
+    }
 
-        ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
+    @Test
+    public void shouldReadStringShiftedBuffer() throws Exception
+    {
+        StringBuilder s = new StringBuilder( 1100 );
+        for ( int i = 0; i < 11; i++ )
         {
-            @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 )
-            {
+            s.append( PATTERN1 );
+        }
 
-            }
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
 
-            @Nonnull
-            @Override
-            public ConsoleLogger getConsoleLogger()
-            {
-                return null;
-            }
-        };
+        EventConsumerThread thread = new EventConsumerThread( "t", channel,
+            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
 
-        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©" );*/
+        Memento memento = thread.new Memento();
+        // whatever position will be compacted to 0
+        memento.bb.position( 974 ).limit( 974 );
+        assertThat( thread.readString( memento, PATTERN1.length() + 3 ) )
+            .isEqualTo( PATTERN1 + "012" );
     }
 
     @Test
-    public void test6() throws InterruptedException
+    public void shouldReadStringShiftedInput() throws Exception
     {
-        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++ )
+        StringBuilder s = new StringBuilder( 1100 );
+        for ( int i = 0; i < 11; i++ )
         {
-            decoder
-                .reset()
-                .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
-            chars.flip().toString();//CharsetDecoder + toString = 91 nanos
-            buffer.clear();
-            chars.clear();
+            s.append( PATTERN1 );
         }
-        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);
-        }
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
+        channel.read( ByteBuffer.allocate( 997 ) );
 
-        long l2 = System.currentTimeMillis();
-        System.out.println(l2 - l1);
-        System.out.println(s);
-    }
+        EventConsumerThread thread = new EventConsumerThread( "t", channel,
+            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
 
-    @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() );
-        }
+        Memento memento = thread.new Memento();
+        memento.bb.limit( 0 );
+        assertThat( thread.readString( memento, PATTERN1.length() ) )
+            .isEqualTo( "789" + PATTERN1.substring( 0, 97 ) );
     }
 
     @Test
-    public void test9() throws InterruptedException
+    public void shouldReadMultipleStringsAndShiftedInput() throws Exception
     {
-        ForkedProcessEventType[] enums = ForkedProcessEventType.values();
-        TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
-        for (ForkedProcessEventType e : enums) {
-            map.put( e.getOpcode().hashCode(), e );
+        StringBuilder s = new StringBuilder( 5000 );
+
+        for ( int i = 0; i < 50; i++ )
+        {
+            s.append( PATTERN1 );
         }
-        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 );
+
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
+        channel.read( ByteBuffer.allocate( 1997 ) );
+
+        EventConsumerThread thread = new EventConsumerThread( "t", channel,
+            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
+
+        Memento memento = thread.new Memento();
+        // whatever position will be compacted to 0
+        memento.bb.position( 974 ).limit( 974 );
+
+        StringBuilder expected = new StringBuilder( "789" );
+        for ( int i = 0; i < 11; i++ )
+        {
+            expected.append( PATTERN1 );
         }
-        long l2 = System.currentTimeMillis();
-        System.out.println(l2 - l1);
-        System.out.println(s);
+        expected.setLength( 1100 );
+        assertThat( thread.readString( memento, 1100 ) )
+            .isEqualTo( expected.toString() );
     }
 
     @Test
-    public void test10() throws InterruptedException
+    public void shouldDecode3BytesEncodedSymbol() throws Exception
     {
-        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
+        byte[] encodedSymbol = new byte[] {(byte) -30, (byte) -126, (byte) -84};
+        int countSymbols = 1024;
+        byte[] input = new byte[encodedSymbol.length * countSymbols];
+        for ( int i = 0; i < countSymbols; i++ )
+        {
+            System.arraycopy( encodedSymbol, 0, input, encodedSymbol.length * i, encodedSymbol.length );
         }
-        long l2 = System.currentTimeMillis();
-        System.out.println(l2 - l1);
-        System.out.println(s);
+
+        Channel channel = new Channel( input, 2 );
+        EventConsumerThread thread = new EventConsumerThread( "t", channel,
+            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
+        Memento memento = thread.new Memento();
+        memento.bb.position( 0 ).limit( 0 );
+        String decodedOutput = thread.readString( memento, input.length );
+
+        assertThat( decodedOutput )
+            .isEqualTo( new String( input, 0, input.length, UTF_8 ) );
     }
 
     @Test
-    public void test11() throws InterruptedException
+    public void shouldDecode100Bytes()
     {
-        CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+        CharsetDecoder decoder = DEFAULT_STREAM_ENCODING.newDecoder()
             .onMalformedInput( REPLACE )
             .onUnmappableCharacter( REPLACE );
-        ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
+        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+        ByteBuffer buffer = ByteBuffer.wrap( PATTERN1.getBytes( UTF_8 ) );
         CharBuffer chars = CharBuffer.allocate( 100 );
+        /* uncomment this section for a proper measurement of the exec time
         TimeUnit.SECONDS.sleep( 2 );
         System.gc();
         TimeUnit.SECONDS.sleep( 5 );
+        */
+        String s = null;
         long l1 = System.currentTimeMillis();
         for ( int i = 0; i < 10_000_000; i++ )
         {
-            decoder
-                .reset()
-                .decode( buffer, chars, true );
-            String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
+            decoder.reset()
+                .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+            s = chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
             buffer.clear();
             chars.clear();
-            ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
         }
         long l2 = System.currentTimeMillis();
-        System.out.println(l2 - l1);
+        System.out.println( "decoded 100 bytes within " + ( l2 - l1 ) );
+        assertThat( s )
+            .isEqualTo( PATTERN1 );
     }
 
-    @Test
-    public void test12() throws InterruptedException
+    private static class Channel implements ReadableByteChannel
     {
-        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];
-        }
+        private final byte[] bytes;
+        private final int chunkSize;
+        private int i;
 
-        @Override
-        public int hashCode()
+        public Channel( byte[] bytes, int chunkSize )
         {
-            return hashCode;
+            this.bytes = bytes;
+            this.chunkSize = chunkSize;
         }
 
         @Override
-        public boolean equals( Object obj )
+        public int read( ByteBuffer dst )
         {
-            if ( !( obj instanceof Segment ) )
+            if ( i == bytes.length )
             {
-                return false;
+                return -1;
             }
-
-            Segment that = (Segment) obj;
-            if ( that.length != length )
+            else if ( dst.hasRemaining() )
             {
-                return false;
+                int length = min( min( chunkSize, bytes.length - i ), dst.remaining() ) ;
+                dst.put( bytes, i, length );
+                i += length;
+                return length;
             }
-
-            for ( int i = 0; i < length; i++ )
+            else
             {
-                if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
-                {
-                    return false;
-                }
+                return 0;
             }
-            return true;
         }
-    }
 
-
-    private static String toString( List<String> strings )
-    {
-        if ( strings.size() == 1 )
+        @Override
+        public boolean isOpen()
         {
-            return strings.get( 0 );
+            return false;
         }
-        StringBuilder concatenated = new StringBuilder();
-        for ( String s : strings )
+
+        @Override
+        public void close()
         {
-            concatenated.append( s );
         }
-        return concatenated.toString();
     }
 
-    private StreamReadStatus read( ByteBuffer buffer, int recommendedCount ) throws IOException
+    private static class MockCloseable implements Closeable
     {
-        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+        @Override
+        public void close()
         {
-            return OVERFLOW;
         }
-        else
+    }
+
+    private static class MockEventHandler<T> implements EventHandler<T>
+    {
+        @Override
+        public void handleEvent( @Nonnull T event )
         {
-            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 static class MockForkNodeArguments implements ForkNodeArguments
     {
-        private final byte[] bytes;
-        private final int chunkSize;
-        private int i;
-
-        public Channel( byte[] bytes, int chunkSize )
+        @Nonnull
+        @Override
+        public String getSessionId()
         {
-            this.bytes = bytes;
-            this.chunkSize = chunkSize;
+            return null;
         }
 
-        public void reset()
+        @Override
+        public int getForkChannelId()
         {
-            i = 0;
+            return 0;
         }
 
+        @Nonnull
         @Override
-        public int read( ByteBuffer dst )
+        public File dumpStreamText( @Nonnull String text )
         {
-            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;
-            }
+            return null;
         }
 
+        @Nonnull
         @Override
-        public boolean isOpen()
+        public File dumpStreamException( Throwable t )
         {
-            return false;
+            return null;
         }
 
         @Override
-        public void close() throws IOException
+        public void logWarningAtEnd( @Nonnull String text )
         {
         }
-    }
 
-    enum StreamReadStatus
-    {
-        UNDERFLOW,
-        OVERFLOW,
-        EOF
+        @Nonnull
+        @Override
+        public ConsoleLogger getConsoleLogger()
+        {
+            return null;
+        }
     }
 }