diff --git a/main/commons-test/src/main/java/org/cryptomator/common/test/matcher/OptionalMatcher.java b/main/commons-test/src/main/java/org/cryptomator/common/test/matcher/OptionalMatcher.java index 9d1b2c7de..8d1b96077 100644 --- a/main/commons-test/src/main/java/org/cryptomator/common/test/matcher/OptionalMatcher.java +++ b/main/commons-test/src/main/java/org/cryptomator/common/test/matcher/OptionalMatcher.java @@ -37,4 +37,25 @@ public class OptionalMatcher { }; } + public static Matcher> emptyOptional() { + return new TypeSafeDiagnosingMatcher>(Optional.class) { + @Override + public void describeTo(Description description) { + description.appendText("an empty Optional"); + } + + @Override + protected boolean matchesSafely(Optional item, Description mismatchDescription) { + if (item.isPresent()) { + mismatchDescription.appendText("a present Optional of ").appendValue(item.get()); + return false; + } else { + return true; + } + + } + + }; + } + } diff --git a/main/commons/src/main/java/org/cryptomator/common/RunnableThrowingException.java b/main/commons/src/main/java/org/cryptomator/common/RunnableThrowingException.java new file mode 100644 index 000000000..ec453b886 --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/RunnableThrowingException.java @@ -0,0 +1,8 @@ +package org.cryptomator.common; + +@FunctionalInterface +public interface RunnableThrowingException { + + void run() throws T; + +} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderCopyToTests.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderCopyToTests.java new file mode 100644 index 000000000..cf71b4c59 --- /dev/null +++ b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderCopyToTests.java @@ -0,0 +1,146 @@ +package org.cryptomator.filesystem.invariants; + +import static org.cryptomator.filesystem.invariants.matchers.NodeMatchers.hasContent; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeThat; + +import java.io.UncheckedIOException; + +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; +import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile; +import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +@RunWith(Theories.class) +public class FolderCopyToTests { + + private static final String SOURCE_FOLDER_NAME = "sourceFolderName"; + private static final String TARGET_FOLDER_NAME = "targetFolderName"; + + @DataPoints + public static final Iterable FILE_SYSTEM_FACTORIES = new FileSystemFactories(); + + @DataPoints + public static final Iterable WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder(); + + @DataPoints + public static final Iterable WAYS_TO_OBTAIN_A_FILE = new WaysToObtainAFile(); + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Theory + public void testCopyAnExistingFolderToANonExistingFolderCreatesTheTargetFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAnExistingFolder, WayToObtainAFolder wayToObtainANonExistingFolder) { + assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); + assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); + + FileSystem fileSystem = fileSystemFactory.create(); + + Folder source = wayToObtainAnExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); + Folder target = wayToObtainANonExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); + + source.copyTo(target); + + assertThat(source.exists(), is(true)); + assertThat(target.exists(), is(true)); + } + + @Theory + public void testCopyAnExistingFolderToANonExistingFolderWhooseParentDoesNotExistCreatesTheParentAndTargetFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAnExistingFolder, + WayToObtainAFolder wayToObtainANonExistingFolder) { + assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); + assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); + + FileSystem fileSystem = fileSystemFactory.create(); + + Folder source = wayToObtainAnExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); + Folder parentOfTarget = wayToObtainANonExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); + Folder target = wayToObtainANonExistingFolder.folderWithName(parentOfTarget, TARGET_FOLDER_NAME); + + source.copyTo(target); + + assertThat(source.exists(), is(true)); + assertThat(parentOfTarget.exists(), is(true)); + assertThat(target.exists(), is(true)); + } + + @Theory + public void testCopyANonExistingFolderFails(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainANonExistingFolder) { + assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); + + FileSystem fileSystem = fileSystemFactory.create(); + + Folder source = wayToObtainANonExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); + Folder target = wayToObtainANonExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); + + thrown.expect(UncheckedIOException.class); + + source.copyTo(target); + } + + @Theory + public void testCopyAnExistingFolderToAnExistingFolderSucceeds(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAnExistingFolder) { + assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); + + FileSystem fileSystem = fileSystemFactory.create(); + + Folder source = wayToObtainAnExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); + Folder target = wayToObtainAnExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); + + source.copyTo(target); + + assertThat(source.exists(), is(true)); + assertThat(target.exists(), is(true)); + } + + @Theory + @Ignore + // FIXME not working yet due to concurrent access to and interruption of channel + public void testCopyAFolderWithChildrenCopiesChildrenRecursive(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAnExistingFolder, WayToObtainAFile wayToObtainAnExisitingFile, + WayToObtainAFolder wayToObtainANonExistingFolder) { + + assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); + assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); + assumeThat(wayToObtainAnExisitingFile.returnedFilesExist(), is(true)); + + String childFolderName = "childFolderName"; + String childFileName = "childFileName"; + String childFoldersChildFileName = "childFoldersChildFile"; + byte[] content1 = {23, 127, 3, 10, 101}; + byte[] content2 = {43, 22, 103, 67, 51, 5, 15, 93, 33}; + FileSystem fileSystem = fileSystemFactory.create(); + + Folder source = wayToObtainAnExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); + Folder childFolder = wayToObtainAnExistingFolder.folderWithName(source, childFolderName); + File childFile = wayToObtainAnExisitingFile.fileWithNameAndContent(source, childFileName, content1); + File childFoldersChildFile = wayToObtainAnExisitingFile.fileWithNameAndContent(childFolder, childFoldersChildFileName, content2); + + Folder target = wayToObtainANonExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); + Folder targetChildFolder = target.folder(childFolderName); + File targetChildFile = target.file(childFileName); + File targetChildFoldersChildFile = targetChildFolder.file(childFoldersChildFileName); + + source.copyTo(target); + + assertThat(source.exists(), is(true)); + assertThat(childFolder.exists(), is(true)); + assertThat(childFile.exists(), is(true)); + assertThat(childFoldersChildFile.exists(), is(true)); + + assertThat(target.exists(), is(true)); + assertThat(targetChildFolder.exists(), is(true)); + assertThat(targetChildFile, hasContent(content1)); + assertThat(targetChildFoldersChildFile, hasContent(content2)); + } + +} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java index ec150293b..0d34c4f34 100644 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java +++ b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java @@ -17,17 +17,17 @@ class WaysToObtainAFile implements Iterable { public WaysToObtainAFile() { addNonExisting("invoke file", this::invokeFile); - addExisting("create file by writing to it", this::createFileUsingTouch); + addExisting("create file by writing to it", this::createFileByWritingToIt); } - private File invokeFile(Folder parent, String name) { + private File invokeFile(Folder parent, String name, byte[] content) { return parent.file(name); } - private File createFileUsingTouch(Folder parent, String name) { + private File createFileByWritingToIt(Folder parent, String name, byte[] content) { File result = parent.file(name); try (WritableFile writable = result.openWritable()) { - writable.write(ByteBuffer.wrap(new byte[] {1})); + writable.write(ByteBuffer.wrap(content)); } return result; } @@ -35,8 +35,8 @@ class WaysToObtainAFile implements Iterable { private void addExisting(String name, WayToObtainAFileThatExists factory) { values.add(new WayToObtainAFileThatExists() { @Override - public File fileWithName(Folder parent, String name) { - return factory.fileWithName(parent, name); + public File fileWithNameAndContent(Folder parent, String name, byte[] content) { + return factory.fileWithNameAndContent(parent, name, content); } @Override @@ -49,8 +49,8 @@ class WaysToObtainAFile implements Iterable { private void addNonExisting(String name, WayToObtainAFileThatDoesntExist factory) { values.add(new WayToObtainAFileThatDoesntExist() { @Override - public File fileWithName(Folder parent, String name) { - return factory.fileWithName(parent, name); + public File fileWithNameAndContent(Folder parent, String name, byte[] content) { + return factory.fileWithNameAndContent(parent, name, content); } @Override @@ -62,20 +62,24 @@ class WaysToObtainAFile implements Iterable { public interface WayToObtainAFile { - File fileWithName(Folder parent, String name); + default File fileWithName(Folder parent, String name) { + return fileWithNameAndContent(parent, name, new byte[0]); + } + + File fileWithNameAndContent(Folder parent, String name, byte[] content); boolean returnedFilesExist(); } - public interface WayToObtainAFileThatExists extends WayToObtainAFile { + private interface WayToObtainAFileThatExists extends WayToObtainAFile { @Override default boolean returnedFilesExist() { return true; } } - public interface WayToObtainAFileThatDoesntExist extends WayToObtainAFile { + private interface WayToObtainAFileThatDoesntExist extends WayToObtainAFile { @Override default boolean returnedFilesExist() { return false; diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java index 17ec0f9ee..ac16b8f9f 100644 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java +++ b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java @@ -100,14 +100,14 @@ class WaysToObtainAFolder implements Iterable { } - public interface WayToObtainAFolderThatExists extends WayToObtainAFolder { + private interface WayToObtainAFolderThatExists extends WayToObtainAFolder { @Override default boolean returnedFoldersExist() { return true; } } - public interface WayToObtainAFolderThatDoesntExists extends WayToObtainAFolder { + private interface WayToObtainAFolderThatDoesntExists extends WayToObtainAFolder { @Override default boolean returnedFoldersExist() { return false; diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/NodeMatchers.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/NodeMatchers.java index 852d9d404..922f7f970 100644 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/NodeMatchers.java +++ b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/NodeMatchers.java @@ -2,9 +2,13 @@ package org.cryptomator.filesystem.invariants.matchers; import static org.hamcrest.CoreMatchers.is; +import java.nio.ByteBuffer; +import java.util.function.Function; + import org.cryptomator.common.test.matcher.PropertyMatcher; import org.cryptomator.filesystem.File; import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.ReadableFile; import org.hamcrest.Matcher; public class NodeMatchers { @@ -17,4 +21,24 @@ public class NodeMatchers { return new PropertyMatcher<>(File.class, File::name, "name", is(name)); } + public static Matcher hasContent(byte[] content) { + return new PropertyMatcher<>(File.class, readFileContentWithLength(content.length), "content", is(content)); + } + + private static Function readFileContentWithLength(int expectedLength) { + return file -> { + try (ReadableFile readable = file.openReadable()) { + ByteBuffer buffer = ByteBuffer.allocate(expectedLength + 1); + readable.read(buffer); + if (buffer.remaining() == 0) { + throw new IllegalStateException("File content > expectedLength of " + expectedLength); + } + buffer.flip(); + byte[] result = new byte[buffer.limit()]; + buffer.get(result); + return result; + } + }; + } + } diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenCloseCounter.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenCloseCounter.java new file mode 100644 index 000000000..d0998768c --- /dev/null +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenCloseCounter.java @@ -0,0 +1,25 @@ +package org.cryptomator.filesystem.nio; + +import java.util.concurrent.atomic.AtomicInteger; + +class OpenCloseCounter { + + private final AtomicInteger counter = new AtomicInteger(); + + public void countOpen() { + counter.incrementAndGet(); + } + + public void countClose() { + int openCount = counter.decrementAndGet(); + if (openCount < 0) { + counter.incrementAndGet(); + throw new IllegalStateException("Close without corresponding open"); + } + } + + public boolean isOpen() { + return counter.get() > 0; + } + +} 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 17516f146..be89284e6 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 @@ -9,8 +9,6 @@ import java.nio.channels.FileChannel; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -20,8 +18,8 @@ class SharedFileChannel { private final Path path; private final NioAccess nioAccess; + private final OpenCloseCounter openCloseCounter; - private Map openedBy = new ConcurrentHashMap<>(); private Lock lock = new ReentrantLock(); private FileChannel delegate; @@ -29,66 +27,41 @@ class SharedFileChannel { public SharedFileChannel(Path path, NioAccess nioAccess) { this.path = path; this.nioAccess = nioAccess; - } - - public void openIfClosed(OpenMode mode) { - if (!openedBy.containsKey(Thread.currentThread())) { - open(mode); - } + this.openCloseCounter = new OpenCloseCounter(); } public void open(OpenMode mode) { doLocked(() -> { - Thread thread = Thread.currentThread(); boolean failed = true; try { - if (openedBy.put(thread, thread) != null) { - throw new IllegalStateException("SharedFileChannel already open for current thread"); - } + openCloseCounter.countOpen(); if (delegate == null) { createChannel(mode); } failed = false; } finally { if (failed) { - openedBy.remove(thread); + openCloseCounter.countClose(); } } }); } - public void closeIfOpen() { - if (openedBy.containsKey(Thread.currentThread())) { - internalClose(); - } - } - public void close() { - assertOpenedByCurrentThread(); - internalClose(); - } - - private void internalClose() { doLocked(() -> { - openedBy.remove(Thread.currentThread()); + openCloseCounter.countClose(); try { delegate.force(true); } catch (IOException e) { throw new UncheckedIOException(e); } finally { - if (openedBy.isEmpty()) { + if (!openCloseCounter.isOpen()) { closeChannel(); } } }); } - private void assertOpenedByCurrentThread() { - if (!openedBy.containsKey(Thread.currentThread())) { - throw new IllegalStateException("SharedFileChannel closed for current thread"); - } - } - private void createChannel(OpenMode mode) { try { if (nioAccess.isDirectory(path)) { @@ -116,7 +89,7 @@ class SharedFileChannel { } public int readFully(long position, ByteBuffer target) { - assertOpenedByCurrentThread(); + assertOpen(); try { return tryReadFully(position, target); } catch (IOException e) { @@ -140,7 +113,7 @@ class SharedFileChannel { } public void truncate(int i) { - assertOpenedByCurrentThread(); + assertOpen(); try { delegate.truncate(i); } catch (IOException e) { @@ -149,7 +122,7 @@ class SharedFileChannel { } public long size() { - assertOpenedByCurrentThread(); + assertOpen(); try { return delegate.size(); } catch (IOException e) { @@ -158,8 +131,8 @@ class SharedFileChannel { } public long transferTo(long position, long count, SharedFileChannel targetChannel, long targetPosition) { - assertOpenedByCurrentThread(); - targetChannel.assertOpenedByCurrentThread(); + assertOpen(); + targetChannel.assertOpen(); if (count < 0) { throw new IllegalArgumentException("Count must not be negative"); } @@ -187,7 +160,7 @@ class SharedFileChannel { } public int writeFully(long position, ByteBuffer source) { - assertOpenedByCurrentThread(); + assertOpen(); try { return tryWriteFully(position, source); } catch (IOException e) { @@ -204,4 +177,10 @@ class SharedFileChannel { return count; } + private void assertOpen() { + if (!openCloseCounter.isOpen()) { + throw new IllegalStateException("SharedFileChannel is not open"); + } + } + } diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WritableNioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WritableNioFile.java index 9b1968b6b..d0415f582 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WritableNioFile.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WritableNioFile.java @@ -24,6 +24,7 @@ class WritableNioFile implements WritableFile { private Runnable afterCloseCallback; private boolean open = true; + private boolean channelOpened = false; private long position = 0; public WritableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback, NioAccess nioAccess) { @@ -153,11 +154,16 @@ class WritableNioFile implements WritableFile { } void ensureChannelIsOpened() { - channel.openIfClosed(WRITE); + if (!channelOpened) { + channel.open(WRITE); + channelOpened = true; + } } void closeChannelIfOpened() { - channel.closeIfOpen(); + if (channelOpened) { + channel.close(); + } } FileSystem fileSystem() { diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java index cbe579275..75f2282ff 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java @@ -1,6 +1,7 @@ package org.cryptomator.filesystem.nio; import static java.lang.String.format; +import static org.cryptomator.common.test.matcher.OptionalMatcher.emptyOptional; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; @@ -299,7 +300,7 @@ public class NioFileTest { @Test public void testCreationTimeDelegatesToNioAccessCreationTime() throws IOException { - Instant exectedResult = Instant.parse("2016-01-08T19:49:00Z"); + Instant exectedResult = Instant.parse("1970-01-02T00:00:00Z"); when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(exectedResult)); when(nioAccess.exists(path)).thenReturn(true); when(nioAccess.isRegularFile(path)).thenReturn(true); @@ -309,6 +310,15 @@ public class NioFileTest { assertThat(result, is(exectedResult)); } + @Test + public void testCreationTimeReturnsEmptyOptionalIfNioAccessCreationTimeReturnsValueBeforeJanuaryTheSecondNineteenhundredSeventy() throws IOException { + when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(Instant.parse("1970-01-01T23:59:59Z"))); + when(nioAccess.exists(path)).thenReturn(true); + when(nioAccess.isRegularFile(path)).thenReturn(true); + + assertThat(inTest.creationTime(), is(emptyOptional())); + } + @Test public void testCreationTimeWrapsIOExceptionFromNioAccessCreationTimeInUncheckedIOException() throws IOException { IOException exceptionFromCreationTime = new IOException(); 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 index 6a0d9e2c4..49c173ba1 100644 --- 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 @@ -85,16 +85,6 @@ public class SharedFileChannelTest { verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); } - @Test - public void testOpenIfClosedOpensAChannelIfChannelIsNotOpenOpenModeIsReadAndFileExists() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(true); - - inTest.openIfClosed(OpenMode.READ); - - verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - } - @Test public void testOpenOpensAChannelIfOpenModeIsWriteAndFileExists() throws IOException { when(nioAccess.isDirectory(path)).thenReturn(false); @@ -128,41 +118,6 @@ public class SharedFileChannelTest { inTest.open(OpenMode.WRITE); } - @Test - public void testOpenFailsIfInvokedTwiceBeforeClose() { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - - inTest.open(OpenMode.WRITE); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("already open for current thread"); - - inTest.open(OpenMode.WRITE); - } - - @Test - public void testOpenIfClosedDoesDoNothingIfInvokedOnOpenChannel() { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - - inTest.open(OpenMode.WRITE); - - inTest.openIfClosed(OpenMode.READ); - } - - @Test - public void testOpenWorksIfInvokedTwiceAfterClose() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(mock(FileChannel.class)); - - inTest.open(OpenMode.WRITE); - inTest.close(); - - inTest.open(OpenMode.WRITE); - } - @Test public void testOpenDoesNotOpenChannelTwiceIfInvokedTwiceByDifferentThreads() throws IOException { when(nioAccess.isDirectory(path)).thenReturn(false); @@ -182,16 +137,11 @@ public class SharedFileChannelTest { @Test public void testCloseIfNotOpenFails() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("closed for current thread"); + thrown.expectMessage("Close without corresponding open"); inTest.close(); } - @Test - public void testCloseIfOpenDoesNothingIfNotOpen() { - inTest.closeIfOpen(); - } - @Test public void testCloseIfClosedFails() throws IOException { when(nioAccess.isDirectory(path)).thenReturn(false); @@ -201,7 +151,7 @@ public class SharedFileChannelTest { inTest.close(); thrown.expect(IllegalStateException.class); - thrown.expectMessage("closed for current thread"); + thrown.expectMessage("Close without corresponding open"); inTest.close(); } @@ -257,19 +207,6 @@ public class SharedFileChannelTest { inTest.close(); } - @Test - public void testCloseIfOpenClosesChannelIfOpen() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - FileChannel channel = mock(FileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - - inTest.closeIfOpen(); - - verify(nioAccess).close(channel); - } - @Test public void testCloseDoesNotCloseChannelIfOpenedTwice() throws IOException { when(nioAccess.isDirectory(path)).thenReturn(false); @@ -686,7 +623,7 @@ public class SharedFileChannelTest { ByteBuffer irrelevant = null; thrown.expect(IllegalStateException.class); - thrown.expectMessage("closed for current thread"); + thrown.expectMessage("SharedFileChannel is not open"); inTest.readFully(0, irrelevant); } @@ -694,7 +631,7 @@ public class SharedFileChannelTest { @Test public void testTruncateFailsIfNotOpen() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("closed for current thread"); + thrown.expectMessage("SharedFileChannel is not open"); inTest.truncate(0); } @@ -702,7 +639,7 @@ public class SharedFileChannelTest { @Test public void testSizeFailsIfNotOpen() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("closed for current thread"); + thrown.expectMessage("SharedFileChannel is not open"); inTest.size(); } @@ -712,7 +649,7 @@ public class SharedFileChannelTest { SharedFileChannel irrelevant = null; thrown.expect(IllegalStateException.class); - thrown.expectMessage("closed for current thread"); + thrown.expectMessage("SharedFileChannel is not open"); inTest.transferTo(0, 0, irrelevant, 0); } @@ -724,7 +661,7 @@ public class SharedFileChannelTest { inTest.open(OpenMode.WRITE); thrown.expect(IllegalStateException.class); - thrown.expectMessage("closed for current thread"); + thrown.expectMessage("SharedFileChannel is not open"); inTest.transferTo(0, 0, targetInTest, 0); } @@ -734,7 +671,7 @@ public class SharedFileChannelTest { ByteBuffer irrelevant = null; thrown.expect(IllegalStateException.class); - thrown.expectMessage("closed for current thread"); + thrown.expectMessage("SharedFileChannel is not open"); inTest.writeFully(0, irrelevant); } 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 6027ef59b..27577e833 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 @@ -89,7 +89,7 @@ public class WritableNioFileTest { inTest.write(irrelevant); InOrder inOrder = inOrder(channel); - inOrder.verify(channel).openIfClosed(WRITE); + inOrder.verify(channel).open(WRITE); inOrder.verify(channel).writeFully(0, irrelevant); } @@ -217,11 +217,12 @@ public class WritableNioFileTest { when(nioAccess.isDirectory(path)).thenReturn(false); when(nioAccess.isDirectory(pathOfTarget)).thenReturn(false); + inTest.ensureChannelIsOpened(); inTest.moveTo(target); InOrder inOrder = inOrder(target, nioAccess, channel, afterCloseCallback); inOrder.verify(target).assertOpen(); - inOrder.verify(channel).closeIfOpen(); + inOrder.verify(channel).close(); inOrder.verify(target).closeChannelIfOpened(); inOrder.verify(nioAccess).move(path, pathOfTarget, REPLACE_EXISTING); inOrder.verify(target).invokeAfterCloseCallback(); @@ -269,7 +270,7 @@ public class WritableNioFileTest { inTest.setLastModified(instant); InOrder inOrder = inOrder(channel, nioAccess); - inOrder.verify(channel).openIfClosed(OpenMode.WRITE); + inOrder.verify(channel).open(OpenMode.WRITE); inOrder.verify(nioAccess).setLastModifiedTime(path, time); } @@ -308,7 +309,7 @@ public class WritableNioFileTest { inTest.setCreationTime(instant); InOrder inOrder = inOrder(nioAccess, channel); - inOrder.verify(channel).openIfClosed(OpenMode.WRITE); + inOrder.verify(channel).open(OpenMode.WRITE); inOrder.verify(nioAccess).setCreationTime(path, FileTime.from(instant)); } @@ -330,10 +331,11 @@ public class WritableNioFileTest { @Test public void testDeleteClosesChannelIfOpenAndDeletesFileAndInvokesAfterCloseCallback() throws IOException { + inTest.ensureChannelIsOpened(); inTest.delete(); InOrder inOrder = inOrder(channel, nioAccess, afterCloseCallback); - inOrder.verify(channel).closeIfOpen(); + inOrder.verify(channel).close(); inOrder.verify(nioAccess).delete(path); inOrder.verify(afterCloseCallback).run(); } @@ -384,7 +386,7 @@ public class WritableNioFileTest { inTest.truncate(); InOrder inOrder = inOrder(channel); - inOrder.verify(channel).openIfClosed(WRITE); + inOrder.verify(channel).open(WRITE); inOrder.verify(channel).truncate(anyInt()); } @@ -406,7 +408,7 @@ public class WritableNioFileTest { inTest.close(); InOrder inOrder = inOrder(channel, afterCloseCallback); - inOrder.verify(channel).closeIfOpen(); + inOrder.verify(channel).close(); inOrder.verify(afterCloseCallback).run(); } @@ -418,7 +420,7 @@ public class WritableNioFileTest { inTest.close(); InOrder inOrder = inOrder(channel, afterCloseCallback); - inOrder.verify(channel).closeIfOpen(); + inOrder.verify(channel).close(); verify(afterCloseCallback).run(); } @@ -426,7 +428,7 @@ public class WritableNioFileTest { public void testCloseInvokesAfterCloseCallbackEvenIfCloseThrowsException() { inTest.truncate(); String message = "exceptionMessage"; - doThrow(new RuntimeException(message)).when(channel).closeIfOpen(); + doThrow(new RuntimeException(message)).when(channel).close(); thrown.expectMessage(message); @@ -522,17 +524,33 @@ public class WritableNioFileTest { } @Test - public void testEnsureChannelIsOpenedInvokesChannelOpenIfClosedWithModeWrite() { + public void testEnsureChannelIsOpenedInvokesChannelOpenWithModeWrite() { inTest.ensureChannelIsOpened(); - verify(channel).openIfClosed(WRITE); + verify(channel).open(WRITE); } @Test - public void testCloseChannelIfOpenInvokesChannelsCloseIfOpen() { + public void testEnsureChannelIsOpenedInvokesChannelOpenWithModeWriteOnlyOnceIfInvokedTwice() { + inTest.ensureChannelIsOpened(); + inTest.ensureChannelIsOpened(); + + verify(channel).open(WRITE); + } + + @Test + public void testCloseChannelIfOpenInvokesChannelsCloseIfOpenedEarlier() { + inTest.ensureChannelIsOpened(); inTest.closeChannelIfOpened(); - verify(channel).closeIfOpen(); + verify(channel).close(); + } + + @Test + public void testCloseChannelIfOpenDoesNotInvokeChannelsCloseIfNotOpenedEarlier() { + inTest.closeChannelIfOpened(); + + verifyZeroInteractions(channel); } @Test