aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java
diff options
context:
space:
mode:
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.java1049
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
+ }
+ }
+}