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

[maven-surefire] branch maven2surefire-jvm-communication updated: improved coverage in new code

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

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


The following commit(s) were added to refs/heads/maven2surefire-jvm-communication by this push:
     new 8315fb3  improved coverage in new code
8315fb3 is described below

commit 8315fb38b0fccea057b89851d8b3131703d184ea
Author: tibordigana <ti...@apache.org>
AuthorDate: Sun Apr 5 02:25:41 2020 +0200

    improved coverage in new code
---
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   2 -
 .../maven/surefire/util/internal/Channels.java     |  14 +-
 .../java/org/apache/maven/JUnit4SuiteTest.java     |   4 +-
 .../surefire/util/internal}/AsyncSocketTest.java   |   3 +-
 .../surefire/util/internal/ChannelsReaderTest.java | 240 ++++++++++++++++++++
 .../surefire/util/internal/ChannelsWriterTest.java | 243 +++++++++++++++++++++
 .../surefire/booter/ForkedBooterMockTest.java      |  36 ++-
 7 files changed, 531 insertions(+), 11 deletions(-)

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 94754a7..22bf702 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
@@ -40,7 +40,6 @@ import org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfigu
 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.extensions.AsyncSocketTest;
 import org.apache.maven.plugin.surefire.extensions.ConsoleOutputReporterTest;
 import org.apache.maven.plugin.surefire.extensions.E2ETest;
 import org.apache.maven.plugin.surefire.extensions.ForkedProcessEventNotifierTest;
@@ -111,7 +110,6 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( ForkChannelTest.class ) );
         suite.addTest( new JUnit4TestAdapter( StreamFeederTest.class ) );
         suite.addTest( new JUnit4TestAdapter( E2ETest.class ) );
