Просмотр исходного кода

Added FolderCopyToTests

* Tests for CopyTo Operation
* Changes to Matchers and Test-Utilities
* Changes to make things work
* TODO: One test still not working due to access to channel by multiple
threads
Markus Kreusch 9 лет назад
Родитель
Сommit
6479573346

+ 21 - 0
main/commons-test/src/main/java/org/cryptomator/common/test/matcher/OptionalMatcher.java

@@ -37,4 +37,25 @@ public class OptionalMatcher {
 		};
 	}
 
+	public static <T> Matcher<Optional<T>> emptyOptional() {
+		return new TypeSafeDiagnosingMatcher<Optional<T>>(Optional.class) {
+			@Override
+			public void describeTo(Description description) {
+				description.appendText("an empty Optional");
+			}
+
+			@Override
+			protected boolean matchesSafely(Optional<T> item, Description mismatchDescription) {
+				if (item.isPresent()) {
+					mismatchDescription.appendText("a present Optional of ").appendValue(item.get());
+					return false;
+				} else {
+					return true;
+				}
+
+			}
+
+		};
+	}
+
 }

+ 8 - 0
main/commons/src/main/java/org/cryptomator/common/RunnableThrowingException.java

@@ -0,0 +1,8 @@
+package org.cryptomator.common;
+
+@FunctionalInterface
+public interface RunnableThrowingException<T extends Exception> {
+
+	void run() throws T;
+
+}

+ 146 - 0
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<FileSystemFactory> FILE_SYSTEM_FACTORIES = new FileSystemFactories();
+
+	@DataPoints
+	public static final Iterable<WayToObtainAFolder> WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder();
+
+	@DataPoints
+	public static final Iterable<WayToObtainAFile> 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));
+	}
+
+}

+ 15 - 11
main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java

@@ -17,17 +17,17 @@ class WaysToObtainAFile implements Iterable<WayToObtainAFile> {
 	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<WayToObtainAFile> {
 	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<WayToObtainAFile> {
 	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<WayToObtainAFile> {
 
 	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;

+ 2 - 2
main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java

@@ -100,14 +100,14 @@ class WaysToObtainAFolder implements Iterable<WayToObtainAFolder> {
 
 	}
 
-	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;

+ 24 - 0
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<File> hasContent(byte[] content) {
+		return new PropertyMatcher<>(File.class, readFileContentWithLength(content.length), "content", is(content));
+	}
+
+	private static Function<File, byte[]> 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;
+			}
+		};
+	}
+
 }

+ 25 - 0
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;
+	}
+
+}

+ 18 - 39
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<Thread, Thread> 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");
+		}
+	}
+
 }

+ 8 - 2
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() {

+ 11 - 1
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();

+ 8 - 71
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);
 		}

+ 31 - 13
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).close();
+	}
+
+	@Test
+	public void testCloseChannelIfOpenDoesNotInvokeChannelsCloseIfNotOpenedEarlier() {
 		inTest.closeChannelIfOpened();
 
-		verify(channel).closeIfOpen();
+		verifyZeroInteractions(channel);
 	}
 
 	@Test