aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java')
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java262
1 files changed, 262 insertions, 0 deletions
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java
new file mode 100644
index 0000000..7d47588
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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.buffer;
+import static com.google.common.jimfs.TestUtils.regularFile;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.file.StandardOpenOption.READ;
+import static java.nio.file.StandardOpenOption.WRITE;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+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.fail;
+
+import com.google.common.collect.ImmutableSet;
+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.AsynchronousFileChannel;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.CompletionHandler;
+import java.nio.channels.FileLock;
+import java.nio.file.OpenOption;
+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;
+
+/**
+ * Tests for {@link JimfsAsynchronousFileChannel}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class JimfsAsynchronousFileChannelTest {
+
+ private static JimfsAsynchronousFileChannel channel(
+ RegularFile file, ExecutorService executor, OpenOption... options) throws IOException {
+ JimfsFileChannel channel =
+ new JimfsFileChannel(
+ file,
+ Options.getOptionsForChannel(ImmutableSet.copyOf(options)),
+ new FileSystemState(Runnables.doNothing()));
+ return new JimfsAsynchronousFileChannel(channel, executor);
+ }
+
+ /**
+ * Just tests the main read/write methods... the methods all delegate to the non-async channel
+ * anyway.
+ */
+ @Test
+ public void testAsyncChannel() throws Throwable {
+ RegularFile file = regularFile(15);
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE);
+
+ try {
+ assertEquals(15, channel.size());
+
+ assertSame(channel, channel.truncate(5));
+ assertEquals(5, channel.size());
+
+ file.write(5, new byte[5], 0, 5);
+ checkAsyncRead(channel);
+ checkAsyncWrite(channel);
+ checkAsyncLock(channel);
+
+ channel.close();
+ assertFalse(channel.isOpen());
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ @Test
+ public void testClosedChannel() throws Throwable {
+ RegularFile file = regularFile(15);
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ try {
+ JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE);
+ channel.close();
+
+ assertClosed(channel.read(ByteBuffer.allocate(10), 0));
+ assertClosed(channel.write(ByteBuffer.allocate(10), 15));
+ assertClosed(channel.lock());
+ assertClosed(channel.lock(0, 10, true));
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ @Test
+ public void testAsyncClose_write() throws Throwable {
+ RegularFile file = regularFile(15);
+ ExecutorService executor = Executors.newFixedThreadPool(4);
+
+ try {
+ JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE);
+
+ file.writeLock().lock(); // cause another thread trying to write to block
+
+ // future-returning write
+ Future<Integer> future = channel.write(ByteBuffer.allocate(10), 0);
+
+ // completion handler write
+ SettableFuture<Integer> completionHandlerFuture = SettableFuture.create();
+ channel.write(ByteBuffer.allocate(10), 0, null, setFuture(completionHandlerFuture));
+
+ // Despite this 10ms sleep to allow plenty of time, it's possible, though very rare, for a
+ // race to cause the channel to be closed before the asynchronous calls get to the initial
+ // check that the channel is open, causing ClosedChannelException to be thrown rather than
+ // AsynchronousCloseException. This is not a problem in practice, just a quirk of how these
+ // tests work and that we don't have a way of waiting for the operations to get past that
+ // check.
+ Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS);
+
+ channel.close();
+
+ assertAsynchronousClose(future);
+ assertAsynchronousClose(completionHandlerFuture);
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ @Test
+ public void testAsyncClose_read() throws Throwable {
+ RegularFile file = regularFile(15);
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+
+ try {
+ JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE);
+
+ file.writeLock().lock(); // cause another thread trying to read to block
+
+ // future-returning read
+ Future<Integer> future = channel.read(ByteBuffer.allocate(10), 0);
+
+ // completion handler read
+ SettableFuture<Integer> completionHandlerFuture = SettableFuture.create();
+ channel.read(ByteBuffer.allocate(10), 0, null, setFuture(completionHandlerFuture));
+
+ // Despite this 10ms sleep to allow plenty of time, it's possible, though very rare, for a
+ // race to cause the channel to be closed before the asynchronous calls get to the initial
+ // check that the channel is open, causing ClosedChannelException to be thrown rather than
+ // AsynchronousCloseException. This is not a problem in practice, just a quirk of how these
+ // tests work and that we don't have a way of waiting for the operations to get past that
+ // check.
+ Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS);
+
+ channel.close();
+
+ assertAsynchronousClose(future);
+ assertAsynchronousClose(completionHandlerFuture);
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ private static void checkAsyncRead(AsynchronousFileChannel channel) throws Throwable {
+ ByteBuffer buf = buffer("1234567890");
+ assertEquals(10, (int) channel.read(buf, 0).get());
+
+ buf.flip();
+
+ SettableFuture<Integer> future = SettableFuture.create();
+ channel.read(buf, 0, null, setFuture(future));
+
+ assertThat(future.get(10, SECONDS)).isEqualTo(10);
+ }
+
+ private static void checkAsyncWrite(AsynchronousFileChannel asyncChannel) throws Throwable {
+ ByteBuffer buf = buffer("1234567890");
+ assertEquals(10, (int) asyncChannel.write(buf, 0).get());
+
+ buf.flip();
+ SettableFuture<Integer> future = SettableFuture.create();
+ asyncChannel.write(buf, 0, null, setFuture(future));
+
+ assertThat(future.get(10, SECONDS)).isEqualTo(10);
+ }
+
+ private static void checkAsyncLock(AsynchronousFileChannel channel) throws Throwable {
+ assertNotNull(channel.lock().get());
+ assertNotNull(channel.lock(0, 10, true).get());
+
+ SettableFuture<FileLock> future = SettableFuture.create();
+ channel.lock(0, 10, true, null, setFuture(future));
+
+ assertNotNull(future.get(10, SECONDS));
+ }
+
+ /**
+ * Returns a {@code CompletionHandler} that sets the appropriate result or exception on the given
+ * {@code future} on completion.
+ */
+ private static <T> CompletionHandler<T, Object> setFuture(final SettableFuture<T> future) {
+ return new CompletionHandler<T, Object>() {
+ @Override
+ public void completed(T result, Object attachment) {
+ future.set(result);
+ }
+
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ future.setException(exc);
+ }
+ };
+ }
+
+ /** Assert that the future fails, with the failure caused by {@code ClosedChannelException}. */
+ private static void assertClosed(Future<?> future) throws Throwable {
+ try {
+ future.get(10, SECONDS);
+ fail("ChannelClosedException was not thrown");
+ } catch (ExecutionException expected) {
+ assertThat(expected.getCause()).isInstanceOf(ClosedChannelException.class);
+ }
+ }
+
+ /**
+ * Assert that the future fails, with the failure caused by either {@code
+ * AsynchronousCloseException} or (rarely) {@code ClosedChannelException}.
+ */
+ private static void assertAsynchronousClose(Future<?> future) throws Throwable {
+ try {
+ future.get(10, SECONDS);
+ fail("no exception was thrown");
+ } catch (ExecutionException expected) {
+ Throwable t = expected.getCause();
+ if (!(t instanceof AsynchronousCloseException || t instanceof ClosedChannelException)) {
+ fail(
+ "expected AsynchronousCloseException (or in rare cases ClosedChannelException); got "
+ + t);
+ }
+ }
+ }
+}