From ff4448bac06d4de319cdea8668477f1324e1ab55 Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Sat, 2 Jan 2016 02:05:13 +0100 Subject: [PATCH] Tests for filesystem-nio * Renamed tests from ...IntegrationTest back to ...Test ** to allow better integration with moreunit ** because some methods of the classes can only be integration tested some not which lead to a strange splitting of the tests * Added more tests --- .../filesystem/nio/ReadableNioFile.java | 2 +- .../filesystem/nio/SharedFileChannel.java | 35 +- ...rationTest.java => NioFileSystemTest.java} | 2 +- ...eIntegrationTest.java => NioFileTest.java} | 254 +++++++++++- ...ntegrationTest.java => NioFolderTest.java} | 2 +- .../filesystem/nio/PathMatcher.java | 4 + .../filesystem/nio/ReadableNioFileTest.java | 16 +- .../filesystem/nio/SharedFileChannelTest.java | 377 ++++++++++++++++++ .../filesystem/nio/ThreadStackMatcher.java | 58 +++ .../filesystem/nio/WritableNioFileTest.java | 84 ++++ 10 files changed, 816 insertions(+), 18 deletions(-) rename main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/{NioFileSystemIntegrationTest.java => NioFileSystemTest.java} (97%) rename main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/{NioFileIntegrationTest.java => NioFileTest.java} (59%) rename main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/{NioFolderIntegrationTest.java => NioFolderTest.java} (99%) create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ThreadStackMatcher.java diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java index 64f5b596f..408beb646 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java @@ -68,7 +68,7 @@ class ReadableNioFile implements ReadableFile { long size = nioFile.channel().size(); long transferred = 0; while (transferred < size) { - transferred += nioFile.channel().transferTo(transferred, size - transferred, targetChannel); + transferred += nioFile.channel().transferTo(transferred, size - transferred, targetChannel, transferred); } } diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java index 54aac6c37..f1bcd2822 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java @@ -42,12 +42,26 @@ class SharedFileChannel { assertOpenedByCurrentThread(); doLocked(() -> { openedBy.remove(Thread.currentThread()); - if (openedBy.isEmpty()) { - closeChannel(); + try { + delegate.force(true); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + if (openedBy.isEmpty()) { + closeChannel(); + } } }); } + /** + * @deprecated only intended to be used in tests + */ + @Deprecated + void forceClose() { + closeSilently(delegate); + } + private void assertOpenedByCurrentThread() { if (!openedBy.containsKey(Thread.currentThread())) { throw new IllegalStateException("SharedFileChannel closed for current thread"); @@ -61,14 +75,22 @@ class SharedFileChannel { readChannel = FileChannel.open(path, StandardOpenOption.READ); } delegate = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - if (readChannel != null) { - readChannel.close(); - } + closeSilently(readChannel); } catch (IOException e) { throw new UncheckedIOException(e); } } + private void closeSilently(FileChannel channel) { + if (channel != null) { + try { + channel.close(); + } catch (IOException e) { + // ignore + } + } + } + private void closeChannel() { try { delegate.close(); @@ -121,13 +143,14 @@ class SharedFileChannel { } } - public long transferTo(long position, long count, SharedFileChannel targetChannel) { + public long transferTo(long position, long count, SharedFileChannel targetChannel, long targetPosition) { assertOpenedByCurrentThread(); targetChannel.assertOpenedByCurrentThread(); try { long maxPosition = delegate.size(); long maxCount = Math.min(count, maxPosition - position); long remaining = maxCount; + targetChannel.delegate.position(targetPosition); while (remaining > 0) { remaining -= delegate.transferTo(maxPosition - remaining, remaining, targetChannel.delegate); } diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java similarity index 97% rename from main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemIntegrationTest.java rename to main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java index bfa22ddb8..6d151fd37 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemIntegrationTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java @@ -16,7 +16,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -public class NioFileSystemIntegrationTest { +public class NioFileSystemTest { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java similarity index 59% rename from main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileIntegrationTest.java rename to main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java index 84b8a2c5f..05cff08a7 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileIntegrationTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java @@ -8,6 +8,8 @@ import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.folder; import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.testFilesystem; import static org.cryptomator.filesystem.nio.PathMatcher.doesNotExist; import static org.cryptomator.filesystem.nio.PathMatcher.isFile; +import static org.cryptomator.filesystem.nio.ThreadStackMatcher.stackContains; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.sameInstance; @@ -16,19 +18,25 @@ import static org.junit.Assert.assertThat; import java.io.UncheckedIOException; import java.nio.file.Path; import java.time.Instant; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; import org.cryptomator.filesystem.File; import org.cryptomator.filesystem.FileSystem; import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.ReadableFile; +import org.cryptomator.filesystem.WritableFile; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.rules.Timeout; import org.junit.runner.RunWith; import de.bechte.junit.runners.context.HierarchicalContextRunner; @RunWith(HierarchicalContextRunner.class) -public class NioFileIntegrationTest { +public class NioFileTest { @Rule public ExpectedException thrown = ExpectedException.none(); @@ -339,6 +347,250 @@ public class NioFileIntegrationTest { fileWhichIsAFolder.moveTo(target); } + public class OpenReadable { + + @Test + public void testOpenReadableReturnsAReadableNioFileForAnExistingFile() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + + ReadableFile result = file.openReadable(); + + assertThat(result, is(instanceOf(ReadableNioFile.class))); + } + + @Test + public void testOpenReadableThrowsAnUncheckedIOExceptionIfTheFileDoesNotExists() { + Path filesystemPath = emptyFilesystem(); + Path nonExistingFilePath = filesystemPath.resolve("nonExistingFile"); + File nonExistingFile = NioFileSystem.rootedAt(filesystemPath).file("nonExistingFile"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(nonExistingFilePath.toString()); + + nonExistingFile.openReadable(); + } + + @Test + public void testOpenReadableThrowsIllegalStateExceptionIfInvokedTwiceFromWithingTheSameThread() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openReadable(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Current thread is already reading this file"); + + file.openReadable(); + } + + @Test + public void testOpenReadableDoesNotThrowIllegalStateExceptionIfInvokedTwiceFromWithingTheSameThreadButTheFirstReadableFileHasBeenClosed() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openReadable().close(); + + ReadableFile result = file.openReadable(); + + assertThat(result, is(instanceOf(ReadableNioFile.class))); + } + + @Test + public void testOpenReadableThrowsIllegalStateExceptionIfInvokedAfterOpenWritable() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openWritable(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Current thread is currently writing this file"); + + file.openReadable(); + } + + @Test + public void testOpenReadableDoesNotThrowIllegalStateExceptionIfInvokedAfterOpenWritableButTheWritableFileHasBeenClosed() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openWritable().close(); + + ReadableFile result = file.openReadable(); + + assertThat(result, is(instanceOf(ReadableNioFile.class))); + } + + } + + public class OpenWritable { + + @Test + public void testOpenWritableReturnsAWritableNioFileForAnExistingFile() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + + WritableFile result = file.openWritable(); + + assertThat(result, is(instanceOf(WritableNioFile.class))); + } + + @Test + public void testOpenWritableReturnsAWritableNioFileForANonExisitingFile() { + File file = NioFileSystem.rootedAt(emptyFilesystem()).file("nonExistingFile"); + + WritableFile result = file.openWritable(); + + assertThat(result, is(instanceOf(WritableNioFile.class))); + } + + @Test + public void testOpenWritableDoesNotCreateANonExisitingFile() { + Path filesystemPath = emptyFilesystem(); + Path filePath = filesystemPath.resolve("nonExistingFile"); + File file = NioFileSystem.rootedAt(filesystemPath).file("nonExistingFile"); + + file.openWritable(); + + assertThat(filePath, doesNotExist()); + } + + @Test + public void testOpenWritableThrowsIllegalStateExceptionIfInvokedTwiceFromWithingTheSameThread() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openWritable(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Current thread is already writing this file"); + + file.openWritable(); + } + + @Test + public void testOpenWritableDoesNotThrowIllegalStateExceptionIfInvokedTwiceFromWithingTheSameThreadButTheFirstWritableFileHasBeenClosed() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openWritable().close(); + + WritableFile result = file.openWritable(); + + assertThat(result, is(instanceOf(WritableNioFile.class))); + } + + @Test + public void testOpenWritableThrowsIllegalStateExceptionIfInvokedAfterOpenReadable() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openReadable(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Current thread is currently reading this file"); + + file.openWritable(); + } + + @Test + public void testOpenWritableDoesNotThrowIllegalStateExceptionIfInvokedAfterOpenReadableButTheReadableFileHasBeenClosed() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openReadable().close(); + + WritableFile result = file.openWritable(); + + assertThat(result, is(instanceOf(WritableNioFile.class))); + } + + } + + public class OpenWithMultipleThreads { + + @Rule + public Timeout timeout = Timeout.seconds(5); + + @Test + public void testOpenReadableInvokedInTwoThreadsCompletes() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + + ReadableFile readableFileFromThread1 = computeInThread(file::openReadable); + ReadableFile readableFileFromThread2 = computeInThread(file::openReadable); + + assertThat(readableFileFromThread1, is(instanceOf(ReadableNioFile.class))); + assertThat(readableFileFromThread2, is(instanceOf(ReadableNioFile.class))); + assertThat(readableFileFromThread1, is(not(sameInstance(readableFileFromThread2)))); + } + + @Test + public void testOpenReadableInvokedWhileWritableFileIsOpenBlocks() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openWritable(); + + Thread thread = inThread(file::openReadable); + + assertThat(thread, is(stackContains(NioFile.class, "openReadable"))); + } + + @Test + public void testOpenWritableInvokedWhileReadableFileIsOpenBlocks() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openReadable(); + + Thread thread = inThread(file::openWritable); + + assertThat(thread, is(stackContains(NioFile.class, "openWritable"))); + } + + @Test + public void testOpenWritableInvokedWhileWritableFileIsOpenBlocks() { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + file.openWritable(); + + Thread thread = inThread(file::openWritable); + + assertThat(thread, is(stackContains(NioFile.class, "openWritable"))); + } + + @Test + public void testOpenReadableInvokedWhileWritableFileIsOpenCompletesAfterClosingIt() throws InterruptedException { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + WritableFile writableFile = file.openWritable(); + Thread thread = inThread(file::openReadable); + writableFile.close(); + + thread.join(); + } + + @Test + public void testOpenWritableInvokedWhileReadableFileIsOpenCompletesAfterClosingIt() throws InterruptedException { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + ReadableFile readableFile = file.openReadable(); + Thread thread = inThread(file::openWritable); + readableFile.close(); + + thread.join(); + } + + @Test + public void testOpenWritableInvokedWhileWritableFileIsOpenCompletesAfterClosingIt() throws InterruptedException { + File file = NioFileSystem.rootedAt(testFilesystem(file("fileName"))).file("fileName"); + WritableFile writableFile = file.openWritable(); + Thread thread = inThread(file::openWritable); + writableFile.close(); + + thread.join(); + } + + private Thread inThread(Runnable task) { + Thread thread = new Thread(task); + thread.start(); + try { + // give thread time to execute work + Thread.sleep(100); + } catch (InterruptedException e) { + } + return thread; + } + + private T computeInThread(Supplier computation) { + CompletableFuture future = new CompletableFuture<>(); + inThread(() -> { + future.complete(computation.get()); + }); + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + } + private int signum(int value) { if (value > 0) { return 1; diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java similarity index 99% rename from main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderIntegrationTest.java rename to main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java index eb00842f0..3ef5acedf 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderIntegrationTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java @@ -31,7 +31,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -public class NioFolderIntegrationTest { +public class NioFolderTest { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/PathMatcher.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/PathMatcher.java index e892b55bb..3d73b9639 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/PathMatcher.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/PathMatcher.java @@ -67,6 +67,10 @@ class PathMatcher { return new IsFileWithContentMatcher(value); } + public Matcher thatIsEmpty() { + return withContent(new byte[0]); + } + } public static class IsFileWithContentMatcher extends TypeSafeDiagnosingMatcher { diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java index af8f81fb2..0b56f1fc0 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java @@ -213,7 +213,7 @@ public class ReadableNioFileTest { when(otherFile.belongsToSameFilesystem(file)).thenReturn(true); long sizeOfSourceChannel = 3283; when(channel.size()).thenReturn(sizeOfSourceChannel); - when(channel.transferTo(0, sizeOfSourceChannel, otherChannel)).thenReturn(sizeOfSourceChannel); + when(channel.transferTo(0, sizeOfSourceChannel, otherChannel, 0)).thenReturn(sizeOfSourceChannel); inTest.copyTo(writableOtherFile); @@ -221,7 +221,7 @@ public class ReadableNioFileTest { inOrder.verify(writableOtherFile).assertOpen(); inOrder.verify(writableOtherFile).ensureChannelIsOpened(); inOrder.verify(otherChannel).truncate(0); - inOrder.verify(channel).transferTo(0, sizeOfSourceChannel, otherChannel); + inOrder.verify(channel).transferTo(0, sizeOfSourceChannel, otherChannel, 0); } @Test @@ -235,16 +235,16 @@ public class ReadableNioFileTest { long sizeRemainingAfterFirstTransfer = sizeRemainingAfterSecondTransfer + secondTransferAmount; long size = sizeRemainingAfterFirstTransfer + firstTransferAmount; when(channel.size()).thenReturn(size); - when(channel.transferTo(0, size, otherChannel)).thenReturn(firstTransferAmount); - when(channel.transferTo(firstTransferAmount, sizeRemainingAfterFirstTransfer, otherChannel)).thenReturn(secondTransferAmount); - when(channel.transferTo(firstTransferAmount + secondTransferAmount, sizeRemainingAfterSecondTransfer, otherChannel)).thenReturn(thirdTransferAmount); + when(channel.transferTo(0, size, otherChannel, 0)).thenReturn(firstTransferAmount); + when(channel.transferTo(firstTransferAmount, sizeRemainingAfterFirstTransfer, otherChannel, firstTransferAmount)).thenReturn(secondTransferAmount); + when(channel.transferTo(firstTransferAmount + secondTransferAmount, sizeRemainingAfterSecondTransfer, otherChannel, firstTransferAmount + secondTransferAmount)).thenReturn(thirdTransferAmount); inTest.copyTo(writableOtherFile); InOrder inOrder = inOrder(channel); - inOrder.verify(channel).transferTo(0, size, otherChannel); - inOrder.verify(channel).transferTo(firstTransferAmount, sizeRemainingAfterFirstTransfer, otherChannel); - inOrder.verify(channel).transferTo(firstTransferAmount + secondTransferAmount, sizeRemainingAfterSecondTransfer, otherChannel); + inOrder.verify(channel).transferTo(0, size, otherChannel, 0); + inOrder.verify(channel).transferTo(firstTransferAmount, sizeRemainingAfterFirstTransfer, otherChannel, firstTransferAmount); + inOrder.verify(channel).transferTo(firstTransferAmount + secondTransferAmount, sizeRemainingAfterSecondTransfer, otherChannel, firstTransferAmount + secondTransferAmount); } } diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java new file mode 100644 index 000000000..40f0e1beb --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java @@ -0,0 +1,377 @@ +package org.cryptomator.filesystem.nio; + +import static java.util.Arrays.copyOf; +import static java.util.Arrays.copyOfRange; +import static org.apache.commons.io.FileUtils.writeByteArrayToFile; +import static org.cryptomator.filesystem.nio.PathMatcher.isFile; +import static org.cryptomator.filesystem.nio.SharedFileChannel.EOF; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.xml.ws.Holder; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import de.bechte.junit.runners.context.HierarchicalContextRunner; + +@RunWith(HierarchicalContextRunner.class) +public class SharedFileChannelTest { + + private static final byte[] FILE_CONTENT = "FileContent".getBytes(); + private static final byte[] OTHER_FILE_CONTENT = "Other".getBytes(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Path pathOfTestfile; + private Path pathOfOtherTestfile; + + private SharedFileChannel inTest; + private SharedFileChannel otherInTest; + + @Before + public void setup() throws IOException { + pathOfTestfile = Files.createTempFile("sharedFileChannelTest", "tmp"); + writeByteArrayToFile(pathOfTestfile.toFile(), FILE_CONTENT); + pathOfOtherTestfile = Files.createTempFile("sharedFileChannelTest", "tmp"); + writeByteArrayToFile(pathOfOtherTestfile.toFile(), OTHER_FILE_CONTENT); + + assertThat(pathOfTestfile, isFile().withContent(FILE_CONTENT)); + assertThat(pathOfOtherTestfile, isFile().withContent(OTHER_FILE_CONTENT)); + + inTest = new SharedFileChannel(pathOfTestfile); + otherInTest = new SharedFileChannel(pathOfOtherTestfile); + } + + public class ReadFully { + + @Before + public void setup() { + inTest.open(OpenMode.READ); + } + + @Test + public void testReadFullyReadsFileContents() { + byte[] result = new byte[FILE_CONTENT.length]; + ByteBuffer buffer = ByteBuffer.wrap(result); + + int read = inTest.readFully(0, buffer); + + assertThat(result, is(FILE_CONTENT)); + assertThat(read, is(FILE_CONTENT.length)); + } + + @Test + public void testReadFullyReadsFromPositionContents() { + int position = 5; + byte[] result = new byte[FILE_CONTENT.length - position]; + ByteBuffer buffer = ByteBuffer.wrap(result); + + int read = inTest.readFully(position, buffer); + + assertThat(result, is(copyOfRange(FILE_CONTENT, position, FILE_CONTENT.length))); + assertThat(read, is(FILE_CONTENT.length - position)); + } + + @Test + public void testReadFullyReadsTillEndIfBufferHasMoreSpaceAvailabe() { + int position = 5; + byte[] result = new byte[FILE_CONTENT.length]; + ByteBuffer buffer = ByteBuffer.wrap(result); + byte[] expected = copyOf(copyOfRange(FILE_CONTENT, position, FILE_CONTENT.length), FILE_CONTENT.length); + + int read = inTest.readFully(position, buffer); + + assertThat(result, is(expected)); + assertThat(read, is(FILE_CONTENT.length - position)); + } + + @Test + public void testReadReadsNothingIfBufferHasNoSpaceAvailabe() { + ByteBuffer buffer = ByteBuffer.allocate(5); + buffer.position(5); + + int read = inTest.readFully(0, buffer); + + assertThat(buffer.array(), is(new byte[5])); + assertThat(read, is(0)); + } + + @Test + public void testReadReadsNothingIfPositionIsEndOfFile() { + ByteBuffer buffer = ByteBuffer.allocate(5); + + int read = inTest.readFully(FILE_CONTENT.length, buffer); + + assertThat(buffer.array(), is(new byte[5])); + assertThat(read, is(EOF)); + } + + @Test + public void testReadReadsNothingIfAfterEndOfFile() { + ByteBuffer buffer = ByteBuffer.allocate(5); + + int read = inTest.readFully(FILE_CONTENT.length + 10, buffer); + + assertThat(buffer.array(), is(new byte[5])); + assertThat(read, is(EOF)); + } + + @Test + public void testReadThrowsIllegalArgumentExceptionIfPositionIsNegative() { + thrown.expect(IllegalArgumentException.class); + + inTest.readFully(-1, ByteBuffer.allocate(0)); + } + + } + + public class Truncate { + + @Before + public void setup() { + inTest.open(OpenMode.WRITE); + } + + @Test + public void testTruncateToZeroDeletesFileContent() { + inTest.truncate(0); + inTest.close(); + + assertThat(pathOfTestfile, isFile().thatIsEmpty()); + } + + @Test + public void testTruncateToSmallerSizeTruncatesFile() { + inTest.truncate(5); + inTest.close(); + + assertThat(pathOfTestfile, isFile().withContent(copyOfRange(FILE_CONTENT, 0, 5))); + } + + @Test + public void testTruncateToGreaterSizeDoesNotChangeFile() { + inTest.truncate(FILE_CONTENT.length + 10); + inTest.close(); + + assertThat(pathOfTestfile, isFile().withContent(FILE_CONTENT)); + } + } + + @Test + public void testSizeReturnsFileSize() { + inTest.open(OpenMode.READ); + + assertThat(inTest.size(), is((long) FILE_CONTENT.length)); + } + + public class TransferTo { + + @Before + public void setup() { + inTest.open(OpenMode.READ); + otherInTest.open(OpenMode.WRITE); + } + + @Test + public void testTransferToCopiesSelectedRegionToTargetPosition() { + // TODO Markus Kreusch + } + + @Test + public void testTransferToDoesNothingIfCountIsZero() { + // TODO Markus Kreusch + } + + @Test + public void testTransferToTransfersLessThanCountBytesIfEndOfSourceFileIsReached() { + // TODO Markus Kreusch + } + + @Test + public void testTransferToThrowsIllegalArgumentExceptionIfPositionIsSmallerZero() { + // TODO Markus Kreusch + } + + @Test + public void testTransferToThrowsIllegalArgumentExceptionIfCountIsSmallerZero() { + // TODO Markus Kreusch + } + + @Test + public void testTransferToThrowsIllegalArgumentExceptionIfTargetPositionIsSmallerZero() { + // TODO Markus Kreusch + } + + @Test + public void testTransferToTransfersSelectedRegionToTargetAfterEndOfFile() { + // TODO Markus Kreusch + } + + } + + public class WriteFully { + + @Before + public void setup() { + inTest.open(OpenMode.WRITE); + } + + @Test + public void testWriteFullyWritesFileContents() { + // TODO Markus Kreusch + } + + @Test + public void testWriteFullyWritesContentsToPosition() { + // TODO Markus Kreusch + } + + @Test + public void testWriteFullyWritesContentsEvenIfPositionIsGreaterCurrentEndOfFile() { + // TODO Markus Kreusch + } + + @Test + public void testWriteWritesNothingBufferHasNothingAvailabe() { + // TODO Markus Kreusch + } + + @Test + public void testWriteThrowsIllegalArgumentExceptionIfPositionIsNegative() { + thrown.expect(IllegalArgumentException.class); + + inTest.writeFully(-1, ByteBuffer.allocate(0)); + } + + } + + public class MultipleOpenInvocation { + + @Test + public void testInvokeOpenMultipleTimesFromSameThreadThrowsException() { + inTest.open(OpenMode.READ); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("A thread can only open a SharedFileChannel once"); + + inTest.open(OpenMode.READ); + } + + @Test + public void testInvokeOpenMultipleTimesFromDifferentThreadsWorks() { + inTest.open(OpenMode.READ); + inThreadRethrowingExceptions(() -> inTest.open(OpenMode.READ)); + } + + } + + public class NonOpenChannel { + + @Test + public void testClosingNonOpenChannelThrowsException() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("SharedFileChannel closed for current thread"); + + inTest.close(); + } + + @Test + public void testInvokingReadFullyOnNonOpenChannelThrowsException() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("SharedFileChannel closed for current thread"); + + inTest.readFully(0, null); + } + + @Test + public void testInvokingTruncateOnNonOpenChannelThrowsException() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("SharedFileChannel closed for current thread"); + + inTest.truncate(0); + } + + @Test + public void testInvokingSizeOnNonOpenChannelThrowsException() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("SharedFileChannel closed for current thread"); + + inTest.size(); + } + + @Test + public void testInvokingTransferToOnNonOpenChannelThrowsException() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("SharedFileChannel closed for current thread"); + + inTest.transferTo(0, 1, null, 0); + } + + @Test + public void testInvokingTransferToPassingNonOpenChannelThrowsException() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("SharedFileChannel closed for current thread"); + + inTest.open(OpenMode.READ); + + inTest.transferTo(0, 1, otherInTest, 0); + } + + @Test + public void testInvokinWriteFullyOnNonOpenChannelThrowsException() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("SharedFileChannel closed for current thread"); + + inTest.writeFully(0, null); + } + + } + + @After + @SuppressWarnings("deprecation") + public void teardown() { + inTest.forceClose(); + otherInTest.forceClose(); + try { + Files.delete(pathOfTestfile); + } catch (IOException e) { + // ignore + } + try { + Files.delete(pathOfOtherTestfile); + } catch (IOException e) { + // ignore + } + } + + private void inThreadRethrowingExceptions(Runnable task) { + Holder holder = new Holder<>(); + Thread thread = new Thread(() -> { + try { + task.run(); + } catch (RuntimeException e) { + holder.value = e; + } + }); + thread.start(); + try { + thread.join(); + } catch (InterruptedException e) { + } + if (holder.value != null) { + throw holder.value; + } + } + +} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ThreadStackMatcher.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ThreadStackMatcher.java new file mode 100644 index 000000000..5185f4797 --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ThreadStackMatcher.java @@ -0,0 +1,58 @@ +package org.cryptomator.filesystem.nio; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +class ThreadStackMatcher extends TypeSafeDiagnosingMatcher { + + public static Matcher stackContains(Class type, String methodName) { + return new ThreadStackMatcher(type, methodName); + } + + private final Class type; + private final String methodName; + + private ThreadStackMatcher(Class type, String methodName) { + super(Thread.class); + this.type = type; + this.methodName = methodName; + } + + @Override + public void describeTo(Description description) { + description.appendText("a thread executing ") // + .appendValue(type) // + .appendText(".") // + .appendText(methodName); + } + + @Override + protected boolean matchesSafely(Thread item, Description mismatchDescription) { + StackTraceElement[] stack = item.getStackTrace(); + if (!containsNeededStackTraceElement(stack)) { + mismatchDescription.appendText("a thread not executing ") // + .appendValue(type) // + .appendText(".") // + .appendText(methodName); + return false; + } else { + return true; + } + } + + private boolean containsNeededStackTraceElement(StackTraceElement[] stack) { + for (StackTraceElement stackTraceElement : stack) { + if (isNeededStackTraceElement(stackTraceElement)) { + return true; + } + } + return false; + } + + private boolean isNeededStackTraceElement(StackTraceElement stackTraceElement) { + return type.getName().equals(stackTraceElement.getClassName()) // + && methodName.equals(stackTraceElement.getMethodName()); + } + +} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WritableNioFileTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WritableNioFileTest.java index 023ac41b5..44dcf3ee2 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WritableNioFileTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WritableNioFileTest.java @@ -3,6 +3,8 @@ package org.cryptomator.filesystem.nio; import static org.cryptomator.filesystem.nio.OpenMode.WRITE; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -158,6 +160,88 @@ public class WritableNioFileTest { assertThat(result, is(resultOfWriteFully)); } + @Test + public void testTruncateFailsIfNotOpen() { + WritableNioFile inTest = new WritableNioFile(file); + inTest.close(); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("already closed"); + + inTest.truncate(); + } + + @Test + public void testTruncateInvokesChannelsOpenWithModeWriteIfInvokedForTheFirstTimeBeforeInvokingTruncate() { + WritableNioFile inTest = new WritableNioFile(file); + + inTest.truncate(); + + InOrder inOrder = inOrder(channel); + inOrder.verify(channel).open(WRITE); + inOrder.verify(channel).truncate(anyInt()); + } + + @Test + public void testTruncateChannelsTruncateWithZeroAsParameter() { + WritableNioFile inTest = new WritableNioFile(file); + + inTest.truncate(); + + verify(channel).truncate(0); + } + + @Test + public void testCloseClosesChannelIfOpenedAndUnlocksWriteLock() { + WritableNioFile inTest = new WritableNioFile(file); + inTest.truncate(); + + inTest.close(); + + InOrder inOrder = inOrder(channel, writeLock); + inOrder.verify(channel).close(); + inOrder.verify(writeLock).unlock(); + } + + @Test + public void testCloseDoesNothingOnSecondInvocation() { + WritableNioFile inTest = new WritableNioFile(file); + inTest.truncate(); + + inTest.close(); + inTest.close(); + + InOrder inOrder = inOrder(channel, writeLock); + inOrder.verify(channel).close(); + inOrder.verify(writeLock).unlock(); + } + + @Test + public void testCloseOnlyUnlocksWriteLockIfChannelNotOpen() { + WritableNioFile inTest = new WritableNioFile(file); + + inTest.close(); + + verify(writeLock).unlock(); + verifyZeroInteractions(channel); + } + + @Test + public void testCloseUnlocksWriteLockEvenIfCloseThrowsException() { + WritableNioFile inTest = new WritableNioFile(file); + inTest.truncate(); + String message = "exceptionMessage"; + doThrow(new RuntimeException(message)).when(channel).close(); + + thrown.expectMessage(message); + + try { + inTest.close(); + } finally { + verify(writeLock).unlock(); + } + } + @Test public void testToString() { String fileToString = file.toString();