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:47:28 UTC

[maven-surefire] branch maven2surefire-jvm-communication updated (ac09933 -> b1a4930)

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

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


 discard ac09933  improved coverage in new code
     new b1a4930  improved coverage in new code

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (ac09933)
            \
             N -- N -- N   refs/heads/maven2surefire-jvm-communication (b1a4930)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../maven/surefire/booter/ForkedBooterTest.java      | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)


[maven-surefire] 01/01: improved coverage in new code

Posted by ti...@apache.org.
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

commit b1a4930c554c7b9a4aad36d8465a4571dad42f60
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 | 239 ++++++++++++++++++++
 .../surefire/util/internal/ChannelsWriterTest.java | 243 +++++++++++++++++++++
 .../surefire/booter/ForkedBooterMockTest.java      |  36 ++-
 .../maven/surefire/booter/ForkedBooterTest.java    |  20 +-
 8 files changed, 549 insertions(+), 12 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..8a3b0a4 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,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.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InterruptedIOException;
 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 +321,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" );
+    }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
index aac78f7..ef2ba61 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
@@ -148,7 +148,7 @@ public class ForkedBooterTest
     }
 
     @Test( timeout = 10_000 )
-    public void testBarrier() throws Exception
+    public void testBarrier1() throws Exception
     {
         Semaphore semaphore = new Semaphore( 2 );
         boolean acquiredOnePermit = invokeMethod( ForkedBooter.class, "acquireOnePermit", semaphore, 30_000L );
@@ -158,6 +158,24 @@ public class ForkedBooterTest
     }
 
     @Test
+    public void testBarrier2() throws Exception
+    {
+        Semaphore semaphore = new Semaphore( 0 );
+        Thread.currentThread().interrupt();
+        try
+        {
+            boolean acquiredOnePermit = invokeMethod( ForkedBooter.class, "acquireOnePermit", semaphore, 30_000L );
+
+            assertThat( acquiredOnePermit ).isTrue();
+            assertThat( semaphore.availablePermits() ).isEqualTo( 0 );
+        }
+        finally
+        {
+            assertThat( Thread.interrupted() ).isFalse();
+        }
+    }
+
+    @Test
     public void testScheduler() throws Exception
     {
         ScheduledThreadPoolExecutor executor = invokeMethod( ForkedBooter.class, "createPingScheduler" );