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 2017/08/03 12:48:05 UTC

[1/3] maven-surefire git commit: [SUREFIRE-1222] ForkClient attempts to consume unrelated lines

Repository: maven-surefire
Updated Branches:
  refs/heads/SUREFIRE-1222 [created] 64ae8e88d


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java
new file mode 100644
index 0000000..2552af1
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java
@@ -0,0 +1,481 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.SafeThrowable;
+import org.apache.maven.surefire.report.StackTraceWriter;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import static java.nio.charset.Charset.defaultCharset;
+import static javax.xml.bind.DatatypeConverter.printBase64Binary;
+import static org.apache.maven.surefire.booter.ForkedChannelEncoder.encode;
+import static org.apache.maven.surefire.booter.ForkedChannelEncoder.encodeHeader;
+import static org.apache.maven.surefire.booter.ForkedChannelEncoder.encodeMessage;
+import static org.apache.maven.surefire.booter.ForkedChannelEncoder.encodeOpcode;
+import static org.apache.maven.surefire.booter.ForkedChannelEncoder.print;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER;
+import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class ForkedChannelEncoderTest
+{
+
+    @Test
+    public void shouldBeFailSafe()
+    {
+        Charset encoding = Charset.defaultCharset();
+        assertThat( ForkedChannelEncoder.toBase64( null, encoding ) ).isEqualTo( "-" );
+        assertThat( ForkedChannelEncoder.base64WithUtf8( null ) ).isEqualTo( "-" );
+        StringBuilder builder = new StringBuilder();
+        ForkedChannelEncoder.base64WithUtf8( builder, null );
+        assertThat( builder.toString() ).isEqualTo( "-" );
+    }
+
+    @Test
+    public void shouldHaveSystemProperty()
+    {
+        StringBuilder actualEncoded = encode( BOOTERCODE_SYSPROPS, NORMAL_RUN, "arg1", "arg2" );
+        String expected = MAGIC_NUMBER + BOOTERCODE_SYSPROPS.getOpcode() + ":normal-run:UTF-8:YXJnMQ==:YXJnMg==";
+
+        assertThat( actualEncoded.toString() )
+                .isEqualTo( expected );
+    }
+
+    @Test
+    public void safeThrowableShouldBeEncoded()
+    {
+        final Charset utf8 = Charset.forName( "UTF-8" );
+
+        final String exceptionMessage = "msg";
+        final String encodedExceptionMsg = printBase64Binary( toArray( utf8.encode( exceptionMessage ) ) );
+
+        final String smartStackTrace = "MyTest:86 >> Error";
+        final String encodedSmartStackTrace = printBase64Binary( toArray( utf8.encode( smartStackTrace ) ) );
+
+        final String stackTrace = "trace line 1\ntrace line 2";
+        final String encodedStackTrace = printBase64Binary( toArray( utf8.encode( stackTrace ) ) );
+
+        final String trimmedStackTrace = "trace line 1\ntrace line 2";
+        final String encodedTrimmedStackTrace = printBase64Binary( toArray( utf8.encode( trimmedStackTrace ) ) );
+
+        SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
+        StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+        when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+        when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+        when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
+        when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
+
+        StringBuilder encoded = new StringBuilder();
+        encode( encoded, stackTraceWriter, false );
+        assertThat( encoded.toString() )
+                .isEqualTo( ":" + encodedExceptionMsg + ":" + encodedSmartStackTrace + ":" + encodedStackTrace );
+
+        encoded = new StringBuilder();
+        encode( encoded, stackTraceWriter, true );
+        assertThat( encoded.toString() )
+                .isEqualTo( ":" + encodedExceptionMsg + ":" + encodedSmartStackTrace + ":" + encodedTrimmedStackTrace );
+    }
+
+    @Test
+    public void emptySafeThrowable()
+    {
+        SafeThrowable safeThrowable = new SafeThrowable( new Exception( "" ) );
+
+        StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+        when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+        when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "" );
+        when( stackTraceWriter.writeTraceToString() ).thenReturn( "" );
+
+        StringBuilder encoded = new StringBuilder();
+        encode( encoded, stackTraceWriter, false );
+
+        assertThat( encoded.toString() )
+                .isEqualTo( ":::" );
+    }
+
+    @Test
+    public void nullSafeThrowable()
+    {
+        SafeThrowable safeThrowable = new SafeThrowable( new Exception() );
+
+        StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+        when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+
+        StringBuilder encoded = new StringBuilder();
+        encode( encoded, stackTraceWriter, false );
+
+        assertThat( encoded.toString() )
+                .isEqualTo( ":-:-:-" );
+    }
+
+    @Test
+    public void reportEntry() throws IOException
+    {
+        final Charset utf8 = Charset.forName( "UTF-8" );
+
+        final String exceptionMessage = "msg";
+        final String encodedExceptionMsg = printBase64Binary( toArray( utf8.encode( exceptionMessage ) ) );
+
+        final String smartStackTrace = "MyTest:86 >> Error";
+        final String encodedSmartStackTrace = printBase64Binary( toArray( utf8.encode( smartStackTrace ) ) );
+
+        final String stackTrace = "trace line 1\ntrace line 2";
+        final String encodedStackTrace = printBase64Binary( toArray( utf8.encode( stackTrace ) ) );
+
+        final String trimmedStackTrace = "trace line 1\ntrace line 2";
+        final String encodedTrimmedStackTrace = printBase64Binary( toArray( utf8.encode( trimmedStackTrace ) ) );
+
+        SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
+        StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+        when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+        when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+        when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
+        when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
+
+
+        ReportEntry reportEntry = mock( ReportEntry.class );
+        when( reportEntry.getElapsed() ).thenReturn( 102 );
+        when( reportEntry.getGroup() ).thenReturn( "this group" );
+        when( reportEntry.getMessage() ).thenReturn( "skipped test" );
+        when( reportEntry.getName() ).thenReturn( "my test" );
+        when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
+        when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+        when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
+
+        String encodedSourceName = printBase64Binary( toArray( utf8.encode( reportEntry.getSourceName() ) ) );
+        String encodedName = printBase64Binary( toArray( utf8.encode( reportEntry.getName() ) ) );
+        String encodedGroup = printBase64Binary( toArray( utf8.encode( reportEntry.getGroup() ) ) );
+        String encodedMessage = printBase64Binary( toArray( utf8.encode( reportEntry.getMessage() ) ) );
+
+        StringBuilder encode = encode( "X", "normal-run", reportEntry, false );
+        assertThat( encode.toString() )
+                .isEqualTo( ":maven:surefire:std:out:X:normal-run:UTF-8:"
+                                    + encodedSourceName
+                                    + ":"
+                                    + encodedName
+                                    + ":"
+                                    + encodedGroup
+                                    + ":"
+                                    + encodedMessage
+                                    + ":"
+                                    + 102
+                                    + ":"
+
+                                    + encodedExceptionMsg
+                                    + ":"
+                                    + encodedSmartStackTrace
+                                    + ":"
+                                    + encodedStackTrace
+                );
+
+        encode = encode( "X", "normal-run", reportEntry, true );
+        assertThat( encode.toString() )
+                .isEqualTo( ":maven:surefire:std:out:X:normal-run:UTF-8:"
+                                    + encodedSourceName
+                                    + ":"
+                                    + encodedName
+                                    + ":"
+                                    + encodedGroup
+                                    + ":"
+                                    + encodedMessage
+                                    + ":"
+                                    + 102
+                                    + ":"
+
+                                    + encodedExceptionMsg
+                                    + ":"
+                                    + encodedSmartStackTrace
+                                    + ":"
+                                    + encodedTrimmedStackTrace
+                );
+
+        Stream out = Stream.newStream();
+        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+
+        forkedChannelEncoder.testSetStarting( reportEntry, true );
+        LineNumberReader printedLines = out.newReader( defaultCharset() );
+        assertThat( printedLines.readLine() )
+                .isEqualTo( ":maven:surefire:std:out:testSetStarting:normal-run:UTF-8:"
+                                    + encodedSourceName
+                                    + ":"
+                                    + encodedName
+                                    + ":"
+                                    + encodedGroup
+                                    + ":"
+                                    + encodedMessage
+                                    + ":"
+                                    + 102
+                                    + ":"
+
+                                    + encodedExceptionMsg
+                                    + ":"
+                                    + encodedSmartStackTrace
+                                    + ":"
+                                    + encodedTrimmedStackTrace
+                );
+        assertThat( printedLines.readLine() ).isNull();
+
+        out = Stream.newStream();
+        forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+
+        forkedChannelEncoder.testSetStarting( reportEntry, false );
+        printedLines = out.newReader( defaultCharset() );
+        assertThat( printedLines.readLine() )
+                .isEqualTo( ":maven:surefire:std:out:testSetStarting:normal-run:UTF-8:"
+                                    + encodedSourceName
+                                    + ":"
+                                    + encodedName
+                                    + ":"
+                                    + encodedGroup
+                                    + ":"
+                                    + encodedMessage
+                                    + ":"
+                                    + 102
+                                    + ":"
+
+                                    + encodedExceptionMsg
+                                    + ":"
+                                    + encodedSmartStackTrace
+                                    + ":"
+                                    + encodedStackTrace
+                );
+        assertThat( printedLines.readLine() ).isNull();
+    }
+
+    @Test
+    public void testSendOpcode()
+    {
+        StringBuilder encoded = encodeOpcode( "some-opcode", "normal-run" );
+        assertThat( encoded.toString() )
+                .isEqualTo( ":maven:surefire:std:out:some-opcode:normal-run" );
+
+        encoded = encodeHeader( "some-opcode", "normal-run", Charset.forName( "UTF-8" ) );
+        assertThat( encoded.toString() )
+                .isEqualTo( ":maven:surefire:std:out:some-opcode:normal-run:utf-8" );
+
+        encoded = encodeMessage( "some-opcode", "normal-run", Charset.forName( "UTF-8" ), "msg" );
+        assertThat( encoded.toString() )
+                .isEqualTo( ":maven:surefire:std:out:some-opcode:normal-run:utf-8:msg" );
+
+        encoded = print( "some-opcode", "normal-run", Charset.forName( "UTF-8" ), "msg" );
+        assertThat( encoded.toString() )
+                .isEqualTo( ":maven:surefire:std:out:some-opcode:normal-run:UTF-8:bXNn" );
+
+        encoded = print( "some-opcode", "normal-run", Charset.forName( "UTF-8" ), new String[] { null } );
+        assertThat( encoded.toString() )
+                .isEqualTo( ":maven:surefire:std:out:some-opcode:normal-run:UTF-8:-" );
+    }
+
+    @Test
+    public void testConsole()
+    {
+        Stream out = Stream.newStream();
+        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+
+        forkedChannelEncoder.console( "msg" );
+
+        String encoded = new String( out.toByteArray(), defaultCharset() );
+
+        final Charset utf8 = Charset.forName( "UTF-8" );
+
+        String expected = ":maven:surefire:std:out:console:normal-run:UTF-8:"
+                                  + printBase64Binary( toArray( utf8.encode( "msg" ) ) )
+                                  + "\n";
+
+        assertThat( encoded )
+                .isEqualTo( expected );
+    }
+
+    @Test
+    public void testError()
+    {
+        Stream out = Stream.newStream();
+        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+
+        forkedChannelEncoder.error( "msg" );
+
+        String encoded = new String( out.toByteArray(), defaultCharset() );
+
+        final Charset utf8 = Charset.forName( "UTF-8" );
+
+        String expected = ":maven:surefire:std:out:error:normal-run:UTF-8:"
+                                  + printBase64Binary( toArray( utf8.encode( "msg" ) ) )
+                                  + "\n";
+
+        assertThat( encoded )
+                .isEqualTo( expected );
+    }
+
+    @Test
+    public void testDebug()
+    {
+        Stream out = Stream.newStream();
+        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+
+        forkedChannelEncoder.debug( "msg" );
+
+        String encoded = new String( out.toByteArray(), defaultCharset() );
+
+        final Charset utf8 = Charset.forName( "UTF-8" );
+
+        String expected = ":maven:surefire:std:out:debug:normal-run:UTF-8:"
+                                  + printBase64Binary( toArray( utf8.encode( "msg" ) ) )
+                                  + "\n";
+
+        assertThat( encoded )
+                .isEqualTo( expected );
+    }
+
+    @Test
+    public void testWarning()
+    {
+        Stream out = Stream.newStream();
+        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+
+        forkedChannelEncoder.warning( "msg" );
+
+        String encoded = new String( out.toByteArray(), defaultCharset() );
+
+        final Charset utf8 = Charset.forName( "UTF-8" );
+
+        String expected = ":maven:surefire:std:out:warning:normal-run:UTF-8:"
+                                  + printBase64Binary( toArray( utf8.encode( "msg" ) ) )
+                                  + "\n";
+
+        assertThat( encoded )
+                .isEqualTo( expected );
+    }
+
+    @Test
+    public void testStdOutStream() throws IOException
+    {
+        Charset streamEncoding = Charset.forName( "UTF-8" );
+        Stream out = Stream.newStream();
+        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( streamEncoding, out );
+
+        final Charset encoding = defaultCharset();
+        byte[] msgArray = toArray( encoding.encode( "msg" ) );
+        assertThat( encoding.decode( ByteBuffer.wrap( msgArray ) ).toString() ).isEqualTo( "msg" );
+        forkedChannelEncoder.stdOut( msgArray, 0, msgArray.length );
+
+        String expected = ":maven:surefire:std:out:stdOutStream:normal-run:" + encoding.name() + ":"
+                                  + printBase64Binary( msgArray );
+
+        LineNumberReader printedLines = out.newReader( defaultCharset() );
+        assertThat( printedLines.readLine() )
+                .isEqualTo( expected );
+        assertThat( printedLines.readLine() )
+                .isNull();
+    }
+
+    @Test
+    public void testStdErrStream() throws IOException
+    {
+        Charset streamEncoding = Charset.forName( "ISO-8859-1" );
+        Stream out = Stream.newStream();
+        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( streamEncoding, out );
+
+        final Charset encoding = defaultCharset();
+        byte[] msgArray = toArray( encoding.encode( "msg" ) );
+        assertThat( encoding.decode( ByteBuffer.wrap( msgArray ) ).toString() ).isEqualTo( "msg" );
+        forkedChannelEncoder.stdErr( msgArray, 0, msgArray.length );
+
+        String expected = ":maven:surefire:std:out:stdErrStream:normal-run:" + encoding.name() + ":"
+                                  + printBase64Binary( msgArray );
+
+        LineNumberReader printedLines = out.newReader( defaultCharset() );
+        assertThat( printedLines.readLine() )
+                .isEqualTo( expected );
+        assertThat( printedLines.readLine() )
+                .isNull();
+    }
+
+    @Test
+    public void shouldCountSameNumberOfSystemProperties() throws IOException
+    {
+        Charset streamEncoding = Charset.forName( "ISO-8859-1" );
+        Stream out = Stream.newStream();
+        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( streamEncoding, out );
+
+        int expectedSize = System.getProperties().size();
+        forkedChannelEncoder.sendSystemProperties();
+
+        LineNumberReader printedLines = out.newReader( streamEncoding );
+
+        int size = 0;
+        for ( String line; ( line = printedLines.readLine() ) != null; size++ )
+        {
+            assertThat( line )
+                    .startsWith( ":maven:surefire:std:out:sysProp:normal-run:UTF-8:" );
+        }
+
+        assertThat( size )
+                .isEqualTo( expectedSize );
+    }
+
+    private static class Stream extends PrintStream
+    {
+        private final ByteArrayOutputStream out;
+
+        public Stream( ByteArrayOutputStream out )
+        {
+            super( out, true );
+            this.out = out;
+        }
+
+        byte[] toByteArray()
+        {
+            return out.toByteArray();
+        }
+
+        LineNumberReader newReader( Charset streamCharset )
+        {
+            return new LineNumberReader( new StringReader( new String( toByteArray(), streamCharset ) ) );
+        }
+
+        static Stream newStream()
+        {
+            return new Stream( new ByteArrayOutputStream() );
+        }
+    }
+
+    private static byte[] toArray( ByteBuffer buffer )
+    {
+        return Arrays.copyOfRange( buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.remaining() );
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
index b71eda2..d613205 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
@@ -34,7 +34,7 @@ public class ForkingRunListenerTest
     {
         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         PrintStream target = new PrintStream( byteArrayOutputStream );
-        ForkingRunListener forkingRunListener = new ForkingRunListener( target, 1, true );
+        ForkingRunListener forkingRunListener = new ForkingRunListener( new ForkedChannelEncoder( target ), 1, true );
         forkingRunListener.info( new String( new byte[]{ 65 } ) );
         forkingRunListener.info( new String( new byte[]{ } ) );
 

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/StringUtilsTest.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/StringUtilsTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/StringUtilsTest.java
deleted file mode 100644
index c1c8d73..0000000
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/StringUtilsTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.apache.maven.surefire.util.internal;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.nio.charset.Charset;
-
-import junit.framework.TestCase;
-
-/**
- * @author Andreas Gudian
- */
-public class StringUtilsTest
-    extends TestCase
-{
-
-    public void testUnescapeString()
-    {
-        CharSequence inputString = createInputString();
-
-        StringBuilder escaped = new StringBuilder( inputString.length() * 5 );
-        int initialCapacity = escaped.capacity();
-
-        StringUtils.escapeToPrintable( escaped, inputString );
-
-        assertEquals( initialCapacity, escaped.capacity() );
-
-        StringBuilder unescaped = new StringBuilder( inputString.length() );
-        StringUtils.unescapeString( unescaped, escaped );
-
-        assertEquals( inputString.length(), unescaped.length() );
-
-        for ( int i = 0; i < inputString.length(); i++ )
-        {
-            if ( inputString.charAt( i ) != unescaped.charAt( i ) )
-            {
-                fail( "Input and Unescaped String are not equal at position " + i );
-            }
-        }
-    }
-
-    private CharSequence createInputString()
-    {
-        StringBuilder sb = new StringBuilder();
-        for ( int i = 0; i < Character.MAX_CODE_POINT; i++ )
-        {
-            sb.appendCodePoint( i );
-        }
-
-        return sb;
-    }
-
-    public void testUnescapeBytes()
-    {
-        byte[] input = new byte[256];
-
-        for ( int i = 0; i <= 0xFF; i++ )
-        {
-            byte b = (byte) ( 0xFF & i );
-            input[i] = b;
-        }
-
-        byte[] escaped = new byte[input.length * 3];
-
-        int escapedBytes = StringUtils.escapeBytesToPrintable( escaped, 0, input, 0, input.length );
-
-        String escapedString = new String( escaped, 0, escapedBytes );
-
-        assertEquals( escapedBytes, escapedString.length() );
-
-        java.nio.ByteBuffer unescaped = StringUtils.unescapeBytes( escapedString, Charset.defaultCharset().name() );
-
-        assertEquals( input.length, unescaped.remaining() - unescaped.position() );
-
-        for ( int i = 0; i < input.length; i++ )
-        {
-            assertEquals( "At position " + i, input[i], unescaped.get() );
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
index e7d97c8..cc1f0eb 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
@@ -84,7 +84,7 @@ public class Classpath implements Iterable<String>
         for ( String element : elements )
         {
             element = element.trim();
-            if ( element.length() != 0 )
+            if ( !element.isEmpty() )
             {
                 newCp.add( element );
             }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index fef21d1..b42c0e2 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -87,7 +87,7 @@ public final class ForkedBooter
     {
         final CommandReader reader = startupMasterProcessReader();
         final ScheduledFuture<?> pingScheduler = listenToShutdownCommands( reader );
-        final PrintStream originalOut = out;
+        final ForkedChannelEncoder eventChannel = new ForkedChannelEncoder( out );
         try
         {
             final String tmpDir = args[0];
@@ -128,7 +128,7 @@ public final class ForkedBooter
             }
             else if ( readTestsFromInputStream )
             {
-                testSet = new LazyTestsToRun( originalOut );
+                testSet = new LazyTestsToRun( eventChannel );
             }
             else
             {
@@ -137,7 +137,7 @@ public final class ForkedBooter
 
             try
             {
-                runSuitesInProcess( testSet, startupConfiguration, providerConfiguration, originalOut );
+                runSuitesInProcess( testSet, startupConfiguration, providerConfiguration, eventChannel );
             }
             catch ( InvocationTargetException t )
             {
@@ -146,7 +146,8 @@ public final class ForkedBooter
                     new LegacyPojoStackTraceWriter( "test subsystem", "no method", t.getTargetException() );
                 StringBuilder stringBuilder = new StringBuilder();
                 encode( stringBuilder, stackTraceWriter, false );
-                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" , originalOut );
+                //eventChannel.error( t );
+                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" , eventChannel );
             }
             catch ( Throwable t )
             {
@@ -154,9 +155,9 @@ public final class ForkedBooter
                 StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t );
                 StringBuilder stringBuilder = new StringBuilder();
                 encode( stringBuilder, stackTraceWriter, false );
-                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n", originalOut );
+                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n", eventChannel );
             }
-            acknowledgedExit( reader, originalOut );
+            acknowledgedExit( reader, eventChannel );
         }
         catch ( Throwable t )
         {
@@ -233,14 +234,14 @@ public final class ForkedBooter
         };
     }
 
-    private static void encodeAndWriteToOutput( String string, PrintStream out )
+    private static void encodeAndWriteToOutput( String string, ForkedChannelEncoder eventChannel )
     {
         byte[] encodeBytes = encodeStringForForkCommunication( string );
         //noinspection SynchronizationOnLocalVariableOrMethodParameter
-        synchronized ( out )
+        synchronized ( eventChannel )
         {
-            out.write( encodeBytes, 0, encodeBytes.length );
-            out.flush();
+            eventChannel.write( encodeBytes, 0, encodeBytes.length );
+            eventChannel.flush();
         }
     }
 
@@ -255,7 +256,7 @@ public final class ForkedBooter
         System.exit( returnCode );
     }
 
-    private static void acknowledgedExit( CommandReader reader, PrintStream originalOut )
+    private static void acknowledgedExit( CommandReader reader, ForkedChannelEncoder eventChannel )
     {
         final Semaphore barrier = new Semaphore( 0 );
         reader.addByeAckListener( new CommandListener()
@@ -267,7 +268,7 @@ public final class ForkedBooter
                                       }
                                   }
         );
-        encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut );
+        encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", eventChannel );
         launchLastDitchDaemonShutdownThread( 0 );
         long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS );
         acquireOnePermit( barrier, timeoutMillis );
@@ -288,20 +289,20 @@ public final class ForkedBooter
 
     private static RunResult runSuitesInProcess( Object testSet, StartupConfiguration startupConfiguration,
                                                  ProviderConfiguration providerConfiguration,
-                                                 PrintStream originalSystemOut )
+                                                 ForkedChannelEncoder eventChannel )
         throws SurefireExecutionException, TestSetFailedException, InvocationTargetException
     {
-        final ReporterFactory factory = createForkingReporterFactory( providerConfiguration, originalSystemOut );
+        final ReporterFactory factory = createForkingReporterFactory( providerConfiguration, eventChannel );
 
         return invokeProviderInSameClassLoader( testSet, factory, providerConfiguration, true, startupConfiguration,
                                                       false );
     }
 
     private static ReporterFactory createForkingReporterFactory( ProviderConfiguration providerConfiguration,
-                                                                 PrintStream originalSystemOut )
+                                                                 ForkedChannelEncoder eventChannel )
     {
         final boolean trimStackTrace = providerConfiguration.getReporterConfiguration().isTrimStackTrace();
-        return new ForkingReporterFactory( trimStackTrace, originalSystemOut );
+        return new ForkingReporterFactory( trimStackTrace, eventChannel );
     }
 
     private static synchronized ScheduledThreadPoolExecutor getJvmTerminator()

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java
index bbd0f70..606e76b 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java
@@ -51,8 +51,8 @@ public class TypeEncodedValue
 
     public Object getDecodedValue( ClassLoader classLoader )
     {
-        // todo: use jdk6 switch case
-        if ( type.trim().length() == 0 )
+        // todo: use jdk7 switch case
+        if ( type.trim().isEmpty() )
         {
             return null;
         }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
index 4cb162d..78a1450 100755
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
@@ -70,7 +70,7 @@ public final class SurefireLauncher
     public SurefireLauncher setInProcessJavaHome()
     {
         String javaHome = System.getenv( "JAVA_HOME" );
-        if ( javaHome != null && javaHome.length() > 0 )
+        if ( javaHome != null && !javaHome.isEmpty() )
         {
             try
             {

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerUtils.java
----------------------------------------------------------------------
diff --git a/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerUtils.java b/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerUtils.java
index 7eca90b..b535758 100644
--- a/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerUtils.java
+++ b/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerUtils.java
@@ -33,6 +33,11 @@ public final class ConsoleLoggerUtils
         throw new IllegalStateException( "non instantiable constructor" );
     }
 
+    public static String toString( Throwable t )
+    {
+        return toString( null, t );
+    }
+
     public static String toString( String message, Throwable t )
     {
         StringWriter result = new StringWriter( 512 );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-providers/common-junit48/src/main/java/org/apache/maven/surefire/common/junit48/RequestedTest.java
----------------------------------------------------------------------
diff --git a/surefire-providers/common-junit48/src/main/java/org/apache/maven/surefire/common/junit48/RequestedTest.java b/surefire-providers/common-junit48/src/main/java/org/apache/maven/surefire/common/junit48/RequestedTest.java
index 0eca387..52c2d52 100644
--- a/surefire-providers/common-junit48/src/main/java/org/apache/maven/surefire/common/junit48/RequestedTest.java
+++ b/surefire-providers/common-junit48/src/main/java/org/apache/maven/surefire/common/junit48/RequestedTest.java
@@ -59,7 +59,7 @@ final class RequestedTest
     public String describe()
     {
         String description = test.toString();
-        return description.length() == 0 ? "*" : description;
+        return description.isEmpty() ? "*" : description;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java b/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java
index 9ca397e..a8071cb 100644
--- a/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java
+++ b/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java
@@ -24,6 +24,7 @@ import java.util.List;
 import org.apache.maven.surefire.common.junit3.JUnit3Reflector;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 
 import junit.framework.Test;
@@ -104,6 +105,11 @@ public class JUnitTestSetTest
         {
         }
 
+        public RunMode markAs( RunMode currentRunMode )
+        {
+            return RunMode.NORMAL_RUN;
+        }
+
         public void testSkippedByUser( ReportEntry report )
         {
             testSkipped( report );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-providers/surefire-junit47/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/pom.xml b/surefire-providers/surefire-junit47/pom.xml
index 39f68f1..aa91468 100644
--- a/surefire-providers/surefire-junit47/pom.xml
+++ b/surefire-providers/surefire-junit47/pom.xml
@@ -90,7 +90,7 @@
           </execution>
           <execution>
             <id>test</id>
-            <phase>process-sources</phase>
+            <phase>process-test-sources</phase>
             <goals>
               <goal>copy</goal>
             </goals>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ConcurrentRunListener.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ConcurrentRunListener.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ConcurrentRunListener.java
index 89c6104..ebcc31c 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ConcurrentRunListener.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ConcurrentRunListener.java
@@ -25,6 +25,7 @@ import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 
@@ -118,6 +119,11 @@ public abstract class ConcurrentRunListener
         reporterManagerThreadLocal.get().testExecutionSkippedByUser();
     }
 
+    public RunMode markAs( RunMode currentRunMode )
+    {
+        return reporterManagerThreadLocal.get().markAs( currentRunMode );
+    }
+
     public void testAssumptionFailure( ReportEntry failure )
     {
         final TestMethod testMethod = getOrCreateThreadAttachedTestMethod( failure );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java
index 2abfa58..656d55b 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java
@@ -140,7 +140,7 @@ final class JUnitCoreWrapper
         if ( computer instanceof ParallelComputer )
         {
             String timeoutMessage = ( (ParallelComputer) computer ).describeElapsedTimeout();
-            if ( timeoutMessage.length() != 0 )
+            if ( !timeoutMessage.isEmpty() )
             {
                 throw new TestSetFailedException( timeoutMessage );
             }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-providers/surefire-testng-utils/src/main/java/org/apache/maven/surefire/testng/utils/GroupMatcherMethodSelector.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-testng-utils/src/main/java/org/apache/maven/surefire/testng/utils/GroupMatcherMethodSelector.java b/surefire-providers/surefire-testng-utils/src/main/java/org/apache/maven/surefire/testng/utils/GroupMatcherMethodSelector.java
index db3bb41..9ff5fb4 100644
--- a/surefire-providers/surefire-testng-utils/src/main/java/org/apache/maven/surefire/testng/utils/GroupMatcherMethodSelector.java
+++ b/surefire-providers/surefire-testng-utils/src/main/java/org/apache/maven/surefire/testng/utils/GroupMatcherMethodSelector.java
@@ -19,19 +19,21 @@ package org.apache.maven.surefire.testng.utils;
  * under the License.
  */
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 import org.apache.maven.surefire.group.match.AndGroupMatcher;
 import org.apache.maven.surefire.group.match.GroupMatcher;
 import org.apache.maven.surefire.group.match.InverseGroupMatcher;
 import org.apache.maven.surefire.group.parse.GroupMatcherParser;
 import org.apache.maven.surefire.group.parse.ParseException;
-
 import org.testng.IMethodSelector;
 import org.testng.IMethodSelectorContext;
 import org.testng.ITestNGMethod;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank;
+
 /**
  * Method selector delegating to {@link GroupMatcher} to decide if a method is included or not.
  *
@@ -77,7 +79,7 @@ public class GroupMatcherMethodSelector
         {
             AndGroupMatcher matcher = new AndGroupMatcher();
             GroupMatcher in = null;
-            if ( groups != null && groups.trim().length() > 0 )
+            if ( isNotBlank( groups ) )
             {
                 in = new GroupMatcherParser( groups ).parse();
             }
@@ -88,7 +90,7 @@ public class GroupMatcherMethodSelector
             }
 
             GroupMatcher ex = null;
-            if ( excludedGroups != null && excludedGroups.trim().length() > 0 )
+            if ( isNotBlank( excludedGroups ) )
             {
                 ex = new GroupMatcherParser( excludedGroups ).parse();
             }


[2/3] maven-surefire git commit: [SUREFIRE-1222] ForkClient attempts to consume unrelated lines

Posted by ti...@apache.org.
http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
new file mode 100644
index 0000000..06849f0
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
@@ -0,0 +1,666 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.booter.ForkedChannelEncoder;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+import org.apache.maven.surefire.report.SafeThrowable;
+import org.apache.maven.surefire.report.StackTraceWriter;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Map;
+
+import static java.nio.charset.Charset.defaultCharset;
+import static javax.xml.bind.DatatypeConverter.printBase64Binary;
+import static org.apache.maven.plugin.surefire.booterclient.output.ForkedChannelDecoder.toReportEntry;
+import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.rules.ExpectedException.none;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+@RunWith( Enclosed.class )
+public class ForkedChannelDecoderTest
+{
+    public static class DecoderOperationsTest
+    {
+        @Rule
+        public final ExpectedException rule = none();
+
+        @Test
+        public void shouldBeFailSafe()
+        {
+            Charset encoding = Charset.defaultCharset();
+            assertThat( ForkedChannelDecoder.decode( null, encoding ) ).isNull();
+            assertThat( ForkedChannelDecoder.decode( "-", encoding ) ).isNull();
+            assertThat( ForkedChannelDecoder.decodeToInteger( null ) ).isNull();
+            assertThat( ForkedChannelDecoder.decodeToInteger( "-" ) ).isNull();
+            assertThat( ForkedChannelDecoder.decodeToBytes( null ) ).isNull();
+            assertThat( ForkedChannelDecoder.decodeToBytes( "-" ) ).isNull();
+        }
+
+        @Test
+        public void shouldHaveSystemProperty() throws IOException
+        {
+            Stream out = Stream.newStream();
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+            forkedChannelEncoder.sendSystemProperties();
+
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setSystemPropertiesListener( new PropertyEventAssertionListener( NORMAL_RUN ) );
+            LineNumberReader reader = out.newReader( defaultCharset() );
+            for ( String line; ( line = reader.readLine() ) != null; )
+            {
+                decoder.handleEvent( line, new AssertionErrorHandler() );
+            }
+            assertThat( reader.getLineNumber() ).isPositive();
+        }
+
+        @Test
+        public void shouldRecognizeEmptyStream4ReportEntry()
+        {
+            ReportEntry reportEntry = toReportEntry( null, null, "", null, null, "", "", "", null );
+            assertThat( reportEntry ).isNull();
+
+            reportEntry = toReportEntry( defaultCharset(), "", "", "", "", "-", "", "", "" );
+            assertThat( reportEntry ).isNotNull();
+            assertThat( reportEntry.getStackTraceWriter() ).isNull();
+            assertThat( reportEntry.getSourceName() ).isEmpty();
+            assertThat( reportEntry.getName() ).isEmpty();
+            assertThat( reportEntry.getGroup() ).isEmpty();
+            assertThat( reportEntry.getNameWithGroup() ).isEmpty();
+            assertThat( reportEntry.getMessage() ).isEmpty();
+            assertThat( reportEntry.getElapsed() ).isNull();
+
+            rule.expect( NumberFormatException.class );
+            toReportEntry( defaultCharset(), "", "", "", "", "", "", "", "" );
+            fail();
+        }
+
+        @Test
+        public void testCreatingReportEntry()
+        {
+            final Charset utf8 = Charset.forName( "UTF-8" );
+
+            final String exceptionMessage = "msg";
+            final String encodedExceptionMsg = printBase64Binary( toArray( utf8.encode( exceptionMessage ) ) );
+
+            final String smartStackTrace = "MyTest:86 >> Error";
+            final String encodedSmartStackTrace = printBase64Binary( toArray( utf8.encode( smartStackTrace ) ) );
+
+            final String stackTrace = "Exception: msg\ntrace line 1\ntrace line 2";
+            final String encodedStackTrace = printBase64Binary( toArray( utf8.encode( stackTrace ) ) );
+
+            final String trimmedStackTrace = "trace line 1\ntrace line 2";
+            final String encodedTrimmedStackTrace = printBase64Binary( toArray( utf8.encode( trimmedStackTrace ) ) );
+
+            SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
+            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+            when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
+            when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
+
+            ReportEntry reportEntry = mock( ReportEntry.class );
+            when( reportEntry.getElapsed() ).thenReturn( 102 );
+            when( reportEntry.getGroup() ).thenReturn( "this group" );
+            when( reportEntry.getMessage() ).thenReturn( "skipped test" );
+            when( reportEntry.getName() ).thenReturn( "my test" );
+            when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
+            when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
+
+            String encodedSourceName = printBase64Binary( toArray( utf8.encode( reportEntry.getSourceName() ) ) );
+            String encodedName = printBase64Binary( toArray( utf8.encode( reportEntry.getName() ) ) );
+            String encodedGroup = printBase64Binary( toArray( utf8.encode( reportEntry.getGroup() ) ) );
+            String encodedMessage = printBase64Binary( toArray( utf8.encode( reportEntry.getMessage() ) ) );
+
+            ReportEntry decodedReportEntry = toReportEntry( utf8, encodedSourceName, encodedName, encodedGroup,
+                                                                  encodedMessage, "-", null, null, null
+            );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
+
+            decodedReportEntry = toReportEntry( utf8, encodedSourceName, encodedName, encodedGroup, encodedMessage, "-",
+                                                      encodedExceptionMsg, encodedSmartStackTrace, null
+            );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getElapsed() ).isNull();
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
+
+            decodedReportEntry = toReportEntry( utf8, encodedSourceName, encodedName, encodedGroup, encodedMessage,
+                                                      "1003", encodedExceptionMsg, encodedSmartStackTrace, null
+            );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
+
+            decodedReportEntry = toReportEntry( utf8, encodedSourceName, encodedName, encodedGroup, encodedMessage,
+                                                      "1003", encodedExceptionMsg, encodedSmartStackTrace,
+                                                      encodedStackTrace
+            );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+                    .isEqualTo( exceptionMessage );
+            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+                    .isEqualTo( smartStackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
+
+            decodedReportEntry = toReportEntry( utf8, encodedSourceName, encodedName, encodedGroup, encodedMessage,
+                                                      "1003", encodedExceptionMsg, encodedSmartStackTrace,
+                                                      encodedTrimmedStackTrace
+            );
+
+            assertThat( decodedReportEntry ).isNotNull();
+            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
+            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+                    .isEqualTo( exceptionMessage );
+            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+                    .isEqualTo( smartStackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( trimmedStackTrace );
+            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() )
+                    .isEqualTo( trimmedStackTrace );
+        }
+
+        @Test
+        public void shouldSendByeEvent() throws IOException
+        {
+            Stream out = Stream.newStream();
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+            forkedChannelEncoder.bye();
+            String read = new String( out.toByteArray(), defaultCharset() );
+            assertThat( read )
+                    .isEqualTo( ":maven:surefire:std:out:bye:normal-run\n" );
+            LineNumberReader lines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setByeListener( new EventAssertionListener( NORMAL_RUN ) );
+            decoder.handleEvent( lines.readLine(), new AssertionErrorHandler() );
+            assertThat( lines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void shouldSendStopOnNextTestEvent() throws IOException
+        {
+
+            Stream out = Stream.newStream();
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+            forkedChannelEncoder.stopOnNextTest();
+            String read = new String( out.toByteArray(), defaultCharset() );
+            assertThat( read )
+                    .isEqualTo( ":maven:surefire:std:out:stopOnNextTest:normal-run\n" );
+            LineNumberReader lines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setStopOnNextTestListener( new EventAssertionListener( NORMAL_RUN ) );
+            decoder.handleEvent( lines.readLine(), new AssertionErrorHandler() );
+            assertThat( lines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void shouldSendNextTestEvent() throws IOException
+        {
+
+            Stream out = Stream.newStream();
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+            forkedChannelEncoder.acquireNextTest();
+            String read = new String( out.toByteArray(), defaultCharset() );
+            assertThat( read )
+                    .isEqualTo( ":maven:surefire:std:out:nextTest:normal-run\n" );
+            LineNumberReader lines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setAcquireNextTestListener( new EventAssertionListener( NORMAL_RUN ) );
+            decoder.handleEvent( lines.readLine(), new AssertionErrorHandler() );
+            assertThat( lines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void testConsole() throws IOException
+        {
+            Stream out = Stream.newStream();
+
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+            forkedChannelEncoder.console( "msg" );
+
+            LineNumberReader lines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setConsoleInfoListener( new StringEventAssertionListener( "msg" ) );
+            decoder.handleEvent( lines.readLine(), new AssertionErrorHandler() );
+            assertThat( lines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void testError() throws IOException
+        {
+            Stream out = Stream.newStream();
+
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+            forkedChannelEncoder.error( "msg" );
+
+            LineNumberReader lines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setConsoleErrorListener( new StackTraceEventListener( "msg", "stack trace" ) );
+            decoder.handleEvent( lines.readLine(), new AssertionErrorHandler() );
+            assertThat( lines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void testDebug() throws IOException
+        {
+            Stream out = Stream.newStream();
+
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+            forkedChannelEncoder.debug( "msg" );
+
+            LineNumberReader lines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setConsoleDebugListener( new StringEventAssertionListener( "msg" ) );
+            decoder.handleEvent( lines.readLine(), new AssertionErrorHandler() );
+            assertThat( lines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void testWarning() throws IOException
+        {
+            Stream out = Stream.newStream();
+
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+            forkedChannelEncoder.warning( "msg" );
+
+            LineNumberReader lines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setConsoleWarningListener( new StringEventAssertionListener( "msg" ) );
+            decoder.handleEvent( lines.readLine(), new AssertionErrorHandler() );
+            assertThat( lines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void testStdOutStream() throws IOException
+        {
+            Charset streamEncoding = Charset.forName( "UTF-8" );
+            Stream out = Stream.newStream();
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( streamEncoding, out );
+
+            final Charset encoding = defaultCharset();
+            byte[] msgArray = toArray( encoding.encode( "msg" ) );
+            assertThat( encoding.decode( ByteBuffer.wrap( msgArray ) ).toString() ).isEqualTo( "msg" );
+            forkedChannelEncoder.stdOut( msgArray, 0, msgArray.length );
+
+            LineNumberReader printedLines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setStdOutListener( new BinaryEventAssertionListener( NORMAL_RUN, encoding,
+                                                                               "msg".getBytes( encoding )
+                    )
+            );
+            decoder.handleEvent( printedLines.readLine(), new AssertionErrorHandler() );
+            assertThat( printedLines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void testStdErrStream() throws IOException
+        {
+            Charset streamEncoding = Charset.forName( "ISO-8859-1" );
+            Stream out = Stream.newStream();
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( streamEncoding, out );
+
+            final Charset encoding = defaultCharset();
+            byte[] msgArray = toArray( encoding.encode( "msg" ) );
+            assertThat( encoding.decode( ByteBuffer.wrap( msgArray ) ).toString() ).isEqualTo( "msg" );
+            forkedChannelEncoder.stdErr( msgArray, 0, msgArray.length );
+
+            LineNumberReader printedLines = out.newReader( defaultCharset() );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setStdErrListener( new BinaryEventAssertionListener( NORMAL_RUN, encoding,
+                                                                               "msg".getBytes( encoding )
+                    )
+            );
+            decoder.handleEvent( printedLines.readLine(), new AssertionErrorHandler() );
+            assertThat( printedLines.readLine() )
+                    .isNull();
+        }
+
+        @Test
+        public void shouldCountSameNumberOfSystemProperties() throws IOException
+        {
+            Stream out = Stream.newStream();
+
+            Charset streamEncoding = Charset.forName( "ISO-8859-1" );
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( streamEncoding, out );
+            forkedChannelEncoder.sendSystemProperties();
+
+            LineNumberReader printedLines = out.newReader( streamEncoding );
+            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            decoder.setSystemPropertiesListener( new PropertyEventAssertionListener( NORMAL_RUN ) );
+            decoder.handleEvent( printedLines.readLine(), new AssertionErrorHandler() );
+        }
+    }
+
+    @RunWith( Theories.class )
+    public static class ReportEntryTest
+    {
+        @DataPoints( value = "operation" )
+        public static String[][] operations = { { "testSetStarting", "setTestSetStartingListener" },
+                                                { "testSetCompleted", "setTestSetCompletedListener" },
+                                                { "testStarting", "setTestStartingListener" },
+                                                { "testSucceeded", "setTestSucceededListener" },
+                                                { "testFailed", "setTestFailedListener" },
+                                                { "testSkipped", "setTestSkippedListener" },
+                                                { "testError", "setTestErrorListener" },
+                                                { "testAssumptionFailure", "setTestAssumptionFailureListener" }
+        };
+
+        @DataPoints( value = "reportedMessage" )
+        public static String[] reportedMessage = { null, "skipped test" };
+
+        @DataPoints( value = "elapsed" )
+        public static Integer[] elapsed = { null, 102 };
+
+        @DataPoints( value = "trim" )
+        public static boolean[] trim = { false, true };
+
+        @DataPoints( value = "msg" )
+        public static boolean[] msg = { false, true };
+
+        @DataPoints( value = "smart" )
+        public static boolean[] smart = { false, true };
+
+        @DataPoints( value = "trace" )
+        public static boolean[] trace = { false, true };
+
+        @Theory
+        public void testReportEntryOperations( @FromDataPoints( "operation" ) String[] operation,
+                                               @FromDataPoints( "reportedMessage" ) String reportedMessage,
+                                               @FromDataPoints( "elapsed" ) Integer elapsed,
+                                               @FromDataPoints( "trim" ) boolean trim,
+                                               @FromDataPoints( "msg" ) boolean msg,
+                                               @FromDataPoints( "smart" ) boolean smart,
+                                               @FromDataPoints( "trace" ) boolean trace )
+                throws Exception
+        {
+            String exceptionMessage = msg ? "msg" : null;
+            String smartStackTrace = smart ? "MyTest:86 >> Error" : null;
+            String exceptionStackTrace =
+                    trace ? ( trim ? "trace line 1\ntrace line 2" : "Exception: msg\ntrace line 1\ntrace line 2" )
+                            : null;
+
+            StackTraceWriter stackTraceWriter = null;
+            if ( exceptionStackTrace != null )
+            {
+                SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
+                stackTraceWriter = mock( StackTraceWriter.class );
+                when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+                when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+                when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( exceptionStackTrace );
+                when( stackTraceWriter.writeTraceToString() ).thenReturn( exceptionStackTrace );
+            }
+
+            ReportEntry reportEntry = mock( ReportEntry.class );
+            when( reportEntry.getElapsed() ).thenReturn( elapsed );
+            when( reportEntry.getGroup() ).thenReturn( "this group" );
+            when( reportEntry.getMessage() ).thenReturn( reportedMessage );
+            when( reportEntry.getName() ).thenReturn( "my test" );
+            when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
+            when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
+
+            Stream out = Stream.newStream();
+
+            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( defaultCharset(), out );
+
+            ForkedChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
+                    .invoke( forkedChannelEncoder, reportEntry, trim );
+
+            ForkedChannelDecoder forkedChannelDecoder = new ForkedChannelDecoder();
+
+            ForkedChannelDecoder.class.getMethod( operation[1], ForkedProcessReportEventListener.class )
+                    .invoke( forkedChannelDecoder, new ReportEventAssertionListener( reportEntry ) );
+
+            forkedChannelDecoder.handleEvent( out.newReader( defaultCharset() ).readLine(),
+                                                    new AssertionErrorHandler()
+            );
+        }
+    }
+
+    private static class AssertionErrorHandler implements ForkedChannelDecoderErrorHandler
+    {
+        public void handledError( String line, Throwable e )
+        {
+            if ( e != null )
+            {
+                e.printStackTrace();
+            }
+            fail( line + ( e == null ? "" : "\n" + e.getLocalizedMessage() ) );
+        }
+    }
+
+    private static class PropertyEventAssertionListener implements ForkedProcessPropertyEventListener
+    {
+        private final Map sysProps = System.getProperties();
+        private final RunMode runMode;
+
+        PropertyEventAssertionListener( RunMode runMode )
+        {
+            this.runMode = runMode;
+        }
+
+        public void handle( String key, String value )
+        {
+            assertTrue( sysProps.containsKey( key ) );
+            assertThat( sysProps.get( key ) ).isEqualTo( value );
+        }
+    }
+
+    private static class EventAssertionListener implements ForkedProcessEventListener
+    {
+        public void handle()
+        {
+        }
+    }
+
+    private static class StringEventAssertionListener implements ForkedProcessStringEventListener
+    {
+        private final String msg;
+
+        StringEventAssertionListener( String msg )
+        {
+            this.msg = msg;
+        }
+
+        public void handle( String msg )
+        {
+            assertThat( msg )
+                    .isEqualTo( this.msg );
+        }
+    }
+
+    private static class StackTraceEventListener implements ForkedProcessStackTraceEventListener
+    {
+        private final String msg;
+        private final String stackTrace;
+
+        StackTraceEventListener( String msg, String stackTrace )
+        {
+            this.msg = msg;
+            this.stackTrace = stackTrace;
+        }
+
+        @Override
+        public void handle( String msg, String stackTrace )
+        {
+            assertThat( msg )
+                    .isEqualTo( this.msg );
+
+            assertThat( stackTrace )
+                    .isEqualTo( this.stackTrace );
+        }
+    }
+
+    private static class BinaryEventAssertionListener implements ForkedProcessBinaryEventListener
+    {
+        private final RunMode runMode;
+        private final Charset encoding;
+        private final byte[] binary;
+
+        BinaryEventAssertionListener( RunMode runMode, Charset encoding, byte[] binary )
+        {
+            this.runMode = runMode;
+            this.encoding = encoding;
+            this.binary = binary;
+        }
+
+        public void handle( RunMode runMode, Charset encoding, byte[] binary )
+        {
+            assertThat( runMode )
+                    .isEqualTo( this.runMode );
+
+            assertThat( encoding )
+                    .isEqualTo( this.encoding );
+
+            assertThat( binary )
+                    .isEqualTo( this.binary );
+        }
+    }
+
+    private static class ReportEventAssertionListener implements ForkedProcessReportEventListener
+    {
+        private final ReportEntry reportEntry;
+
+        ReportEventAssertionListener( ReportEntry reportEntry )
+        {
+            this.reportEntry = reportEntry;
+        }
+
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            assertThat( reportEntry.getSourceName() ).isEqualTo( this.reportEntry.getSourceName() );
+            assertThat( reportEntry.getName() ).isEqualTo( this.reportEntry.getName() );
+            assertThat( reportEntry.getGroup() ).isEqualTo( this.reportEntry.getGroup() );
+            assertThat( reportEntry.getMessage() ).isEqualTo( this.reportEntry.getMessage() );
+            assertThat( reportEntry.getElapsed() ).isEqualTo( this.reportEntry.getElapsed() );
+            if ( reportEntry.getStackTraceWriter() == null )
+            {
+                assertThat( this.reportEntry.getStackTraceWriter() ).isNull();
+            }
+            else
+            {
+                assertThat( this.reportEntry.getStackTraceWriter() ).isNotNull();
+
+                assertThat( reportEntry.getStackTraceWriter().getThrowable().getMessage() )
+                        .isEqualTo( this.reportEntry.getStackTraceWriter().getThrowable().getMessage() );
+
+                assertThat( reportEntry.getStackTraceWriter().getThrowable().getLocalizedMessage() )
+                        .isEqualTo( this.reportEntry.getStackTraceWriter().getThrowable().getLocalizedMessage() );
+
+                assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+                        .isEqualTo( this.reportEntry.getStackTraceWriter().smartTrimmedStackTrace() );
+            }
+        }
+    }
+
+    private static class Stream extends PrintStream
+    {
+        private final ByteArrayOutputStream out;
+
+        public Stream( ByteArrayOutputStream out )
+        {
+            super( out, true );
+            this.out = out;
+        }
+
+        byte[] toByteArray()
+        {
+            return out.toByteArray();
+        }
+
+        LineNumberReader newReader( Charset streamCharset )
+        {
+            return new LineNumberReader( new StringReader( new String( toByteArray(), streamCharset ) ) );
+        }
+
+        static Stream newStream()
+        {
+            return new Stream( new ByteArrayOutputStream() );
+        }
+    }
+
+    private static byte[] toArray( ByteBuffer buffer )
+    {
+        return Arrays.copyOfRange( buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.remaining() );
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index 9fb45bf..0c92635 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -30,6 +30,7 @@ import org.apache.maven.plugin.surefire.booterclient.ForkConfigurationTest;
 import org.apache.maven.plugin.surefire.booterclient.ForkingRunListenerTest;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkClientTest;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactoryTest;
 import org.apache.maven.plugin.surefire.report.StatelessXmlReporterTest;
 import org.apache.maven.plugin.surefire.report.WrappedReportEntryTest;
@@ -75,6 +76,7 @@ import org.junit.runners.Suite;
     SurefireReflectorTest.class,
     ImmutableMapTest.class,
     SurefireHelperTest.class,
+    ForkClientTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-api/pom.xml b/surefire-api/pom.xml
index 5f0f2fd..b705e0a 100644
--- a/surefire-api/pom.xml
+++ b/surefire-api/pom.xml
@@ -40,6 +40,11 @@
       <groupId>org.apache.maven.shared</groupId>
       <artifactId>maven-shared-utils</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
index 2a713ef..a794255 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
@@ -136,7 +136,8 @@ public class BaseProviderFactory
     {
         boolean trim = reporterConfiguration.isTrimStackTrace();
         PrintStream out = reporterConfiguration.getOriginalSystemOut();
-        return insideFork ? new ForkingRunListener( out, ROOT_CHANNEL, trim ) : new DefaultDirectConsoleReporter( out );
+        return insideFork ? new ForkingRunListener( new ForkedChannelEncoder( out ), ROOT_CHANNEL, trim )
+                       : new DefaultDirectConsoleReporter( out );
     }
 
     public void setTestRequest( TestRequest testRequest )

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java
new file mode 100644
index 0000000..f63fd7e
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java
@@ -0,0 +1,432 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+import org.apache.maven.surefire.report.SafeThrowable;
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import static java.lang.System.arraycopy;
+import static javax.xml.bind.DatatypeConverter.printBase64Binary;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_INFO;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_DEBUG;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_ERROR;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STOP_ON_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ERROR;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_FAILED;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SKIPPED;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SUCCEEDED;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_WARNING;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER;
+import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
+import static org.apache.maven.surefire.report.RunMode.RERUN;
+import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
+
+/**
+ * magic number : opcode : run mode [: opcode specific data]*
+ * <p/>
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public final class ForkedChannelEncoder
+{
+    private static final Charset ASCII = Charset.forName( "US-ASCII" );
+    private static final Charset UTF_8 = Charset.forName( "UTF-8" );
+
+    private final Charset defaultCharset = Charset.defaultCharset();
+    private final Charset streamCharset;
+    private final OutputStream out;
+    private final RunMode runMode;
+    private boolean trouble;
+
+    public ForkedChannelEncoder( OutputStream out )
+    {
+        this( ASCII, out, NORMAL_RUN );
+    }
+
+    public ForkedChannelEncoder( Charset streamCharset, OutputStream out )
+    {
+        this( streamCharset, out, NORMAL_RUN );
+    }
+
+    private ForkedChannelEncoder( Charset streamCharset, OutputStream out, RunMode runMode )
+    {
+        this.streamCharset = requireNonNull( streamCharset );
+        this.out = requireNonNull( out );
+        this.runMode = requireNonNull( runMode );
+    }
+
+    public ForkedChannelEncoder asRerunMode()
+    {
+        return new ForkedChannelEncoder( streamCharset, out, RERUN );
+    }
+
+    public ForkedChannelEncoder asNormalMode()
+    {
+        return new ForkedChannelEncoder( streamCharset, out, NORMAL_RUN );
+    }
+
+    public boolean checkError()
+    {
+        return trouble;
+    }
+
+    public void sendSystemProperties()
+    {
+        SortedMap<String, String> sortedProperties = new TreeMap<String, String>();
+        for ( Entry<?, ?> entry : System.getProperties().entrySet() )
+        {
+            Object key = entry.getKey();
+            Object value = entry.getValue();
+            if ( key instanceof String && ( value == null || value instanceof String ) )
+            {
+                sortedProperties.put( (String) key, (String) value );
+            }
+        }
+
+        for ( Entry<String, String> entry : sortedProperties.entrySet() )
+        {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            String valueAsString = value == null ? null : value.toString();
+            StringBuilder event = encode( BOOTERCODE_SYSPROPS, runMode, key, valueAsString );
+            encodeAndPrintEvent( event );
+        }
+    }
+
+    public void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces );
+    }
+
+    public void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces );
+    }
+
+    public void testStarting( ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces );
+    }
+
+    public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces );
+    }
+
+    public void testFailed( ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces );
+    }
+
+    public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces );
+    }
+
+    public void testError( ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces );
+    }
+
+    public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces );
+    }
+
+    public void stdOut( byte[] buf, int off, int len )
+    {
+        StringBuilder event =
+                stdOutErr( BOOTERCODE_STDOUT.getOpcode(), runMode.geRunName(), buf, off, len, defaultCharset );
+        encodeAndPrintEvent( event );
+    }
+
+    public void stdErr( byte[] buf, int off, int len )
+    {
+        StringBuilder event =
+                stdOutErr( BOOTERCODE_STDERR.getOpcode(), runMode.geRunName(), buf, off, len, defaultCharset );
+        encodeAndPrintEvent( event );
+    }
+
+    public void console( String msg )
+    {
+        StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), runMode.geRunName(), UTF_8, msg );
+        encodeAndPrintEvent( event );
+    }
+
+    public void error( String msg )
+    {
+        StringBuilder event = print( BOOTERCODE_CONSOLE_ERROR.getOpcode(), runMode.geRunName(), UTF_8, msg );
+        encodeAndPrintEvent( event );
+    }
+
+    public void error( Throwable t )
+    {
+        error( t.getLocalizedMessage(), ConsoleLoggerUtils.toString( t ) );
+    }
+
+    public void error( String msg, String stackTrace )
+    {
+        StringBuilder event = print( BOOTERCODE_CONSOLE_ERROR.getOpcode(), runMode.geRunName(), UTF_8, msg, stackTrace );
+        encodeAndPrintEvent( event );
+    }
+
+    public void debug( String msg )
+    {
+        StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), runMode.geRunName(), UTF_8, msg );
+        encodeAndPrintEvent( event );
+    }
+
+    public void warning( String msg )
+    {
+        StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), runMode.geRunName(), UTF_8, msg );
+        encodeAndPrintEvent( event );
+    }
+
+    public void bye()
+    {
+        encodeOpcode( BOOTERCODE_BYE );
+    }
+
+    public void stopOnNextTest()
+    {
+        encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST );
+    }
+
+    public void acquireNextTest()
+    {
+        encodeOpcode( BOOTERCODE_NEXT_TEST );
+    }
+
+    private void encode( ForkedProcessEvent operation, RunMode runMode, ReportEntry reportEntry,
+                         boolean trimStackTraces )
+    {
+        StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces );
+        encodeAndPrintEvent( event );
+    }
+
+    private void encodeOpcode( ForkedProcessEvent operation )
+    {
+        StringBuilder event = encodeOpcode( operation.getOpcode(), runMode.geRunName() );
+        encodeAndPrintEvent( event );
+    }
+
+    private void encodeAndPrintEvent( StringBuilder command )
+    {
+        byte[] array = command.append( '\n' ).toString().getBytes( streamCharset );
+        synchronized ( out )
+        {
+            try
+            {
+                out.write( array );
+                out.flush();
+            }
+            catch ( IOException e )
+            {
+                // todo loguj do dum filu pozri ForkingRunListener
+                trouble = true;
+            }
+        }
+    }
+
+    static StringBuilder encode( ForkedProcessEvent operation, RunMode runMode, String... args )
+    {
+        StringBuilder encodedTo = encodeHeader( operation.getOpcode(), runMode.geRunName(), UTF_8 )
+                                          .append( ':' );
+
+        for ( int i = 0; i < args.length; )
+        {
+            String arg = args[i++];
+            base64WithUtf8( encodedTo, arg == null ? "-" : arg );
+            if ( i != args.length )
+            {
+                encodedTo.append( ':' );
+            }
+        }
+        return encodedTo;
+    }
+
+    static void encode( StringBuilder encoded, StackTraceWriter stw, boolean trimStackTraces )
+    {
+        encoded.append( ':' );
+
+        SafeThrowable throwable = stw == null ? null : stw.getThrowable();
+        String message = throwable == null ? null : throwable.getLocalizedMessage();
+        base64WithUtf8( encoded, message );
+
+        encoded.append( ':' );
+
+        String smartStackTrace = stw == null ? null : stw.smartTrimmedStackTrace();
+        base64WithUtf8( encoded, smartStackTrace );
+
+        encoded.append( ':' );
+
+        String stackTrace = stw == null ? null : toStackTrace( stw, trimStackTraces );
+        base64WithUtf8( encoded, stackTrace );
+    }
+
+    /**
+     * Used operations:<br/>
+     * <p>
+     * <ul>
+     * <li>{@link ForkedProcessEvent#BOOTERCODE_TESTSET_STARTING},</li>
+     * <li>{@link ForkedProcessEvent#BOOTERCODE_TESTSET_COMPLETED},</li>
+     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_STARTING},</li>
+     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_SUCCEEDED},</li>
+     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_FAILED},</li>
+     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_ERROR},</li>
+     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_SKIPPED},</li>
+     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
+     * </ul>
+     * </p>
+     */
+    static StringBuilder encode( String operation, String runMode, ReportEntry reportEntry, boolean trimStackTraces )
+    {
+        StringBuilder encodedTo = encodeHeader( operation, runMode, UTF_8 )
+                                          .append( ':' );
+
+        base64WithUtf8( encodedTo, reportEntry.getSourceName() );
+        encodedTo.append( ':' );
+        base64WithUtf8( encodedTo, reportEntry.getName() );
+        encodedTo.append( ':' );
+        base64WithUtf8( encodedTo, reportEntry.getGroup() );
+        encodedTo.append( ':' );
+        base64WithUtf8( encodedTo, reportEntry.getMessage() );
+        encodedTo.append( ':' )
+                .append( reportEntry.getElapsed() == null ? "-" : reportEntry.getElapsed().toString() );
+        encode( encodedTo, reportEntry.getStackTraceWriter(), trimStackTraces );
+
+        return encodedTo;
+    }
+
+    static StringBuilder stdOutErr( String operation, String runMode, byte[] buf, int off, int len,
+                                    Charset bufEncoding )
+    {
+        final byte[] encodeBytes;
+        if ( off == 0 && buf.length == len )
+        {
+            encodeBytes = buf;
+        }
+        else
+        {
+            encodeBytes = new byte[len];
+            arraycopy( buf, off, encodeBytes, 0, len );
+        }
+        return encodeMessage( operation, runMode, bufEncoding, printBase64Binary( encodeBytes ) );
+    }
+
+    /**
+     * Used in {@link #console(String)}, {@link #error(String)}, {@link #debug(String)} and {@link #warning(String)}
+     * and private methods extending the buffer.
+     */
+    static StringBuilder print( String operation, String runMode, Charset msgEncoding, String... msgs )
+    {
+        String[] encodedMsgs = new String[msgs.length];
+        for ( int i = 0; i < encodedMsgs.length; i++ )
+        {
+            String msg = encodedMsgs[i];
+            encodedMsgs[i] = toBase64( msg, msgEncoding );
+        }
+        return encodeMessage( operation, runMode, msgEncoding, encodedMsgs );
+    }
+
+    static StringBuilder encodeMessage( String operation, String runMode, Charset encoding, String... msgs )
+    {
+        StringBuilder builder = encodeHeader( operation, runMode, encoding );
+        for ( String msg : msgs )
+        {
+            builder.append( ':' )
+                    .append( msg );
+
+        }
+        return builder;
+    }
+
+    static StringBuilder encodeHeader( String operation, String runMode, Charset encoding )
+    {
+        return encodeOpcode( operation, runMode )
+                       .append( ':' )
+                       .append( encoding.name() );
+    }
+
+    /**
+     * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEvent)}
+     * and private methods extending the buffer.
+     *
+     * @param operation opcode
+     * @param runMode   run mode
+     * @return encoded command
+     */
+    static StringBuilder encodeOpcode( String operation, String runMode )
+    {
+        return new StringBuilder( 128 )
+                       .append( MAGIC_NUMBER )
+                       .append( operation )
+                       .append( ':' )
+                       .append( runMode );
+    }
+
+    static String base64WithUtf8( String msg )
+    {
+        if ( msg == null )
+        {
+            return "-";
+        }
+        else
+        {
+            byte[] binary = msg.getBytes( UTF_8 );
+            return printBase64Binary( binary );
+        }
+    }
+
+    static void base64WithUtf8( StringBuilder encoded, String msg )
+    {
+        encoded.append( base64WithUtf8( msg ) );
+    }
+
+    private static String toStackTrace( StackTraceWriter stw, boolean trimStackTraces )
+    {
+        return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
+    }
+
+    static String toBase64( String msg, Charset encoding )
+    {
+        return msg == null ? "-" : printBase64Binary( msg.getBytes( encoding ) );
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java
new file mode 100644
index 0000000..229367e
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java
@@ -0,0 +1,123 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static java.util.Collections.unmodifiableMap;
+
+/**
+ * Events sent back to the plugin process.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public enum ForkedProcessEvent
+{
+    BOOTERCODE_SYSPROPS( "sysProp", 0 ),
+
+    BOOTERCODE_TESTSET_STARTING( "testSetStarting", 1 ),
+    BOOTERCODE_TESTSET_COMPLETED( "testSetCompleted", 1 ),
+    BOOTERCODE_TEST_STARTING( "testStarting", 1 ),
+    BOOTERCODE_TEST_SUCCEEDED( "testSucceeded", 1 ),
+    BOOTERCODE_TEST_FAILED( "testFailed", 1 ),
+    BOOTERCODE_TEST_SKIPPED( "testSkipped", 1 ),
+    BOOTERCODE_TEST_ERROR( "testError", 1 ),
+    BOOTERCODE_TEST_ASSUMPTIONFAILURE( "testAssumptionFailure", 1 ),
+
+    BOOTERCODE_STDOUT( "stdOutStream", 2 ),
+    BOOTERCODE_STDERR( "stdErrStream", 2 ),
+
+    BOOTERCODE_CONSOLE_INFO( "console", 3 ),
+    BOOTERCODE_CONSOLE_DEBUG( "debug", 3 ),
+    BOOTERCODE_CONSOLE_WARNING( "warning", 3 ),
+    BOOTERCODE_CONSOLE_ERROR( "error", 4 ),
+
+    BOOTERCODE_BYE( "bye", 5 ),
+    BOOTERCODE_STOP_ON_NEXT_TEST( "stopOnNextTest", 5 ),
+    BOOTERCODE_NEXT_TEST( "nextTest", 5 ),
+
+    BOOTERCODE_JVM_EXIT_ERROR( "jvmExitError", 6 );
+
+    public static final String MAGIC_NUMBER = ":maven:surefire:std:out:";
+
+    public static final Map<String, ForkedProcessEvent> EVENTS = events();
+
+    private static Map<String, ForkedProcessEvent> events()
+    {
+        Map<String, ForkedProcessEvent> events = new ConcurrentHashMap<String, ForkedProcessEvent>();
+        for ( ForkedProcessEvent event : values() )
+        {
+            events.put( event.getOpcode(), event );
+        }
+        return unmodifiableMap( events );
+    }
+
+
+    private final String opcode;
+    private final int category;
+
+    ForkedProcessEvent( String opcode, int category )
+    {
+        this.opcode = opcode;
+        this.category = category;
+    }
+
+    public String getOpcode()
+    {
+        return opcode;
+    }
+
+    public boolean isSysPropCategory()
+    {
+        return category == 0;
+    }
+
+    public boolean isTestCategory()
+    {
+        return category == 1;
+    }
+
+    public boolean isStandardStreamCategory()
+    {
+        return category == 2;
+    }
+
+    public boolean isConsoleCategory()
+    {
+        return category == 3;
+    }
+
+    public boolean isConsoleErrorCategory()
+    {
+        return category == 4;
+    }
+
+    public boolean isControlCategory()
+    {
+        return category == 5;
+    }
+
+    public boolean isJvmExitError()
+    {
+        return category == 6;
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
index def345d..59c6bcb 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
@@ -19,13 +19,12 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import java.io.PrintStream;
-import java.util.concurrent.atomic.AtomicInteger;
-
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.suite.RunResult;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Creates ForkingReporters, which are typically one instance per TestSet or thread.
  * This factory is only used inside forks.
@@ -39,19 +38,19 @@ public class ForkingReporterFactory
 {
     private final boolean isTrimstackTrace;
 
-    private final PrintStream originalSystemOut;
+    private final ForkedChannelEncoder eventChannel;
 
     private final AtomicInteger testSetChannelId = new AtomicInteger( 1 );
 
-    public ForkingReporterFactory( boolean trimstackTrace, PrintStream originalSystemOut )
+    public ForkingReporterFactory( boolean trimstackTrace, ForkedChannelEncoder eventChannel )
     {
         isTrimstackTrace = trimstackTrace;
-        this.originalSystemOut = originalSystemOut;
+        this.eventChannel = eventChannel;
     }
 
     public RunListener createReporter()
     {
-        return new ForkingRunListener( originalSystemOut, testSetChannelId.getAndIncrement(), isTrimstackTrace );
+        return new ForkingRunListener( eventChannel, testSetChannelId.getAndIncrement(), isTrimstackTrace );
     }
 
     public RunResult close()

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
index 6c0842a..ffe029f 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
@@ -28,15 +28,19 @@ import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
 
 import static java.lang.Integer.toHexString;
 import static java.nio.charset.Charset.defaultCharset;
+import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
+import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
 import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
 import static org.apache.maven.surefire.util.internal.StringUtils.escapeBytesToPrintable;
 import static org.apache.maven.surefire.util.internal.StringUtils.escapeToPrintable;
+import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
 
 /**
  * Encodes the full output of the test run to the stdout stream.
@@ -84,11 +88,11 @@ public class ForkingRunListener
      */
     public static final byte BOOTERCODE_CONSOLE = (byte) 'H';
 
-    public static final byte BOOTERCODE_SYSPROPS = (byte) 'I';
+    public static final byte BOOTERCODE_SYSPROPS = (byte) 'I';//
 
-    public static final byte BOOTERCODE_NEXT_TEST = (byte) 'N';
+    public static final byte BOOTERCODE_NEXT_TEST = (byte) 'N';//
 
-    public static final byte BOOTERCODE_STOP_ON_NEXT_TEST = (byte) 'S';
+    public static final byte BOOTERCODE_STOP_ON_NEXT_TEST = (byte) 'S';//
 
     /**
      * ERROR logger
@@ -111,7 +115,7 @@ public class ForkingRunListener
     public static final byte BOOTERCODE_WARNING = (byte) 'W';
 
 
-    private final PrintStream target;
+    private final ForkedChannelEncoder target;
 
     private final int testSetChannelId;
 
@@ -121,7 +125,9 @@ public class ForkingRunListener
 
     private final byte[] stdErrHeader;
 
-    public ForkingRunListener( PrintStream target, int testSetChannelId, boolean trimStackTraces )
+    private volatile RunMode runMode = NORMAL_RUN;
+
+    public ForkingRunListener( ForkedChannelEncoder target, int testSetChannelId, boolean trimStackTraces )
     {
         this.target = target;
         this.testSetChannelId = testSetChannelId;
@@ -176,6 +182,13 @@ public class ForkingRunListener
         encodeAndWriteToTarget( toString( BOOTERCODE_STOP_ON_NEXT_TEST, new SimpleReportEntry(), testSetChannelId ) );
     }
 
+    public RunMode markAs( RunMode currentRunMode )
+    {
+        RunMode runMode = this.runMode;
+        this.runMode = requireNonNull( currentRunMode );
+        return runMode;
+    }
+
     void sendProps()
     {
         Properties systemProperties = System.getProperties();
@@ -260,7 +273,7 @@ public class ForkingRunListener
 
     public void error( String message, Throwable t )
     {
-        error( ConsoleLoggerUtils.toString( message, t ) );
+        error( ConsoleLoggerUtils.toString( message, t ) );//tu daj localized msg a potom string:stacktrace
     }
 
     public void error( Throwable t )
@@ -355,7 +368,7 @@ public class ForkingRunListener
 
     private static void nullableEncoding( StringBuilder stringBuilder, String source )
     {
-        if ( source == null || source.length() == 0 )
+        if ( isBlank( source ) )
         {
             stringBuilder.append( "null" );
         }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java
index b964430..74e8e9b 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java
@@ -93,4 +93,13 @@ public interface RunListener
      * (The event is fired after the Nth test failed to signal skipping the rest of test-set.)
      */
     void testExecutionSkippedByUser();
+
+    /**
+     * Marks the listener with run mode, e.g. normal run or re-run.
+     *
+     * @param currentRunMode    set current run
+     * @return previous run mode; never returns null
+     * @throws NullPointerException if <code>currentRunMode</code> is null
+     */
+    RunMode markAs( RunMode currentRunMode );
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/report/RunMode.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/RunMode.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/RunMode.java
new file mode 100644
index 0000000..0323d9a
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/RunMode.java
@@ -0,0 +1,60 @@
+package org.apache.maven.surefire.report;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static java.util.Collections.unmodifiableMap;
+
+/**
+ * Determines the purpose the provider started the tests. It can be either normal run or re-run.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public enum RunMode
+{
+    NORMAL_RUN( "normal-run" ), RERUN( "re-run" );
+
+    public static final Map<String, RunMode> MODES = modes();
+
+    private static Map<String, RunMode> modes()
+    {
+        Map<String, RunMode> modes = new ConcurrentHashMap<String, RunMode>();
+        for ( RunMode mode : values() )
+        {
+            modes.put( mode.geRunName(), mode );
+        }
+        return unmodifiableMap( modes );
+    }
+
+    private final String runName;
+
+    RunMode( String runName )
+    {
+        this.runName = runName;
+    }
+
+    public String geRunName()
+    {
+        return runName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
index e2ae963..66cef8d 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
@@ -74,7 +74,7 @@ public class TestListResolver
                 for ( String request : split( csvTests, "," ) )
                 {
                     request = request.trim();
-                    if ( request.length() != 0 && !request.equals( "!" ) )
+                    if ( !request.isEmpty() && !request.equals( "!" ) )
                     {
                         resolveTestRequest( request, patterns, includedFilters, excludedFilters );
                     }
@@ -249,7 +249,7 @@ public class TestListResolver
         }
 
         aggregatedTest += aggregatedTest( "!", getExcludedPatterns() );
-        return aggregatedTest.length() == 0 ? "" : aggregatedTest;
+        return aggregatedTest.isEmpty() ? "" : aggregatedTest;
     }
 
     public Set<ResolvedTest> getIncludedPatterns()
@@ -309,7 +309,7 @@ public class TestListResolver
 
     static String removeExclamationMark( String s )
     {
-        return s.length() != 0 && s.charAt( 0 ) == '!' ? s.substring( 1 ) : s;
+        return !s.isEmpty() && s.charAt( 0 ) == '!' ? s.substring( 1 ) : s;
     }
 
     private static void updatedFilters( boolean isExcluded, ResolvedTest test, IncludedExcludedPatterns patterns,
@@ -334,7 +334,7 @@ public class TestListResolver
         for ( ResolvedTest test : tests )
         {
             String readableTest = test.toString();
-            if ( readableTest.length() != 0 )
+            if ( !readableTest.isEmpty() )
             {
                 if ( aggregatedTest.length() != 0 )
                 {
@@ -357,7 +357,7 @@ public class TestListResolver
             if ( exc != null )
             {
                 exc = exc.trim();
-                if ( exc.length() != 0 )
+                if ( !exc.isEmpty() )
                 {
                     if ( exc.contains( "!" ) )
                     {
@@ -457,8 +457,8 @@ public class TestListResolver
         if ( isRegexPrefixedPattern( request ) )
         {
             final String[] unwrapped = unwrapRegex( request );
-            final boolean hasClass = unwrapped[0].length() != 0;
-            final boolean hasMethod = unwrapped[1].length() != 0;
+            final boolean hasClass = !unwrapped[0].isEmpty();
+            final boolean hasMethod = !unwrapped[1].isEmpty();
             if ( hasClass && hasMethod )
             {
                 test = new ResolvedTest( unwrapped[0], unwrapped[1], true );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
index d1838b2..44410de 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
@@ -19,11 +19,6 @@ package org.apache.maven.surefire.util.internal;
  * under the License.
  */
 
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
 import java.util.StringTokenizer;
 
 /**
@@ -62,8 +57,6 @@ public final class StringUtils
     private static final byte[] HEX_CHARS = {
                     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
 
-    private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
-
     // 8-bit charset Latin-1
     public static final String FORK_STREAM_CHARSET_NAME = "ISO-8859-1";
 
@@ -148,216 +141,6 @@ public final class StringUtils
     }
 
     /**
-     * Escape the specified string to a representation that only consists of nicely printable characters, without any
-     * newlines and without a comma.
-     * <p>
-     * The reverse-method is {@link #unescapeString(StringBuilder, CharSequence)}.
-     *
-     * @param target target string buffer. The required space will be up to {@code str.getBytes().length * 5} chars.
-     * @param str String to escape values in, may be {@code null}.
-     */
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    public static void escapeToPrintable( StringBuilder target, CharSequence str )
-    {
-        if ( target == null )
-        {
-            throw new IllegalArgumentException( "The target buffer must not be null" );
-        }
-        if ( str == null )
-        {
-            return;
-        }
-
-        for ( int i = 0; i < str.length(); i++ )
-        {
-            char c = str.charAt( i );
-
-            // handle non-nicely printable chars and the comma
-            if ( c < 32 || c > 126 || c == '\\' || c == ',' )
-            {
-                target.append( '\\' );
-                target.append( (char) HEX_CHARS[( 0xF000 & c ) >> 12] );
-                target.append( (char) HEX_CHARS[( 0x0F00 & c ) >> 8] );
-                target.append( (char) HEX_CHARS[( 0x00F0 & c ) >> 4] );
-                target.append( (char) HEX_CHARS[( 0x000F & c )] );
-            }
-            else
-            {
-                target.append( c );
-            }
-        }
-    }
-
-    /**
-     * Reverses the effect of {@link #escapeToPrintable(StringBuilder, CharSequence)}.
-     *
-     * @param target target string buffer
-     * @param str the String to un-escape, as created by {@link #escapeToPrintable(StringBuilder, CharSequence)}
-     */
-    public static void unescapeString( StringBuilder target, CharSequence str )
-    {
-        if ( target == null )
-        {
-            throw new IllegalArgumentException( "The target buffer must not be null" );
-        }
-        if ( str == null )
-        {
-            return;
-        }
-
-        for ( int i = 0; i < str.length(); i++ )
-        {
-            char ch = str.charAt( i );
-
-            if ( ch == '\\' )
-            {
-                target.append( (char) (
-                                  digit( str.charAt( ++i ) ) << 12
-                                | digit( str.charAt( ++i ) ) << 8
-                                | digit( str.charAt( ++i ) ) << 4
-                                | digit( str.charAt( ++i ) )
-                                ) );
-            }
-            else
-            {
-                target.append( ch );
-            }
-        }
-    }
-
-    private static int digit( char ch )
-    {
-        if ( ch >= 'a' )
-        {
-            return 10 + ch - 'a';
-        }
-        else if ( ch >= 'A' )
-        {
-            return 10 + ch - 'A';
-        }
-        else
-        {
-            return ch - '0';
-        }
-    }
-
-    /**
-     * Escapes the bytes in the array {@code str} to contain only 'printable' bytes.
-     * <p>
-     * Escaping is done by encoding the non-nicely printable bytes to {@code '\' + upperCaseHexBytes(byte)}.
-     * <p>
-     * A save length of {@code out} is {@code len * 3 + outoff}.
-     * <p>
-     * The reverse-method is {@link #unescapeBytes(byte[], String)}.
-     *
-     * @param out output buffer
-     * @param outoff offset in the output buffer
-     * @param input input buffer
-     * @param off offset in the input buffer
-     * @param len number of bytes to copy from the input buffer
-     * @return number of bytes written to {@code out}
-     */
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    public static int escapeBytesToPrintable( byte[] out, int outoff, byte[] input, int off, int len )
-    {
-        if ( out == null )
-        {
-            throw new IllegalArgumentException( "The output array must not be null" );
-        }
-        if ( input == null || input.length == 0 )
-        {
-            return 0;
-        }
-        int outputPos = outoff;
-        int end = off + len;
-        for ( int i = off; i < end; i++ )
-        {
-            byte b = input[i];
-
-            // handle non-nicely printable bytes
-            if ( b < 32 || b > 126 || b == '\\' || b == ',' )
-            {
-                int upper = ( 0xF0 & b ) >> 4;
-                int lower = ( 0x0F & b );
-                out[outputPos++] = '\\';
-                out[outputPos++] = HEX_CHARS[upper];
-                out[outputPos++] = HEX_CHARS[lower];
-            }
-            else
-            {
-                out[outputPos++] = b;
-            }
-        }
-
-        return outputPos - outoff;
-    }
-
-    /**
-     * Reverses the effect of {@link #escapeBytesToPrintable(byte[], int, byte[], int, int)}.
-     *
-     * @param str the input String
-     * @param charsetName the charset name
-     * @return the number of bytes written to {@code out}
-     */
-    public static ByteBuffer unescapeBytes( String str, String charsetName  )
-    {
-        int outPos = 0;
-
-        if ( str == null )
-        {
-            return ByteBuffer.wrap( new byte[0] );
-        }
-
-        byte[] out = new byte[str.length()];
-        for ( int i = 0; i < str.length(); i++ )
-        {
-            char ch = str.charAt( i );
-
-            if ( ch == '\\' )
-            {
-                int upper = digit( str.charAt( ++i ) );
-                int lower = digit( str.charAt( ++i ) );
-                out[outPos++] = (byte) ( upper << 4 | lower );
-            }
-            else
-            {
-                out[outPos++] = (byte) ch;
-            }
-        }
-
-        Charset sourceCharset = Charset.forName( charsetName );
-        if ( !DEFAULT_CHARSET.equals( sourceCharset ) )
-        {
-            CharBuffer decodedFromSourceCharset;
-            try
-            {
-                decodedFromSourceCharset = sourceCharset.newDecoder().decode( ByteBuffer.wrap( out, 0, outPos ) );
-                ByteBuffer defaultEncoded = DEFAULT_CHARSET.encode( decodedFromSourceCharset );
-
-                return defaultEncoded;
-            }
-            catch ( CharacterCodingException e )
-            {
-                // ignore and fall through to the non-recoded version
-            }
-        }
-
-        return ByteBuffer.wrap( out, 0, outPos );
-    }
-
-    public static byte[] encodeStringForForkCommunication( String string )
-    {
-        try
-        {
-            return string.getBytes( FORK_STREAM_CHARSET_NAME );
-        }
-        catch ( UnsupportedEncodingException e )
-        {
-           throw new RuntimeException( "The JVM must support Charset " + FORK_STREAM_CHARSET_NAME, e );
-        }
-    }
-
-    /**
      *
      * @param buffer     Examined StringBuffer
      * @param pattern    a pattern which should start in <code>buffer</code>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
index c43a3a6..d64b5a5 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -39,7 +39,6 @@ import org.apache.maven.surefire.util.TestsToRunTest;
 import org.apache.maven.surefire.util.UrlUtilsTest;
 import org.apache.maven.surefire.util.internal.ByteBufferTest;
 import org.apache.maven.surefire.util.internal.ConcurrencyUtilsTest;
-import org.apache.maven.surefire.util.internal.StringUtilsTest;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 
@@ -60,7 +59,6 @@ import org.junit.runners.Suite;
     TestListResolverTest.class,
     ByteBufferTest.class,
     ConcurrencyUtilsTest.class,
-    StringUtilsTest.class,
     DefaultDirectoryScannerTest.class,
     RunOrderCalculatorTest.class,
     RunOrderTest.class,


[3/3] maven-surefire git commit: [SUREFIRE-1222] ForkClient attempts to consume unrelated lines

Posted by ti...@apache.org.
[SUREFIRE-1222] ForkClient attempts to consume unrelated lines


Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo
Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/64ae8e88
Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/64ae8e88
Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/64ae8e88

Branch: refs/heads/SUREFIRE-1222
Commit: 64ae8e88d9e67b8eb7371b6ba94f060253db4465
Parents: e7bb6fc
Author: Tibor17 <ti...@apache.org>
Authored: Thu Aug 3 14:47:49 2017 +0200
Committer: Tibor17 <ti...@apache.org>
Committed: Thu Aug 3 14:47:49 2017 +0200

----------------------------------------------------------------------
 .../plugin/surefire/AbstractSurefireMojo.java   |   5 +-
 .../surefire/booterclient/ForkStarter.java      |   9 +-
 .../booterclient/output/ErrorInFork.java        |  68 ++
 .../booterclient/output/ForkClient.java         | 451 ++++++-------
 .../output/ForkedChannelDecoder.java            | 346 ++++++++++
 .../ForkedChannelDecoderErrorHandler.java       |  29 +
 .../ForkedProcessBinaryEventListener.java       |  33 +
 .../output/ForkedProcessEventListener.java      |  31 +
 .../output/ForkedProcessExitErrorListener.java  |  29 +
 .../ForkedProcessPropertyEventListener.java     |  31 +
 .../ForkedProcessReportEventListener.java       |  32 +
 .../ForkedProcessStackTraceEventListener.java   |  31 +
 .../ForkedProcessStringEventListener.java       |  29 +
 .../output/MultipleFailureException.java        |   3 +-
 .../surefire/report/StatelessXmlReporter.java   |   5 +-
 .../surefire/report/TestSetRunListener.java     |  22 +-
 .../Utf8RecodingDeferredFileOutputStream.java   |   6 +-
 .../surefire/report/WrappedReportEntry.java     |   3 +-
 .../surefire/runorder/StatisticsReporter.java   |   4 +-
 .../maven/plugin/surefire/util/FileScanner.java |   6 +-
 .../booterclient/ForkingRunListenerTest.java    |   7 +-
 .../booterclient/output/ForkClientTest.java     |  38 ++
 .../output/ForkedChannelDecoderTest.java        | 666 +++++++++++++++++++
 .../apache/maven/surefire/JUnit4SuiteTest.java  |   2 +
 surefire-api/pom.xml                            |   5 +
 .../surefire/booter/BaseProviderFactory.java    |   3 +-
 .../surefire/booter/ForkedChannelEncoder.java   | 432 ++++++++++++
 .../surefire/booter/ForkedProcessEvent.java     | 123 ++++
 .../surefire/booter/ForkingReporterFactory.java |  13 +-
 .../surefire/booter/ForkingRunListener.java     |  27 +-
 .../maven/surefire/report/RunListener.java      |   9 +
 .../apache/maven/surefire/report/RunMode.java   |  60 ++
 .../surefire/testset/TestListResolver.java      |  14 +-
 .../surefire/util/internal/StringUtils.java     | 217 ------
 .../java/org/apache/maven/JUnit4SuiteTest.java  |   2 -
 .../booter/ForkedChannelEncoderTest.java        | 481 ++++++++++++++
 .../surefire/booter/ForkingRunListenerTest.java |   2 +-
 .../surefire/util/internal/StringUtilsTest.java |  96 ---
 .../apache/maven/surefire/booter/Classpath.java |   2 +-
 .../maven/surefire/booter/ForkedBooter.java     |  33 +-
 .../maven/surefire/booter/TypeEncodedValue.java |   4 +-
 .../surefire/its/fixture/SurefireLauncher.java  |   2 +-
 .../surefire/log/api/ConsoleLoggerUtils.java    |   5 +
 .../surefire/common/junit48/RequestedTest.java  |   2 +-
 .../maven/surefire/junit/JUnitTestSetTest.java  |   6 +
 surefire-providers/surefire-junit47/pom.xml     |   2 +-
 .../junitcore/ConcurrentRunListener.java        |   6 +
 .../surefire/junitcore/JUnitCoreWrapper.java    |   2 +-
 .../utils/GroupMatcherMethodSelector.java       |  14 +-
 49 files changed, 2823 insertions(+), 625 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index 16c25ec..23c3642 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -232,6 +232,7 @@ public abstract class AbstractSurefireMojo
      * being tested. This directory is declared by the parameter <code>testClassesDirectory</code> which defaults
      * to the POM property <code>${project.build.testOutputDirectory}</code>, typically <em>src/test/java</em>
      * unless overridden.
+     * //todo use regex for fully qualified class names and change the filtering abilities
      */
     @Parameter
     // TODO use regex for fully qualified class names in 3.0 and change the filtering abilities
@@ -1260,7 +1261,7 @@ public abstract class AbstractSurefireMojo
 
     protected boolean isAnyConcurrencySelected()
     {
-        return this.getParallel() != null && this.getParallel().trim().length() > 0;
+        return getParallel() != null && !getParallel().trim().isEmpty();
     }
 
     protected boolean isAnyGroupsSelected()
@@ -1821,7 +1822,7 @@ public abstract class AbstractSurefireMojo
             if ( item != null )
             {
                 item = item.trim();
-                if ( item.length() != 0 )
+                if ( !item.isEmpty() )
                 {
                     result.add( item );
                 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index b3a3f91..272f082 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
@@ -27,6 +27,7 @@ import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Notifiable
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
+import org.apache.maven.plugin.surefire.booterclient.output.ErrorInFork;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
 import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
 import org.apache.maven.plugin.surefire.booterclient.output.NativeStdErrStreamConsumer;
@@ -46,7 +47,6 @@ import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
 import org.apache.maven.surefire.booter.SurefireExecutionException;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
-import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.testset.TestRequest;
 import org.apache.maven.surefire.util.DefaultScanResult;
@@ -660,12 +660,13 @@ public class ForkStarter
 
                 if ( forkClient.isErrorInFork() )
                 {
-                    StackTraceWriter errorInFork = forkClient.getErrorInFork();
-                    // noinspection ThrowFromFinallyBlock
+                    ErrorInFork errorInFork = forkClient.getErrorInFork();
+                    //todo boolean showStackTrace = providerConfiguration.getMainCliOptions().contains( SHOW_ERRORS );
+                    //noinspection ThrowFromFinallyBlock
                     throw new SurefireBooterForkException( "There was an error in the forked process"
                                                         + detail
                                                         + '\n'
-                                                        + errorInFork.getThrowable().getLocalizedMessage(), cause );
+                                                        + errorInFork, cause );
                 }
                 if ( !forkClient.isSaidGoodBye() )
                 {

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ErrorInFork.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ErrorInFork.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ErrorInFork.java
new file mode 100644
index 0000000..cd851f6
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ErrorInFork.java
@@ -0,0 +1,68 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public final class ErrorInFork
+{
+    private final String localizedMessage;
+    private final String stackTrace;
+
+    public ErrorInFork( String localizedMessage, String stackTrace )
+    {
+        this.localizedMessage = localizedMessage;
+        this.stackTrace = stackTrace;
+    }
+
+    public String getLocalizedMessage()
+    {
+        return localizedMessage;
+    }
+
+    public String getStackTrace()
+    {
+        return stackTrace;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        if ( isNotBlank( localizedMessage ) )
+        {
+            builder.append( localizedMessage );
+        }
+
+        if ( isNotBlank( stackTrace ) )
+        {
+            if ( builder.length() != 0 )
+            {
+                builder.append( '\n' );
+            }
+            builder.append( stackTrace );
+        }
+        return super.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
index 583460f..cd6f00d 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
@@ -26,46 +26,23 @@ import org.apache.maven.shared.utils.cli.StreamConsumer;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
-import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.RunMode;
 
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
 import java.io.StringReader;
-import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
 import java.util.Properties;
 import java.util.Queue;
 import java.util.Set;
-import java.util.StringTokenizer;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicLong;
 
-import static java.lang.Integer.decode;
 import static java.lang.System.currentTimeMillis;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_CONSOLE;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_DEBUG;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_ERROR;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_NEXT_TEST;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_STDERR;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_STDOUT;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_STOP_ON_NEXT_TEST;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_SYSPROPS;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TESTSET_COMPLETED;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TESTSET_STARTING;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_ERROR;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_FAILED;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_SKIPPED;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_STARTING;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_SUCCEEDED;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_WARNING;
 import static org.apache.maven.surefire.booter.Shutdown.KILL;
-import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
 import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank;
-import static org.apache.maven.surefire.util.internal.StringUtils.unescapeBytes;
-import static org.apache.maven.surefire.util.internal.StringUtils.unescapeString;
 
 // todo move to the same package with ForkStarter
 
@@ -94,13 +71,17 @@ public class ForkClient
      */
     private final AtomicLong testSetStartedAt = new AtomicLong( START_TIME_ZERO );
 
+    private final ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+
     private final ConsoleLogger log;
 
+    private final ForkedChannelDecoderErrorHandler errorHandler;
+
     private RunListener testSetReporter;
 
     private volatile boolean saidGoodBye;
 
-    private volatile StackTraceWriter errorInFork;
+    private volatile ErrorInFork errorInFork;
 
     private volatile int forkNumber;
 
@@ -114,6 +95,211 @@ public class ForkClient
         this.testVmSystemProperties = testVmSystemProperties;
         this.notifiableTestStream = notifiableTestStream;
         this.log = log;
+        decoder.setTestSetStartingListener( new TestSetStartingListener() );
+        decoder.setTestSetCompletedListener( new TestSetCompletedListener() );
+        decoder.setTestStartingListener( new TestStartingListener() );
+        decoder.setTestSucceededListener( new TestSucceededListener() );
+        decoder.setTestFailedListener( new TestFailedListener() );
+        decoder.setTestSkippedListener( new TestSkippedListener() );
+        decoder.setTestErrorListener( new TestErrorListener() );
+        decoder.setTestAssumptionFailureListener( new TestAssumptionFailureListener() );
+        decoder.setSystemPropertiesListener( new SystemPropertiesListener() );
+        decoder.setStdOutListener( new StdOutListener() );
+        decoder.setStdErrListener( new StdErrListener() );
+        decoder.setConsoleInfoListener( new ConsoleListener() );
+        decoder.setAcquireNextTestListener( new AcquireNextTestListener() );
+        decoder.setConsoleErrorListener( new ErrorListener() );
+        decoder.setByeListener( new ByeListener() );
+        decoder.setStopOnNextTestListener( new StopOnNextTestListener() );
+        decoder.setConsoleDebugListener( new DebugListener() );
+        decoder.setConsoleWarningListener( new WarningListener() );
+        errorHandler = new ErrorHandler();
+    }
+
+    private final class ErrorHandler implements ForkedChannelDecoderErrorHandler
+    {
+        @Override
+        public void handledError( String line, Throwable e )
+        {
+            logStreamWarning( line, e );
+        }
+    }
+
+    private final class TestSetStartingListener implements ForkedProcessReportEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            getTestSetReporter().testSetStarting( reportEntry );
+            setCurrentStartTime();
+        }
+    }
+
+    private final class TestSetCompletedListener implements ForkedProcessReportEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            testsInProgress.clear();
+            getTestSetReporter().testSetCompleted( reportEntry );
+        }
+    }
+
+    private final class TestStartingListener implements ForkedProcessReportEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            testsInProgress.offer( reportEntry.getSourceName() );
+            getTestSetReporter().testStarting( reportEntry );
+        }
+    }
+
+    private final class TestSucceededListener implements ForkedProcessReportEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            testsInProgress.remove( reportEntry.getSourceName() );
+            getTestSetReporter().testSucceeded( reportEntry );
+        }
+    }
+
+    private final class TestFailedListener implements ForkedProcessReportEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            testsInProgress.remove( reportEntry.getSourceName() );
+            getTestSetReporter().testFailed( reportEntry );
+        }
+    }
+
+    private final class TestSkippedListener implements ForkedProcessReportEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            testsInProgress.remove( reportEntry.getSourceName() );
+            getTestSetReporter().testSkipped( reportEntry );
+        }
+    }
+
+    private final class TestErrorListener implements ForkedProcessReportEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            testsInProgress.remove( reportEntry.getSourceName() );
+            getTestSetReporter().testError( reportEntry );
+        }
+    }
+
+    private final class TestAssumptionFailureListener implements ForkedProcessReportEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, ReportEntry reportEntry )
+        {
+            testsInProgress.remove( reportEntry.getSourceName() );
+            getTestSetReporter().testAssumptionFailure( reportEntry );
+        }
+    }
+
+    private final class SystemPropertiesListener implements ForkedProcessPropertyEventListener
+    {
+        @Override
+        public void handle( String key, String value )
+        {
+            synchronized ( testVmSystemProperties )
+            {
+                testVmSystemProperties.put( key, value );
+            }
+        }
+    }
+
+    private final class StdOutListener implements ForkedProcessBinaryEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, Charset encoding, byte[] binary )
+        { //todo new String( binary, encoding ) v inom PR
+            writeTestOutput( binary, true );
+        }
+    }
+
+    private final class StdErrListener implements ForkedProcessBinaryEventListener
+    {
+        @Override
+        public void handle( RunMode runMode, Charset encoding, byte[] binary )
+        { //todo new String( binary, encoding ) v inom PR
+            writeTestOutput( binary, false );
+        }
+    }
+
+    private final class ConsoleListener implements ForkedProcessStringEventListener
+    {
+        @Override
+        public void handle( String msg )
+        {
+            getOrCreateConsoleLogger()
+                    .info( msg );
+        }
+    }
+
+    private final class AcquireNextTestListener implements ForkedProcessEventListener
+    {
+        @Override
+        public void handle()
+        {
+            notifiableTestStream.provideNewTest();
+        }
+    }
+
+    private class ErrorListener implements ForkedProcessStackTraceEventListener
+    {
+        @Override
+        public void handle( String msg, String stackTrace )
+        {
+            errorInFork = new ErrorInFork( msg, stackTrace );
+        }
+    }
+
+    private final class ByeListener implements ForkedProcessEventListener
+    {
+        @Override
+        public void handle()
+        {
+            saidGoodBye = true;
+            notifiableTestStream.acknowledgeByeEventReceived();
+        }
+    }
+
+    private final class StopOnNextTestListener implements ForkedProcessEventListener
+    {
+        @Override
+        public void handle()
+        {
+            stopOnNextTest();
+        }
+    }
+
+    private final class DebugListener implements ForkedProcessStringEventListener
+    {
+        @Override
+        public void handle( String msg )
+        {
+            getOrCreateConsoleLogger()
+                    .debug( msg );
+        }
+    }
+
+    private final class WarningListener implements ForkedProcessStringEventListener
+    {
+        @Override
+        public void handle( String msg )
+        {
+            getOrCreateConsoleLogger()
+                    .warning( msg );
+        }
     }
 
     protected void stopOnNextTest()