-        suite.addTest( new JUnit4TestAdapter( AsyncSocketTest.class ) );
         return suite;
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
index a536fc6..b15e6f6 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
@@ -28,7 +28,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousByteChannel;
-import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 import java.util.concurrent.ExecutionException;
@@ -99,7 +99,9 @@ public final class Channels
                         catch ( ExecutionException e )
                         {
                             Throwable t = e.getCause();
-                            throw new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
+                            throw t instanceof IOException
+                                ? (IOException) t
+                                : new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
                         }
                         catch ( Exception e )
                         {
@@ -124,7 +126,7 @@ public final class Channels
                     {
                         channel.close();
                     }
-                    catch ( AsynchronousCloseException e )
+                    catch ( ClosedChannelException e )
                     {
                         // closed channel anyway
                     }
@@ -157,7 +159,9 @@ public final class Channels
                 catch ( ExecutionException e )
                 {
                     Throwable t = e.getCause();
-                    throw new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
+                    throw t instanceof IOException
+                        ? (IOException) t
+                        : new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
                 }
                 catch ( Exception e )
                 {
@@ -188,7 +192,7 @@ public final class Channels
                     {
                         channel.close();
                     }
-                    catch ( AsynchronousCloseException e )
+                    catch ( ClosedChannelException e )
                     {
                         // closed channel anyway
                     }
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 650807f..231ff74 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -35,6 +35,7 @@ import org.apache.maven.surefire.util.RunOrderCalculatorTest;
 import org.apache.maven.surefire.util.RunOrderTest;
 import org.apache.maven.surefire.util.ScanResultTest;
 import org.apache.maven.surefire.util.TestsToRunTest;
+import org.apache.maven.surefire.util.internal.AsyncSocketTest;
 import org.apache.maven.surefire.util.internal.ChannelsReaderTest;
 import org.apache.maven.surefire.util.internal.ChannelsWriterTest;
 import org.apache.maven.surefire.util.internal.ConcurrencyUtilsTest;
@@ -66,7 +67,8 @@ import org.junit.runners.Suite;
     ImmutableMapTest.class,
     ReflectionUtilsTest.class,
     ChannelsReaderTest.class,
-    ChannelsWriterTest.class
+    ChannelsWriterTest.class,
+    AsyncSocketTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/AsyncSocketTest.java
similarity index 98%
rename from maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java
rename to surefire-api/src/test/java/org/apache/maven/surefire/util/internal/AsyncSocketTest.java
index 88cba07..8363e29 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/AsyncSocketTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.util.internal;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,7 +19,6 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
 import org.junit.Test;
 
 import java.io.BufferedInputStream;
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
index 0b64a42..5cd6fde 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
@@ -23,19 +23,38 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonReadableChannelException;
 import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ShutdownChannelGroupException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 import static java.nio.file.Files.write;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 /**
  * The tests for {@link Channels#newChannel(InputStream)} and {@link Channels#newBufferedChannel(InputStream)}.
@@ -303,4 +322,225 @@ public class ChannelsReaderTest
         assertThat( bb.array() )
             .isEqualTo( new byte[] {1, 2, 3, 0} );
     }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput1() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[0], -1, 0 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput2() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[0], 0, -1 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput3() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[0], 1, 0 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput4() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[0], 0, 1 );
+    }
+
+    @Test
+    public void shouldClose() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( true );
+        InputStream is = Channels.newInputStream( channel );
+        is.close();
+        verify( channel, times( 1 ) ).close();
+    }
+
+    @Test
+    public void shouldNotClose() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( false );
+        InputStream is = Channels.newInputStream( channel );
+        is.close();
+        verify( channel, never() ).close();
+    }
+
+    @Test
+    public void shouldAlreadyClosed() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( true );
+        doThrow( ClosedChannelException.class ).when( channel ).close();
+        InputStream is = Channels.newInputStream( channel );
+        is.close();
+        verify( channel ).close();
+    }
+
+    @Test
+    public void shouldReadZeroLength() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        InputStream is = Channels.newInputStream( channel );
+        is.read( new byte[] { 5 }, 0, 0 );
+        verifyZeroInteractions( channel );
+    }
+
+    @Test
+    public void shouldReadArray() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Future<Integer>>()
+            {
+                @Override
+                public Future<Integer> answer( InvocationOnMock invocation ) throws Throwable
+                {
+                    ByteBuffer bb = (ByteBuffer) invocation.getArguments()[0];
+                    bb.put( (byte) 3 );
+                    bb.put( (byte) 4 );
+                    Future<Integer> future = mock( Future.class );
+                    when( future.get() ).thenReturn( 2 );
+                    return future;
+                }
+            } );
+
+        InputStream is = Channels.newInputStream( channel );
+        ArgumentCaptor<ByteBuffer> captured = ArgumentCaptor.forClass( ByteBuffer.class );
+        byte[] b = new byte[] { 1, 2, 0, 0, 5 };
+        is.read( b, 2, 2 );
+
+        verify( channel ).read( captured.capture() );
+        verifyNoMoreInteractions( channel );
+
+        assertThat( captured.getAllValues() )
+            .hasSize( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).array() )
+            .containsOnly( new byte[] { 1, 2, 3, 4, 5 } );
+
+        assertThat( captured.getAllValues().get( 0 ).arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( captured.getAllValues().get( 0 ).position() )
+            .isEqualTo( 4 );
+
+        assertThat( captured.getAllValues().get( 0 ).limit() )
+            .isEqualTo( 4 );
+
+        assertThat( captured.getAllValues().get( 0 ).capacity() )
+            .isEqualTo( 5 );
+    }
+
+    @Test
+    public void shouldRead() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Future<Integer>>()
+            {
+                @Override
+                public Future<Integer> answer( InvocationOnMock invocation ) throws Throwable
+                {
+                    ByteBuffer bb = (ByteBuffer) invocation.getArguments()[0];
+                    bb.put( (byte) 3 );
+                    Future<Integer> future = mock( Future.class );
+                    when( future.get() ).thenReturn( 1 );
+                    return future;
+                }
+            } );
+
+        InputStream is = Channels.newInputStream( channel );
+        ArgumentCaptor<ByteBuffer> captured = ArgumentCaptor.forClass( ByteBuffer.class );
+        int b = is.read();
+        assertThat( b )
+            .isEqualTo( 3 );
+
+        verify( channel ).read( captured.capture() );
+        verifyNoMoreInteractions( channel );
+
+        assertThat( captured.getAllValues() )
+            .hasSize( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).array() )
+            .containsOnly( new byte[] { 3 } );
+
+        assertThat( captured.getAllValues().get( 0 ).arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( captured.getAllValues().get( 0 ).position() )
+            .isEqualTo( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).limit() )
+            .isEqualTo( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).capacity() )
+            .isEqualTo( 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnRead() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenThrow( ShutdownChannelGroupException.class );
+        InputStream is = Channels.newInputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectCause( instanceOf( ShutdownChannelGroupException.class ) );
+        is.read( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture1() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( new InterruptedIOException() ) );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        InputStream is = Channels.newInputStream( channel );
+        ee.expect( InterruptedIOException.class );
+        is.read( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture2() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( new RuntimeException( "msg" ) ) );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        InputStream is = Channels.newInputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectCause( instanceOf( RuntimeException.class ) );
+        ee.expectMessage( "msg" );
+        is.read( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture3() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( "msg", null ) );
+        when( channel.read( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        InputStream is = Channels.newInputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectMessage( "msg" );
+        is.read( new byte[1], 0, 1 );
+    }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
index 35c9ddd..24ed3f3 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
@@ -23,19 +23,37 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.ShutdownChannelGroupException;
 import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 import static java.nio.file.Files.readAllBytes;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 /**
  * The tests for {@link Channels#newChannel(OutputStream)} and {@link Channels#newBufferedChannel(OutputStream)}.
@@ -207,4 +225,229 @@ public class ChannelsWriterTest
             .hasSize( 3 )
             .isEqualTo( new byte[] {1, 2, 3} );
     }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput1() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[0], -1, 0 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput2() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[0], 0, -1 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput3() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[0], 1, 0 );
+    }
+
+    @Test( expected = IndexOutOfBoundsException.class )
+    public void shouldValidateInput4() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[0], 0, 1 );
+    }
+
+    @Test
+    public void shouldClose() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( true );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.close();
+        verify( channel, times( 1 ) ).close();
+    }
+
+    @Test
+    public void shouldNotClose() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( false );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.close();
+        verify( channel, never() ).close();
+    }
+
+    @Test
+    public void shouldAlreadyClosed() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.isOpen() ).thenReturn( true );
+        doThrow( ClosedChannelException.class ).when( channel ).close();
+        OutputStream os = Channels.newOutputStream( channel );
+        os.close();
+        verify( channel ).close();
+    }
+
+    @Test
+    public void shouldWriteZeroLength() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        os.write( new byte[] { 5 }, 0, 0 );
+        verifyZeroInteractions( channel );
+    }
+
+    @Test
+    public void shouldWriteArray() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Future<Integer>>()
+            {
+                @Override
+                public Future<Integer> answer( InvocationOnMock invocation ) throws Throwable
+                {
+                    ByteBuffer bb = (ByteBuffer) invocation.getArguments()[0];
+                    int i = 0;
+                    for ( ; bb.hasRemaining(); i++ )
+                    {
+                        bb.get();
+                    }
+                    Future<Integer> future = mock( Future.class );
+                    when( future.get() ).thenReturn( i );
+                    return future;
+                }
+            } );
+
+        OutputStream os = Channels.newOutputStream( channel );
+        ArgumentCaptor<ByteBuffer> captured = ArgumentCaptor.forClass( ByteBuffer.class );
+        os.write( new byte[] { 1, 2, 3, 4, 5 }, 2, 2 );
+
+        verify( channel ).write( captured.capture() );
+        verifyNoMoreInteractions( channel );
+
+        assertThat( captured.getAllValues() )
+            .hasSize( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).array() )
+            .containsOnly( new byte[] { 1, 2, 3, 4, 5 } );
+
+        assertThat( captured.getAllValues().get( 0 ).arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( captured.getAllValues().get( 0 ).position() )
+            .isEqualTo( 4 );
+
+        assertThat( captured.getAllValues().get( 0 ).limit() )
+            .isEqualTo( 4 );
+
+        assertThat( captured.getAllValues().get( 0 ).capacity() )
+            .isEqualTo( 5 );
+    }
+
+    @Test
+    public void shouldWrite() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenAnswer( new Answer<Future<Integer>>()
+            {
+                @Override
+                public Future<Integer> answer( InvocationOnMock invocation ) throws Throwable
+                {
+                    ByteBuffer bb = (ByteBuffer) invocation.getArguments()[0];
+                    int i = 0;
+                    for ( ; bb.hasRemaining(); i++ )
+                    {
+                        bb.get();
+                    }
+                    Future<Integer> future = mock( Future.class );
+                    when( future.get() ).thenReturn( i );
+                    return future;
+                }
+            } );
+
+        OutputStream os = Channels.newOutputStream( channel );
+        ArgumentCaptor<ByteBuffer> captured = ArgumentCaptor.forClass( ByteBuffer.class );
+        os.write( 3 );
+
+        verify( channel ).write( captured.capture() );
+        verifyNoMoreInteractions( channel );
+
+        assertThat( captured.getAllValues() )
+            .hasSize( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).array() )
+            .containsOnly( new byte[] { 3 } );
+
+        assertThat( captured.getAllValues().get( 0 ).arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( captured.getAllValues().get( 0 ).position() )
+            .isEqualTo( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).limit() )
+            .isEqualTo( 1 );
+
+        assertThat( captured.getAllValues().get( 0 ).capacity() )
+            .isEqualTo( 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnWrite() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenThrow( ShutdownChannelGroupException.class );
+        OutputStream os = Channels.newOutputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectCause( instanceOf( ShutdownChannelGroupException.class ) );
+        os.write( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture1() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( new InterruptedIOException() ) );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        OutputStream os = Channels.newOutputStream( channel );
+        ee.expect( InterruptedIOException.class );
+        os.write( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture2() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( new RuntimeException( "msg" ) ) );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        OutputStream os = Channels.newOutputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectCause( instanceOf( RuntimeException.class ) );
+        ee.expectMessage( "msg" );
+        os.write( new byte[1], 0, 1 );
+    }
+
+    @Test
+    public void shouldThrowExceptionOnFuture3() throws Exception
+    {
+        AsynchronousByteChannel channel = mock( AsynchronousByteChannel.class );
+        Future<Integer> future = mock( Future.class );
+        when( future.get() )
+            .thenThrow( new ExecutionException( "msg", null ) );
+        when( channel.write( any( ByteBuffer.class ) ) )
+            .thenReturn( future );
+        OutputStream os = Channels.newOutputStream( channel );
+        ee.expect( IOException.class );
+        ee.expectMessage( "msg" );
+        os.write( new byte[1], 0, 1 );
+    }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
index 5604fd1..4a3690e 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
@@ -24,6 +24,7 @@ import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import org.junit.Rule;
 import org.junit.Test;
@@ -33,6 +34,8 @@ import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
@@ -50,9 +53,11 @@ import static org.fest.assertions.Fail.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.powermock.api.mockito.PowerMockito.doAnswer;
 import static org.powermock.api.mockito.PowerMockito.doCallRealMethod;
 import static org.powermock.api.mockito.PowerMockito.doNothing;
 import static org.powermock.api.mockito.PowerMockito.doThrow;
@@ -67,7 +72,12 @@ import static org.powermock.reflect.Whitebox.setInternalState;
  * PowerMock tests for {@link ForkedBooter}.
  */
 @RunWith( PowerMockRunner.class )
-@PrepareForTest( { PpidChecker.class, ForkedBooter.class, LegacyMasterProcessChannelEncoder.class } )
+@PrepareForTest( {
+                     PpidChecker.class,
+                     ForkedBooter.class,
+                     LegacyMasterProcessChannelEncoder.class,
+                     ShutdownHookUtils.class
+} )
 @PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
 public class ForkedBooterMockTest
 {
@@ -349,4 +359,28 @@ public class ForkedBooterMockTest
             }
         }
     }
+
+    @Test
+    public void testFlushEventChannelOnExit() throws Exception
+    {
+        mockStatic( ShutdownHookUtils.class );
+
+        final MasterProcessChannelEncoder eventChannel = mock( MasterProcessChannelEncoder.class );
+        ForkedBooter booter = new ForkedBooter();
+        setInternalState( booter, "eventChannel", eventChannel );
+
+        doAnswer( new Answer<Object>()
+        {
+            @Override
+            public Object answer( InvocationOnMock invocation )
+            {
+                Thread t = invocation.getArgument( 0 );
+                assertThat( t.isDaemon() ).isTrue();
+                t.run();
+                verify( eventChannel, times( 1 ) ).onJvmExit();
+                return null;
+            }
+        } ).when( ShutdownHookUtils.class, "addShutDownHook", any( Thread.class ) );
+        invokeMethod( booter, "flushEventChannelOnExit" );
+    }
 }