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:06 UTC

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

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,