@@ -184,117 +370,10 @@ public class ForkClient
 
     private void processLine( String event )
     {
-        final OperationalData op;
-        try
-        {
-            op = new OperationalData( event );
-        }
-        catch ( RuntimeException e )
-        {
-            logStreamWarning( e, event );
-            return;
-        }
-        final String remaining = op.getData();
-        switch ( op.getOperationId() )
-        {
-            case BOOTERCODE_TESTSET_STARTING:
-                getTestSetReporter().testSetStarting( createReportEntry( remaining ) );
-                setCurrentStartTime();
-                break;
-            case BOOTERCODE_TESTSET_COMPLETED:
-                testsInProgress.clear();
-
-                getTestSetReporter().testSetCompleted( createReportEntry( remaining ) );
-                break;
-            case BOOTERCODE_TEST_STARTING:
-                ReportEntry reportEntry = createReportEntry( remaining );
-                testsInProgress.offer( reportEntry.getSourceName() );
-
-                getTestSetReporter().testStarting( createReportEntry( remaining ) );
-                break;
-            case BOOTERCODE_TEST_SUCCEEDED:
-                reportEntry = createReportEntry( remaining );
-                testsInProgress.remove( reportEntry.getSourceName() );
-
-                getTestSetReporter().testSucceeded( createReportEntry( remaining ) );
-                break;
-            case BOOTERCODE_TEST_FAILED:
-                reportEntry = createReportEntry( remaining );
-                testsInProgress.remove( reportEntry.getSourceName() );
-
-                getTestSetReporter().testFailed( createReportEntry( remaining ) );
-                break;
-            case BOOTERCODE_TEST_SKIPPED:
-                reportEntry = createReportEntry( remaining );
-                testsInProgress.remove( reportEntry.getSourceName() );
-
-                getTestSetReporter().testSkipped( createReportEntry( remaining ) );
-                break;
-            case BOOTERCODE_TEST_ERROR:
-                reportEntry = createReportEntry( remaining );
-                testsInProgress.remove( reportEntry.getSourceName() );
-
-                getTestSetReporter().testError( createReportEntry( remaining ) );
-                break;
-            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                reportEntry = createReportEntry( remaining );
-                testsInProgress.remove( reportEntry.getSourceName() );
-
-                getTestSetReporter().testAssumptionFailure( createReportEntry( remaining ) );
-                break;
-            case BOOTERCODE_SYSPROPS:
-                int keyEnd = remaining.indexOf( "," );
-                StringBuilder key = new StringBuilder();
-                StringBuilder value = new StringBuilder();
-                unescapeString( key, remaining.substring( 0, keyEnd ) );
-                unescapeString( value, remaining.substring( keyEnd + 1 ) );
-                synchronized ( testVmSystemProperties )
-                {
-                    testVmSystemProperties.put( key.toString(), value.toString() );
-                }
-                break;
-            case BOOTERCODE_STDOUT:
-                writeTestOutput( remaining, true );
-                break;
-            case BOOTERCODE_STDERR:
-                writeTestOutput( remaining, false );
-                break;
-            case BOOTERCODE_CONSOLE:
-                getOrCreateConsoleLogger()
-                        .info( createConsoleMessage( remaining ) );
-                break;
-            case BOOTERCODE_NEXT_TEST:
-                notifiableTestStream.provideNewTest();
-                break;
-            case BOOTERCODE_ERROR:
-                errorInFork = deserializeStackTraceWriter( new StringTokenizer( remaining, "," ) );
-                break;
-            case BOOTERCODE_BYE:
-                saidGoodBye = true;
-                notifiableTestStream.acknowledgeByeEventReceived();
-                break;
-            case BOOTERCODE_STOP_ON_NEXT_TEST:
-                stopOnNextTest();
-                break;
-            case BOOTERCODE_DEBUG:
-                getOrCreateConsoleLogger()
-                        .debug( createConsoleMessage( remaining ) );
-                break;
-            case BOOTERCODE_WARNING:
-                getOrCreateConsoleLogger()
-                        .warning( createConsoleMessage( remaining ) );
-                break;
-            default:
-                logStreamWarning( event );
-        }
-    }
-
-    private void logStreamWarning( String event )
-    {
-        logStreamWarning( null, event );
+        decoder.handleEvent( event, errorHandler );
     }
 
