diff options
Diffstat (limited to 'jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java')
-rw-r--r-- | jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java | 1049 |
1 files changed, 1049 insertions, 0 deletions
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java new file mode 100644 index 0000000..c525ef1 --- /dev/null +++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java @@ -0,0 +1,1049 @@ +/* + * Copyright 2013 Google Inc. + * + * Licensed 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. + */ + +package com.google.common.jimfs; + +import static com.google.common.jimfs.TestUtils.assertNotEquals; +import static com.google.common.jimfs.TestUtils.buffer; +import static com.google.common.jimfs.TestUtils.bytes; +import static com.google.common.jimfs.TestUtils.regularFile; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.nio.file.StandardOpenOption.APPEND; +import static java.nio.file.StandardOpenOption.READ; +import static java.nio.file.StandardOpenOption.WRITE; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableSet; +import com.google.common.testing.NullPointerTester; +import com.google.common.util.concurrent.Runnables; +import com.google.common.util.concurrent.SettableFuture; +import com.google.common.util.concurrent.Uninterruptibles; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.FileLockInterruptionException; +import java.nio.channels.NonReadableChannelException; +import java.nio.channels.NonWritableChannelException; +import java.nio.file.OpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Most of the behavior of {@link JimfsFileChannel} is handled by the {@link RegularFile} + * implementations, so the thorough tests of that are in {@link RegularFileTest}. This mostly tests + * interactions with the file and channel positions. + * + * @author Colin Decker + */ +@RunWith(JUnit4.class) +public class JimfsFileChannelTest { + + private static FileChannel channel(RegularFile file, OpenOption... options) throws IOException { + return new JimfsFileChannel( + file, + Options.getOptionsForChannel(ImmutableSet.copyOf(options)), + new FileSystemState(Runnables.doNothing())); + } + + @Test + public void testPosition() throws IOException { + FileChannel channel = channel(regularFile(10), READ); + assertEquals(0, channel.position()); + assertSame(channel, channel.position(100)); + assertEquals(100, channel.position()); + } + + @Test + public void testSize() throws IOException { + RegularFile file = regularFile(10); + FileChannel channel = channel(file, READ); + + assertEquals(10, channel.size()); + + file.write(10, new byte[90], 0, 90); + assertEquals(100, channel.size()); + } + + @Test + public void testRead() throws IOException { + RegularFile file = regularFile(20); + FileChannel channel = channel(file, READ); + assertEquals(0, channel.position()); + + ByteBuffer buf = buffer("1234567890"); + ByteBuffer buf2 = buffer("123457890"); + assertEquals(10, channel.read(buf)); + assertEquals(10, channel.position()); + + buf.flip(); + assertEquals(10, channel.read(new ByteBuffer[] {buf, buf2})); + assertEquals(20, channel.position()); + + buf.flip(); + buf2.flip(); + file.write(20, new byte[10], 0, 10); + assertEquals(10, channel.read(new ByteBuffer[] {buf, buf2}, 0, 2)); + assertEquals(30, channel.position()); + + buf.flip(); + assertEquals(10, channel.read(buf, 5)); + assertEquals(30, channel.position()); + + buf.flip(); + assertEquals(-1, channel.read(buf)); + assertEquals(30, channel.position()); + } + + @Test + public void testWrite() throws IOException { + RegularFile file = regularFile(0); + FileChannel channel = channel(file, WRITE); + assertEquals(0, channel.position()); + + ByteBuffer buf = buffer("1234567890"); + ByteBuffer buf2 = buffer("1234567890"); + assertEquals(10, channel.write(buf)); + assertEquals(10, channel.position()); + + buf.flip(); + assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2})); + assertEquals(30, channel.position()); + + buf.flip(); + buf2.flip(); + assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}, 0, 2)); + assertEquals(50, channel.position()); + + buf.flip(); + assertEquals(10, channel.write(buf, 5)); + assertEquals(50, channel.position()); + } + + @Test + public void testAppend() throws IOException { + RegularFile file = regularFile(0); + FileChannel channel = channel(file, WRITE, APPEND); + assertEquals(0, channel.position()); + + ByteBuffer buf = buffer("1234567890"); + ByteBuffer buf2 = buffer("1234567890"); + + assertEquals(10, channel.write(buf)); + assertEquals(10, channel.position()); + + buf.flip(); + channel.position(0); + assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2})); + assertEquals(30, channel.position()); + + buf.flip(); + buf2.flip(); + channel.position(0); + assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}, 0, 2)); + assertEquals(50, channel.position()); + + buf.flip(); + channel.position(0); + assertEquals(10, channel.write(buf, 5)); + assertEquals(60, channel.position()); + + buf.flip(); + channel.position(0); + assertEquals(10, channel.transferFrom(new ByteBufferChannel(buf), 0, 10)); + assertEquals(70, channel.position()); + } + + @Test + public void testTransferTo() throws IOException { + RegularFile file = regularFile(10); + FileChannel channel = channel(file, READ); + + ByteBufferChannel writeChannel = new ByteBufferChannel(buffer("1234567890")); + assertEquals(10, channel.transferTo(0, 100, writeChannel)); + assertEquals(0, channel.position()); + } + + @Test + public void testTransferFrom() throws IOException { + RegularFile file = regularFile(0); + FileChannel channel = channel(file, WRITE); + + ByteBufferChannel readChannel = new ByteBufferChannel(buffer("1234567890")); + assertEquals(10, channel.transferFrom(readChannel, 0, 100)); + assertEquals(0, channel.position()); + } + + @Test + public void testTruncate() throws IOException { + RegularFile file = regularFile(10); + FileChannel channel = channel(file, WRITE); + + channel.truncate(10); // no resize, >= size + assertEquals(10, file.size()); + channel.truncate(11); // no resize, > size + assertEquals(10, file.size()); + channel.truncate(5); // resize down to 5 + assertEquals(5, file.size()); + + channel.position(20); + channel.truncate(10); + assertEquals(10, channel.position()); + channel.truncate(2); + assertEquals(2, channel.position()); + } + + @Test + public void testFileTimeUpdates() throws IOException { + RegularFile file = regularFile(10); + FileChannel channel = + new JimfsFileChannel( + file, + ImmutableSet.<OpenOption>of(READ, WRITE), + new FileSystemState(Runnables.doNothing())); + + // accessed + long accessTime = file.getLastAccessTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.read(ByteBuffer.allocate(10)); + assertNotEquals(accessTime, file.getLastAccessTime()); + + accessTime = file.getLastAccessTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.read(ByteBuffer.allocate(10), 0); + assertNotEquals(accessTime, file.getLastAccessTime()); + + accessTime = file.getLastAccessTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)}); + assertNotEquals(accessTime, file.getLastAccessTime()); + + accessTime = file.getLastAccessTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1); + assertNotEquals(accessTime, file.getLastAccessTime()); + + accessTime = file.getLastAccessTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.transferTo(0, 10, new ByteBufferChannel(10)); + assertNotEquals(accessTime, file.getLastAccessTime()); + + // modified + long modifiedTime = file.getLastModifiedTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.write(ByteBuffer.allocate(10)); + assertNotEquals(modifiedTime, file.getLastModifiedTime()); + + modifiedTime = file.getLastModifiedTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.write(ByteBuffer.allocate(10), 0); + assertNotEquals(modifiedTime, file.getLastModifiedTime()); + + modifiedTime = file.getLastModifiedTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)}); + assertNotEquals(modifiedTime, file.getLastModifiedTime()); + + modifiedTime = file.getLastModifiedTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1); + assertNotEquals(modifiedTime, file.getLastModifiedTime()); + + modifiedTime = file.getLastModifiedTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.truncate(0); + assertNotEquals(modifiedTime, file.getLastModifiedTime()); + + modifiedTime = file.getLastModifiedTime(); + Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + + channel.transferFrom(new ByteBufferChannel(10), 0, 10); + assertNotEquals(modifiedTime, file.getLastModifiedTime()); + } + + @Test + public void testClose() throws IOException { + FileChannel channel = channel(regularFile(0), READ, WRITE); + ExecutorService executor = Executors.newSingleThreadExecutor(); + assertTrue(channel.isOpen()); + channel.close(); + assertFalse(channel.isOpen()); + + try { + channel.position(); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.position(0); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.lock(); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.lock(0, 10, true); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.tryLock(); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.tryLock(0, 10, true); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.force(true); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.write(buffer("111")); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.write(buffer("111"), 10); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.transferFrom(new ByteBufferChannel(bytes("1111")), 0, 4); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.truncate(0); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.read(buffer("111")); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.read(buffer("111"), 10); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2); + fail(); + } catch (ClosedChannelException expected) { + } + + try { + channel.transferTo(0, 10, new ByteBufferChannel(buffer("111"))); + fail(); + } catch (ClosedChannelException expected) { + } + + executor.shutdown(); + } + + @Test + public void testWritesInReadOnlyMode() throws IOException { + FileChannel channel = channel(regularFile(0), READ); + + try { + channel.write(buffer("111")); + fail(); + } catch (NonWritableChannelException expected) { + } + + try { + channel.write(buffer("111"), 10); + fail(); + } catch (NonWritableChannelException expected) { + } + + try { + channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}); + fail(); + } catch (NonWritableChannelException expected) { + } + + try { + channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2); + fail(); + } catch (NonWritableChannelException expected) { + } + + try { + channel.transferFrom(new ByteBufferChannel(bytes("1111")), 0, 4); + fail(); + } catch (NonWritableChannelException expected) { + } + + try { + channel.truncate(0); + fail(); + } catch (NonWritableChannelException expected) { + } + + try { + channel.lock(0, 10, false); + fail(); + } catch (NonWritableChannelException expected) { + } + } + + @Test + public void testReadsInWriteOnlyMode() throws IOException { + FileChannel channel = channel(regularFile(0), WRITE); + + try { + channel.read(buffer("111")); + fail(); + } catch (NonReadableChannelException expected) { + } + + try { + channel.read(buffer("111"), 10); + fail(); + } catch (NonReadableChannelException expected) { + } + + try { + channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}); + fail(); + } catch (NonReadableChannelException expected) { + } + + try { + channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2); + fail(); + } catch (NonReadableChannelException expected) { + } + + try { + channel.transferTo(0, 10, new ByteBufferChannel(buffer("111"))); + fail(); + } catch (NonReadableChannelException expected) { + } + + try { + channel.lock(0, 10, true); + fail(); + } catch (NonReadableChannelException expected) { + } + } + + @Test + public void testPositionNegative() throws IOException { + FileChannel channel = channel(regularFile(0), READ, WRITE); + + try { + channel.position(-1); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testTruncateNegative() throws IOException { + FileChannel channel = channel(regularFile(0), READ, WRITE); + + try { + channel.truncate(-1); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testWriteNegative() throws IOException { + FileChannel channel = channel(regularFile(0), READ, WRITE); + + try { + channel.write(buffer("111"), -1); + fail(); + } catch (IllegalArgumentException expected) { + } + + ByteBuffer[] bufs = {buffer("111"), buffer("111")}; + try { + channel.write(bufs, -1, 10); + fail(); + } catch (IndexOutOfBoundsException expected) { + } + + try { + channel.write(bufs, 0, -1); + fail(); + } catch (IndexOutOfBoundsException expected) { + } + } + + @Test + public void testReadNegative() throws IOException { + FileChannel channel = channel(regularFile(0), READ, WRITE); + + try { + channel.read(buffer("111"), -1); + fail(); + } catch (IllegalArgumentException expected) { + } + + ByteBuffer[] bufs = {buffer("111"), buffer("111")}; + try { + channel.read(bufs, -1, 10); + fail(); + } catch (IndexOutOfBoundsException expected) { + } + + try { + channel.read(bufs, 0, -1); + fail(); + } catch (IndexOutOfBoundsException expected) { + } + } + + @Test + public void testTransferToNegative() throws IOException { + FileChannel channel = channel(regularFile(0), READ, WRITE); + + try { + channel.transferTo(-1, 0, new ByteBufferChannel(10)); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + channel.transferTo(0, -1, new ByteBufferChannel(10)); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testTransferFromNegative() throws IOException { + FileChannel channel = channel(regularFile(0), READ, WRITE); + + try { + channel.transferFrom(new ByteBufferChannel(10), -1, 0); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + channel.transferFrom(new ByteBufferChannel(10), 0, -1); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testLockNegative() throws IOException { + FileChannel channel = channel(regularFile(0), READ, WRITE); + + try { + channel.lock(-1, 10, true); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + channel.lock(0, -1, true); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + channel.tryLock(-1, 10, true); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + channel.tryLock(0, -1, true); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testNullPointerExceptions() throws IOException { + FileChannel channel = channel(regularFile(100), READ, WRITE); + + NullPointerTester tester = new NullPointerTester(); + tester.testAllPublicInstanceMethods(channel); + } + + @Test + public void testLock() throws IOException { + FileChannel channel = channel(regularFile(10), READ, WRITE); + + assertNotNull(channel.lock()); + assertNotNull(channel.lock(0, 10, false)); + assertNotNull(channel.lock(0, 10, true)); + + assertNotNull(channel.tryLock()); + assertNotNull(channel.tryLock(0, 10, false)); + assertNotNull(channel.tryLock(0, 10, true)); + + FileLock lock = channel.lock(); + assertTrue(lock.isValid()); + lock.release(); + assertFalse(lock.isValid()); + } + + @Test + public void testAsynchronousClose() throws Exception { + RegularFile file = regularFile(10); + final FileChannel channel = channel(file, READ, WRITE); + + file.writeLock().lock(); // ensure all operations on the channel will block + + ExecutorService executor = Executors.newCachedThreadPool(); + + CountDownLatch latch = new CountDownLatch(BLOCKING_OP_COUNT); + List<Future<?>> futures = queueAllBlockingOperations(channel, executor, latch); + + // wait for all the threads to have started running + latch.await(); + // then ensure time for operations to start blocking + Uninterruptibles.sleepUninterruptibly(20, MILLISECONDS); + + // close channel on this thread + channel.close(); + + // the blocking operations are running on different threads, so they all get + // AsynchronousCloseException + for (Future<?> future : futures) { + try { + future.get(); + fail(); + } catch (ExecutionException expected) { + assertWithMessage("blocking thread exception") + .that(expected.getCause()) + .isInstanceOf(AsynchronousCloseException.class); + } + } + } + + @Test + public void testCloseByInterrupt() throws Exception { + RegularFile file = regularFile(10); + final FileChannel channel = channel(file, READ, WRITE); + + file.writeLock().lock(); // ensure all operations on the channel will block + + ExecutorService executor = Executors.newCachedThreadPool(); + + final CountDownLatch threadStartLatch = new CountDownLatch(1); + final SettableFuture<Throwable> interruptException = SettableFuture.create(); + + // This thread, being the first to run, will be blocking on the interruptible lock (the byte + // file's write lock) and as such will be interrupted properly... the other threads will be + // blocked on the lock that guards the position field and the specification that only one method + // on the channel will be in progress at a time. That lock is not interruptible, so we must + // interrupt this thread. + Thread thread = + new Thread( + new Runnable() { + @Override + public void run() { + threadStartLatch.countDown(); + try { + channel.write(ByteBuffer.allocate(20)); + interruptException.set(null); + } catch (Throwable e) { + interruptException.set(e); + } + } + }); + thread.start(); + + // let the thread start running + threadStartLatch.await(); + // then ensure time for thread to start blocking on the write lock + Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS); + + CountDownLatch blockingStartLatch = new CountDownLatch(BLOCKING_OP_COUNT); + List<Future<?>> futures = queueAllBlockingOperations(channel, executor, blockingStartLatch); + + // wait for all blocking threads to start + blockingStartLatch.await(); + // then ensure time for the operations to start blocking + Uninterruptibles.sleepUninterruptibly(20, MILLISECONDS); + + // interrupting this blocking thread closes the channel and makes all the other threads + // throw AsynchronousCloseException... the operation on this thread should throw + // ClosedByInterruptException + thread.interrupt(); + + // get the exception that caused the interrupted operation to terminate + assertWithMessage("interrupted thread exception") + .that(interruptException.get(200, MILLISECONDS)) + .isInstanceOf(ClosedByInterruptException.class); + + // check that each other thread got AsynchronousCloseException (since the interrupt, on a + // different thread, closed the channel) + for (Future<?> future : futures) { + try { + future.get(); + fail(); + } catch (ExecutionException expected) { + assertWithMessage("blocking thread exception") + .that(expected.getCause()) + .isInstanceOf(AsynchronousCloseException.class); + } + } + } + + private static final int BLOCKING_OP_COUNT = 10; + + /** + * Queues blocking operations on the channel in separate threads using the given executor. The + * given latch should have a count of BLOCKING_OP_COUNT to allow the caller wants to wait for all + * threads to start executing. + */ + private List<Future<?>> queueAllBlockingOperations( + final FileChannel channel, ExecutorService executor, final CountDownLatch startLatch) { + List<Future<?>> futures = new ArrayList<>(); + + final ByteBuffer buffer = ByteBuffer.allocate(10); + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.write(buffer); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.write(buffer, 0); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.write(new ByteBuffer[] {buffer, buffer}); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.write(new ByteBuffer[] {buffer, buffer, buffer}, 0, 2); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.read(buffer); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.read(buffer, 0); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.read(new ByteBuffer[] {buffer, buffer}); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.read(new ByteBuffer[] {buffer, buffer, buffer}, 0, 2); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.transferTo(0, 10, new ByteBufferChannel(buffer)); + return null; + } + })); + + futures.add( + executor.submit( + new Callable<Object>() { + @Override + public Object call() throws Exception { + startLatch.countDown(); + channel.transferFrom(new ByteBufferChannel(buffer), 0, 10); + return null; + } + })); + + return futures; + } + + /** + * Tests that the methods on the default FileChannel that support InterruptibleChannel behavior + * also support it on JimfsFileChannel, by just interrupting the thread before calling the method. + */ + @Test + public void testInterruptedThreads() throws IOException { + final ByteBuffer buf = ByteBuffer.allocate(10); + final ByteBuffer[] bufArray = {buf}; + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.size(); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.position(); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.position(0); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.write(buf); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.write(bufArray, 0, 1); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.read(buf); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.read(bufArray, 0, 1); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.write(buf, 0); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.read(buf, 0); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.transferTo(0, 1, channel(regularFile(10), READ, WRITE)); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.transferFrom(channel(regularFile(10), READ, WRITE), 0, 1); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.force(true); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.truncate(0); + } + }); + + assertClosedByInterrupt( + new FileChannelMethod() { + @Override + public void call(FileChannel channel) throws IOException { + channel.lock(0, 1, true); + } + }); + + // tryLock() does not handle interruption + // map() always throws UOE; it doesn't make sense for it to try to handle interruption + } + + private interface FileChannelMethod { + void call(FileChannel channel) throws IOException; + } + + /** + * Asserts that when the given operation is run on an interrupted thread, {@code + * ClosedByInterruptException} is thrown, the channel is closed and the thread is no longer + * interrupted. + */ + private static void assertClosedByInterrupt(FileChannelMethod method) throws IOException { + FileChannel channel = channel(regularFile(10), READ, WRITE); + Thread.currentThread().interrupt(); + try { + method.call(channel); + fail( + "expected the method to throw ClosedByInterruptException or " + + "FileLockInterruptionException"); + } catch (ClosedByInterruptException | FileLockInterruptionException expected) { + assertFalse("expected the channel to be closed", channel.isOpen()); + assertTrue("expected the thread to still be interrupted", Thread.interrupted()); + } finally { + Thread.interrupted(); // ensure the thread isn't interrupted when this method returns + } + } +} |