-    private void logStreamWarning( Throwable e, String event )
+    private void logStreamWarning( String event, Throwable e )
     {
         final String msg = "Corrupted stdin stream in forked JVM " + forkNumber + ".";
         final InPluginProcessDumpSingleton util = InPluginProcessDumpSingleton.getSingleton();
@@ -309,26 +388,11 @@ public class ForkClient
         }
     }
 
-    private void writeTestOutput( String remaining, boolean isStdout )
+    private void writeTestOutput( byte[] binary, boolean isStdout )
     {
-        int csNameEnd = remaining.indexOf( ',' );
-        String charsetName = remaining.substring( 0, csNameEnd );
-        String byteEncoded = remaining.substring( csNameEnd + 1 );
-        ByteBuffer unescaped = unescapeBytes( byteEncoded, charsetName );
-
-        if ( unescaped.hasArray() )
-        {
-            byte[] convertedBytes = unescaped.array();
-            getOrCreateConsoleOutputReceiver()
-                .writeTestOutput( convertedBytes, unescaped.position(), unescaped.remaining(), isStdout );
-        }
-        else
-        {
-            byte[] convertedBytes = new byte[unescaped.remaining()];
-            unescaped.get( convertedBytes, 0, unescaped.remaining() );
-            getOrCreateConsoleOutputReceiver()
-                .writeTestOutput( convertedBytes, 0, convertedBytes.length, isStdout );
-        }
+        getOrCreateConsoleOutputReceiver()
+                .writeTestOutput( binary, 0, binary.length, isStdout );
+        // todo zmen v inom PR na kodovanie podla sys prop ...encoding.reporting
     }
 
     public final void consumeMultiLineContent( String s )
@@ -341,54 +405,6 @@ public class ForkClient
         }
     }
 
-    private String createConsoleMessage( String remaining )
-    {
-        return unescape( remaining );
-    }
-
-    private ReportEntry createReportEntry( String untokenized )
-    {
-        StringTokenizer tokens = new StringTokenizer( untokenized, "," );
-        try
-        {
-            String source = nullableCsv( tokens.nextToken() );
-            String name = nullableCsv( tokens.nextToken() );
-            String group = nullableCsv( tokens.nextToken() );
-            String message = nullableCsv( tokens.nextToken() );
-            String elapsedStr = tokens.nextToken();
-            Integer elapsed = "null".equals( elapsedStr ) ? null : decode( elapsedStr );
-            final StackTraceWriter stackTraceWriter =
-                    tokens.hasMoreTokens() ? deserializeStackTraceWriter( tokens ) : null;
-
-            return reportEntry( source, name, group, stackTraceWriter, elapsed, message );
-        }
-        catch ( RuntimeException e )
-        {
-            throw new RuntimeException( untokenized, e );
-        }
-    }
-
-    private StackTraceWriter deserializeStackTraceWriter( StringTokenizer tokens )
-    {
-        String stackTraceMessage = nullableCsv( tokens.nextToken() );
-        String smartStackTrace = nullableCsv( tokens.nextToken() );
-        String stackTrace = tokens.hasMoreTokens() ? nullableCsv( tokens.nextToken() ) : null;
-        boolean hasTrace = stackTrace != null;
-        return hasTrace ? new DeserializedStacktraceWriter( stackTraceMessage, smartStackTrace, stackTrace ) : null;
-    }
-
-    private String nullableCsv( String source )
-    {
-        return "null".equals( source ) ? null : unescape( source );
-    }
-
-    private String unescape( String source )
-    {
-        StringBuilder stringBuffer = new StringBuilder( source.length() );
-        unescapeString( stringBuffer, source );
-        return stringBuffer.toString();
-    }
-
     /**
      * Used when getting reporters on the plugin side of a fork.
      * Used by testing purposes only. May not be volatile variable.
@@ -420,7 +436,7 @@ public class ForkClient
         return saidGoodBye;
     }
 
-    public final StackTraceWriter getErrorInFork()
+    public final ErrorInFork getErrorInFork()
     {
         return errorInFork;
     }
@@ -445,33 +461,4 @@ public class ForkClient
         assert this.forkNumber == 0;
         this.forkNumber = forkNumber;
     }
-
-    private static final class OperationalData
-    {
-        private final byte operationId;
-        private final String data;
-
-        OperationalData( String event )
-        {
-            operationId = (byte) event.charAt( 0 );
-            int comma = event.indexOf( ",", 3 );
-            if ( comma < 0 )
-            {
-                throw new IllegalArgumentException( "Stream stdin corrupted. Expected comma after third character "
-                                                            + "in command '" + event + "'." );
-            }
-            int rest = event.indexOf( ",", comma );
-            data = event.substring( rest + 1 );
-        }
-
-        byte getOperationId()
-        {
-            return operationId;
-        }
-
-        String getData()
-        {
-            return data;
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
new file mode 100644
index 0000000..dd8161a
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
@@ -0,0 +1,346 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.booter.ForkedProcessEvent;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import java.nio.charset.Charset;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static javax.xml.bind.DatatypeConverter.parseBase64Binary;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_INFO;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_DEBUG;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STOP_ON_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ERROR;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_FAILED;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SKIPPED;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SUCCEEDED;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_WARNING;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.EVENTS;
+import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
+import static org.apache.maven.surefire.report.RunMode.MODES;
+import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
+import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
+import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank;
+
+/**
+ * magic number : opcode : run mode [: opcode specific data]*
+ * <p/>
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public final class ForkedChannelDecoder
+{
+    private static final byte[] EMPTY = {};
+
+    private volatile ForkedProcessPropertyEventListener propertyEventListener;
+    private volatile ForkedProcessStackTraceEventListener consoleErrorEventListener;
+    private volatile ForkedProcessExitErrorListener exitErrorEventListener;
+
+    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessReportEventListener> reportEventListeners =
+            new ConcurrentHashMap<ForkedProcessEvent, ForkedProcessReportEventListener>();
+
+    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessBinaryEventListener> binaryEventListeners =
+            new ConcurrentHashMap<ForkedProcessEvent, ForkedProcessBinaryEventListener>();
+
+    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessStringEventListener> consoleEventListeners =
+            new ConcurrentHashMap<ForkedProcessEvent, ForkedProcessStringEventListener>();
+
+    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessEventListener> controlEventListeners =
+            new ConcurrentHashMap<ForkedProcessEvent, ForkedProcessEventListener>();
+
+    public void setSystemPropertiesListener( ForkedProcessPropertyEventListener listener )
+    {
+        propertyEventListener = requireNonNull( listener );
+    }
+
+    public void setTestSetStartingListener( ForkedProcessReportEventListener listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TESTSET_STARTING, requireNonNull( listener ) );
+    }
+
+    public void setTestSetCompletedListener( ForkedProcessReportEventListener listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TESTSET_COMPLETED, requireNonNull( listener ) );
+    }
+
+    public void setTestStartingListener( ForkedProcessReportEventListener listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_STARTING, requireNonNull( listener ) );
+    }
+
+    public void setTestSucceededListener( ForkedProcessReportEventListener listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_SUCCEEDED, requireNonNull( listener ) );
+    }
+
+    public void setTestFailedListener( ForkedProcessReportEventListener listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_FAILED, requireNonNull( listener ) );
+    }
+
+    public void setTestSkippedListener( ForkedProcessReportEventListener listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_SKIPPED, requireNonNull( listener ) );
+    }
+
+    public void setTestErrorListener( ForkedProcessReportEventListener listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_ERROR, requireNonNull( listener ) );
+    }
+
+    public void setTestAssumptionFailureListener( ForkedProcessReportEventListener listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_ASSUMPTIONFAILURE, requireNonNull( listener ) );
+    }
+
+    public void setStdOutListener( ForkedProcessBinaryEventListener listener )
+    {
+        binaryEventListeners.put( BOOTERCODE_STDOUT, requireNonNull( listener ) );
+    }
+
+    public void setStdErrListener( ForkedProcessBinaryEventListener listener )
+    {
+        binaryEventListeners.put( BOOTERCODE_STDERR, requireNonNull( listener ) );
+    }
+
+    public void setConsoleInfoListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_INFO, requireNonNull( listener ) );
+    }
+
+    public void setConsoleErrorListener( ForkedProcessStackTraceEventListener listener )
+    {
+        consoleErrorEventListener = requireNonNull( listener );
+    }
+
+    public void setConsoleDebugListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_DEBUG, requireNonNull( listener ) );
+    }
+
+    public void setConsoleWarningListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_WARNING, requireNonNull( listener ) );
+    }
+
+    public void setByeListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_BYE, requireNonNull( listener ) );
+    }
+
+    public void setStopOnNextTestListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_STOP_ON_NEXT_TEST, requireNonNull( listener ) );
+    }
+
+    public void setAcquireNextTestListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_NEXT_TEST, requireNonNull( listener ) );
+    }
+
+    public void setExitErrorEventListener( ForkedProcessExitErrorListener listener )
+    {
+        exitErrorEventListener = requireNonNull( listener );
+    }
+
+    public void handleEvent( String line, ForkedChannelDecoderErrorHandler errorHandler )
+    {
+        if ( line == null || !line.startsWith( MAGIC_NUMBER ) )
+        {
+            errorHandler.handledError( line, null );
+            return;
+        }
+
+        StringTokenizer tokenizer = new StringTokenizer( line.substring( MAGIC_NUMBER.length() ), ":" );
+        String opcode = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+        if ( opcode != null && EVENTS.containsKey( opcode ) )
+        {
+            final ForkedProcessEvent event = EVENTS.get( opcode );
+            try
+            {
+                if ( event.isControlCategory() )
+                {
+                    ForkedProcessEventListener listener = controlEventListeners.get( event );
+                    if ( listener != null )
+                    {
+                        listener.handle();
+                        return;
+                    }
+                }
+                else if ( event.isConsoleCategory() )
+                {
+                    ForkedProcessStringEventListener listener = consoleEventListeners.get( event );
+                    Charset encoding = tokenizer.hasMoreTokens() ? Charset.forName( tokenizer.nextToken() ) : null;
+                    if ( listener != null && encoding != null )
+                    {
+                        String msg = tokenizer.hasMoreTokens() ? decode( tokenizer.nextToken(), encoding ) : "";
+                        listener.handle( msg );
+                        return;
+                    }
+                }
+                else if ( event.isConsoleErrorCategory() )
+                {
+                    Charset encoding = tokenizer.hasMoreTokens() ? Charset.forName( tokenizer.nextToken() ) : null;
+                    if ( consoleErrorEventListener != null && encoding != null )
+                    {
+                        String msg = tokenizer.hasMoreTokens() ? decode( tokenizer.nextToken(), encoding ) : "";
+                        String stackTrace = tokenizer.hasMoreTokens() ? decode( tokenizer.nextToken(), encoding ) : "";
+                        consoleErrorEventListener.handle( msg, stackTrace );
+                        return;
+                    }
+                }
+                else if ( event.isStandardStreamCategory() )
+                {
+                    ForkedProcessBinaryEventListener listener = binaryEventListeners.get( event );
+                    Charset encoding = tokenizer.hasMoreTokens() ? Charset.forName( tokenizer.nextToken() ) : null;
+                    RunMode mode = tokenizer.hasMoreTokens() ? MODES.get( tokenizer.nextToken() ) : null;
+                    if ( listener != null && encoding != null && mode != null )
+                    {
+                        byte[] stream = tokenizer.hasMoreTokens() ? decodeToBytes( tokenizer.nextToken() ) : EMPTY;
+                        listener.handle( mode, encoding, stream );
+                        return;
+                    }
+                }
+                else if ( event.isSysPropCategory() )
+                {
+                    Charset encoding = tokenizer.hasMoreTokens() ? Charset.forName( tokenizer.nextToken() ) : null;
+                    String key = tokenizer.hasMoreTokens() ? decode( tokenizer.nextToken(), encoding ) : "";
+                    if ( propertyEventListener != null && encoding != null && isNotBlank( key ) )
+                    {
+                        String value = tokenizer.hasMoreTokens() ? decode( tokenizer.nextToken(), encoding ) : "";
+                        propertyEventListener.handle( key, value );
+                        return;
+                    }
+                }
+                else if ( event.isTestCategory() )
+                {
+                    ForkedProcessReportEventListener listener = reportEventListeners.get( event );
+                    Charset encoding = tokenizer.hasMoreTokens() ? Charset.forName( tokenizer.nextToken() ) : null;
+                    RunMode mode = tokenizer.hasMoreTokens() ? MODES.get( tokenizer.nextToken() ) : null;
+                    if ( listener != null && encoding != null && mode != null )
+                    {
+                        String sourceName = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                        String name = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                        String group = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                        String message = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                        String elapsed = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                        String traceMessage = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                        String smartTrimmedStackTrace = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                        String stackTrace = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+
+                        listener.handle( mode, toReportEntry( encoding, sourceName, name, group, message, elapsed,
+                                                              traceMessage, smartTrimmedStackTrace, stackTrace ) );
+                        return;
+                    }
+                }
+                else if ( event.isJvmExitError() )
+                {//run mode nedavat vsade
+                    if ( exitErrorEventListener != null )
+                    {
+                        Charset encoding = tokenizer.hasMoreTokens() ? Charset.forName( tokenizer.nextToken() ) : null;
+                        String message = tokenizer.hasMoreTokens() ? decode( tokenizer.nextToken(), encoding ) : "";
+                        String smartTrimmedStackTrace =
+                                tokenizer.hasMoreTokens() ? decode( tokenizer.nextToken(), encoding ) : "";
+                        String stackTrace = tokenizer.hasMoreTokens() ? decode( tokenizer.nextToken(), encoding ) : "";
+                        exitErrorEventListener.handle( message, smartTrimmedStackTrace, stackTrace );
+                    }
+                }
+            }
+            catch ( IllegalArgumentException e )
+            {
+                errorHandler.handledError( line, e );
+                return;
+            }
+        }
+
+        errorHandler.handledError( line, null );
+    }
+
+    static ReportEntry
+    toReportEntry( Charset encoding,
+                   // ReportEntry:
+                   String encSource, String encName, String encGroup, String encMessage, String encTimeElapsed,
+                   // StackTraceWriter:
+                   String encTraceMessage, String encSmartTrimmedStackTrace, String encStackTrace )
+            throws NumberFormatException
+    {
+        if ( encoding == null )
+        {
+            // corrupted or incomplete stream
+            return null;
+        }
+
+        String source = decode( encSource, encoding );
+        String name = decode( encName, 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, name, group, stackTraceWriter, elapsed, message );
+    }
+
+    static String decode( String line, Charset encoding )
+    {
+        return line == null || "-".equals( line ) ? null : new String( parseBase64Binary( line ), encoding );
+    }
+
+    static Integer decodeToInteger( String line )
+    {
+        return line == null || "-".equals( line ) ? null : Integer.decode( line );
+    }
+
+    static byte[] decodeToBytes( String line )
+    {
+        return "-".equals( line ) ? null : parseBase64Binary( line );
+    }
+
+    private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
+                                                 String encSmartTrimmedStackTrace, String encStackTrace )
+    {
+        if ( isBlank( encStackTrace ) || "-".equals( encStackTrace ) )
+        {
+            return null;
+        }
+        else
+        {
+            String traceMessage = decode( encTraceMessage, encoding );
+            String stackTrace = decode( encStackTrace, encoding );
+            String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
+            return new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java
new file mode 100644
index 0000000..7a2bc9b
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java
@@ -0,0 +1,29 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public interface ForkedChannelDecoderErrorHandler
+{
+    void handledError( String line, Throwable e );
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessBinaryEventListener.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessBinaryEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessBinaryEventListener.java
new file mode 100644
index 0000000..5e3a57b
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessBinaryEventListener.java
@@ -0,0 +1,33 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.report.RunMode;
+
+import java.nio.charset.Charset;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public interface ForkedProcessBinaryEventListener
+{
+    void handle( RunMode runMode, Charset encoding, byte[] binary );
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventListener.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventListener.java
new file mode 100644
index 0000000..318d3ec
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventListener.java
@@ -0,0 +1,31 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.report.RunMode;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public interface ForkedProcessEventListener
+{
+    void handle();
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
new file mode 100644
index 0000000..b9d88c2
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
@@ -0,0 +1,29 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public interface ForkedProcessExitErrorListener
+{
+    void handle( String exceptionMessage, String smartTrimmedStackTrace, String stackTrace );
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessPropertyEventListener.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessPropertyEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessPropertyEventListener.java
new file mode 100644
index 0000000..a737c15
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessPropertyEventListener.java
@@ -0,0 +1,31 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.report.RunMode;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public interface ForkedProcessPropertyEventListener
+{
+    void handle( String key, String value );
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessReportEventListener.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessReportEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessReportEventListener.java
new file mode 100644
index 0000000..476f90b
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessReportEventListener.java
@@ -0,0 +1,32 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public interface ForkedProcessReportEventListener
+{
+    void handle( RunMode runMode, ReportEntry reportEntry );
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
new file mode 100644
index 0000000..841b6a6
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
@@ -0,0 +1,31 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.report.RunMode;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public interface ForkedProcessStackTraceEventListener
+{
+    void handle( String msg, String stackTrace );
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStringEventListener.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStringEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStringEventListener.java
new file mode 100644
index 0000000..3641ba1
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStringEventListener.java
@@ -0,0 +1,29 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public interface ForkedProcessStringEventListener
+{
+    void handle( String msg );
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/MultipleFailureException.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/MultipleFailureException.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/MultipleFailureException.java
index 4149ad9..f465393 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/MultipleFailureException.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/MultipleFailureException.java
@@ -58,7 +58,8 @@ final class MultipleFailureException
     public String getMessage()
     {
         StringBuilder messages = new StringBuilder();
-        for ( Throwable exception = exceptions.peek(); exception != null; exception = exceptions.peek() )
+        for ( //noinspection ThrowableResultOfMethodCallIgnored
+                Throwable exception = exceptions.peek(); exception != null; exception = exceptions.peek() )
         {
             if ( messages.length() != 0 )
             {

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
index 397c4c1..85788f5 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
@@ -45,6 +45,7 @@ import java.util.StringTokenizer;
 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType;
 import static org.apache.maven.plugin.surefire.report.FileReporterUtils.stripIllegalFilenameChars;
 import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
+import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank;
 
 // CHECKSTYLE_OFF: LineLength
 /**
@@ -335,7 +336,7 @@ public class StatelessXmlReporter
         }
         if ( report.getSourceName() != null )
         {
-            if ( reportNameSuffix != null && reportNameSuffix.length() > 0 )
+            if ( isNotBlank( reportNameSuffix ) )
             {
                 ppw.addAttribute( "classname", report.getSourceName() + "(" + reportNameSuffix + ")" );
             }
@@ -381,7 +382,7 @@ public class StatelessXmlReporter
 
         String stackTrace = report.getStackTrace( trimStackTrace );
 
-        if ( report.getMessage() != null && report.getMessage().length() > 0 )
+        if ( isNotBlank( report.getMessage() ) )
         {
             ppw.addAttribute( "message", extraEscape( report.getMessage(), true ) );
         }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
index f0f996d..f61368e 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
@@ -19,21 +19,24 @@ package org.apache.maven.plugin.surefire.report;
  * under the License.
  */
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.runorder.StatisticsReporter;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.report.RunMode;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 import static org.apache.maven.plugin.surefire.report.ReportEntryType.ERROR;
 import static org.apache.maven.plugin.surefire.report.ReportEntryType.FAILURE;
 import static org.apache.maven.plugin.surefire.report.ReportEntryType.SKIPPED;
 import static org.apache.maven.plugin.surefire.report.ReportEntryType.SUCCESS;
+import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
+import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
 
 /**
  * Reports data for a single test set.
@@ -69,6 +72,8 @@ public class TestSetRunListener
 
     private final StatisticsReporter statisticsReporter;
 
+    private volatile RunMode runMode = NORMAL_RUN;
+
     @SuppressWarnings( "checkstyle:parameternumber" )
     public TestSetRunListener( ConsoleReporter consoleReporter, FileReporter fileReporter,
                                StatelessXmlReporter simpleXMLReporter,
@@ -219,6 +224,13 @@ public class TestSetRunListener
     {
     }
 
+    public RunMode markAs( RunMode currentRunMode )
+    {
+        RunMode runMode = this.runMode;
+        this.runMode = requireNonNull( currentRunMode );
+        return runMode;
+    }
+
     public void testAssumptionFailure( ReportEntry report )
     {
         testSkipped( report );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java
index 954a2bb..c9e71b8 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java
@@ -44,7 +44,7 @@ class Utf8RecodingDeferredFileOutputStream
     @SuppressWarnings( "checkstyle:magicnumber" )
     public Utf8RecodingDeferredFileOutputStream( String channel )
     {
-        this.deferredFileOutputStream = new DeferredFileOutputStream( 1000000, channel, "deferred", null );
+        deferredFileOutputStream = new DeferredFileOutputStream( 1000000, channel, "deferred", null );
     }
 
     public synchronized void write( byte[] buf, int off, int len )
@@ -57,14 +57,14 @@ class Utf8RecodingDeferredFileOutputStream
 
         if ( !Charset.defaultCharset().equals( UTF8 ) )
         {
-            CharBuffer decodedFromDefaultCharset = Charset.defaultCharset().decode( ByteBuffer.wrap( buf, off, len ) );
+            CharBuffer decodedFromDefaultCharset = Charset.defaultCharset().decode( ByteBuffer.wrap( buf, off, len ) );//tu musi prist kodovanie z VM a nie default
             ByteBuffer utf8Encoded = UTF8.encode( decodedFromDefaultCharset );
 
             if ( utf8Encoded.hasArray() )
             {
                 byte[] convertedBytes = utf8Encoded.array();
 
-                deferredFileOutputStream.write( convertedBytes, utf8Encoded.position(), utf8Encoded.remaining() );
+                deferredFileOutputStream.write( convertedBytes, utf8Encoded.arrayOffset(), utf8Encoded.remaining() );
             }
             else
             {

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
index 2394ff0..16b97cf 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
@@ -23,6 +23,7 @@ import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
 
 import static org.apache.maven.surefire.util.internal.StringUtils.NL;
+import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank;
 
 /**
  * @author Kristian Rosenvold
@@ -125,7 +126,7 @@ public class WrappedReportEntry
 
     public String getReportName( String suffix )
     {
-        return suffix != null && suffix.length() > 0 ? getReportName() + "(" + suffix + ")" : getReportName();
+        return isNotBlank( suffix ) ? getReportName() + "(" + suffix + ")" : getReportName();
     }
 
     public String getOutput( boolean trimStackTrace )

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java
index a53db02..256e252 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java
@@ -41,11 +41,11 @@ public class StatisticsReporter
         this( dataFile, fromFile( dataFile ), new RunEntryStatisticsMap() );
     }
 
-    protected StatisticsReporter( File dataFile, RunEntryStatisticsMap existing, RunEntryStatisticsMap newRestuls )
+    protected StatisticsReporter( File dataFile, RunEntryStatisticsMap existing, RunEntryStatisticsMap newResults )
     {
         this.dataFile = dataFile;
         this.existing = existing;
-        this.newResults = newRestuls;
+        this.newResults = newResults;
     }
 
     public synchronized void testSetCompleted()

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java
index 6636f29..8f4f0fd 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java
@@ -62,7 +62,7 @@ final class FileScanner
             for ( File fileOrDir : filesAndDirs )
             {
                 String name = fileOrDir.getName();
-                if ( name.length() != 0 )
+                if ( !name.isEmpty() )
                 {
                     if ( fileOrDir.isFile() )
                     {
@@ -74,7 +74,7 @@ final class FileScanner
                             if ( filter.shouldRun( toFile( path, simpleClassName ), null ) )
                             {
                                 String fullyQualifiedClassName =
-                                    pAckage.length() == 0 ? simpleClassName : pAckage + '.' + simpleClassName;
+                                    pAckage.isEmpty() ? simpleClassName : pAckage + '.' + simpleClassName;
                                 scannedJavaClassNames.add( fullyQualifiedClassName );
                             }
                         }
@@ -122,7 +122,7 @@ final class FileScanner
     private String toFile( String path, String fileNameWithoutExtension )
     {
         String pathWithoutExtension =
-            path.length() == 0 ? fileNameWithoutExtension : path + '/' + fileNameWithoutExtension;
+            path.isEmpty() ? fileNameWithoutExtension : path + '/' + fileNameWithoutExtension;
         return pathWithoutExtension + ext;
     }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
index 19356f5..f202b96 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
@@ -25,6 +25,7 @@ import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Notifiable
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
+import org.apache.maven.surefire.booter.ForkedChannelEncoder;
 import org.apache.maven.surefire.booter.ForkingRunListener;
 import org.apache.maven.surefire.report.CategorizedReportEntry;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
@@ -269,10 +270,10 @@ public class ForkingRunListenerTest
         ReportEntry expected = createDefaultReportEntry();
         final SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
 
-        new ForkingRunListener( printStream, defaultChannel, false )
+        new ForkingRunListener( new ForkedChannelEncoder( printStream ), defaultChannel, false )
                 .testStarting( expected );
 
-        new ForkingRunListener( anotherPrintStream, anotherChannel, false )
+        new ForkingRunListener( new ForkedChannelEncoder( anotherPrintStream ), anotherChannel, false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
@@ -338,7 +339,7 @@ public class ForkingRunListenerTest
 
     private RunListener createForkingRunListener( Integer testSetChannel )
     {
-        return new ForkingRunListener( printStream, testSetChannel, false );
+        return new ForkingRunListener( new ForkedChannelEncoder( printStream ), testSetChannel, false );
     }
 
     private class StandardTestRun

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/64ae8e88/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
new file mode 100644
index 0000000..3927ebc
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -0,0 +1,38 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class ForkClientTest
+{
+    @Test
+    public void test()
+    {
+        ForkClient client = new ForkClient( null, null, null, null );
+    }
+
+}
\ No newline at end of file