From 801253aa270916d11969516747b16098d7775a26 Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Sun, 3 Jan 2016 20:08:52 +0100 Subject: [PATCH] Tests for filesystem-nio * Split tests into integration and unit tests * Rewritten and completed unit tests ** By introducing a layer around Files.* to allow mocking of NIO-Operations ** And introducing a factory to allow mocking of constructors * Integration tests ignored temporarily ** TODO reduce amount of testcases and enable --- main/commons/pom.xml | 7 + .../java/org/cryptomator/common/Holder.java | 34 + .../org/cryptomator/common/HolderTest.java | 42 + .../java/org/cryptomator/filesystem/Node.java | 13 + .../cryptomator/filesystem/PathResolver.java | 2 +- main/filesystem-nio/pom.xml | 5 + .../nio/DefaultInstanceFactory.java | 35 + .../filesystem/nio/DefaultNioAccess.java | 77 ++ .../filesystem/nio/InstanceFactory.java | 23 + .../cryptomator/filesystem/nio/NioAccess.java | 43 + .../cryptomator/filesystem/nio/NioFile.java | 37 +- .../filesystem/nio/NioFileSystem.java | 2 +- .../cryptomator/filesystem/nio/NioFolder.java | 53 +- .../cryptomator/filesystem/nio/NioNode.java | 24 +- .../filesystem/nio/ReadableNioFile.java | 30 +- .../filesystem/nio/SharedFileChannel.java | 75 +- .../filesystem/nio/WritableNioFile.java | 74 +- .../nio/DefaultInstanceFactoryTest.java | 93 ++ .../filesystem/nio/DefaultNioAccessTest.java | 194 ++++ .../filesystem/nio/FileChannelAdapter.java | 96 ++ .../filesystem/nio/InstanceFactoryTest.java | 18 + .../filesystem/nio/NioAccessTest.java | 18 + .../filesystem/nio/NioFileSystemTest.java | 64 +- .../filesystem/nio/NioFileTest.java | 839 ++++++----------- .../filesystem/nio/NioFolderTest.java | 857 +++++++++--------- .../filesystem/nio/ReadableNioFileTest.java | 121 +-- .../nio/ReflectiveClassMatchers.java | 79 ++ .../filesystem/nio/SharedFileChannelTest.java | 779 ++++++++++++---- .../filesystem/nio/WritableNioFileTest.java | 656 +++++++++----- .../FilesystemSetupUtils.java | 2 +- .../NioFileIntegrationTest.java | 265 ++++++ .../NioFileSystemIntegrationTest.java | 46 + .../NioFolderIntegrationTest.java | 349 +++++++ .../NioNodeMatcher.java | 2 +- .../{ => integrationtests}/PathMatcher.java | 28 +- 35 files changed, 3450 insertions(+), 1632 deletions(-) create mode 100644 main/commons/src/main/java/org/cryptomator/common/Holder.java create mode 100644 main/commons/src/test/java/org/cryptomator/common/HolderTest.java create mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java create mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioAccess.java create mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java create mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioAccess.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultNioAccessTest.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FileChannelAdapter.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/InstanceFactoryTest.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioAccessTest.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReflectiveClassMatchers.java rename main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/{ => integrationtests}/FilesystemSetupUtils.java (97%) create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileIntegrationTest.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileSystemIntegrationTest.java create mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFolderIntegrationTest.java rename main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/{ => integrationtests}/NioNodeMatcher.java (90%) rename main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/{ => integrationtests}/PathMatcher.java (78%) diff --git a/main/commons/pom.xml b/main/commons/pom.xml index b2e8b1bf3..08fb094ed 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -12,5 +12,12 @@ commons Cryptomator common Shared utilities + + + + org.cryptomator + commons-test + + diff --git a/main/commons/src/main/java/org/cryptomator/common/Holder.java b/main/commons/src/main/java/org/cryptomator/common/Holder.java new file mode 100644 index 000000000..f147ea3e6 --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/Holder.java @@ -0,0 +1,34 @@ +package org.cryptomator.common; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class Holder implements Supplier, Consumer { + + private final V initial; + + private V value; + + public Holder(W initial) { + this.initial = initial; + reset(); + } + + public V get() { + return value; + } + + public void set(V value) { + this.value = value; + } + + public void reset() { + set(initial); + } + + @Override + public void accept(V value) { + set(value); + } + +} diff --git a/main/commons/src/test/java/org/cryptomator/common/HolderTest.java b/main/commons/src/test/java/org/cryptomator/common/HolderTest.java new file mode 100644 index 000000000..ad71e866f --- /dev/null +++ b/main/commons/src/test/java/org/cryptomator/common/HolderTest.java @@ -0,0 +1,42 @@ +package org.cryptomator.common; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class HolderTest { + + private static final Object INITIAL = new Object(); + private static final Object VALUE = new Object(); + + private Holder inTest = new Holder<>(INITIAL); + + @Test + public void testInitialValueIsInitial() { + assertThat(inTest.get(), is(INITIAL)); + } + + @Test + public void testSetChangesValue() { + inTest.set(VALUE); + + assertThat(inTest.get(), is(VALUE)); + } + + @Test + public void testAcceptChangesValue() { + inTest.accept(VALUE); + + assertThat(inTest.get(), is(VALUE)); + } + + @Test + public void testResetChangesValueToInitial() { + inTest.set(VALUE); + inTest.reset(); + + assertThat(inTest.get(), is(INITIAL)); + } + +} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java index f86b52c77..8a8cc81fe 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java @@ -33,4 +33,17 @@ public interface Node { Instant lastModified() throws UncheckedIOException; + /** + * @return the {@link FileSystem} this Node belongs to + */ + default FileSystem fileSystem() { + return parent() // + .map(Node::fileSystem) // + .orElseGet(() -> (FileSystem) this); + } + + default boolean belongsToSameFilesystem(Node other) { + return fileSystem() == other.fileSystem(); + } + } diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java index 6c456b2df..d7e0a714b 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java @@ -8,7 +8,7 @@ import java.util.Iterator; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; -public final class PathResolver { +final class PathResolver { private static final String DOT = "."; private static final String DOTDOT = ".."; diff --git a/main/filesystem-nio/pom.xml b/main/filesystem-nio/pom.xml index 51bc7e259..5e3571e5f 100644 --- a/main/filesystem-nio/pom.xml +++ b/main/filesystem-nio/pom.xml @@ -36,6 +36,11 @@ com.google.guava guava + + + org.cryptomator + commons + diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java new file mode 100644 index 000000000..4d1a6d965 --- /dev/null +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java @@ -0,0 +1,35 @@ +package org.cryptomator.filesystem.nio; + +import java.nio.file.Path; +import java.util.Optional; + +import org.cryptomator.filesystem.FileSystem; + +class DefaultInstanceFactory implements InstanceFactory { + + @Override + public NioFolder nioFolder(Optional parent, Path path, NioAccess nioAccess) { + return new NioFolder(parent, path, nioAccess, this); + } + + @Override + public NioFile nioFile(Optional parent, Path path, NioAccess nioAccess) { + return new NioFile(parent, path, nioAccess, this); + } + + @Override + public SharedFileChannel sharedFileChannel(Path path, NioAccess nioAccess) { + return new SharedFileChannel(path, nioAccess); + } + + @Override + public WritableNioFile writableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback, NioAccess nioAccess) { + return new WritableNioFile(fileSystem, path, channel, afterCloseCallback, nioAccess); + } + + @Override + public ReadableNioFile readableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback) { + return new ReadableNioFile(fileSystem, path, channel, afterCloseCallback); + } + +} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioAccess.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioAccess.java new file mode 100644 index 000000000..9e1518e27 --- /dev/null +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioAccess.java @@ -0,0 +1,77 @@ +package org.cryptomator.filesystem.nio; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.CopyOption; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; +import java.util.stream.Stream; + +class DefaultNioAccess implements NioAccess { + + @Override + public FileChannel open(Path path, OpenOption... options) throws IOException { + return FileChannel.open(path, options); + } + + @Override + public boolean isRegularFile(Path path, LinkOption... options) { + return Files.isRegularFile(path, options); + } + + @Override + public boolean exists(Path path, LinkOption... options) { + return Files.exists(path, options); + } + + @Override + public boolean isDirectory(Path path, LinkOption... options) { + return Files.isDirectory(path, options); + } + + @Override + public Stream list(Path dir) throws IOException { + return Files.list(dir); + } + + @Override + public void createDirectories(Path dir, FileAttribute... attrs) throws IOException { + Files.createDirectories(dir, attrs); + } + + @Override + public FileTime getLastModifiedTime(Path path, LinkOption... options) throws IOException { + return Files.getLastModifiedTime(path, options); + } + + @Override + public void delete(Path path) throws IOException { + Files.delete(path); + } + + @Override + public void close(FileChannel channel) throws IOException { + channel.close(); + } + + @Override + public void move(Path source, Path target, CopyOption... options) throws IOException { + Files.move(source, target, options); + } + + @Override + public void setLastModifiedTime(Path path, FileTime time) throws IOException { + Files.setLastModifiedTime(path, time); + } + + @Override + public String separator() { + return FileSystems.getDefault().getSeparator(); + } + +} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java new file mode 100644 index 000000000..461e507e1 --- /dev/null +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java @@ -0,0 +1,23 @@ +package org.cryptomator.filesystem.nio; + +import java.nio.file.Path; +import java.util.Optional; + +import org.cryptomator.common.Holder; +import org.cryptomator.filesystem.FileSystem; + +interface InstanceFactory { + + public static final Holder DEFAULT = new Holder<>(new DefaultInstanceFactory()); + + NioFolder nioFolder(Optional parent, Path path, NioAccess nioAccess); + + NioFile nioFile(Optional parent, Path path, NioAccess nioAccess); + + SharedFileChannel sharedFileChannel(Path path, NioAccess nioAccess); + + WritableNioFile writableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback, NioAccess nioAccess); + + ReadableNioFile readableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback); + +} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioAccess.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioAccess.java new file mode 100644 index 000000000..e8cb7004f --- /dev/null +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioAccess.java @@ -0,0 +1,43 @@ +package org.cryptomator.filesystem.nio; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.CopyOption; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; +import java.util.stream.Stream; + +import org.cryptomator.common.Holder; + +interface NioAccess { + + public static final Holder DEFAULT = new Holder<>(new DefaultNioAccess()); + + FileChannel open(Path path, OpenOption... options) throws IOException; + + boolean isRegularFile(Path path, LinkOption... options); + + boolean exists(Path path, LinkOption... options); + + boolean isDirectory(Path childPath, LinkOption... options); + + Stream list(Path dir) throws IOException; + + void createDirectories(Path dir, FileAttribute... attrs) throws IOException; + + FileTime getLastModifiedTime(Path path, LinkOption... options) throws IOException; + + void delete(Path path) throws IOException; + + void close(FileChannel channel) throws IOException; + + void move(Path source, Path target, CopyOption... options) throws IOException; + + void setLastModifiedTime(Path path, FileTime time) throws IOException; + + String separator(); + +} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java index 2b3342455..195e6e0bf 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java @@ -4,11 +4,9 @@ import static java.lang.String.format; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.util.Optional; -import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.cryptomator.filesystem.File; @@ -20,17 +18,9 @@ class NioFile extends NioNode implements File { private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); private SharedFileChannel sharedChannel; - public NioFile(Optional parent, Path path) { - super(parent, path); - sharedChannel = new SharedFileChannel(path); - } - - SharedFileChannel channel() { - return sharedChannel; - } - - public ReadWriteLock lock() { - return lock; + public NioFile(Optional parent, Path eventuallyNonAbsolutePath, NioAccess nioAccess, InstanceFactory instanceFactory) { + super(parent, eventuallyNonAbsolutePath, nioAccess, instanceFactory); + sharedChannel = instanceFactory.sharedFileChannel(path, nioAccess); } @Override @@ -42,7 +32,11 @@ class NioFile extends NioNode implements File { throw new IllegalStateException("Current thread is already reading this file"); } lock.readLock().lock(); - return new ReadableNioFile(this); + return instanceFactory.readableNioFile(fileSystem(), path, sharedChannel, this::unlockReadLock); + } + + private void unlockReadLock() { + lock.readLock().unlock(); } @Override @@ -54,17 +48,21 @@ class NioFile extends NioNode implements File { throw new IllegalStateException("Current thread is currently reading this file"); } lock.writeLock().lock(); - return new WritableNioFile(this); + return instanceFactory.writableNioFile(fileSystem(), path, sharedChannel, this::unlockWriteLock, nioAccess); + } + + private void unlockWriteLock() { + lock.writeLock().unlock(); } @Override public boolean exists() throws UncheckedIOException { - return Files.isRegularFile(path); + return nioAccess.isRegularFile(path); } @Override public Instant lastModified() throws UncheckedIOException { - if (Files.exists(path) && !exists()) { + if (nioAccess.exists(path) && !exists()) { throw new UncheckedIOException(new IOException(format("%s is a folder", path))); } return super.lastModified(); @@ -73,7 +71,6 @@ class NioFile extends NioNode implements File { @Override public int compareTo(File o) { if (belongsToSameFilesystem(o)) { - assert o instanceof NioNode; return path.compareTo(((NioFile) o).path); } else { throw new IllegalArgumentException("Can not mix File objects from different file systems"); @@ -85,8 +82,4 @@ class NioFile extends NioNode implements File { return format("NioFile(%s)", path); } - Path path() { - return path; - } - } diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java index 97f045ed4..827c75ec5 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java @@ -12,7 +12,7 @@ public class NioFileSystem extends NioFolder implements FileSystem { } private NioFileSystem(Path root) { - super(Optional.empty(), root); + super(Optional.empty(), root, NioAccess.DEFAULT.get(), InstanceFactory.DEFAULT.get()); create(); } diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java index 868c468e8..bd83bf17b 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java @@ -1,11 +1,9 @@ package org.cryptomator.filesystem.nio; import static java.lang.String.format; -import static org.cryptomator.filesystem.FileSystemVisitor.fileSystemVisitor; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.util.Optional; @@ -21,21 +19,21 @@ class NioFolder extends NioNode implements Folder { private final WeakValuedCache folders = WeakValuedCache.usingLoader(this::folderFromPath); private final WeakValuedCache files = WeakValuedCache.usingLoader(this::fileFromPath); - public NioFolder(Optional parent, Path path) { - super(parent, path); + public NioFolder(Optional parent, Path path, NioAccess nioAccess, InstanceFactory instanceFactory) { + super(parent, path, nioAccess, instanceFactory); } @Override public Stream children() throws UncheckedIOException { try { - return Files.list(path).map(this::childPathToNode); + return nioAccess.list(path).map(this::childPathToNode); } catch (IOException e) { throw new UncheckedIOException(e); } } private NioNode childPathToNode(Path childPath) { - if (Files.isDirectory(childPath)) { + if (nioAccess.isDirectory(childPath)) { return folders.get(childPath); } else { return files.get(childPath); @@ -43,27 +41,35 @@ class NioFolder extends NioNode implements Folder { } private NioFile fileFromPath(Path path) { - return new NioFile(Optional.of(this), path); + return instanceFactory.nioFile(Optional.of(this), path, nioAccess); } private NioFolder folderFromPath(Path path) { - return new NioFolder(Optional.of(this), path); + return instanceFactory.nioFolder(Optional.of(this), path, nioAccess); } @Override public File file(String name) throws UncheckedIOException { + assertDoesNotContainsSeparator(name); return files.get(path.resolve(name)); } @Override public Folder folder(String name) throws UncheckedIOException { + assertDoesNotContainsSeparator(name); return folders.get(path.resolve(name)); } + private void assertDoesNotContainsSeparator(String name) { + if (name.contains(nioAccess.separator())) { + throw new IllegalArgumentException(format("Name must not contain file system separator (name: %s, separator: %s)", name, nioAccess.separator())); + } + } + @Override public void create() throws UncheckedIOException { try { - Files.createDirectories(path); + nioAccess.createDirectories(path); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -71,7 +77,7 @@ class NioFolder extends NioNode implements Folder { @Override public Instant lastModified() throws UncheckedIOException { - if (Files.exists(path) && !Files.isDirectory(path)) { + if (nioAccess.exists(path) && !nioAccess.isDirectory(path)) { throw new UncheckedIOException(new IOException(format("%s is a file", path))); } return super.lastModified(); @@ -79,7 +85,7 @@ class NioFolder extends NioNode implements Folder { @Override public boolean exists() throws UncheckedIOException { - return Files.isDirectory(path); + return nioAccess.isDirectory(path); } @Override @@ -95,12 +101,16 @@ class NioFolder extends NioNode implements Folder { try { target.delete(); target.parent().ifPresent(folder -> folder.create()); - Files.move(path, target.path); + nioAccess.move(path(), target.path()); } catch (IOException e) { throw new UncheckedIOException(e); } } + Path path() { + return path; + } + @Override public String toString() { return format("NioFolder(%s)", path); @@ -111,10 +121,13 @@ class NioFolder extends NioNode implements Folder { if (!exists()) { return; } - fileSystemVisitor() // - .forEachFile(NioFolder::deleteFile) // - .afterFolder(NioFolder::deleteEmptyFolder) // - .visit(this); + folders().forEach(Folder::delete); + files().forEach(NioFolder::deleteFile); + try { + nioAccess.delete(path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } private static final void deleteFile(File file) { @@ -123,12 +136,4 @@ class NioFolder extends NioNode implements Folder { } } - private static final void deleteEmptyFolder(Folder folder) { - try { - Files.delete(((NioFolder) folder).path); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java index 174e73d1b..9fb0a972a 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java @@ -2,7 +2,6 @@ package org.cryptomator.filesystem.nio; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.util.Optional; @@ -15,25 +14,14 @@ abstract class NioNode implements Node { protected final Optional parent; protected final Path path; - private NioFileSystem fileSystem; + protected final NioAccess nioAccess; + protected final InstanceFactory instanceFactory; - public NioNode(Optional parent, Path path) { + public NioNode(Optional parent, Path path, NioAccess nioAccess, InstanceFactory instanceFactoy) { this.path = path.toAbsolutePath(); this.parent = parent; - } - - NioFileSystem fileSystem() { - if (fileSystem == null) { - fileSystem = parent // - .map(NioNode::fileSystem) // - .orElseGet(() -> (NioFileSystem) this); - } - return fileSystem; - } - - boolean belongsToSameFilesystem(Node other) { - return other instanceof NioNode // - && ((NioNode) other).fileSystem() == fileSystem(); + this.nioAccess = nioAccess; + this.instanceFactory = instanceFactoy; } @Override @@ -49,7 +37,7 @@ abstract class NioNode implements Node { @Override public Instant lastModified() throws UncheckedIOException { try { - return Files.getLastModifiedTime(path).toInstant(); + return nioAccess.getLastModifiedTime(path).toInstant(); } catch (IOException e) { throw new UncheckedIOException(e); } 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 408beb646..6b107360c 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 @@ -6,26 +6,34 @@ import static org.cryptomator.filesystem.nio.OpenMode.READ; import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; +import java.nio.file.Path; +import org.cryptomator.filesystem.FileSystem; import org.cryptomator.filesystem.ReadableFile; import org.cryptomator.filesystem.WritableFile; class ReadableNioFile implements ReadableFile { - private final NioFile nioFile; + private final FileSystem fileSystem; + private final Path path; + private final SharedFileChannel channel; + private final Runnable afterCloseCallback; private boolean open = true; private long position = 0; - public ReadableNioFile(NioFile nioFile) { - this.nioFile = nioFile; - nioFile.channel().open(READ); + public ReadableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback) { + this.fileSystem = fileSystem; + this.path = path; + this.channel = channel; + this.afterCloseCallback = afterCloseCallback; + channel.open(READ); } @Override public int read(ByteBuffer target) throws UncheckedIOException { assertOpen(); - int read = nioFile.channel().readFully(position, target); + int read = channel.readFully(position, target); if (read != SharedFileChannel.EOF) { position += read; } @@ -57,7 +65,7 @@ class ReadableNioFile implements ReadableFile { } private boolean belongsToSameFilesystem(WritableFile other) { - return other instanceof WritableNioFile && ((WritableNioFile) other).nioFile().belongsToSameFilesystem(nioFile); + return other instanceof WritableNioFile && ((WritableNioFile) other).fileSystem() == fileSystem; } private void internalCopyTo(WritableNioFile target) { @@ -65,10 +73,10 @@ class ReadableNioFile implements ReadableFile { target.ensureChannelIsOpened(); SharedFileChannel targetChannel = target.channel(); targetChannel.truncate(0); - long size = nioFile.channel().size(); + long size = channel.size(); long transferred = 0; while (transferred < size) { - transferred += nioFile.channel().transferTo(transferred, size - transferred, targetChannel, transferred); + transferred += channel.transferTo(transferred, size - transferred, targetChannel, transferred); } } @@ -79,9 +87,9 @@ class ReadableNioFile implements ReadableFile { } open = false; try { - nioFile.channel().close(); + channel.close(); } finally { - nioFile.lock().readLock().unlock(); + afterCloseCallback.run(); } } @@ -93,7 +101,7 @@ class ReadableNioFile implements ReadableFile { @Override public String toString() { - return format("Readable%s", nioFile); + return format("ReadableNioFile(%s)", path); } } \ No newline at end of file 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 f1bcd2822..304fcc746 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 @@ -1,9 +1,12 @@ package org.cryptomator.filesystem.nio; +import static java.lang.String.format; + import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Map; @@ -16,21 +19,29 @@ class SharedFileChannel { public static final int EOF = -1; private final Path path; + private final NioAccess nioAccess; private Map openedBy = new ConcurrentHashMap<>(); private Lock lock = new ReentrantLock(); private FileChannel delegate; - public SharedFileChannel(Path path) { + public SharedFileChannel(Path path, NioAccess nioAccess) { this.path = path; + this.nioAccess = nioAccess; + } + + public void openIfClosed(OpenMode mode) { + if (!openedBy.containsKey(Thread.currentThread())) { + open(mode); + } } public void open(OpenMode mode) { doLocked(() -> { Thread thread = Thread.currentThread(); if (openedBy.put(thread, thread) != null) { - throw new IllegalStateException("A thread can only open a SharedFileChannel once"); + throw new IllegalStateException("SharedFileChannel already open for current thread"); } if (delegate == null) { createChannel(mode); @@ -38,8 +49,18 @@ class SharedFileChannel { }); } + public void closeIfOpen() { + if (openedBy.containsKey(Thread.currentThread())) { + internalClose(); + } + } + public void close() { assertOpenedByCurrentThread(); + internalClose(); + } + + private void internalClose() { doLocked(() -> { openedBy.remove(Thread.currentThread()); try { @@ -54,14 +75,6 @@ class SharedFileChannel { }); } - /** - * @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"); @@ -70,30 +83,23 @@ class SharedFileChannel { private void createChannel(OpenMode mode) { try { - FileChannel readChannel = null; - if (mode == OpenMode.READ) { - readChannel = FileChannel.open(path, StandardOpenOption.READ); + if (nioAccess.isDirectory(path)) { + throw new IOException(format("%s is a directory", path)); } - delegate = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - closeSilently(readChannel); + if (mode == OpenMode.READ) { + if (!nioAccess.isRegularFile(path)) { + throw new NoSuchFileException(format("%s does not exist", path)); + } + } + delegate = nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); } 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(); + nioAccess.close(delegate); } catch (IOException e) { throw new UncheckedIOException(e); } finally { @@ -146,15 +152,18 @@ class SharedFileChannel { public long transferTo(long position, long count, SharedFileChannel targetChannel, long targetPosition) { assertOpenedByCurrentThread(); targetChannel.assertOpenedByCurrentThread(); + if (count < 0) { + throw new IllegalArgumentException("Count must not be negative"); + } try { - long maxPosition = delegate.size(); - long maxCount = Math.min(count, maxPosition - position); - long remaining = maxCount; + long maxPosition = Math.min(delegate.size(), position + count); + long transferCount = Math.max(0, maxPosition - position); + long remaining = transferCount; targetChannel.delegate.position(targetPosition); while (remaining > 0) { remaining -= delegate.transferTo(maxPosition - remaining, remaining, targetChannel.delegate); } - return maxCount; + return transferCount; } catch (IOException e) { throw new UncheckedIOException(e); } @@ -179,12 +188,12 @@ class SharedFileChannel { } private int tryWriteFully(long position, ByteBuffer source) throws IOException { - int initialRemaining = source.remaining(); - long maxPosition = position + initialRemaining; + int count = source.remaining(); + long maxPosition = position + count; do { delegate.write(source, maxPosition - source.remaining()); } while (source.hasRemaining()); - return initialRemaining - source.remaining(); + return count; } } 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 4826e10ef..c6670431c 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 @@ -8,30 +8,37 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.time.Instant; +import org.cryptomator.filesystem.FileSystem; import org.cryptomator.filesystem.WritableFile; class WritableNioFile implements WritableFile { - private final NioFile nioFile; + private final FileSystem fileSystem; + private final Path path; + private final SharedFileChannel channel; + private final NioAccess nioAccess; + private Runnable afterCloseCallback; - private boolean channelOpened = false; private boolean open = true; private long position = 0; - public WritableNioFile(NioFile nioFile) { - this.nioFile = nioFile; + public WritableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback, NioAccess nioAccess) { + this.fileSystem = fileSystem; + this.path = path; + this.channel = channel; + this.afterCloseCallback = afterCloseCallback; + this.nioAccess = nioAccess; } @Override public int write(ByteBuffer source) throws UncheckedIOException { assertOpen(); ensureChannelIsOpened(); - int written = nioFile.channel().writeFully(position, source); + int written = channel.writeFully(position, source); position += written; return written; } @@ -48,7 +55,7 @@ class WritableNioFile implements WritableFile { } private boolean belongsToSameFilesystem(WritableFile other) { - return other instanceof WritableNioFile && ((WritableNioFile) other).nioFile().belongsToSameFilesystem(nioFile); + return other instanceof WritableNioFile && ((WritableNioFile) other).fileSystem() == fileSystem; } @Override @@ -65,27 +72,27 @@ class WritableNioFile implements WritableFile { private void internalMoveTo(WritableNioFile other) { other.assertOpen(); + assertMovePreconditionsAreMet(other); try { - assertMovePreconditionsAreMet(other); closeChannelIfOpened(); other.closeChannelIfOpened(); - Files.move(path(), other.path(), REPLACE_EXISTING); + nioAccess.move(path, other.path(), REPLACE_EXISTING); } catch (IOException e) { throw new UncheckedIOException(e); } finally { open = false; other.open = false; - other.nioFile.lock().writeLock().unlock(); - nioFile.lock().writeLock().unlock(); + other.invokeAfterCloseCallback(); + invokeAfterCloseCallback(); } } private void assertMovePreconditionsAreMet(WritableNioFile other) { - if (Files.isDirectory(path())) { - throw new UncheckedIOException(new IOException(format("Can not move %s to %s. Source is a directory", path(), other.path()))); + if (nioAccess.isDirectory(path)) { + throw new UncheckedIOException(new IOException(format("Can not move %s to %s. Source is a directory", path, other.path()))); } - if (Files.isDirectory(other.path())) { - throw new UncheckedIOException(new IOException(format("Can not move %s to %s. Target is a directory", path(), other.path()))); + if (nioAccess.isDirectory(other.path())) { + throw new UncheckedIOException(new IOException(format("Can not move %s to %s. Target is a directory", path, other.path()))); } } @@ -94,7 +101,7 @@ class WritableNioFile implements WritableFile { assertOpen(); ensureChannelIsOpened(); try { - Files.setLastModifiedTime(path(), FileTime.from(instant)); + nioAccess.setLastModifiedTime(path, FileTime.from(instant)); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -105,12 +112,12 @@ class WritableNioFile implements WritableFile { assertOpen(); try { closeChannelIfOpened(); - Files.delete(nioFile.path()); + nioAccess.delete(path); } catch (IOException e) { throw new UncheckedIOException(e); } finally { open = false; - nioFile.lock().writeLock().unlock(); + invokeAfterCloseCallback(); } } @@ -118,7 +125,7 @@ class WritableNioFile implements WritableFile { public void truncate() throws UncheckedIOException { assertOpen(); ensureChannelIsOpened(); - nioFile.channel().truncate(0); + channel.truncate(0); } @Override @@ -130,33 +137,32 @@ class WritableNioFile implements WritableFile { try { closeChannelIfOpened(); } finally { - nioFile.lock().writeLock().unlock(); + afterCloseCallback.run(); } } void ensureChannelIsOpened() { - if (!channelOpened) { - nioFile.channel().open(WRITE); - channelOpened = true; - } + channel.openIfClosed(WRITE); } - private void closeChannelIfOpened() { - if (channelOpened) { - channel().close(); - } + void closeChannelIfOpened() { + channel.closeIfOpen(); } - SharedFileChannel channel() { - return nioFile.channel(); + FileSystem fileSystem() { + return fileSystem; } Path path() { - return nioFile.path; + return path; } - NioFile nioFile() { - return nioFile; + SharedFileChannel channel() { + return channel; + } + + void invokeAfterCloseCallback() { + afterCloseCallback.run(); } void assertOpen() { @@ -167,7 +173,7 @@ class WritableNioFile implements WritableFile { @Override public String toString() { - return format("Writable%s", this.nioFile); + return format("WritableNioFile(%s)", path); } } \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java new file mode 100644 index 000000000..4181273ba --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java @@ -0,0 +1,93 @@ +package org.cryptomator.filesystem.nio; + +import static java.lang.String.format; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; + +public class DefaultInstanceFactoryTest { + + private NioFileSystem fileSystem; + private Optional parent; + private Path path; + private NioAccess nioAccess; + private SharedFileChannel channel; + + private DefaultInstanceFactory inTest = new DefaultInstanceFactory(); + + @Before + public void setUp() { + fileSystem = mock(NioFileSystem.class); + parent = Optional.of(fileSystem); + path = mock(Path.class); + channel = mock(SharedFileChannel.class); + when(path.toAbsolutePath()).thenReturn(path); + nioAccess = mock(NioAccess.class); + } + + @Test + public void testNioFolderCreatesNioFolder() { + NioFolder result = inTest.nioFolder(parent, path, nioAccess); + + assertThat(result.parent(), is(parent)); + assertThat(result.path(), is(path)); + + result.exists(); + verify(nioAccess).isDirectory(path); + } + + @Test + public void testNioFileCreatesNioFile() { + NioFile result = inTest.nioFile(parent, path, nioAccess); + + assertThat(result.parent(), is(parent)); + + result.exists(); + verify(nioAccess).isRegularFile(path); + } + + @Test + public void testSharedFileChannelCreatesSharedFileChannel() throws IOException { + SharedFileChannel result = inTest.sharedFileChannel(path, nioAccess); + + result.open(OpenMode.WRITE); + verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); + } + + @Test + public void testWritableNioFileCreatesWritableNioFile() throws IOException { + Runnable afterCloseCallback = mock(Runnable.class); + WritableNioFile result = inTest.writableNioFile(fileSystem, path, channel, afterCloseCallback, nioAccess); + + assertThat(result.path(), is(path)); + assertThat(result.channel(), is(channel)); + assertThat(result.fileSystem(), is(fileSystem)); + + result.delete(); + verify(nioAccess).delete(path); + verify(afterCloseCallback).run(); + } + + @Test + public void testReadableNioFileCreatesWritableNioFile() throws IOException { + Runnable afterCloseCallback = mock(Runnable.class); + ReadableNioFile result = inTest.readableNioFile(fileSystem, path, channel, afterCloseCallback); + + assertThat(result.toString(), is(format("ReadableNioFile(%s)", path))); + + result.close(); + verify(channel).close(); + verify(afterCloseCallback).run(); + } + +} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultNioAccessTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultNioAccessTest.java new file mode 100644 index 000000000..d2caa1b2b --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultNioAccessTest.java @@ -0,0 +1,194 @@ +package org.cryptomator.filesystem.nio; + +import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.cryptomator.common.test.matcher.ContainsMatcher.contains; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; +import java.nio.file.spi.FileSystemProvider; +import java.time.Instant; +import java.util.HashSet; + +import org.junit.Before; +import org.junit.Test; + +public class DefaultNioAccessTest { + + private DefaultNioAccess inTest = new DefaultNioAccess(); + + private Path path; + private Path otherPath; + + private FileSystem fileSystem; + private FileSystemProvider provider; + + @Before + public void setUp() { + path = mock(Path.class); + otherPath = mock(Path.class); + fileSystem = mock(FileSystem.class); + provider = mock(FileSystemProvider.class); + + when(fileSystem.provider()).thenReturn(provider); + when(path.getFileSystem()).thenReturn(fileSystem); + when(otherPath.getFileSystem()).thenReturn(fileSystem); + } + + @Test + public void testOpenCallsOpenOnProvider() throws IOException { + OpenOption[] options = {StandardOpenOption.APPEND}; + FileChannel channel = mock(FileChannel.class); + when(provider.newFileChannel(path, new HashSet<>(asList(options)))).thenReturn(channel); + + FileChannel result = inTest.open(path, options); + + assertThat(result, is(channel)); + } + + @Test + public void testIsRegularFileCallsIsRegularFileOnProvider() throws IOException { + LinkOption[] options = {LinkOption.NOFOLLOW_LINKS}; + BasicFileAttributes attributes = mock(BasicFileAttributes.class); + when(attributes.isRegularFile()).thenReturn(true); + when(provider.readAttributes(path, BasicFileAttributes.class, options)).thenReturn(attributes); + + boolean result = inTest.isRegularFile(path, options); + + assertThat(result, is(true)); + verify(attributes).isRegularFile(); + } + + @Test + public void testIsDirectoryCallsIsDirectoryOnProvider() throws IOException { + LinkOption[] options = {LinkOption.NOFOLLOW_LINKS}; + BasicFileAttributes attributes = mock(BasicFileAttributes.class); + when(attributes.isDirectory()).thenReturn(true); + when(provider.readAttributes(path, BasicFileAttributes.class, options)).thenReturn(attributes); + + boolean result = inTest.isDirectory(path, options); + + assertThat(result, is(true)); + verify(attributes).isDirectory(); + } + + @Test + public void testExistsCallsProviderCheckAccessAndReturnsTrueIfItWorks() throws IOException { + boolean result = inTest.exists(path); + + assertThat(result, is(true)); + verify(provider).checkAccess(path); + } + + @Test + public void testExistsCallsProviderCheckAccessAndReturnsFalseIfItThrowsAFileNotFoundException() throws IOException { + doThrow(new FileNotFoundException()).when(provider).checkAccess(path); + + boolean result = inTest.exists(path); + + assertThat(result, is(false)); + } + + @Test + public void testListReturnsStreamCreatedFromDirectoryStream() throws IOException { + @SuppressWarnings("unchecked") + DirectoryStream directoryStream = mock(DirectoryStream.class); + Path aPath = mock(Path.class); + Path anotherPath = mock(Path.class); + when(directoryStream.iterator()).thenReturn(asList(aPath, anotherPath).iterator()); + when(provider.newDirectoryStream(same(path), any())).thenReturn(directoryStream); + + assertThat(inTest.list(path).collect(toList()), contains(is(aPath), is(anotherPath))); + } + + @Test + public void testCreateDirectoriesCreatesDirectoryUsingProvider() throws IOException { + FileAttribute[] attributes = {mock(FileAttribute.class)}; + + inTest.createDirectories(path, attributes); + + verify(provider).createDirectory(path, attributes); + } + + @Test + public void testGetLastModifiedTimeGetsLastModifiedTimeUsingProvider() throws IOException { + LinkOption[] options = {LinkOption.NOFOLLOW_LINKS}; + FileTime expectedResult = FileTime.from(Instant.now()); + BasicFileAttributes attributes = mock(BasicFileAttributes.class); + when(attributes.lastModifiedTime()).thenReturn(expectedResult); + when(provider.readAttributes(path, BasicFileAttributes.class, options)).thenReturn(attributes); + + FileTime result = inTest.getLastModifiedTime(path, options); + + assertThat(result, is(expectedResult)); + } + + @Test + public void testSetLastModifiedTimeSetsLastModifiedTimeUsingProvider() throws IOException { + FileTime fileTime = FileTime.from(Instant.now()); + BasicFileAttributeView attributes = mock(BasicFileAttributeView.class); + when(provider.getFileAttributeView(path, BasicFileAttributeView.class)).thenReturn(attributes); + + inTest.setLastModifiedTime(path, fileTime); + + verify(attributes).setTimes(fileTime, null, null); + } + + @Test + public void testDeleteDeletesUsingProvider() throws IOException { + inTest.delete(path); + + verify(provider).delete(path); + } + + @Test + public void testDeleteMovesUsingProvider() throws IOException { + CopyOption[] options = {ATOMIC_MOVE}; + inTest.move(path, otherPath, options); + + verify(provider).move(path, otherPath, options); + } + + @Test + public void testCloseInvokesCloseOnChannel() throws IOException { + Runnable implCloseChannel = mock(Runnable.class); + FileChannel channel = new FileChannelAdapter() { + @Override + public void implCloseChannel() throws IOException { + implCloseChannel.run(); + } + }; + + inTest.close(channel); + + verify(implCloseChannel).run(); + } + + @Test + public void testSeparatorReturnsSeparatorOfDefaultFileSystem() { + assertThat(inTest.separator(), is(FileSystems.getDefault().getSeparator())); + } + +} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FileChannelAdapter.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FileChannelAdapter.java new file mode 100644 index 000000000..095685ab9 --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FileChannelAdapter.java @@ -0,0 +1,96 @@ +package org.cryptomator.filesystem.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +class FileChannelAdapter extends FileChannel { + + @Override + public int read(ByteBuffer dst) throws IOException { + return 0; + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + return 0; + } + + @Override + public int write(ByteBuffer src) throws IOException { + return 0; + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + return 0; + } + + @Override + public long position() throws IOException { + return 0; + } + + @Override + public FileChannel position(long newPosition) throws IOException { + return null; + } + + @Override + public long size() throws IOException { + return 0; + } + + @Override + public FileChannel truncate(long size) throws IOException { + return null; + } + + @Override + public void force(boolean metaData) throws IOException { + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + return 0; + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + return 0; + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + return 0; + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + return 0; + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { + return null; + } + + @Override + public FileLock lock(long position, long size, boolean shared) throws IOException { + return null; + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + return null; + } + + @Override + public void implCloseChannel() throws IOException { + } + +} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/InstanceFactoryTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/InstanceFactoryTest.java new file mode 100644 index 000000000..742148116 --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/InstanceFactoryTest.java @@ -0,0 +1,18 @@ +package org.cryptomator.filesystem.nio; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class InstanceFactoryTest { + + @Test + public void testInitialDefaultIsDefaultInstanceFactory() { + InstanceFactory.DEFAULT.reset(); + + assertThat(InstanceFactory.DEFAULT.get(), is(instanceOf(DefaultInstanceFactory.class))); + } + +} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioAccessTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioAccessTest.java new file mode 100644 index 000000000..e780c9ef1 --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioAccessTest.java @@ -0,0 +1,18 @@ +package org.cryptomator.filesystem.nio; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class NioAccessTest { + + @Test + public void testInitialDefaultIsDefaultNioAccess() { + NioAccess.DEFAULT.reset(); + + assertThat(NioAccess.DEFAULT.get(), is(instanceOf(DefaultNioAccess.class))); + } + +} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java index 6d151fd37..07ef1eb66 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java @@ -1,52 +1,60 @@ package org.cryptomator.filesystem.nio; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.emptyFilesystem; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.file; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.testFilesystem; -import static org.cryptomator.filesystem.nio.PathMatcher.isDirectory; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; -import org.junit.Rule; +import org.junit.After; +import org.junit.Before; import org.junit.Test; -import org.junit.rules.ExpectedException; public class NioFileSystemTest { - @Rule - public ExpectedException thrown = ExpectedException.none(); + private Path path; - @Test - public void testParentIsEmpty() throws IOException { - NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem()); + private InstanceFactory instanceFactory; - assertThat(fileSystem.parent().isPresent(), is(false)); + private NioAccess nioAccess; + + private NioFileSystem inTest; + + @Before + public void setUp() { + path = mock(Path.class); + instanceFactory = mock(InstanceFactory.class); + nioAccess = mock(NioAccess.class); + + when(path.toAbsolutePath()).thenReturn(path); + + InstanceFactory.DEFAULT.set(instanceFactory); + NioAccess.DEFAULT.set(nioAccess); + + inTest = NioFileSystem.rootedAt(path); } @Test - public void testObtainingAFileSystemWithNonExistingRootCreatesItIncludingAllParentFolders() throws IOException { - Path emptyFilesystem = emptyFilesystem(); - Path nonExistingFilesystemRoot = emptyFilesystem.resolve("nonExistingRoot"); - Files.delete(emptyFilesystem); - - NioFileSystem.rootedAt(nonExistingFilesystemRoot); - - assertThat(nonExistingFilesystemRoot, isDirectory()); + public void testRootedAtCreatesNioFileSystemWithPath() { + assertThat(inTest.instanceFactory, is(instanceFactory)); + assertThat(inTest.path, is(path)); + assertThat(inTest.nioAccess, is(nioAccess)); + assertThat(inTest.parent, is(Optional.empty())); } @Test - public void testObtainingAFileSystemWhooseRootIsAFileFails() throws IOException { - Path emptyFilesystem = testFilesystem(file("rootWhichIsAFile")); - Path rootWhichIsAFile = emptyFilesystem.resolve("rootWhichIsAFile"); + public void testRootedAtCreatesFolder() throws IOException { + verify(nioAccess).createDirectories(path); + } - thrown.expect(UncheckedIOException.class); - - NioFileSystem.rootedAt(rootWhichIsAFile); + @After + public void tearDown() { + InstanceFactory.DEFAULT.reset(); + NioAccess.DEFAULT.reset(); } } \ No newline at end of file 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 05cff08a7..09460c007 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,37 +1,31 @@ package org.cryptomator.filesystem.nio; import static java.lang.String.format; -import static org.cryptomator.common.test.matcher.OptionalMatcher.presentOptionalWithValueThat; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.emptyFilesystem; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.file; -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; import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; +import java.nio.file.attribute.FileTime; import java.time.Instant; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.function.Supplier; +import java.util.Optional; 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.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.rules.Timeout; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import de.bechte.junit.runners.context.HierarchicalContextRunner; @@ -41,564 +35,279 @@ public class NioFileTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @Test - public void testExistsForExistingFileReturnsTrue() { - File existingFile = NioFileSystem.rootedAt(testFilesystem(file("testFile"))) // - .file("testFile"); + private NioFileSystem fileSystem; + + private Optional parent; + + private Path path; + private NioAccess nioAccess; + private InstanceFactory instanceFactory; + + private SharedFileChannel channel; + + private NioFile inTest; + + @Before + public void setUp() { + fileSystem = mock(NioFileSystem.class); + path = mock(Path.class); + nioAccess = mock(NioAccess.class); + instanceFactory = mock(InstanceFactory.class); + channel = mock(SharedFileChannel.class); + parent = Optional.of(fileSystem); + + Path maybeNonAbsolutePath = mock(Path.class); + when(maybeNonAbsolutePath.toAbsolutePath()).thenReturn(path); + + when(fileSystem.fileSystem()).thenReturn(fileSystem); + when(instanceFactory.sharedFileChannel(path, nioAccess)).thenReturn(channel); + + inTest = new NioFile(parent, maybeNonAbsolutePath, nioAccess, instanceFactory); + } + + public class Constructor { + + @Test + public void testConstructorCreatesASharedFileChannelFromAbsolutePathAndNioAccessUsingTheInstanceFactory() { + verify(instanceFactory).sharedFileChannel(path, nioAccess); + } + + @Test + public void testConstructorSetsParentPassedToIt() { + assertThat(inTest.parent(), is(parent)); + } + + } + + public class Open { + + @Test + public void testOpenReadableCreatesReadableNioFileFromNioFile() { + ReadableNioFile readableNioFile = mock(ReadableNioFile.class); + when(instanceFactory.readableNioFile(same(fileSystem), same(path), same(channel), any())).thenReturn(readableNioFile); + + ReadableFile readableFile = inTest.openReadable(); + + assertThat(readableFile, is(readableNioFile)); + } + + @Test + public void testOpenReadableInvokedBeforeInvokingAfterCloseOperationThrowsIllegalStateException() { + inTest.openReadable(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("already reading this file"); + + inTest.openReadable(); + } + + @Test + public void testOpenReadableInvokedAfterAfterCloseOperationCreatesNewReadableFile() { + ReadableNioFile readableNioFile = mock(ReadableNioFile.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + when(instanceFactory.readableNioFile(same(fileSystem), same(path), same(channel), captor.capture())).thenReturn(null, readableNioFile); + inTest.openReadable(); + captor.getValue().run(); + + ReadableFile readableFile = inTest.openReadable(); + + assertThat(readableFile, is(readableNioFile)); + } + + @Test + public void testOpenReadableInvokedBeforeInvokingAfterCloseOperationOfOpenWritableThrowsIllegalStateException() { + inTest.openWritable(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("currently writing this file"); + + inTest.openReadable(); + } + + @Test + public void testOpenReadableInvokedAfterInvokingAfterCloseOperationWorks() { + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + when(instanceFactory.writableNioFile(same(fileSystem), same(path), same(channel), captor.capture(), same(nioAccess))).thenReturn(null); + inTest.openWritable(); + captor.getValue().run(); + + inTest.openReadable(); + } + + @Test + public void testOpenWritableCreatesWritableNioFileFromNioFileAndNioAccessUsingInstanceFactory() { + WritableNioFile writableNioFile = mock(WritableNioFile.class); + when(instanceFactory.writableNioFile(same(fileSystem), same(path), same(channel), any(), same(nioAccess))).thenReturn(writableNioFile); + + WritableFile writableFile = inTest.openWritable(); + + assertThat(writableFile, is(writableNioFile)); + } + + @Test + public void testOpenWritableInvokedAfterAfterCloseOperationCreatesNewWritableFile() { + WritableNioFile writableNioFile = mock(WritableNioFile.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + when(instanceFactory.writableNioFile(same(fileSystem), same(path), same(channel), captor.capture(), same(nioAccess))).thenReturn(null, writableNioFile); + inTest.openWritable(); + captor.getValue().run(); + + WritableFile writableFile = inTest.openWritable(); + + assertThat(writableFile, is(writableNioFile)); + } + + @Test + public void testOpenWritableInvokedBeforeInvokingAfterCloseOperationThrowsIllegalStateException() { + inTest.openWritable(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("already writing this file"); + + inTest.openWritable(); + } + + @Test + public void testOpenWritableInvokedBeforeInvokingAfterCloseOperationFromOpenReadableThrowsIllegalStateException() { + inTest.openReadable(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("currently reading this file"); + + inTest.openWritable(); + } + + @Test + public void testOpenWritableInvokedAfterInvokingAfterCloseOperationWorks() { + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + when(instanceFactory.readableNioFile(same(fileSystem), same(path), same(channel), captor.capture())).thenReturn(null); + inTest.openReadable(); + captor.getValue().run(); + + inTest.openWritable(); + } + + } + + public class Exists { + + @Test + public void testExistsReturnsTrueIfPathIsRegularFile() { + when(nioAccess.isRegularFile(path)).thenReturn(true); + + assertThat(inTest.exists(), is(true)); + } + + @Test + public void testExistsReturnsFalseIfPathIsntRegularFile() { + when(nioAccess.isRegularFile(path)).thenReturn(false); + + assertThat(inTest.exists(), is(false)); + } + + } + + public class LastModified { + + @Test + public void testLastModifiedReturnsLastModifiedTimeForExistingFile() throws IOException { + Instant instant = Instant.parse("2016-01-04T01:24:32Z"); + when(nioAccess.exists(path)).thenReturn(true); + when(nioAccess.isRegularFile(path)).thenReturn(true); + when(nioAccess.getLastModifiedTime(path)).thenReturn(FileTime.from(instant)); + + Instant result = inTest.lastModified(); + + assertThat(result, is(instant)); + } + + @Test + public void testLastModifiedInvokesGetLastModifiedTimeForNonExistingFile() throws IOException { + Instant instant = Instant.parse("2016-01-04T01:24:32Z"); + when(nioAccess.exists(path)).thenReturn(false); + when(nioAccess.getLastModifiedTime(path)).thenReturn(FileTime.from(instant)); + + Instant result = inTest.lastModified(); + + assertThat(result, is(instant)); + } + + @Test + public void testLastModifiedWrapsIOExceptionThrownByGetLastModifiedTimeInUncheckedIOException() throws IOException { + IOException exceptionThrownFromGetLastModifiedTime = new IOException(); + when(nioAccess.exists(path)).thenReturn(true); + when(nioAccess.isRegularFile(path)).thenReturn(true); + when(nioAccess.getLastModifiedTime(path)).thenThrow(exceptionThrownFromGetLastModifiedTime); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionThrownFromGetLastModifiedTime)); + + inTest.lastModified(); + } + + @Test + public void testLastModifiedThrowsUncheckedIOExceptionIfPathExistsButIsNoRegularFile() { + when(nioAccess.exists(path)).thenReturn(true); + when(nioAccess.isRegularFile(path)).thenReturn(false); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(format("%s is a folder", path)); + + inTest.lastModified(); + } + + } + + public class CompareTo { + + private Path otherPath; + private NioFile otherInTest; + + @Before + public void setUp() { + otherPath = mock(Path.class); + + Path maybeNonAbsolutePath = mock(Path.class); + when(maybeNonAbsolutePath.toAbsolutePath()).thenReturn(otherPath); + + otherInTest = new NioFile(parent, maybeNonAbsolutePath, nioAccess, instanceFactory); + } + + @Test + public void testCompareToFileFromOtherFileSystemThrowsIllegalArgumentException() { + File other = mock(File.class); + when(other.fileSystem()).thenReturn(mock(FileSystem.class)); + + thrown.expect(IllegalArgumentException.class); + + inTest.compareTo(other); + } + + @Test + public void testCompareToReturnsResultOfPathsCompareTo() { + int expectedResult = 2873; + when(path.compareTo(otherPath)).thenReturn(expectedResult); + + int result = inTest.compareTo(otherInTest); + + assertThat(result, is(expectedResult)); + } - assertThat(existingFile.exists(), is(true)); } @Test - public void testExistsForNonExistingFileReturnsFalse() { - File nonExistingFile = NioFileSystem.rootedAt(emptyFilesystem()) // - .file("testFile"); + public void testNameReturnsFileNameOfPath() { + Path fileName = mock(Path.class); + when(path.getFileName()).thenReturn(fileName); - assertThat(nonExistingFile.exists(), is(false)); - } + String name = inTest.name(); - @Test - public void testExistsForFileWhichIsAFolderReturnsFalse() { - File fileWhichIsAFolder = NioFileSystem.rootedAt(testFilesystem(folder("nameOfAnExistingFolder"))) // - .file("nameOfAnExistingFolder"); - - assertThat(fileWhichIsAFolder.exists(), is(false)); - } - - @Test - public void testLastModifiedForExistingFileReturnsLastModifiedValue() { - Instant expectedLastModified = Instant.parse("2015-12-31T15:03:34Z"); - File existingFile = NioFileSystem - .rootedAt(testFilesystem( // - file("testFile").withLastModified(expectedLastModified))) // - .file("testFile"); - - assertThat(existingFile.lastModified(), is(expectedLastModified)); - } - - @Test - public void testLastModifiedForNonExistingFileThrowsUncheckedIOExceptionWithPathInMessage() { - Path filesystemPath = emptyFilesystem(); - Path pathOfNonExistingFile = filesystemPath.resolve("nonExistingFile"); - File nonExistingFile = NioFileSystem.rootedAt(filesystemPath) // - .file("nonExistingFile"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(pathOfNonExistingFile.toString()); - - nonExistingFile.lastModified(); - } - - @Test - public void testLastModifiedForNonFileWhichIsAFolderThrowsUncheckedIOExceptionWithPathInMessage() { - Path filesystemPath = testFilesystem(folder("nameOfAnExistingFolder")); - Path pathOfNonExistingFile = filesystemPath.resolve("nameOfAnExistingFolder"); - File fileWhichIsAFolder = NioFileSystem.rootedAt(filesystemPath) // - .file("nameOfAnExistingFolder"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(pathOfNonExistingFile.toString()); - - fileWhichIsAFolder.lastModified(); - } - - @Test - public void testCompareToReturnsZeroForSameInstance() { - File file = NioFileSystem.rootedAt(emptyFilesystem()).file("fileName"); - - assertThat(file.compareTo(file), is(0)); - } - - @Test - public void testCompareToReturnsZeroForSameFile() { - FileSystem filesystem = NioFileSystem.rootedAt(emptyFilesystem()); - File fileA = filesystem.file("fileName"); - File fileB = filesystem.file("fileName"); - - assertThat(fileA.compareTo(fileB), is(0)); - assertThat(fileB.compareTo(fileA), is(0)); - } - - @Test - public void testCompareToReturnsNonZeroForOtherFile() { - FileSystem filesystem = NioFileSystem.rootedAt(emptyFilesystem()); - File fileA = filesystem.file("aFileName"); - File fileB = filesystem.file("anotherFileName"); - - int compareAWithB = fileA.compareTo(fileB); - int compareBWithA = fileB.compareTo(fileA); - assertThat(compareAWithB, not(is(0))); - assertThat(compareBWithA, not(is(0))); - assertThat(signum(compareAWithB) + signum(compareBWithA), is(0)); - } - - @Test - public void testCompareToThrowsExceptionForFileFromDifferentFileSystem() { - File fileA = NioFileSystem.rootedAt(emptyFilesystem()).file("aFileName"); - File fileB = NioFileSystem.rootedAt(emptyFilesystem()).file("aFileName"); - - thrown.expect(IllegalArgumentException.class); - - fileA.compareTo(fileB); + assertThat(name, is(fileName.toString())); } @Test public void testToString() { - Path filesystemPath = emptyFilesystem(); - Path absoluteFilePath = filesystemPath.resolve("fileName").toAbsolutePath(); - File file = NioFileSystem.rootedAt(filesystemPath).file("fileName"); - - assertThat(file.toString(), is(format("NioFile(%s)", absoluteFilePath))); + assertThat(inTest.toString(), is(format("NioFile(%s)", path))); } - @Test - public void testNameReturnsNameOfFile() { - String fileName = "fileName"; - File file = NioFileSystem.rootedAt(emptyFilesystem()).file(fileName); - - assertThat(file.name(), is(fileName)); - } - - @Test - public void testParentForDirectChildOfFileSystemReturnsFileSystem() { - FileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem()); - File file = fileSystem.file("fileName"); - - assertThat(file.parent(), presentOptionalWithValueThat(is(sameInstance(fileSystem)))); - } - - @Test - public void testParentForChildOfFolderReturnsFolder() { - Folder folder = NioFileSystem.rootedAt(emptyFilesystem()).folder("folderName"); - File file = folder.file("fileName"); - - assertThat(file.parent(), presentOptionalWithValueThat(is(sameInstance(folder)))); - } - - @Test - public void testCopyToNonExistingTargetCreatesTargetWithContent() { - Path filesystemPath = testFilesystem(file("sourceFile").withData("fileContents")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path sourceFilePath = filesystemPath.resolve("sourceFile"); - Path targetFilePath = filesystemPath.resolve("targetFile"); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("targetFile"); - - source.copyTo(target); - - assertThat(sourceFilePath, isFile().withContent("fileContents")); - assertThat(targetFilePath, isFile().withContent("fileContents")); - } - - @Test - public void testCopyToExistingTargetOverwritesTargetWithContent() { - Path filesystemPath = testFilesystem( // - file("sourceFile").withData("fileContents"), // - file("targetFile").withData("wrongFileContents")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path sourceFilePath = filesystemPath.resolve("sourceFile"); - Path targetFilePath = filesystemPath.resolve("targetFile"); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("targetFile"); - - source.copyTo(target); - - assertThat(sourceFilePath, isFile().withContent("fileContents")); - assertThat(targetFilePath, isFile().withContent("fileContents")); - } - - @Test - public void testCopyToSameFileThrowsIllegalArgumentException() { - File file = NioFileSystem.rootedAt(testFilesystem(file("sourceFile"))).file("fileName"); - - thrown.expect(IllegalArgumentException.class); - - file.copyTo(file); - } - - @Test - public void testCopyToDirectoryTargetThrowsUncheckedIOExceptionWithPathInMessage() { - Path filesystemPath = testFilesystem( // - file("sourceFile").withData("fileContents"), // - folder("aFolderName")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path targetFilePath = filesystemPath.resolve("aFolderName").toAbsolutePath(); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("aFolderName"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(targetFilePath.toAbsolutePath().toString()); - - source.copyTo(target); - } - - @Test - public void testCopyToOfNonExistingFileThrowsUncheckedIOExceptionWithPathInMessage() { - Path filesystemPath = emptyFilesystem(); - Path filePath = filesystemPath.resolve("nonExistingFile").toAbsolutePath(); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - File nonExistingFile = fileSystem.file("nonExistingFile"); - File target = fileSystem.file("target"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filePath.toString()); - - nonExistingFile.copyTo(target); - } - - @Test - public void testCopyToOfFileWhichIsAFolderThrowsUncheckedIOExceptionWithPathInMessage() { - Path filesystemPath = testFilesystem(folder("folderName")); - Path filePath = filesystemPath.resolve("folderName").toAbsolutePath(); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - File fileWhichIsAFolder = fileSystem.file("folderName"); - File target = fileSystem.file("target"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filePath.toString()); - - fileWhichIsAFolder.copyTo(target); - } - - @Test - public void testMoveToNonExistingTargetCreatesTargetWithContentAndDeletesSource() { - Path filesystemPath = testFilesystem(file("sourceFile").withData("fileContents")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path sourceFilePath = filesystemPath.resolve("sourceFile"); - Path targetFilePath = filesystemPath.resolve("targetFile"); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("targetFile"); - - source.moveTo(target); - - assertThat(sourceFilePath, doesNotExist()); - assertThat(targetFilePath, isFile().withContent("fileContents")); - } - - @Test - public void testMoveToExistingTargetOverwritesTargetWithContentAndDeletesSource() { - Path filesystemPath = testFilesystem( // - file("sourceFile").withData("fileContents"), // - file("targetFile").withData("wrongFileContents")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path sourceFilePath = filesystemPath.resolve("sourceFile"); - Path targetFilePath = filesystemPath.resolve("targetFile"); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("targetFile"); - - source.moveTo(target); - - assertThat(sourceFilePath, doesNotExist()); - assertThat(targetFilePath, isFile().withContent("fileContents")); - } - - @Test - public void testMoveToSameFileDoesNothing() { - Path filesystemPath = testFilesystem(file("fileName").withData("fileContents")); - Path filePath = filesystemPath.resolve("fileName"); - File file = NioFileSystem.rootedAt(filesystemPath).file("fileName"); - - file.moveTo(file); - - assertThat(filePath, isFile().withContent("fileContents")); - } - - @Test - public void testMoveToDirectoryTargetThrowsUncheckedIOExceptionWithPathInMessage() { - Path filesystemPath = testFilesystem( // - file("sourceFile").withData("fileContents"), // - folder("aFolderName")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path targetFilePath = filesystemPath.resolve("aFolderName").toAbsolutePath(); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("aFolderName"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(targetFilePath.toAbsolutePath().toString()); - - source.moveTo(target); - } - - @Test - public void testMoveToOfNonExistingFileThrowsUncheckedIOExceptionWithPathInMessage() { - Path filesystemPath = emptyFilesystem(); - Path filePath = filesystemPath.resolve("nonExistingFile").toAbsolutePath(); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - File nonExistingFile = fileSystem.file("nonExistingFile"); - File target = fileSystem.file("target"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filePath.toString()); - - nonExistingFile.moveTo(target); - } - - @Test - public void testMoveToOfFileWhichIsAFolderThrowsUncheckedIOExceptionWithPathInMessage() { - Path filesystemPath = testFilesystem(folder("folderName")); - Path filePath = filesystemPath.resolve("folderName").toAbsolutePath(); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - File fileWhichIsAFolder = fileSystem.file("folderName"); - File target = fileSystem.file("target"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filePath.toString()); - - 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; - } else if (value < 0) { - return -1; - } else { - return 0; - } - } - -} +} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java index 3ef5acedf..8b74ff94e 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java @@ -2,479 +2,498 @@ package org.cryptomator.filesystem.nio; import static java.lang.String.format; import static java.util.stream.Collectors.toList; -import static org.cryptomator.common.test.matcher.ContainsMatcher.containsInAnyOrder; -import static org.cryptomator.common.test.matcher.OptionalMatcher.presentOptionalWithValueThat; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.emptyFilesystem; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.file; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.folder; -import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.testFilesystem; -import static org.cryptomator.filesystem.nio.NioNodeMatcher.fileWithName; -import static org.cryptomator.filesystem.nio.NioNodeMatcher.folderWithName; -import static org.cryptomator.filesystem.nio.PathMatcher.doesNotExist; -import static org.cryptomator.filesystem.nio.PathMatcher.isDirectory; -import static org.cryptomator.filesystem.nio.PathMatcher.isFile; +import static org.cryptomator.common.test.matcher.ContainsMatcher.contains; +import static org.cryptomator.filesystem.nio.ReflectiveClassMatchers.aClassThatDoesDeclareMethod; +import static org.cryptomator.filesystem.nio.ReflectiveClassMatchers.aClassThatDoesNotDeclareMethod; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.sameInstance; -import static org.hamcrest.Matchers.empty; +import static org.hamcrest.CoreMatchers.theInstance; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.FileTime; import java.time.Instant; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; import org.cryptomator.filesystem.File; import org.cryptomator.filesystem.FileSystem; import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.WritableFile; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import de.bechte.junit.runners.context.HierarchicalContextRunner; + +@RunWith(HierarchicalContextRunner.class) public class NioFolderTest { + private static final String SEPARATOR = "/"; + @Rule public ExpectedException thrown = ExpectedException.none(); - @Test - public void testNameIsNameOfFolder() throws IOException { - final String folderName = "nameOfFolder"; - NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem()); - Folder folder = fileSystem.folder(folderName); + private NioFileSystem fileSystem; + private Optional parent; - assertThat(folder, folderWithName(folderName)); + private Path path; + private NioAccess nioAccess; + private InstanceFactory instanceFactory; + + private NioFolder inTest; + + @Before + public void setUp() { + path = mock(Path.class); + nioAccess = mock(NioAccess.class); + instanceFactory = mock(InstanceFactory.class); + fileSystem = mock(NioFileSystem.class); + parent = Optional.of(fileSystem); + + Path maybeNonAbsolutePath = mock(Path.class); + when(maybeNonAbsolutePath.toAbsolutePath()).thenReturn(path); + + when(parent.get().fileSystem()).thenReturn(fileSystem); + + when(nioAccess.separator()).thenReturn(SEPARATOR); + + inTest = new NioFolder(parent, maybeNonAbsolutePath, nioAccess, instanceFactory); } - @Test - public void testCreateSucceedsIfFolderExists() throws IOException { - String folderName = "nameOfFolder"; - Path testFilesystemPath = testFilesystem( // - folder(folderName)); - NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); - Folder existingFolder = fileSystem.folder(folderName); + public class ChildrenTests { - existingFolder.create(); + @Test + public void testChildrenConvertsPathWhichIsADirectoryToAnNioFolderUsingTheInstanceFactory() throws IOException { + Path childFolderPath = mock(Path.class); + NioFolder nioFolderCreatedFromChildFolderPath = mock(NioFolder.class); + Stream childrenPaths = Stream.builder().add(childFolderPath).build(); + when(nioAccess.isDirectory(childFolderPath)).thenReturn(true); + when(nioAccess.list(path)).thenReturn(childrenPaths); + when(instanceFactory.nioFolder(Optional.of(inTest), childFolderPath, nioAccess)).thenReturn(nioFolderCreatedFromChildFolderPath); + + assertThat(inTest.children().collect(toList()), contains(theInstance(nioFolderCreatedFromChildFolderPath))); + } + + @Test + public void testChildrenConvertsPathWhichIsAFileToAnNioFileUsingTheInstanceFactory() throws IOException { + Path childFilePath = mock(Path.class); + NioFile nioFileCreatedFromChildFolderPath = mock(NioFile.class); + Stream childrenPaths = Stream.builder().add(childFilePath).build(); + when(nioAccess.isDirectory(childFilePath)).thenReturn(false); + when(nioAccess.list(path)).thenReturn(childrenPaths); + when(instanceFactory.nioFile(Optional.of(inTest), childFilePath, nioAccess)).thenReturn(nioFileCreatedFromChildFolderPath); + + assertThat(inTest.children().collect(toList()), contains(theInstance(nioFileCreatedFromChildFolderPath))); + } + + @Test + public void testChildrenConvertsAllPathsToFilesAndFolders() throws IOException { + Path childFilePath = mock(Path.class); + Path childFolderPath = mock(Path.class); + Path anotherChildFilePath = mock(Path.class); + Path anotherChildFolderPath = mock(Path.class); + NioFile nioFileCreatedFromChildFilePath = mock(NioFile.class); + NioFile anotherNioFileCreatedFromChildFilePath = mock(NioFile.class); + NioFolder nioFolderCreatedFromChildFolderPath = mock(NioFolder.class); + NioFolder anotherNioFolderCreatedFromChildFolderPath = mock(NioFolder.class); + Stream childrenPaths = Stream.builder() // + .add(childFilePath) // NioFolder + .add(childFolderPath) // + .add(anotherChildFilePath) // + .add(anotherChildFolderPath).build(); + when(nioAccess.isDirectory(childFilePath)).thenReturn(false); + when(nioAccess.isDirectory(childFolderPath)).thenReturn(true); + when(nioAccess.isDirectory(anotherChildFilePath)).thenReturn(false); + when(nioAccess.isDirectory(anotherChildFolderPath)).thenReturn(true); + when(nioAccess.list(path)).thenReturn(childrenPaths); + when(instanceFactory.nioFile(Optional.of(inTest), childFilePath, nioAccess)).thenReturn(nioFileCreatedFromChildFilePath); + when(instanceFactory.nioFolder(Optional.of(inTest), childFolderPath, nioAccess)).thenReturn(nioFolderCreatedFromChildFolderPath); + when(instanceFactory.nioFile(Optional.of(inTest), anotherChildFilePath, nioAccess)).thenReturn(anotherNioFileCreatedFromChildFilePath); + when(instanceFactory.nioFolder(Optional.of(inTest), anotherChildFolderPath, nioAccess)).thenReturn(anotherNioFolderCreatedFromChildFolderPath); + + assertThat(inTest.children().collect(toList()), + contains( // + theInstance(nioFileCreatedFromChildFilePath), // + theInstance(nioFolderCreatedFromChildFolderPath), // + theInstance(anotherNioFileCreatedFromChildFilePath), // + theInstance(anotherNioFolderCreatedFromChildFolderPath))); + } + + @Test + public void testFilesIsNotOverwritten() { + assertThat(Folder.class, aClassThatDoesDeclareMethod("files")); + assertThat(NioFolder.class, aClassThatDoesNotDeclareMethod("files")); + } + + @Test + public void testFoldersIsNotOverwritten() { + assertThat(Folder.class, aClassThatDoesDeclareMethod("folders")); + assertThat(NioFolder.class, aClassThatDoesNotDeclareMethod("folders")); + } + + @Test + public void testChildrenWrapsIOExceptionFromListInUncheckedIOException() throws IOException { + IOException exceptionFromList = new IOException(); + when(nioAccess.list(path)).thenThrow(exceptionFromList); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromList)); + + inTest.children(); + } - assertThat(Files.isDirectory(testFilesystemPath.resolve(folderName)), is(true)); } - @Test - public void testCreateSucceedsIfFolderDoesNotExist() throws IOException { - String folderName = "nameOfFolder"; - Path testFilesystemPath = emptyFilesystem(); - NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); - Folder nonExistingFolder = fileSystem.folder(folderName); + public class FileTests { - nonExistingFolder.create(); + @Test + public void testFileResolvesTheNameAgainstThePathAndCreatesAnNioFileUsingTheInstanceFactory() { + String name = "theFileName"; + Path resolvedPath = mock(Path.class); + NioFile fileCreatedByInstanceFactory = mock(NioFile.class); + when(path.resolve(name)).thenReturn(resolvedPath); + when(instanceFactory.nioFile(Optional.of(inTest), resolvedPath, nioAccess)).thenReturn(fileCreatedByInstanceFactory); + + File result = inTest.file(name); + + assertThat(result, is(fileCreatedByInstanceFactory)); + } + + @Test + public void testSecondInvocationOfFileReturnsChachedResult() { + String name = "theFileName"; + Path resolvedPath = mock(Path.class); + NioFile fileCreatedByInstanceFactory = mock(NioFile.class); + when(path.resolve(name)).thenReturn(resolvedPath); + when(instanceFactory.nioFile(Optional.of(inTest), resolvedPath, nioAccess)).thenReturn(fileCreatedByInstanceFactory); + + File result = inTest.file(name); + File secondResult = inTest.file(name); + + assertThat(result, is(fileCreatedByInstanceFactory)); + assertThat(secondResult, is(fileCreatedByInstanceFactory)); + verify(instanceFactory).nioFile(Optional.of(inTest), resolvedPath, nioAccess); + } + + @Test + public void testFileInvokedWithNameContainingSeparatorThrowsIllegalArgumentException() { + String name = "nameContaining" + SEPARATOR; + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(name); + + inTest.file(name); + } + + @Test + public void testResolveFileIsNotOverwritten() { + assertThat(Folder.class, is(aClassThatDoesDeclareMethod("resolveFile", String.class))); + assertThat(NioFolder.class, is(aClassThatDoesNotDeclareMethod("resolveFile", String.class))); + } - assertThat(Files.isDirectory(testFilesystemPath.resolve(folderName)), is(true)); } - @Test - public void testCreateSucceedsIfParentIsMissing() throws IOException { - String parentFolderName = "nameOfParentFolder"; - String folderName = "nameOfFolder"; - Path emptyFilesystemPath = emptyFilesystem(); - NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystemPath); - Folder folderWithNonExistingParent = fileSystem.folder(parentFolderName).folder(folderName); + public class FolderTests { - folderWithNonExistingParent.create(); + @Test + public void testFolderResolvesTheNameAgainstThePathAndCreatesAnNioFolderUsingTheInstanceFactory() { + String name = "theFolderName"; + Path resolvedPath = mock(Path.class); + NioFolder folderCreatedByInstanceFactory = mock(NioFolder.class); + when(path.resolve(name)).thenReturn(resolvedPath); + when(instanceFactory.nioFolder(Optional.of(inTest), resolvedPath, nioAccess)).thenReturn(folderCreatedByInstanceFactory); + + Folder result = inTest.folder(name); + + assertThat(result, is(folderCreatedByInstanceFactory)); + } + + @Test + public void testSecondInvocationOfFileReturnsChachedResult() { + String name = "theFolderName"; + Path resolvedPath = mock(Path.class); + NioFolder folderCreatedByInstanceFactory = mock(NioFolder.class); + when(path.resolve(name)).thenReturn(resolvedPath); + when(instanceFactory.nioFolder(Optional.of(inTest), resolvedPath, nioAccess)).thenReturn(folderCreatedByInstanceFactory); + + Folder result = inTest.folder(name); + Folder secondResult = inTest.folder(name); + + assertThat(result, is(folderCreatedByInstanceFactory)); + assertThat(secondResult, is(folderCreatedByInstanceFactory)); + verify(instanceFactory).nioFolder(Optional.of(inTest), resolvedPath, nioAccess); + } + + @Test + public void testFolderInvokedWithNameContainingSeparatorThrowsIllegalArgumentException() { + String name = "nameContaining" + SEPARATOR; + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(name); + + inTest.folder(name); + } + + @Test + public void testResolveFolderIsNotOverwritten() { + assertThat(Folder.class, is(aClassThatDoesDeclareMethod("resolveFolder", String.class))); + assertThat(NioFolder.class, is(aClassThatDoesNotDeclareMethod("resolveFolder", String.class))); + } - assertThat(Files.isDirectory(emptyFilesystemPath.resolve(parentFolderName).resolve(folderName)), is(true)); } - @Test - public void testCreateWithFolderWhichIsAFileThrowsUncheckedIOExceptionWithAbsolutePathOfFolderInMessage() throws IOException { - String folderName = "nameOfFolder"; - Path testFilesystemPath = testFilesystem(file(folderName)); - NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); - Folder folderWhichIsAFile = fileSystem.folder(folderName); + public class CreateTests { - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(testFilesystemPath.resolve(folderName).toString()); + @Test + public void testCreateDelegatesToNioAccessCreateDirectories() throws IOException { + inTest.create(); + + verify(nioAccess).createDirectories(path); + } + + @Test + public void testWrapesIOExceptionThrownByCreateDirectoriesInUncheckedIOException() throws IOException { + IOException exceptionFromCreateDirectories = new IOException(); + doThrow(exceptionFromCreateDirectories).when(nioAccess).createDirectories(path); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromCreateDirectories)); + + inTest.create(); + } - folderWhichIsAFile.create(); } - @Test - public void testChildrenOfEmptyNioFolderAreEmpty() throws IOException { - NioFolder folder = NioFileSystem.rootedAt(emptyFilesystem()); + public class LastModifiedTests { + + @Test + public void testLastModifiedReturnsLastModifiedTimeForExistingFolder() throws IOException { + Instant instant = Instant.parse("2016-01-04T01:24:32Z"); + when(nioAccess.exists(path)).thenReturn(true); + when(nioAccess.isDirectory(path)).thenReturn(true); + when(nioAccess.getLastModifiedTime(path)).thenReturn(FileTime.from(instant)); + + Instant result = inTest.lastModified(); + + assertThat(result, is(instant)); + } + + @Test + public void testLastModifiedInvokesGetLastModifiedTimeForNonExistingFolder() throws IOException { + Instant instant = Instant.parse("2016-01-04T01:24:32Z"); + when(nioAccess.exists(path)).thenReturn(false); + when(nioAccess.getLastModifiedTime(path)).thenReturn(FileTime.from(instant)); + + Instant result = inTest.lastModified(); + + assertThat(result, is(instant)); + } + + @Test + public void testLastModifiedWrapsIOExceptionThrownByGetLastModifiedTimeInUncheckedIOException() throws IOException { + IOException exceptionThrownFromGetLastModifiedTime = new IOException(); + when(nioAccess.exists(path)).thenReturn(true); + when(nioAccess.isDirectory(path)).thenReturn(true); + when(nioAccess.getLastModifiedTime(path)).thenThrow(exceptionThrownFromGetLastModifiedTime); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionThrownFromGetLastModifiedTime)); + + inTest.lastModified(); + } + + @Test + public void testLastModifiedThrowsUncheckedIOExceptionIfPathExistsButIsNoFolder() { + when(nioAccess.exists(path)).thenReturn(true); + when(nioAccess.isDirectory(path)).thenReturn(false); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(format("%s is a file", path)); + + inTest.lastModified(); + } - assertThat(folder.children().collect(toList()), is(empty())); } - @Test - public void testChildrenOfNonExistingFolderThrowsUncheckedIOExceptionWithAbolutePathOfFolderInMessage() throws IOException { - String nameOfNonExistingFolder = "nameOfFolder"; - Path filesystemPath = emptyFilesystem(); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(nameOfNonExistingFolder); + public class ExistTests { - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve(nameOfNonExistingFolder).toString()); + @Test + public void testExistsIfExisting() { + when(nioAccess.isDirectory(path)).thenReturn(true); + + assertThat(inTest.exists(), is(true)); + } + + @Test + public void testExistsIfNotExisting() { + when(nioAccess.isDirectory(path)).thenReturn(false); + + assertThat(inTest.exists(), is(false)); + } - folder.children(); } - @Test - public void testChildrenOfFolderWhichIsAFileThrowsUncheckedIOExceptionWithAbsolutePathOfFolderInMessage() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem(file(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + public class MoveToTests { - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve(folderName).toString()); + @Test + public void testMoveToThrowsIllegalArgumentExceptionIfTargetDoesNotBelongToSameFilesystem() { + Folder target = mock(Folder.class); + when(target.fileSystem()).thenReturn(mock(FileSystem.class)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Can only move a Folder to a Folder in the same FileSystem"); + + inTest.moveTo(target); + } + + @Test + public void testMoveToWrapsIOExceptionThrownByNioAccessMoveInUncheckedIOException() throws IOException { + NioFolder target = mock(NioFolder.class); + Path targetPath = mock(Path.class); + when(target.fileSystem()).thenReturn(fileSystem); + when(target.path()).thenReturn(targetPath); + when(target.parent()).thenReturn(Optional.empty()); + IOException exceptionThrownByMoveTo = new IOException(); + doThrow(exceptionThrownByMoveTo).when(nioAccess).move(path, targetPath); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionThrownByMoveTo)); + + inTest.moveTo(target); + } + + @Test + public void testMoveToDeletesTargetAndDelegatesToNioAccessMoveIfTargetHasNoParent() throws IOException { + NioFolder target = mock(NioFolder.class); + Path targetPath = mock(Path.class); + when(target.fileSystem()).thenReturn(fileSystem); + when(target.path()).thenReturn(targetPath); + when(target.parent()).thenReturn(Optional.empty()); + + inTest.moveTo(target); + + InOrder inOrder = inOrder(target, nioAccess); + inOrder.verify(target).delete(); + inOrder.verify(nioAccess).move(path, targetPath); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Test + public void testMoveToDeletesTargetCreatesTargetsParentAndDelegatesToNioAccessMoveIfTargetHasAParent() throws IOException { + NioFolder target = mock(NioFolder.class); + NioFolder parentOfTarget = mock(NioFolder.class); + Path targetPath = mock(Path.class); + when(target.fileSystem()).thenReturn(fileSystem); + when(target.path()).thenReturn(targetPath); + when(target.parent()).thenReturn((Optional) Optional.of(parentOfTarget)); + + inTest.moveTo(target); + + InOrder inOrder = inOrder(target, parentOfTarget, nioAccess); + inOrder.verify(target).delete(); + inOrder.verify(parentOfTarget).create(); + inOrder.verify(nioAccess).move(path, targetPath); + } - folder.children(); } - @Test - public void testChildrenOfFolderAreCorrect() throws IOException { - String folderName1 = "folder1"; - String folderName2 = "folder2"; - String fileName1 = "file1"; - String fileName2 = "file2"; + public class DeleteTests { + + @Test + public void testDeleteDoesNothingIfFolderDoesNotExist() { + when(nioAccess.isDirectory(path)).thenReturn(false); + + inTest.delete(); + } + + @Test + public void testDeleteInvokesDeleteOnChildFolderAndNioAccessDeleteAfterwards() throws IOException { + Path folderChildPath = mock(Path.class); + NioFolder folderChild = mock(NioFolder.class); + when(nioAccess.isDirectory(path)).thenReturn(true); + when(nioAccess.isDirectory(folderChildPath)).thenReturn(true); + when(instanceFactory.nioFolder(Optional.of(inTest), folderChildPath, nioAccess)).thenReturn(folderChild); + when(nioAccess.list(path)).thenAnswer(answerFrom(() -> Stream.of(folderChildPath))); + + inTest.delete(); + + InOrder inOrder = inOrder(nioAccess, folderChild); + inOrder.verify(nioAccess).isDirectory(path); + inOrder.verify(nioAccess).list(path); + inOrder.verify(folderChild).delete(); + inOrder.verify(nioAccess).list(path); + inOrder.verify(nioAccess).delete(path); + } + + @Test + public void testDeleteInvokesDeleteOnChildFileAndNioAccessDeleteAfterwards() throws IOException { + Path fileChildPath = mock(Path.class); + NioFile fileChild = mock(NioFile.class); + WritableFile writableFile = mock(WritableFile.class); + when(fileChild.openWritable()).thenReturn(writableFile); + when(nioAccess.isDirectory(path)).thenReturn(true); + when(nioAccess.isDirectory(fileChildPath)).thenReturn(false); + when(instanceFactory.nioFile(Optional.of(inTest), fileChildPath, nioAccess)).thenReturn(fileChild); + when(nioAccess.list(path)).thenAnswer(answerFrom(() -> Stream.of(fileChildPath))); + + inTest.delete(); + + InOrder inOrder = inOrder(nioAccess, fileChild, writableFile); + inOrder.verify(nioAccess).isDirectory(path); + inOrder.verify(nioAccess).list(path); + inOrder.verify(nioAccess).list(path); + inOrder.verify(fileChild).openWritable(); + inOrder.verify(writableFile).delete(); + inOrder.verify(writableFile).close(); + inOrder.verify(nioAccess).delete(path); + } + + @Test + public void testDeleteWrapsIOExceptionFromFolderDeleteInUncheckedIOException() throws IOException { + Path fileChildPath = mock(Path.class); + NioFile fileChild = mock(NioFile.class); + WritableFile writableFile = mock(WritableFile.class); + when(fileChild.openWritable()).thenReturn(writableFile); + when(nioAccess.isDirectory(path)).thenReturn(true); + when(nioAccess.isDirectory(fileChildPath)).thenReturn(false); + when(instanceFactory.nioFile(Optional.of(inTest), fileChildPath, nioAccess)).thenReturn(fileChild); + when(nioAccess.list(path)).thenAnswer(answerFrom(() -> Stream.of(fileChildPath))); + IOException exceptionFromDelete = new IOException(); + doThrow(exceptionFromDelete).when(nioAccess).delete(path); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromDelete)); + + inTest.delete(); + } - NioFolder folder = NioFileSystem.rootedAt(testFilesystem( // - folder(folderName1), // - folder(folderName2), // - file(fileName1), // - file(fileName2))); - - assertThat(folder.children().collect(toList()), - containsInAnyOrder( // - folderWithName(folderName1), // - folderWithName(folderName2), // - fileWithName(fileName1), // - fileWithName(fileName2))); - } - - @Test - public void testFilesDoesContainOnlyFileChildren() throws IOException { - String folderName1 = "folder1"; - String folderName2 = "folder2"; - String fileName1 = "file1"; - String fileName2 = "file2"; - - NioFolder folder = NioFileSystem.rootedAt(testFilesystem( // - folder(folderName1), // - folder(folderName2), // - file(fileName1), // - file(fileName2))); - - assertThat(folder.files().collect(toList()), - containsInAnyOrder( // - fileWithName(fileName1), // - fileWithName(fileName2))); - } - - @Test - public void testFilesOfNonExistingFolderThrowsUncheckedIOExceptionWithAbolutePathOfFolderInMessage() throws IOException { - Path emptyFolderPath = emptyFilesystem(); - NioFolder folder = NioFileSystem.rootedAt(emptyFolderPath); - Files.delete(emptyFolderPath); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(emptyFolderPath.toString()); - - folder.files(); - } - - @Test - public void testFoldersDoesContainOnlyFolderChildren() throws IOException { - String folderName1 = "folder1"; - String folderName2 = "folder2"; - String fileName1 = "file1"; - String fileName2 = "file2"; - - NioFolder folder = NioFileSystem.rootedAt(testFilesystem( // - folder(folderName1), // - folder(folderName2), // - file(fileName1), // - file(fileName2))); - - assertThat(folder.folders().collect(toList()), - containsInAnyOrder( // - folderWithName(folderName1), // - folderWithName(folderName2))); - } - - @Test - public void testFoldersOfNonExistingFolderThrowsUncheckedIOExceptionWithAbolutePathOfFolderInMessage() throws IOException { - Path emptyFilesystemPath = emptyFilesystem(); - NioFolder folder = NioFileSystem.rootedAt(emptyFilesystemPath); - Files.delete(emptyFilesystemPath); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(emptyFilesystemPath.toString()); - - folder.folders(); - } - - @Test - public void testDeleteOfEmptyFolderDeletesIt() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - folder(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - folder.delete(); - - assertThat(filesystemPath.resolve(folderName), doesNotExist()); - } - - @Test - public void testDeleteOfFolderWithChildrenDeletesItAndAllChildrenRecursive() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - folder(folderName), // - folder(folderName + "/subfolder1"), // - file(folderName + "/subfolder1/fileName1"), // - folder(folderName + "/subfolder2"), // - file(folderName + "/fileName1"), // - file(folderName + "/fileName2")); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - folder.delete(); - - assertThat(filesystemPath.resolve(folderName), doesNotExist()); - } - - @Test - public void testDeleteOfNonExistingFolderDoesNothing() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = emptyFilesystem(); - Folder nonExistingFolder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - nonExistingFolder.delete(); - - assertThat(filesystemPath.resolve(folderName), doesNotExist()); - } - - @Test - public void testDeleteOfFolderWhichIsAFileDoesNothing() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - file(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - folder.delete(); - - assertThat(filesystemPath.resolve(folderName), isFile()); - } - - @Test - public void testExistsReturnsTrueForExistingDirectory() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - folder(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - assertThat(folder.exists(), is(true)); - } - - @Test - public void testExistsReturnsFalseForNonExistingDirectory() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = emptyFilesystem(); - Folder nonExistingFolder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - assertThat(nonExistingFolder.exists(), is(false)); - } - - @Test - public void testExistsReturnsFalseForDirectoryWhichIsAFile() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - file(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - assertThat(folder.exists(), is(false)); - } - - @Test - public void testIsAncestorOfWithChildReturnsTrue() throws IOException { - Folder folder = NioFileSystem.rootedAt(emptyFilesystem()).folder("a"); - Folder child = folder.folder("b"); - - assertThat(folder.isAncestorOf(child), is(true)); - } - - @Test - public void testIsAncestorOfWithChildOfChildReturnsTrue() throws IOException { - Folder folder = NioFileSystem.rootedAt(emptyFilesystem()).folder("a"); - Folder child = folder.folder("b"); - File childOfChild = child.file("c"); - - assertThat(folder.isAncestorOf(childOfChild), is(true)); - } - - @Test - public void testIsAncestorOfWithSiblingReturnsFalse() throws IOException { - FileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem()); - Folder folder = fileSystem.folder("a"); - Folder sibling = fileSystem.folder("b"); - - assertThat(folder.isAncestorOf(sibling), is(false)); - } - - @Test - public void testIsAncestorOfWithParentReturnsFalse() throws IOException { - Folder parent = NioFileSystem.rootedAt(emptyFilesystem()).folder("a"); - Folder folder = parent.folder("b"); - - assertThat(folder.isAncestorOf(parent), is(false)); - } - - @Test - public void testLastModifiedOfExistingFolderReturnsLastModifiedDate() throws IOException { - String folderName = "nameOfFolder"; - Instant lastModified = Instant.parse("2015-12-29T15:36:10.00Z"); - Folder folder = NioFileSystem - .rootedAt(testFilesystem( // - folder(folderName).withLastModified(lastModified))) // - .folder(folderName); - - assertThat(folder.lastModified(), is(lastModified)); - } - - @Test - public void testLastModifiedOfNonExistingFolderThrowsUncheckedIOExceptionWithAbsolutePathOfFolder() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = emptyFilesystem(); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve(folderName).toString()); - - folder.lastModified(); - } - - @Test - public void testLastModifiedOfFolderWhichIsAFileThrowsUncheckedIOExceptionWithAbsolutePathOfFolder() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem(file(folderName)); - Folder folder = NioFileSystem // - .rootedAt(filesystemPath) // - .folder(folderName); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve(folderName).toString()); - - folder.lastModified(); - } - - @Test - public void testParentOfDirectChildOfFilesystemReturnsFilesystem() throws IOException { - FileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem()); - Folder folder = fileSystem.folder("aName"); - - assertThat(folder.parent(), presentOptionalWithValueThat(is(sameInstance(fileSystem)))); - } - - @Test - public void testParentOfChildOfFolderReturnsFolder() throws IOException { - Folder folder = NioFileSystem.rootedAt(emptyFilesystem()).folder("aName"); - Folder child = folder.folder("anotherName"); - - assertThat(child.parent(), presentOptionalWithValueThat(is(sameInstance(folder)))); - } - - @Test - public void testMoveToOfFolderToNonExistingFolderMovesFolder() throws IOException { - Path filesystemPath = testFilesystem( // - folder("folderToMove")); - NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Folder folderToMove = fileSystem.folder("folderToMove"); - Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); - - folderToMove.moveTo(folderToMoveTo); - - assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); - assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); - } - - @Test - public void testMoveToOfFolderWithChildrenMovesFolderAndChildren() throws IOException { - Path filesystemPath = testFilesystem( // - folder("folderToMove"), // - folder("folderToMove/subfolder1"), // - folder("folderToMove/subfolder2"), // - file("folderToMove/subfolder1/file1").withData("dataOfFile1"), // - file("folderToMove/file2").withData("dataOfFile2"), // - file("folderToMove/file3").withData("dataOfFile3")); - NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Folder folderToMove = fileSystem.folder("folderToMove"); - Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); - - folderToMove.moveTo(folderToMoveTo); - - assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); - assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo/subfolder1"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo/subfolder2"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo/subfolder1/file1"), isFile().withContent("dataOfFile1")); - assertThat(filesystemPath.resolve("folderToMoveTo/file2"), isFile().withContent("dataOfFile2")); - assertThat(filesystemPath.resolve("folderToMoveTo/file3"), isFile().withContent("dataOfFile3")); - } - - @Test - public void testMoveToOfFolderToExistingFolderReplacesTargetFolder() throws IOException { - Path filesystemPath = testFilesystem( // - folder("folderToMove"), // - file("folderToMove/file1").withData("dataOfFile1"), // - file("folderToMove/file2").withData("dataOfFile2"), // - folder("folderToMoveTo"), // - file("folderToMoveTo/file1").withData("wrongDataOfFile1"), // - file("folderToMoveTo/fileWhichShouldNotExistAfterMove")); - NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Folder folderToMove = fileSystem.folder("folderToMove"); - Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); - - folderToMove.moveTo(folderToMoveTo); - - assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); - assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo/file1"), isFile().withContent("dataOfFile1")); - assertThat(filesystemPath.resolve("folderToMoveTo/file2"), isFile().withContent("dataOfFile2")); - assertThat(filesystemPath.resolve("folderToMoveTo/fileWhichShouldNotExistAfterMove"), doesNotExist()); - } - - @Test - public void testMoveToOfFolderToExistingFileThrowsUncheckedIOExceptionWithAbsolutePathOfTarget() throws IOException { - Path filesystemPath = testFilesystem( // - folder("folderToMove"), // - file("folderToMoveTo")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Folder folderToMove = fileSystem.folder("folderToMove"); - Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve("folderToMoveTo").toString()); - - folderToMove.moveTo(folderToMoveTo); - } - - @Test - public void testMoveToOfFolderToFolderOfAnotherFileSystemDoesNothingAndhrowsIllegalArgumentException() throws IOException { - Path filesystemPath = testFilesystem(folder("folderToMove")); - Folder folderToMove = NioFileSystem.rootedAt(filesystemPath).folder("folderToMove"); - Folder folderToMoveTo = NioFileSystem.rootedAt(filesystemPath).folder("folderToMoveTo"); - - thrown.expect(IllegalArgumentException.class); - - folderToMove.moveTo(folderToMoveTo); - - assertThat(filesystemPath.resolve("folderToMove"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo"), doesNotExist()); } @Test public void testToString() { - Path filesystemPath = emptyFilesystem(); - Path pathOfFolder = filesystemPath.resolve("aFolder").toAbsolutePath(); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder("aFolder"); - - assertThat(folder.toString(), is(format("NioFolder(%s)", pathOfFolder))); + assertThat(inTest.toString(), is(format("NioFolder(%s)", path))); } -} + private Answer answerFrom(Supplier supplier) { + return new Answer() { + @Override + public T answer(InvocationOnMock invocation) throws Throwable { + return supplier.get(); + } + }; + } + +} \ No newline at end of file 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 0b56f1fc0..d83acee82 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 @@ -1,5 +1,6 @@ package org.cryptomator.filesystem.nio; +import static java.lang.String.format; import static org.cryptomator.filesystem.nio.OpenMode.READ; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -13,57 +14,52 @@ import static org.mockito.Mockito.when; import java.io.UncheckedIOException; import java.nio.ByteBuffer; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; +import java.nio.file.Path; +import org.cryptomator.filesystem.FileSystem; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.InOrder; -import org.mockito.Mock; import org.mockito.Mockito; import de.bechte.junit.runners.context.HierarchicalContextRunner; @RunWith(HierarchicalContextRunner.class) -@SuppressWarnings("resource") public class ReadableNioFileTest { @Rule public ExpectedException thrown = ExpectedException.none(); - private NioFile file; + private FileSystem fileSystem; + + private Path path; private SharedFileChannel channel; - private ReadWriteLock lock; + private Runnable afterCloseCallback; - private Lock readLock; + private ReadableNioFile inTest; @Before public void setup() { - file = mock(NioFile.class); + fileSystem = mock(FileSystem.class); + path = mock(Path.class); channel = mock(SharedFileChannel.class); - lock = mock(ReadWriteLock.class); - readLock = mock(Lock.class); + afterCloseCallback = mock(Runnable.class); - when(file.channel()).thenReturn(channel); - when(file.lock()).thenReturn(lock); - when(lock.readLock()).thenReturn(readLock); + inTest = new ReadableNioFile(fileSystem, path, channel, afterCloseCallback); } @Test public void testConstructorInvokesOpenWithReadModeOnChannelOfNioFile() { - new ReadableNioFile(file); - verify(channel).open(READ); } @Test public void testReadFailsIfClosed() { - ReadableNioFile inTest = new ReadableNioFile(file); ByteBuffer irrelevant = null; inTest.close(); @@ -75,7 +71,6 @@ public class ReadableNioFileTest { @Test public void testPositionFailsIfClosed() { - ReadableNioFile inTest = new ReadableNioFile(file); int irrelevant = 1; inTest.close(); @@ -87,7 +82,6 @@ public class ReadableNioFileTest { @Test public void testPositionFailsIfNegative() { - ReadableNioFile inTest = new ReadableNioFile(file); thrown.expect(IllegalArgumentException.class); @@ -96,7 +90,6 @@ public class ReadableNioFileTest { @Test public void testReadDelegatesToChannelReadFullyWithZeroPositionIfNotSet() { - ReadableNioFile inTest = new ReadableNioFile(file); ByteBuffer buffer = mock(ByteBuffer.class); inTest.read(buffer); @@ -106,7 +99,6 @@ public class ReadableNioFileTest { @Test public void testReadDelegatesToChannelReadFullyWithPositionAtEndOfPreviousReadIfInvokedTwice() { - ReadableNioFile inTest = new ReadableNioFile(file); ByteBuffer buffer = mock(ByteBuffer.class); int endOfPreviousRead = 10; when(channel.readFully(0, buffer)).thenReturn(endOfPreviousRead); @@ -120,7 +112,6 @@ public class ReadableNioFileTest { @Test public void testReadDelegatesToChannelReadFullyWithPositionUnchangedIfPreviousReadReturnedEof() { - ReadableNioFile inTest = new ReadableNioFile(file); ByteBuffer buffer = mock(ByteBuffer.class); when(channel.readFully(0, buffer)).thenReturn(SharedFileChannel.EOF); @@ -132,7 +123,6 @@ public class ReadableNioFileTest { @Test public void testReadDelegatesToChannelReadFullyWithSetPosition() { - ReadableNioFile inTest = new ReadableNioFile(file); ByteBuffer buffer = mock(ByteBuffer.class); int position = 10; inTest.position(position); @@ -144,7 +134,6 @@ public class ReadableNioFileTest { @Test public void testReadReturnsValueOfChannelReadFully() { - ReadableNioFile inTest = new ReadableNioFile(file); ByteBuffer buffer = mock(ByteBuffer.class); int expectedResult = 37028; when(channel.readFully(0, buffer)).thenReturn(expectedResult); @@ -156,7 +145,6 @@ public class ReadableNioFileTest { @Test public void testReadDoesNotModifyBuffer() { - ReadableNioFile inTest = new ReadableNioFile(file); ByteBuffer buffer = mock(ByteBuffer.class); inTest.read(buffer); @@ -166,68 +154,56 @@ public class ReadableNioFileTest { public class CopyTo { - @Mock - private NioFile otherFile; + private WritableNioFile target; - @Mock - private WritableNioFile writableOtherFile; - - @Mock - private SharedFileChannel otherChannel; + private SharedFileChannel channelOfTarget; @Before public void setup() { - otherFile = mock(NioFile.class); - writableOtherFile = mock(WritableNioFile.class); - otherChannel = mock(SharedFileChannel.class); - - when(writableOtherFile.nioFile()).thenReturn(otherFile); - when(writableOtherFile.channel()).thenReturn(otherChannel); + target = mock(WritableNioFile.class); + channelOfTarget = mock(SharedFileChannel.class); + when(target.fileSystem()).thenReturn(fileSystem); + when(target.channel()).thenReturn(channelOfTarget); } @Test public void testCopyToFailsIfTargetBelongsToOtherFileSystem() { - ReadableNioFile inTest = new ReadableNioFile(file); - when(otherFile.belongsToSameFilesystem(file)).thenReturn(false); + WritableNioFile targetFromOtherFileSystem = mock(WritableNioFile.class); + when(targetFromOtherFileSystem.fileSystem()).thenReturn(mock(FileSystem.class)); thrown.expect(IllegalArgumentException.class); - inTest.copyTo(writableOtherFile); + inTest.copyTo(targetFromOtherFileSystem); } @Test public void testCopyToFailsIfSourceIsClosed() { - ReadableNioFile inTest = new ReadableNioFile(file); - when(otherFile.belongsToSameFilesystem(file)).thenReturn(true); + when(target.fileSystem()).thenReturn(fileSystem); inTest.close(); thrown.expect(UncheckedIOException.class); thrown.expectMessage("already closed"); - inTest.copyTo(writableOtherFile); + inTest.copyTo(target); } @Test public void testCopyToAssertsThatTargetIsOpenEnsuresTargetChannelIsOpenTuncatesItAndTransfersDataFromSourceChannel() { - ReadableNioFile inTest = new ReadableNioFile(file); - when(otherFile.belongsToSameFilesystem(file)).thenReturn(true); long sizeOfSourceChannel = 3283; when(channel.size()).thenReturn(sizeOfSourceChannel); - when(channel.transferTo(0, sizeOfSourceChannel, otherChannel, 0)).thenReturn(sizeOfSourceChannel); + when(channel.transferTo(0, sizeOfSourceChannel, channelOfTarget, 0)).thenReturn(sizeOfSourceChannel); - inTest.copyTo(writableOtherFile); + inTest.copyTo(target); - InOrder inOrder = inOrder(writableOtherFile, otherChannel, channel); - inOrder.verify(writableOtherFile).assertOpen(); - inOrder.verify(writableOtherFile).ensureChannelIsOpened(); - inOrder.verify(otherChannel).truncate(0); - inOrder.verify(channel).transferTo(0, sizeOfSourceChannel, otherChannel, 0); + InOrder inOrder = inOrder(target, channel, channelOfTarget); + inOrder.verify(target).assertOpen(); + inOrder.verify(target).ensureChannelIsOpened(); + inOrder.verify(channelOfTarget).truncate(0); + inOrder.verify(channel).transferTo(0, sizeOfSourceChannel, channelOfTarget, 0); } @Test public void testCopyToInvokesTransferToUntilAllBytesHaveBeenTransferred() { - ReadableNioFile inTest = new ReadableNioFile(file); - when(otherFile.belongsToSameFilesystem(file)).thenReturn(true); long firstTransferAmount = 100; long secondTransferAmount = 300; long thirdTransferAmount = 500; @@ -235,30 +211,27 @@ public class ReadableNioFileTest { long sizeRemainingAfterFirstTransfer = sizeRemainingAfterSecondTransfer + secondTransferAmount; long size = sizeRemainingAfterFirstTransfer + firstTransferAmount; when(channel.size()).thenReturn(size); - 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); + when(channel.transferTo(0, size, channelOfTarget, 0)).thenReturn(firstTransferAmount); + when(channel.transferTo(firstTransferAmount, sizeRemainingAfterFirstTransfer, channelOfTarget, firstTransferAmount)).thenReturn(secondTransferAmount); + when(channel.transferTo(firstTransferAmount + secondTransferAmount, sizeRemainingAfterSecondTransfer, channelOfTarget, firstTransferAmount + secondTransferAmount)).thenReturn(thirdTransferAmount); - inTest.copyTo(writableOtherFile); + inTest.copyTo(target); InOrder inOrder = inOrder(channel); - 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); + inOrder.verify(channel).transferTo(0, size, channelOfTarget, 0); + inOrder.verify(channel).transferTo(firstTransferAmount, sizeRemainingAfterFirstTransfer, channelOfTarget, firstTransferAmount); + inOrder.verify(channel).transferTo(firstTransferAmount + secondTransferAmount, sizeRemainingAfterSecondTransfer, channelOfTarget, firstTransferAmount + secondTransferAmount); } } @Test public void testIsOpenReturnsTrueForNewReadableNioFile() { - ReadableNioFile inTest = new ReadableNioFile(file); - assertThat(inTest.isOpen(), is(true)); } @Test public void testIsOpenReturnsFalseForClosed() { - ReadableNioFile inTest = new ReadableNioFile(file); inTest.close(); assertThat(inTest.isOpen(), is(false)); @@ -266,30 +239,25 @@ public class ReadableNioFileTest { @Test public void testCloseClosesChannelAndUnlocksReadLock() { - ReadableNioFile inTest = new ReadableNioFile(file); - inTest.close(); - InOrder inOrder = Mockito.inOrder(channel, readLock); + InOrder inOrder = Mockito.inOrder(channel, afterCloseCallback); inOrder.verify(channel).close(); - inOrder.verify(readLock).unlock(); + inOrder.verify(afterCloseCallback).run(); } @Test public void testCloseClosesChannelAndUnlocksReadLockOnlyOnceIfInvokedTwice() { - ReadableNioFile inTest = new ReadableNioFile(file); - inTest.close(); inTest.close(); - InOrder inOrder = Mockito.inOrder(channel, readLock); + InOrder inOrder = Mockito.inOrder(channel, afterCloseCallback); inOrder.verify(channel).close(); - inOrder.verify(readLock).unlock(); + inOrder.verify(afterCloseCallback).run(); } @Test public void testCloseUnlocksReadLockEvenIfCloseFails() { - ReadableNioFile inTest = new ReadableNioFile(file); String message = "exceptionMessage"; doThrow(new RuntimeException(message)).when(channel).close(); @@ -298,16 +266,13 @@ public class ReadableNioFileTest { try { inTest.close(); } finally { - verify(readLock).unlock(); + verify(afterCloseCallback).run(); } } @Test public void testToString() { - String nioFileToString = file.toString(); - ReadableNioFile inTest = new ReadableNioFile(file); - - assertThat(inTest.toString(), is("Readable" + nioFileToString)); + assertThat(inTest.toString(), is(format("ReadableNioFile(%s)", path))); } } diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReflectiveClassMatchers.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReflectiveClassMatchers.java new file mode 100644 index 000000000..c5b4f2bbe --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReflectiveClassMatchers.java @@ -0,0 +1,79 @@ +package org.cryptomator.filesystem.nio; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +public class ReflectiveClassMatchers { + + public static Matcher> aClassThatDoesNotDeclareMethod(String name, Class... parameterTypes) { + return new TypeSafeDiagnosingMatcher>(Class.class) { + @Override + public void describeTo(Description description) { + description // + .appendText(" a Class that does not declare method ") // + .appendText(name) // + .appendValueList("(", ", ", ")", parameterTypes); + } + + @Override + protected boolean matchesSafely(Class item, Description mismatchDescription) { + if (declaredMethodToCheck(item)) { + mismatchDescription // + .appendText(" a Class that declares method ") // + .appendText(name) // + .appendValueList("(", ", ", ")", parameterTypes); + return false; + } else { + return true; + } + } + + private boolean declaredMethodToCheck(Class item) { + try { + return item.getDeclaredMethod(name, parameterTypes) != null; + } catch (NoSuchMethodException e) { + return false; + } catch (SecurityException e) { + throw new RuntimeException(e); + } + } + }; + } + + public static Matcher> aClassThatDoesDeclareMethod(String name, Class... parameterTypes) { + return new TypeSafeDiagnosingMatcher>(Class.class) { + @Override + public void describeTo(Description description) { + description // + .appendText(" a Class that does declare method ") // + .appendText(name) // + .appendValueList("(", ", ", ")", parameterTypes); + } + + @Override + protected boolean matchesSafely(Class item, Description mismatchDescription) { + if (declaredMethodToCheck(item)) { + return true; + } else { + mismatchDescription // + .appendText(" a Class that does not declare method ") // + .appendText(name) // + .appendValueList("(", ", ", ")", parameterTypes); + return false; + } + } + + private boolean declaredMethodToCheck(Class item) { + try { + return item.getDeclaredMethod(name, parameterTypes) != null; + } catch (NoSuchMethodException e) { + return false; + } catch (SecurityException e) { + throw new RuntimeException(e); + } + } + }; + } + +} 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 40f0e1beb..6a0d9e2c4 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 @@ -1,376 +1,771 @@ 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 java.lang.String.format; import static org.cryptomator.filesystem.nio.SharedFileChannel.EOF; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.ByteBuffer; -import java.nio.file.Files; +import java.nio.channels.FileChannel; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; -import javax.xml.ws.Holder; - -import org.junit.After; +import org.cryptomator.common.Holder; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.rules.Timeout; import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; 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 Path path; + private NioAccess nioAccess; 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 void setUp() { + path = mock(Path.class); + nioAccess = mock(NioAccess.class); + inTest = new SharedFileChannel(path, nioAccess); } - public class ReadFully { + public class Open { + + @Test + public void testOpenFailsIfPathIsADirectory() { + when(nioAccess.isDirectory(path)).thenReturn(true); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(format("%s is a directory", path)); + + inTest.open(OpenMode.WRITE); + } + + @Test + public void testOpenFailsIfFileDoesNotExistAndOpenModeIsRead() { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(false); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(format("%s does not exist", path)); - @Before - public void setup() { inTest.open(OpenMode.READ); } @Test - public void testReadFullyReadsFileContents() { - byte[] result = new byte[FILE_CONTENT.length]; - ByteBuffer buffer = ByteBuffer.wrap(result); + public void testOpenOpensAChannelIfOpenModeIsReadAndFileExists() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(true); - int read = inTest.readFully(0, buffer); + inTest.open(OpenMode.READ); - assertThat(result, is(FILE_CONTENT)); - assertThat(read, is(FILE_CONTENT.length)); + verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); } @Test - public void testReadFullyReadsFromPositionContents() { - int position = 5; - byte[] result = new byte[FILE_CONTENT.length - position]; - ByteBuffer buffer = ByteBuffer.wrap(result); + public void testOpenIfClosedOpensAChannelIfChannelIsNotOpenOpenModeIsReadAndFileExists() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(true); - int read = inTest.readFully(position, buffer); + inTest.openIfClosed(OpenMode.READ); - assertThat(result, is(copyOfRange(FILE_CONTENT, position, FILE_CONTENT.length))); - assertThat(read, is(FILE_CONTENT.length - position)); + verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); } @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); + public void testOpenOpensAChannelIfOpenModeIsWriteAndFileExists() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(true); - int read = inTest.readFully(position, buffer); + inTest.open(OpenMode.WRITE); - assertThat(result, is(expected)); - assertThat(read, is(FILE_CONTENT.length - position)); + verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); } @Test - public void testReadReadsNothingIfBufferHasNoSpaceAvailabe() { - ByteBuffer buffer = ByteBuffer.allocate(5); - buffer.position(5); + public void testOpenOpensAChannelIfOpenModeIsWriteAndFileDoesNotExist() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(false); - int read = inTest.readFully(0, buffer); + inTest.open(OpenMode.WRITE); - assertThat(buffer.array(), is(new byte[5])); - assertThat(read, is(0)); + verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); } @Test - public void testReadReadsNothingIfPositionIsEndOfFile() { - ByteBuffer buffer = ByteBuffer.allocate(5); + public void testOpenWrapsExceptionsFromOpeningChannelInUncheckedIOExceptions() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(false); + IOException exceptionFromOpeningChannel = new IOException(); + when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenThrow(exceptionFromOpeningChannel); - int read = inTest.readFully(FILE_CONTENT.length, buffer); + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromOpeningChannel)); - assertThat(buffer.array(), is(new byte[5])); - assertThat(read, is(EOF)); + inTest.open(OpenMode.WRITE); } @Test - public void testReadReadsNothingIfAfterEndOfFile() { - ByteBuffer buffer = ByteBuffer.allocate(5); + public void testOpenFailsIfInvokedTwiceBeforeClose() { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(false); - int read = inTest.readFully(FILE_CONTENT.length + 10, buffer); + inTest.open(OpenMode.WRITE); - assertThat(buffer.array(), is(new byte[5])); - assertThat(read, is(EOF)); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("already open for current thread"); + + inTest.open(OpenMode.WRITE); } @Test - public void testReadThrowsIllegalArgumentExceptionIfPositionIsNegative() { - thrown.expect(IllegalArgumentException.class); + public void testOpenIfClosedDoesDoNothingIfInvokedOnOpenChannel() { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(false); - inTest.readFully(-1, ByteBuffer.allocate(0)); + 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); + when(nioAccess.isRegularFile(path)).thenReturn(false); + when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(mock(FileChannel.class)); + + inThreadRethrowingException(() -> inTest.open(OpenMode.WRITE)); + inThreadRethrowingException(() -> inTest.open(OpenMode.WRITE)); + + verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); + } + + } + + public class Close { + + @Test + public void testCloseIfNotOpenFails() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("closed for current thread"); + + inTest.close(); + } + + @Test + public void testCloseIfOpenDoesNothingIfNotOpen() { + inTest.closeIfOpen(); + } + + @Test + public void testCloseIfClosedFails() 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(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("closed for current thread"); + + inTest.close(); + } + + @Test + public void testCloseForcesAndClosesChannel() 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.close(); + + InOrder inOrder = inOrder(channel, nioAccess); + inOrder.verify(channel).force(true); + inOrder.verify(nioAccess).close(channel); + } + + @Test + public void testCloseWrapsIOExceptionFromForceInUncheckedIOExceptionAndStillClosesChannel() 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); + IOException exceptionFromForce = new IOException(); + doThrow(exceptionFromForce).when(channel).force(true); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromForce)); + + try { + inTest.close(); + } finally { + verify(nioAccess).close(channel); + } + } + + @Test + public void testCloseWrapsIOExceptionFromCloseInUncheckedIOException() 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); + IOException exceptionFromClose = new IOException(); + doThrow(exceptionFromClose).when(nioAccess).close(channel); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromClose)); + + 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); + 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); + inThreadRethrowingException(() -> inTest.open(OpenMode.WRITE)); + + inTest.close(); + + verify(nioAccess, never()).close(channel); + } + + @Test + public void testLastCloseDoesCloseChannelIfOpenedTwice() 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); + inThreadRethrowingException(() -> { + inTest.open(OpenMode.WRITE); + inTest.close(); + }); + + inTest.close(); + + verify(nioAccess).close(channel); + } + + } + + public class ReadFully { + + @Rule + public Timeout timeoutRule = Timeout.seconds(1); + + private FileChannel channel; + + @Before + public void setUp() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(true); + channel = mock(FileChannel.class); + when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); + inTest.open(OpenMode.READ); + } + + @Test + public void testReadFullyWrapsExceptionFromReadInUncheckedIOException() throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(0); + IOException exceptionFromRead = new IOException(); + when(channel.read(buffer, 0)).thenThrow(exceptionFromRead); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromRead)); + + inTest.readFully(0, buffer); + } + + @Test + public void testReadFullyDelegatesToChannelRead() throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(50); + when(channel.read(buffer, 0)).thenAnswer(new Answer() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + buffer.position(50); + return 50; + } + }); + + int result = inTest.readFully(0, buffer); + + assertThat(result, is(50)); + verify(channel).read(buffer, 0); + verifyNoMoreInteractions(channel); + } + + @Test + public void testReadFullyReturnsEofWhenFirstReadReturnsIt() throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(50); + when(channel.read(buffer, 0)).thenReturn(EOF); + + int result = inTest.readFully(0, buffer); + + assertThat(result, is(EOF)); + verify(channel).read(buffer, 0); + verifyNoMoreInteractions(channel); + } + + @Test + public void testReadStopsReadingIfEofIsReached() throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(50); + when(channel.read(buffer, 0)).thenAnswer(simulateRead(20, buffer)); + when(channel.read(buffer, 20)).thenReturn(EOF); + + int result = inTest.readFully(0, buffer); + + assertThat(result, is(20)); + verify(channel).read(buffer, 0); + verify(channel).read(buffer, 20); + verifyNoMoreInteractions(channel); + } + + @Test + public void testReadFullyInvokesReadUntilBufferIsFull() throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(50); + when(channel.read(buffer, 0)).then(simulateRead(20, buffer)); + when(channel.read(buffer, 20)).then(simulateRead(20, buffer)); + when(channel.read(buffer, 40)).then(simulateRead(10, buffer)); + + int result = inTest.readFully(0, buffer); + + assertThat(result, is(50)); + verify(channel).read(buffer, 0); + verify(channel).read(buffer, 20); + verify(channel).read(buffer, 40); + verifyNoMoreInteractions(channel); + } + + private Answer simulateRead(int amount, ByteBuffer target) { + return new Answer() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + target.position(target.position() + amount); + return amount; + } + }; } } public class Truncate { + private FileChannel channel; + @Before - public void setup() { + public void setUp() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(true); + channel = mock(FileChannel.class); + when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); inTest.open(OpenMode.WRITE); } @Test - public void testTruncateToZeroDeletesFileContent() { - inTest.truncate(0); - inTest.close(); + public void testTruncateDelegatesToChannelTruncate() throws IOException { + int truncateTo = 32; - assertThat(pathOfTestfile, isFile().thatIsEmpty()); + inTest.truncate(truncateTo); + + verify(channel).truncate(truncateTo); } @Test - public void testTruncateToSmallerSizeTruncatesFile() { - inTest.truncate(5); - inTest.close(); + public void testTruncateWrapsIOExceptionInUncheckedIOException() throws IOException { + int truncateTo = 32; + IOException exceptionFromTruncate = new IOException(); + when(channel.truncate(truncateTo)).thenThrow(exceptionFromTruncate); - assertThat(pathOfTestfile, isFile().withContent(copyOfRange(FILE_CONTENT, 0, 5))); + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromTruncate)); + + inTest.truncate(truncateTo); } - @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); + public class Size { + + private FileChannel channel; + + @Before + public void setUp() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(true); + channel = mock(FileChannel.class); + when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); + inTest.open(OpenMode.WRITE); + } + + @Test + public void testSizeDelegatesToChannelSize() throws IOException { + long expectedSize = 832; + when(channel.size()).thenReturn(expectedSize); + + long result = inTest.size(); + + assertThat(result, is(expectedSize)); + } + + @Test + public void testSizeWrapsIOExceptionInUncheckedIOException() throws IOException { + IOException exceptionFromSize = new IOException(); + when(channel.size()).thenThrow(exceptionFromSize); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromSize)); + + inTest.size(); + } - assertThat(inTest.size(), is((long) FILE_CONTENT.length)); } public class TransferTo { + private FileChannel channel; + + private Path targetPath; + private SharedFileChannel targetInTest; + private FileChannel targetChannel; + @Before - public void setup() { - inTest.open(OpenMode.READ); - otherInTest.open(OpenMode.WRITE); + public void setUp() throws IOException { + targetPath = mock(Path.class); + targetInTest = new SharedFileChannel(targetPath, nioAccess); + + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(true); + channel = mock(FileChannel.class); + when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); + inTest.open(OpenMode.WRITE); + + when(nioAccess.isDirectory(targetPath)).thenReturn(false); + when(nioAccess.isRegularFile(targetPath)).thenReturn(true); + targetChannel = mock(FileChannel.class); + when(nioAccess.open(targetPath, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(targetChannel); + targetInTest.open(OpenMode.WRITE); } @Test - public void testTransferToCopiesSelectedRegionToTargetPosition() { - // TODO Markus Kreusch + public void testTransferToThrowsIllegalArugmentExceptionIfCountIsNegative() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Count must not be negative"); + + inTest.transferTo(0, -1, targetInTest, 0); } @Test - public void testTransferToDoesNothingIfCountIsZero() { - // TODO Markus Kreusch + public void testTransferToSetsPositionOfTargetChannelAndThenDelegatesToChannelsTransferTo() throws IOException { + long targetPosition = 43L; + long startPosition = 22L; + long count = 39L; + when(channel.transferTo(startPosition, count, targetChannel)).thenReturn(count); + when(channel.size()).thenReturn(startPosition + count); + + long result = inTest.transferTo(startPosition, count, targetInTest, targetPosition); + + assertThat(result, is(count)); + InOrder inOrder = inOrder(channel, targetChannel); + inOrder.verify(targetChannel).position(targetPosition); + inOrder.verify(channel).transferTo(startPosition, count, targetChannel); } @Test - public void testTransferToTransfersLessThanCountBytesIfEndOfSourceFileIsReached() { - // TODO Markus Kreusch + public void testTransferToInvokesTransferUntilAllBytesHaveBeenTransferred() throws IOException { + long targetPosition = 43L; + long startPosition = 22L; + long count = 39L; + long firstTransferCount = 10L; + long secondTransferCount = 7L; + long thridTransferCount = count - firstTransferCount - secondTransferCount; + when(channel.transferTo(startPosition, count, targetChannel)).thenReturn(firstTransferCount); + when(channel.transferTo(startPosition + firstTransferCount, count - firstTransferCount, targetChannel)).thenReturn(secondTransferCount); + when(channel.transferTo(startPosition + firstTransferCount + secondTransferCount, thridTransferCount, targetChannel)).thenReturn(thridTransferCount); + when(channel.size()).thenReturn(startPosition + count); + + long result = inTest.transferTo(startPosition, count, targetInTest, targetPosition); + + assertThat(result, is(count)); + InOrder inOrder = inOrder(channel, targetChannel); + inOrder.verify(targetChannel).position(targetPosition); + inOrder.verify(channel).transferTo(startPosition, count, targetChannel); + inOrder.verify(channel).transferTo(startPosition + firstTransferCount, count - firstTransferCount, targetChannel); + inOrder.verify(channel).transferTo(startPosition + firstTransferCount + secondTransferCount, thridTransferCount, targetChannel); } @Test - public void testTransferToThrowsIllegalArgumentExceptionIfPositionIsSmallerZero() { - // TODO Markus Kreusch + public void testTransferToStopsTransferAtEndOfSourceFile() throws IOException { + long targetPosition = 43L; + long startPosition = 22L; + long count = 39L; + long countAvailable = 30L; + when(channel.transferTo(startPosition, countAvailable, targetChannel)).thenReturn(countAvailable); + when(channel.size()).thenReturn(startPosition + countAvailable); + + long result = inTest.transferTo(startPosition, count, targetInTest, targetPosition); + + assertThat(result, is(countAvailable)); + InOrder inOrder = inOrder(channel, targetChannel); + inOrder.verify(targetChannel).position(targetPosition); + inOrder.verify(channel).transferTo(startPosition, countAvailable, targetChannel); } @Test - public void testTransferToThrowsIllegalArgumentExceptionIfCountIsSmallerZero() { - // TODO Markus Kreusch + public void testTransferToWrapsIOExceptionFromPositionInUncheckedIOException() throws IOException { + when(channel.size()).thenReturn(Long.MAX_VALUE); + IOException exceptionFromPosition = new IOException(); + when(targetChannel.position(anyLong())).thenThrow(exceptionFromPosition); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromPosition)); + + inTest.transferTo(0L, 10L, targetInTest, 0L); } @Test - public void testTransferToThrowsIllegalArgumentExceptionIfTargetPositionIsSmallerZero() { - // TODO Markus Kreusch - } + public void testTransferToWrapsIOExceptionFromTransferToInUncheckedIOException() throws IOException { + when(channel.size()).thenReturn(Long.MAX_VALUE); + IOException exceptionFromTransferTo = new IOException(); + when(channel.transferTo(anyLong(), anyLong(), any())).thenThrow(exceptionFromTransferTo); - @Test - public void testTransferToTransfersSelectedRegionToTargetAfterEndOfFile() { - // TODO Markus Kreusch + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromTransferTo)); + + inTest.transferTo(0L, 10L, targetInTest, 0L); } } public class WriteFully { + @Rule + public Timeout timeoutRule = Timeout.seconds(1); + + private FileChannel channel; + @Before - public void setup() { + public void setUp() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isRegularFile(path)).thenReturn(true); + channel = mock(FileChannel.class); + when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); inTest.open(OpenMode.WRITE); } @Test - public void testWriteFullyWritesFileContents() { - // TODO Markus Kreusch + public void testWriteFullyWrapsIOExceptionFromWriteIntoUncheckedIOException() throws IOException { + int count = 1; + int position = 0; + ByteBuffer buffer = ByteBuffer.allocate(count); + IOException exceptionFromWrite = new IOException(); + when(channel.write(buffer, position)).thenThrow(exceptionFromWrite); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromWrite)); + + inTest.writeFully(position, buffer); } @Test - public void testWriteFullyWritesContentsToPosition() { - // TODO Markus Kreusch + public void testWriteFullyDelegatesToChannelsWrite() throws IOException { + int count = 50; + int position = 31; + ByteBuffer buffer = ByteBuffer.allocate(count); + when(channel.write(buffer, position)).then(simulateWrite(count, buffer)); + + int result = inTest.writeFully(position, buffer); + + assertThat(result, is(count)); + verify(channel).write(buffer, position); } @Test - public void testWriteFullyWritesContentsEvenIfPositionIsGreaterCurrentEndOfFile() { - // TODO Markus Kreusch + public void testWriteFullyDelegatesToWriteUntilAllBytesFromBufferHaveBeenWritten() throws IOException { + int count = 50; + int countOfFirstWrite = 10; + int countOfSecondWrite = 15; + int countOfThridWrite = count - countOfFirstWrite - countOfSecondWrite; + int position = 31; + ByteBuffer buffer = ByteBuffer.allocate(count); + when(channel.write(buffer, position)).then(simulateWrite(countOfFirstWrite, buffer)); + when(channel.write(buffer, position + countOfFirstWrite)).then(simulateWrite(countOfSecondWrite, buffer)); + when(channel.write(buffer, position + countOfFirstWrite + countOfSecondWrite)).then(simulateWrite(countOfThridWrite, buffer)); + + int result = inTest.writeFully(position, buffer); + + assertThat(result, is(count)); + verify(channel).write(buffer, position); + verify(channel).write(buffer, position + countOfFirstWrite); + verify(channel).write(buffer, position + countOfFirstWrite + countOfSecondWrite); } @Test - public void testWriteWritesNothingBufferHasNothingAvailabe() { - // TODO Markus Kreusch + public void testWriteFullyDelegatesToWriteASingleTimeEvenIfBytesHasNotBytesRemaing() throws IOException { + int count = 0; + int position = 31; + ByteBuffer buffer = ByteBuffer.allocate(count); + when(channel.write(buffer, position)).then(simulateWrite(count, buffer)); + + int result = inTest.writeFully(position, buffer); + + assertThat(result, is(count)); + verify(channel).write(buffer, position); } - @Test - public void testWriteThrowsIllegalArgumentExceptionIfPositionIsNegative() { - thrown.expect(IllegalArgumentException.class); - - inTest.writeFully(-1, ByteBuffer.allocate(0)); + private Answer simulateWrite(int amount, ByteBuffer target) { + return new Answer() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + target.position(target.position() + amount); + return amount; + } + }; } } - public class MultipleOpenInvocation { + public class OperationsFailingIfClosed { @Test - public void testInvokeOpenMultipleTimesFromSameThreadThrowsException() { - inTest.open(OpenMode.READ); + public void testReadFullyFailsIfNotOpen() { + ByteBuffer irrelevant = null; thrown.expect(IllegalStateException.class); - thrown.expectMessage("A thread can only open a SharedFileChannel once"); + thrown.expectMessage("closed for current thread"); - inTest.open(OpenMode.READ); + inTest.readFully(0, irrelevant); } @Test - public void testInvokeOpenMultipleTimesFromDifferentThreadsWorks() { - inTest.open(OpenMode.READ); - inThreadRethrowingExceptions(() -> inTest.open(OpenMode.READ)); - } - - } - - public class NonOpenChannel { - - @Test - public void testClosingNonOpenChannelThrowsException() { + public void testTruncateFailsIfNotOpen() { 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"); + thrown.expectMessage("closed for current thread"); inTest.truncate(0); } @Test - public void testInvokingSizeOnNonOpenChannelThrowsException() { + public void testSizeFailsIfNotOpen() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel closed for current thread"); + thrown.expectMessage("closed for current thread"); inTest.size(); } @Test - public void testInvokingTransferToOnNonOpenChannelThrowsException() { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel closed for current thread"); + public void testTransferToFailsIfSourceNotOpen() { + SharedFileChannel irrelevant = null; - inTest.transferTo(0, 1, null, 0); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("closed for current thread"); + + inTest.transferTo(0, 0, irrelevant, 0); } @Test - public void testInvokingTransferToPassingNonOpenChannelThrowsException() { + public void testTransferToFailsIfTargetNotOpen() { + Path targetPath = mock(Path.class); + SharedFileChannel targetInTest = new SharedFileChannel(targetPath, nioAccess); + inTest.open(OpenMode.WRITE); + thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel closed for current thread"); + thrown.expectMessage("closed for current thread"); - inTest.open(OpenMode.READ); - - inTest.transferTo(0, 1, otherInTest, 0); + inTest.transferTo(0, 0, targetInTest, 0); } @Test - public void testInvokinWriteFullyOnNonOpenChannelThrowsException() { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel closed for current thread"); + public void testWriteFullyFailsIfNotOpen() { + ByteBuffer irrelevant = null; - inTest.writeFully(0, null); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("closed for current thread"); + + inTest.writeFully(0, irrelevant); } } - @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<>(); + private void inThreadRethrowingException(Runnable task) { + Holder exception = new Holder<>(null); Thread thread = new Thread(() -> { try { task.run(); - } catch (RuntimeException e) { - holder.value = e; + } catch (Throwable e) { + exception.set(e); } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } - if (holder.value != null) { - throw holder.value; + rethrowUnchecked(exception.get()); + } + + private void rethrowUnchecked(Throwable exception) { + if (exception instanceof RuntimeException) { + throw (RuntimeException) exception; + } else if (exception instanceof Error) { + throw (Error) exception; + } else if (exception != null) { + throw new RuntimeException(exception); } } 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 44dcf3ee2..6d55e6b64 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 @@ -1,5 +1,7 @@ package org.cryptomator.filesystem.nio; +import static java.lang.String.format; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.cryptomator.filesystem.nio.OpenMode.WRITE; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -11,11 +13,15 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.Instant; +import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.WritableFile; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -26,228 +32,472 @@ import org.mockito.InOrder; import de.bechte.junit.runners.context.HierarchicalContextRunner; @RunWith(HierarchicalContextRunner.class) -@SuppressWarnings("resource") public class WritableNioFileTest { @Rule public ExpectedException thrown = ExpectedException.none(); - private NioFile file; + private FileSystem fileSystem; + + private Path path; private SharedFileChannel channel; - private ReadWriteLock lock; + private Runnable afterCloseCallback; - private Lock writeLock; + private NioAccess nioAccess; + + private WritableNioFile inTest; @Before public void setup() { - file = mock(NioFile.class); + fileSystem = mock(FileSystem.class); channel = mock(SharedFileChannel.class); - lock = mock(ReadWriteLock.class); - writeLock = mock(Lock.class); - - when(file.channel()).thenReturn(channel); - when(file.lock()).thenReturn(lock); - when(lock.writeLock()).thenReturn(writeLock); + path = mock(Path.class); + afterCloseCallback = mock(Runnable.class); + nioAccess = mock(NioAccess.class); + inTest = new WritableNioFile(fileSystem, path, channel, afterCloseCallback, nioAccess); } - @Test - public void testWriteFailsIfClosed() { - WritableNioFile inTest = new WritableNioFile(file); - inTest.close(); - ByteBuffer irrelevant = null; + public class ConstructorTests { - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("already closed"); - - inTest.write(irrelevant); - } - - @Test - public void testPositionFailsIfClosed() { - WritableNioFile inTest = new WritableNioFile(file); - inTest.close(); - long irrelevant = 1023; - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("already closed"); - - inTest.position(irrelevant); - } - - @Test - public void testIsOpenReturnsTrueForNewInstance() { - WritableNioFile inTest = new WritableNioFile(file); - - assertThat(inTest.isOpen(), is(true)); - } - - @Test - public void testIsOpenReturnsFalseForClosedInstance() { - WritableNioFile inTest = new WritableNioFile(file); - inTest.close(); - - assertThat(inTest.isOpen(), is(false)); - } - - @Test - public void testWriteInvokesChannelsOpenWithModeWriteIfInvokedForTheFirstTimeBeforeInvokingWriteFully() { - WritableNioFile inTest = new WritableNioFile(file); - ByteBuffer irrelevant = null; - - inTest.write(irrelevant); - - InOrder inOrder = inOrder(channel); - inOrder.verify(channel).open(WRITE); - inOrder.verify(channel).writeFully(0, irrelevant); - } - - @Test - public void testWriteDoesNotModifyBuffer() { - WritableNioFile inTest = new WritableNioFile(file); - ByteBuffer buffer = mock(ByteBuffer.class); - - inTest.write(buffer); - - verifyZeroInteractions(buffer); - } - - @Test - public void testWriteInvokesWriteFullyWithZeroPositionIfNotSet() { - WritableNioFile inTest = new WritableNioFile(file); - ByteBuffer buffer = mock(ByteBuffer.class); - - inTest.write(buffer); - - verify(channel).writeFully(0, buffer); - } - - @Test - public void testWriteInvokesWriteFullyWithSetPosition() { - WritableNioFile inTest = new WritableNioFile(file); - ByteBuffer buffer = mock(ByteBuffer.class); - long position = 10; - inTest.position(position); - - inTest.write(buffer); - - verify(channel).writeFully(position, buffer); - } - - @Test - public void testWriteInvokesWriteFullyWithEndOfPreviousWriteIfInvokedTwice() { - WritableNioFile inTest = new WritableNioFile(file); - ByteBuffer buffer = mock(ByteBuffer.class); - int endOfPreviousWrite = 10; - when(channel.writeFully(0, buffer)).thenReturn(endOfPreviousWrite); - - inTest.write(buffer); - inTest.write(buffer); - - verify(channel).writeFully(endOfPreviousWrite, buffer); - } - - @Test - public void testWriteReturnsResultOfWriteFully() { - WritableNioFile inTest = new WritableNioFile(file); - ByteBuffer buffer = mock(ByteBuffer.class); - int resultOfWriteFully = 14; - when(channel.writeFully(0, buffer)).thenReturn(resultOfWriteFully); - - int result = inTest.write(buffer); - - 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 testConstructorSetsFileSystem() { + assertThat(inTest.fileSystem(), is(fileSystem)); } + + @Test + public void testConstructorSetsPath() { + assertThat(inTest.path(), is(path)); + } + + @Test + public void testConstructorSetsChannel() { + assertThat(inTest.channel(), is(channel)); + } + + } + + public class WriteTests { + + @Test + public void testWriteOpensChannelIfClosedBeforeInvokingWriteFully() { + ByteBuffer irrelevant = null; + + inTest.write(irrelevant); + + InOrder inOrder = inOrder(channel); + inOrder.verify(channel).openIfClosed(WRITE); + inOrder.verify(channel).writeFully(0, irrelevant); + } + + @Test + public void testWriteDoesNotModifyBuffer() { + ByteBuffer buffer = mock(ByteBuffer.class); + + inTest.write(buffer); + + verifyZeroInteractions(buffer); + } + + @Test + public void testWriteInvokesWriteFullyWithZeroPositionIfNotSet() { + ByteBuffer buffer = mock(ByteBuffer.class); + + inTest.write(buffer); + + verify(channel).writeFully(0, buffer); + } + + @Test + public void testWriteInvokesWriteFullyWithSetPosition() { + ByteBuffer buffer = mock(ByteBuffer.class); + long position = 10; + inTest.position(position); + + inTest.write(buffer); + + verify(channel).writeFully(position, buffer); + } + + @Test + public void testWriteInvokesWriteFullyWithEndOfPreviousWriteIfInvokedTwice() { + ByteBuffer buffer = mock(ByteBuffer.class); + int endOfPreviousWrite = 10; + when(channel.writeFully(0, buffer)).thenReturn(endOfPreviousWrite); + + inTest.write(buffer); + inTest.write(buffer); + + verify(channel).writeFully(endOfPreviousWrite, buffer); + } + + @Test + public void testWriteReturnsResultOfWriteFully() { + ByteBuffer buffer = mock(ByteBuffer.class); + int resultOfWriteFully = 14; + when(channel.writeFully(0, buffer)).thenReturn(resultOfWriteFully); + + int result = inTest.write(buffer); + + assertThat(result, is(resultOfWriteFully)); + } + + } + + public class MoveToTests { + + private WritableNioFile target; + + private Path pathOfTarget; + + @Before + public void setUp() { + target = mock(WritableNioFile.class); + pathOfTarget = mock(Path.class); + when(target.fileSystem()).thenReturn(fileSystem); + when(target.path()).thenReturn(pathOfTarget); + } + + @Test + public void testMoveToFailsIfTargetIsNoWritableNioFile() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Can only move to a WritableFile from the same FileSystem"); + + inTest.moveTo(mock(WritableFile.class)); + } + + @Test + public void testMoveToFailsIfTargetIsNotFromSameFileSystem() { + WritableNioFile targetFromOtherFileSystem = mock(WritableNioFile.class); + when(targetFromOtherFileSystem.fileSystem()).thenReturn(mock(FileSystem.class)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Can only move to a WritableFile from the same FileSystem"); + + inTest.moveTo(mock(WritableFile.class)); + } + + @Test + public void testMoveToFailsIfSourceIsDirectory() { + when(nioAccess.isDirectory(path)).thenReturn(true); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("Source is a directory"); + thrown.expectMessage(path.toString()); + thrown.expectMessage(pathOfTarget.toString()); + + inTest.moveTo(target); + } + + @Test + public void testMoveToFailsIfTargetIsDirectory() { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isDirectory(pathOfTarget)).thenReturn(true); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("Target is a directory"); + thrown.expectMessage(path.toString()); + thrown.expectMessage(pathOfTarget.toString()); + + inTest.moveTo(target); + } + + @Test + public void testMoveToDoesNothingIfTargetIsSameInstance() { + inTest.moveTo(inTest); + + verifyZeroInteractions(nioAccess); + } + + @Test + public void testMoveToAssertsTargetIsOpenClosesChannelsIfOpenMovesFileAndInvokesAfterCloseCallbacks() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isDirectory(pathOfTarget)).thenReturn(false); + + inTest.moveTo(target); + + InOrder inOrder = inOrder(target, nioAccess, channel, afterCloseCallback); + inOrder.verify(target).assertOpen(); + inOrder.verify(channel).closeIfOpen(); + inOrder.verify(target).closeChannelIfOpened(); + inOrder.verify(nioAccess).move(path, pathOfTarget, REPLACE_EXISTING); + inOrder.verify(target).invokeAfterCloseCallback(); + inOrder.verify(afterCloseCallback).run(); + } + + @Test + public void testMoveToWrapsIOExceptionFromMoveInUncheckedIOException() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isDirectory(pathOfTarget)).thenReturn(false); + IOException exceptionFromMove = new IOException(); + doThrow(exceptionFromMove).when(nioAccess).move(path, pathOfTarget, REPLACE_EXISTING); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromMove)); + + inTest.moveTo(target); + } + + @Test + public void testMoveToInvokesAfterCloseCallbacksEvenIfMoveToThrowsException() throws IOException { + when(nioAccess.isDirectory(path)).thenReturn(false); + when(nioAccess.isDirectory(pathOfTarget)).thenReturn(false); + IOException exceptionFromMove = new IOException(); + doThrow(exceptionFromMove).when(nioAccess).move(path, pathOfTarget, REPLACE_EXISTING); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromMove)); + + inTest.moveTo(target); + + verify(target).invokeAfterCloseCallback(); + verify(afterCloseCallback).run(); + } + + } + + public class SetLastModifiedTests { + + @Test + public void testSetLastModifiedOpensChannelIfClosedAndSetsLastModifiedTime() throws IOException { + Instant instant = Instant.parse("2016-01-05T13:51:00Z"); + FileTime time = FileTime.from(instant); + + inTest.setLastModified(instant); + + InOrder inOrder = inOrder(channel, nioAccess); + inOrder.verify(channel).openIfClosed(OpenMode.WRITE); + inOrder.verify(nioAccess).setLastModifiedTime(path, time); + } + + @Test + public void testSetLastModifiedWrapsIOExceptionFromSetLastModifiedInUncheckedIOException() throws IOException { + IOException exceptionFromSetLastModified = new IOException(); + Instant instant = Instant.parse("2016-01-05T13:51:00Z"); + FileTime time = FileTime.from(instant); + doThrow(exceptionFromSetLastModified).when(nioAccess).setLastModifiedTime(path, time); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromSetLastModified)); + + inTest.setLastModified(instant); + } + + } + + public class DeleteTests { + + @Test + public void testDeleteClosesChannelIfOpenAndDeletesFileAndInvokesAfterCloseCallback() throws IOException { + inTest.delete(); + + InOrder inOrder = inOrder(channel, nioAccess, afterCloseCallback); + inOrder.verify(channel).closeIfOpen(); + inOrder.verify(nioAccess).delete(path); + inOrder.verify(afterCloseCallback).run(); + } + + @Test + public void testDeleteClosesWritableNioFile() { + inTest.delete(); + + assertThat(inTest.isOpen(), is(false)); + } + + @Test + public void testDeleteClosesWritableNioFileEvenIfDeleteFails() throws IOException { + IOException exceptionFromDelete = new IOException(); + doThrow(exceptionFromDelete).when(nioAccess).delete(path); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromDelete)); + + try { + inTest.delete(); + } finally { + assertThat(inTest.isOpen(), is(false)); + } + } + + @Test + public void testDeleteInvokesAfterCloseCallbackEvenIfDeleteFails() throws IOException { + IOException exceptionFromDelete = new IOException(); + doThrow(exceptionFromDelete).when(nioAccess).delete(path); + + thrown.expect(UncheckedIOException.class); + thrown.expectCause(is(exceptionFromDelete)); + + try { + inTest.delete(); + } finally { + verify(afterCloseCallback).run(); + } + } + + } + + public class TruncateTests { + + @Test + public void testTruncateInvokesChannelsOpenWithModeWriteIfInvokedForTheFirstTimeBeforeInvokingTruncate() { + inTest.truncate(); + + InOrder inOrder = inOrder(channel); + inOrder.verify(channel).openIfClosed(WRITE); + inOrder.verify(channel).truncate(anyInt()); + } + + @Test + public void testTruncateChannelsTruncateWithZeroAsParameter() { + inTest.truncate(); + + verify(channel).truncate(0); + } + + } + + public class CloseTests { + + @Test + public void testCloseClosesChannelIfOpenedAndInvokesAfterCloseCallback() { + inTest.truncate(); + + inTest.close(); + + InOrder inOrder = inOrder(channel, afterCloseCallback); + inOrder.verify(channel).closeIfOpen(); + inOrder.verify(afterCloseCallback).run(); + } + + @Test + public void testCloseDoesNothingOnSecondInvocation() { + inTest.truncate(); + + inTest.close(); + inTest.close(); + + InOrder inOrder = inOrder(channel, afterCloseCallback); + inOrder.verify(channel).closeIfOpen(); + verify(afterCloseCallback).run(); + } + + @Test + public void testCloseInvokesAfterCloseCallbackEvenIfCloseThrowsException() { + inTest.truncate(); + String message = "exceptionMessage"; + doThrow(new RuntimeException(message)).when(channel).closeIfOpen(); + + thrown.expectMessage(message); + + try { + inTest.close(); + } finally { + verify(afterCloseCallback).run(); + } + } + + } + + public class IsOpenTests { + + @Test + public void testIsOpenReturnsTrueForNewInstance() { + assertThat(inTest.isOpen(), is(true)); + } + + @Test + public void testIsOpenReturnsFalseForClosedInstance() { + inTest.close(); + + assertThat(inTest.isOpen(), is(false)); + } + + } + + public class OperationsFailIfClosedTests { + + @Test + public void testWriteFailsIfClosed() { + inTest.close(); + ByteBuffer irrelevant = null; + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("already closed"); + + inTest.write(irrelevant); + } + + @Test + public void testPositionFailsIfClosed() { + inTest.close(); + long irrelevant = 1023; + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("already closed"); + + inTest.position(irrelevant); + } + + @Test + public void testTruncateFailsIfClosed() { + inTest.close(); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("already closed"); + + inTest.truncate(); + } + + @Test + public void testMoveToFailsIfClosed() { + inTest.close(); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("already closed"); + + inTest.moveTo(null); + } + + @Test + public void testSetLastModifiedFailsIfClosed() { + inTest.close(); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("already closed"); + + inTest.setLastModified(null); + } + + @Test + public void testDeleteFailsIfClosed() { + inTest.close(); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage("already closed"); + + inTest.delete(); + } + + } + + @Test + public void testEnsureChannelIsOpenedInvokesChannelOpenIfClosedWithModeWrite() { + inTest.ensureChannelIsOpened(); + + verify(channel).openIfClosed(WRITE); + } + + @Test + public void testCloseChannelIfOpenInvokesChannelsCloseIfOpen() { + inTest.closeChannelIfOpened(); + + verify(channel).closeIfOpen(); } @Test public void testToString() { - String fileToString = file.toString(); - WritableNioFile inTest = new WritableNioFile(file); - - assertThat(inTest.toString(), is("Writable" + fileToString)); + assertThat(inTest.toString(), is(format("WritableNioFile(%s)", path))); } } diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FilesystemSetupUtils.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/FilesystemSetupUtils.java similarity index 97% rename from main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FilesystemSetupUtils.java rename to main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/FilesystemSetupUtils.java index dac4f57df..db8f544b4 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FilesystemSetupUtils.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/FilesystemSetupUtils.java @@ -1,4 +1,4 @@ -package org.cryptomator.filesystem.nio; +package org.cryptomator.filesystem.nio.integrationtests; import java.io.IOException; import java.io.OutputStream; diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileIntegrationTest.java new file mode 100644 index 000000000..003811eea --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileIntegrationTest.java @@ -0,0 +1,265 @@ +package org.cryptomator.filesystem.nio.integrationtests; + +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.emptyFilesystem; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.file; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.folder; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.testFilesystem; +import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.doesNotExist; +import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.isFile; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.time.Instant; + +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.nio.NioFileSystem; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +@Ignore +public class NioFileIntegrationTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testExistsForExistingFile() { + File existingFile = NioFileSystem.rootedAt(testFilesystem(file("testFile"))) // + .file("testFile"); + + assertThat(existingFile.exists(), is(true)); + } + + @Test + public void testExistsForNonExistingFile() { + File nonExistingFile = NioFileSystem.rootedAt(emptyFilesystem()) // + .file("testFile"); + + assertThat(nonExistingFile.exists(), is(false)); + } + + @Test + public void testExistsForFileWhichIsAFolder() { + File fileWhichIsAFolder = NioFileSystem.rootedAt(testFilesystem(folder("nameOfAnExistingFolder"))) // + .file("nameOfAnExistingFolder"); + + assertThat(fileWhichIsAFolder.exists(), is(false)); + } + + @Test + public void testLastModifiedForExistingFile() { + Instant expectedLastModified = Instant.parse("2015-12-31T15:03:34Z"); + File existingFile = NioFileSystem + .rootedAt(testFilesystem( // + file("testFile").withLastModified(expectedLastModified))) // + .file("testFile"); + + assertThat(existingFile.lastModified(), is(expectedLastModified)); + } + + @Test + public void testLastModifiedForNonExistingFile() { + Path filesystemPath = emptyFilesystem(); + Path pathOfNonExistingFile = filesystemPath.resolve("nonExistingFile"); + File nonExistingFile = NioFileSystem.rootedAt(filesystemPath) // + .file("nonExistingFile"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(pathOfNonExistingFile.toString()); + + nonExistingFile.lastModified(); + } + + @Test + public void testLastModifiedForFileWhichIsAFolder() { + Path filesystemPath = testFilesystem(folder("nameOfAnExistingFolder")); + Path pathOfNonExistingFile = filesystemPath.resolve("nameOfAnExistingFolder"); + File fileWhichIsAFolder = NioFileSystem.rootedAt(filesystemPath) // + .file("nameOfAnExistingFolder"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(pathOfNonExistingFile.toString()); + + fileWhichIsAFolder.lastModified(); + } + + @Test + public void testCopyToNonExistingTarget() { + Path filesystemPath = testFilesystem(file("sourceFile").withData("fileContents")); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Path sourceFilePath = filesystemPath.resolve("sourceFile"); + Path targetFilePath = filesystemPath.resolve("targetFile"); + File source = fileSystem.file("sourceFile"); + File target = fileSystem.file("targetFile"); + + source.copyTo(target); + + assertThat(sourceFilePath, isFile().withContent("fileContents")); + assertThat(targetFilePath, isFile().withContent("fileContents")); + } + + @Test + public void testCopyToExistingTarget() { + Path filesystemPath = testFilesystem( // + file("sourceFile").withData("fileContents"), // + file("targetFile").withData("wrongFileContents")); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Path sourceFilePath = filesystemPath.resolve("sourceFile"); + Path targetFilePath = filesystemPath.resolve("targetFile"); + File source = fileSystem.file("sourceFile"); + File target = fileSystem.file("targetFile"); + + source.copyTo(target); + + assertThat(sourceFilePath, isFile().withContent("fileContents")); + assertThat(targetFilePath, isFile().withContent("fileContents")); + } + + @Test + public void testCopyToFolderTarget() { + Path filesystemPath = testFilesystem( // + file("sourceFile").withData("fileContents"), // + folder("aFolderName")); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Path targetFilePath = filesystemPath.resolve("aFolderName").toAbsolutePath(); + File source = fileSystem.file("sourceFile"); + File target = fileSystem.file("aFolderName"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(targetFilePath.toAbsolutePath().toString()); + + source.copyTo(target); + } + + @Test + public void testCopyToOfNonExistingFile() { + Path filesystemPath = emptyFilesystem(); + Path filePath = filesystemPath.resolve("nonExistingFile").toAbsolutePath(); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + File nonExistingFile = fileSystem.file("nonExistingFile"); + File target = fileSystem.file("target"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filePath.toString()); + + nonExistingFile.copyTo(target); + } + + @Test + public void testCopyToOfFileWhichIsAFolder() { + Path filesystemPath = testFilesystem(folder("folderName")); + Path filePath = filesystemPath.resolve("folderName").toAbsolutePath(); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + File fileWhichIsAFolder = fileSystem.file("folderName"); + File target = fileSystem.file("target"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filePath.toString()); + + fileWhichIsAFolder.copyTo(target); + } + + @Test + public void testMoveToNonExistingTarget() { + Path filesystemPath = testFilesystem(file("sourceFile").withData("fileContents")); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Path sourceFilePath = filesystemPath.resolve("sourceFile"); + Path targetFilePath = filesystemPath.resolve("targetFile"); + File source = fileSystem.file("sourceFile"); + File target = fileSystem.file("targetFile"); + + source.moveTo(target); + + assertThat(sourceFilePath, doesNotExist()); + assertThat(targetFilePath, isFile().withContent("fileContents")); + } + + @Test + public void testMoveToExistingTarget() { + Path filesystemPath = testFilesystem( // + file("sourceFile").withData("fileContents"), // + file("targetFile").withData("wrongFileContents")); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Path sourceFilePath = filesystemPath.resolve("sourceFile"); + Path targetFilePath = filesystemPath.resolve("targetFile"); + File source = fileSystem.file("sourceFile"); + File target = fileSystem.file("targetFile"); + + source.moveTo(target); + + assertThat(sourceFilePath, doesNotExist()); + assertThat(targetFilePath, isFile().withContent("fileContents")); + } + + @Test + public void testMoveToSameFile() { + Path filesystemPath = testFilesystem(file("fileName").withData("fileContents")); + Path filePath = filesystemPath.resolve("fileName"); + File file = NioFileSystem.rootedAt(filesystemPath).file("fileName"); + + file.moveTo(file); + + assertThat(filePath, isFile().withContent("fileContents")); + } + + @Test + public void testMoveToDirectoryTarget() { + Path filesystemPath = testFilesystem( // + file("sourceFile").withData("fileContents"), // + folder("aFolderName")); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Path targetFilePath = filesystemPath.resolve("aFolderName").toAbsolutePath(); + File source = fileSystem.file("sourceFile"); + File target = fileSystem.file("aFolderName"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(targetFilePath.toAbsolutePath().toString()); + + source.moveTo(target); + } + + @Test + public void testMoveToOfNonExistingFile() { + Path filesystemPath = emptyFilesystem(); + Path filePath = filesystemPath.resolve("nonExistingFile").toAbsolutePath(); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + File nonExistingFile = fileSystem.file("nonExistingFile"); + File target = fileSystem.file("target"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filePath.toString()); + + nonExistingFile.moveTo(target); + } + + @Test + public void testMoveToOfFileWhichIsAFolder() { + Path filesystemPath = testFilesystem(folder("folderName")); + Path filePath = filesystemPath.resolve("folderName").toAbsolutePath(); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + File fileWhichIsAFolder = fileSystem.file("folderName"); + File target = fileSystem.file("target"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filePath.toString()); + + fileWhichIsAFolder.moveTo(target); + } + + @Test + public void testOpenWritableDoesNotCreateANonExisitingFile() { + Path filesystemPath = emptyFilesystem(); + Path filePath = filesystemPath.resolve("nonExistingFile"); + File file = NioFileSystem.rootedAt(filesystemPath).file("nonExistingFile"); + + file.openWritable(); + + assertThat(filePath, doesNotExist()); + } + +} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileSystemIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileSystemIntegrationTest.java new file mode 100644 index 000000000..1e0279a5b --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileSystemIntegrationTest.java @@ -0,0 +1,46 @@ +package org.cryptomator.filesystem.nio.integrationtests; + +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.emptyFilesystem; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.file; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.testFilesystem; +import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.isDirectory; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.cryptomator.filesystem.nio.NioFileSystem; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +@Ignore +public class NioFileSystemIntegrationTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testObtainingAFileSystemWithNonExistingRootCreatesItIncludingAllParentFolders() throws IOException { + Path emptyFilesystem = emptyFilesystem(); + Path nonExistingFilesystemRoot = emptyFilesystem.resolve("nonExistingRoot"); + Files.delete(emptyFilesystem); + + NioFileSystem.rootedAt(nonExistingFilesystemRoot); + + assertThat(nonExistingFilesystemRoot, isDirectory()); + } + + @Test + public void testObtainingAFileSystemWhooseRootIsAFileFails() throws IOException { + Path emptyFilesystem = testFilesystem(file("rootWhichIsAFile")); + Path rootWhichIsAFile = emptyFilesystem.resolve("rootWhichIsAFile"); + + thrown.expect(UncheckedIOException.class); + + NioFileSystem.rootedAt(rootWhichIsAFile); + } +} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFolderIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFolderIntegrationTest.java new file mode 100644 index 000000000..af8c7fb72 --- /dev/null +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFolderIntegrationTest.java @@ -0,0 +1,349 @@ +package org.cryptomator.filesystem.nio.integrationtests; + +import static java.util.stream.Collectors.toList; +import static org.cryptomator.common.test.matcher.ContainsMatcher.containsInAnyOrder; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.emptyFilesystem; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.file; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.folder; +import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.testFilesystem; +import static org.cryptomator.filesystem.nio.integrationtests.NioNodeMatcher.fileWithName; +import static org.cryptomator.filesystem.nio.integrationtests.NioNodeMatcher.folderWithName; +import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.doesNotExist; +import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.isDirectory; +import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.isFile; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; + +import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.nio.NioFileSystem; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +@Ignore +public class NioFolderIntegrationTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testCreateExistingFolder() throws IOException { + String folderName = "nameOfFolder"; + Path testFilesystemPath = testFilesystem( // + folder(folderName)); + NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); + Folder existingFolder = fileSystem.folder(folderName); + + existingFolder.create(); + + assertThat(Files.isDirectory(testFilesystemPath.resolve(folderName)), is(true)); + } + + @Test + public void testCreateNonExistingFolder() throws IOException { + String folderName = "nameOfFolder"; + Path testFilesystemPath = emptyFilesystem(); + NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); + Folder nonExistingFolder = fileSystem.folder(folderName); + + nonExistingFolder.create(); + + assertThat(Files.isDirectory(testFilesystemPath.resolve(folderName)), is(true)); + } + + @Test + public void testCreateFolderWithMissingParent() throws IOException { + String parentFolderName = "nameOfParentFolder"; + String folderName = "nameOfFolder"; + Path emptyFilesystemPath = emptyFilesystem(); + NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystemPath); + Folder folderWithNonExistingParent = fileSystem.folder(parentFolderName).folder(folderName); + + folderWithNonExistingParent.create(); + + assertThat(Files.isDirectory(emptyFilesystemPath.resolve(parentFolderName).resolve(folderName)), is(true)); + } + + @Test + public void testCreateFolderWhichIsAFile() throws IOException { + String folderName = "nameOfFolder"; + Path testFilesystemPath = testFilesystem(file(folderName)); + NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); + Folder folderWhichIsAFile = fileSystem.folder(folderName); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(testFilesystemPath.resolve(folderName).toString()); + + folderWhichIsAFile.create(); + } + + @Test + public void testChildrenOfEmptyFolder() throws IOException { + Folder folder = NioFileSystem.rootedAt(emptyFilesystem()); + + assertThat(folder.children().collect(toList()), is(empty())); + } + + @Test + public void testChildrenOfNonExistingFolder() throws IOException { + String nameOfNonExistingFolder = "nameOfFolder"; + Path filesystemPath = emptyFilesystem(); + Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(nameOfNonExistingFolder); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filesystemPath.resolve(nameOfNonExistingFolder).toString()); + + folder.children(); + } + + @Test + public void testChildrenOfFolderWhichIsAFile() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = testFilesystem(file(folderName)); + Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filesystemPath.resolve(folderName).toString()); + + folder.children(); + } + + @Test + public void testChildrenOfFolder() throws IOException { + String folderName1 = "folder1"; + String folderName2 = "folder2"; + String fileName1 = "file1"; + String fileName2 = "file2"; + + Folder folder = NioFileSystem.rootedAt(testFilesystem( // + folder(folderName1), // + folder(folderName2), // + file(fileName1), // + file(fileName2))); + + assertThat(folder.children().collect(toList()), + containsInAnyOrder( // + folderWithName(folderName1), // + folderWithName(folderName2), // + fileWithName(fileName1), // + fileWithName(fileName2))); + } + + @Test + public void testDeleteOfEmptyFolder() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = testFilesystem( // + folder(folderName)); + Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + folder.delete(); + + assertThat(filesystemPath.resolve(folderName), doesNotExist()); + } + + @Test + public void testDeleteOfFolderWithChildren() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = testFilesystem( // + folder(folderName), // + folder(folderName + "/subfolder1"), // + file(folderName + "/subfolder1/fileName1"), // + folder(folderName + "/subfolder2"), // + file(folderName + "/fileName1"), // + file(folderName + "/fileName2")); + Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + folder.delete(); + + assertThat(filesystemPath.resolve(folderName), doesNotExist()); + } + + @Test + public void testDeleteOfNonExistingFolder() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = emptyFilesystem(); + Folder nonExistingFolder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + nonExistingFolder.delete(); + + assertThat(filesystemPath.resolve(folderName), doesNotExist()); + } + + @Test + public void testDeleteOfFolderWhichIsAFile() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = testFilesystem( // + file(folderName)); + Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + folder.delete(); + + assertThat(filesystemPath.resolve(folderName), isFile()); + } + + @Test + public void testExistsForExistingFolder() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = testFilesystem( // + folder(folderName)); + Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + assertThat(folder.exists(), is(true)); + } + + @Test + public void testExistsForNonExistingFolder() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = emptyFilesystem(); + Folder nonExistingFolder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + assertThat(nonExistingFolder.exists(), is(false)); + } + + @Test + public void testExistsForFolderWhichIsAFile() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = testFilesystem( // + file(folderName)); + Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + assertThat(folder.exists(), is(false)); + } + + @Test + public void testLastModifiedOfExistingFolder() throws IOException { + String folderName = "nameOfFolder"; + Instant lastModified = Instant.parse("2015-12-29T15:36:10.00Z"); + Folder folder = NioFileSystem + .rootedAt(testFilesystem( // + folder(folderName).withLastModified(lastModified))) // + .folder(folderName); + + assertThat(folder.lastModified(), is(lastModified)); + } + + @Test + public void testLastModifiedOfNonExistingFolder() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = emptyFilesystem(); + Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filesystemPath.resolve(folderName).toString()); + + folder.lastModified(); + } + + @Test + public void testLastModifiedOfFolderWhichIsAFile() throws IOException { + String folderName = "nameOfFolder"; + Path filesystemPath = testFilesystem(file(folderName)); + Folder folder = NioFileSystem // + .rootedAt(filesystemPath) // + .folder(folderName); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filesystemPath.resolve(folderName).toString()); + + folder.lastModified(); + } + + @Test + public void testMoveToOfFolderToNonExistingFolder() throws IOException { + Path filesystemPath = testFilesystem( // + folder("folderToMove")); + NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Folder folderToMove = fileSystem.folder("folderToMove"); + Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); + + folderToMove.moveTo(folderToMoveTo); + + assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); + assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); + } + + @Test + public void testMoveToOfFolderWithChildren() throws IOException { + Path filesystemPath = testFilesystem( // + folder("folderToMove"), // + folder("folderToMove/subfolder1"), // + folder("folderToMove/subfolder2"), // + file("folderToMove/subfolder1/file1").withData("dataOfFile1"), // + file("folderToMove/file2").withData("dataOfFile2"), // + file("folderToMove/file3").withData("dataOfFile3")); + NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Folder folderToMove = fileSystem.folder("folderToMove"); + Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); + + folderToMove.moveTo(folderToMoveTo); + + assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); + assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); + assertThat(filesystemPath.resolve("folderToMoveTo/subfolder1"), isDirectory()); + assertThat(filesystemPath.resolve("folderToMoveTo/subfolder2"), isDirectory()); + assertThat(filesystemPath.resolve("folderToMoveTo/subfolder1/file1"), isFile().withContent("dataOfFile1")); + assertThat(filesystemPath.resolve("folderToMoveTo/file2"), isFile().withContent("dataOfFile2")); + assertThat(filesystemPath.resolve("folderToMoveTo/file3"), isFile().withContent("dataOfFile3")); + } + + @Test + public void testMoveToOfFolderToExistingFolder() throws IOException { + Path filesystemPath = testFilesystem( // + folder("folderToMove"), // + file("folderToMove/file1").withData("dataOfFile1"), // + file("folderToMove/file2").withData("dataOfFile2"), // + folder("folderToMoveTo"), // + file("folderToMoveTo/file1").withData("wrongDataOfFile1"), // + file("folderToMoveTo/fileWhichShouldNotExistAfterMove")); + NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Folder folderToMove = fileSystem.folder("folderToMove"); + Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); + + folderToMove.moveTo(folderToMoveTo); + + assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); + assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); + assertThat(filesystemPath.resolve("folderToMoveTo/file1"), isFile().withContent("dataOfFile1")); + assertThat(filesystemPath.resolve("folderToMoveTo/file2"), isFile().withContent("dataOfFile2")); + assertThat(filesystemPath.resolve("folderToMoveTo/fileWhichShouldNotExistAfterMove"), doesNotExist()); + } + + @Test + public void testMoveToOfFolderToExistingFile() throws IOException { + Path filesystemPath = testFilesystem( // + folder("folderToMove"), // + file("folderToMoveTo")); + FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); + Folder folderToMove = fileSystem.folder("folderToMove"); + Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); + + thrown.expect(UncheckedIOException.class); + thrown.expectMessage(filesystemPath.resolve("folderToMoveTo").toString()); + + folderToMove.moveTo(folderToMoveTo); + } + + @Test + public void testMoveToOfFolderToFolderOfAnotherFileSystem() throws IOException { + Path filesystemPath = testFilesystem(folder("folderToMove")); + Folder folderToMove = NioFileSystem.rootedAt(filesystemPath).folder("folderToMove"); + Folder folderToMoveTo = NioFileSystem.rootedAt(filesystemPath).folder("folderToMoveTo"); + + thrown.expect(IllegalArgumentException.class); + + folderToMove.moveTo(folderToMoveTo); + + assertThat(filesystemPath.resolve("folderToMove"), isDirectory()); + assertThat(filesystemPath.resolve("folderToMoveTo"), doesNotExist()); + } + +} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioNodeMatcher.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioNodeMatcher.java similarity index 90% rename from main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioNodeMatcher.java rename to main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioNodeMatcher.java index 8ce118294..6a016f978 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioNodeMatcher.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioNodeMatcher.java @@ -1,4 +1,4 @@ -package org.cryptomator.filesystem.nio; +package org.cryptomator.filesystem.nio.integrationtests; import static org.hamcrest.CoreMatchers.equalTo; 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/integrationtests/PathMatcher.java similarity index 78% rename from main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/PathMatcher.java rename to main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/PathMatcher.java index 3d73b9639..b9e64de32 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/integrationtests/PathMatcher.java @@ -1,5 +1,6 @@ -package org.cryptomator.filesystem.nio; +package org.cryptomator.filesystem.nio.integrationtests; +import static java.util.Arrays.copyOfRange; import static org.hamcrest.CoreMatchers.is; import java.io.IOException; @@ -63,6 +64,10 @@ class PathMatcher { return new IsFileWithContentMatcher(value.getBytes()); } + public Matcher withPartialContentAt(int offset, byte[] value) { + return new IsFileWithContentMatcher(offset, value); + } + public Matcher withContent(byte[] value) { return new IsFileWithContentMatcher(value); } @@ -75,10 +80,19 @@ class PathMatcher { public static class IsFileWithContentMatcher extends TypeSafeDiagnosingMatcher { + private static final int NO_OFFSET = -1; + private final byte[] expectedContent; + private final int offset; + + public IsFileWithContentMatcher(int offset, byte[] content) { + this.expectedContent = content; + this.offset = offset; + } public IsFileWithContentMatcher(byte[] content) { this.expectedContent = content; + this.offset = NO_OFFSET; } @Override @@ -86,6 +100,10 @@ class PathMatcher { description // .appendText("a file with content ") // .appendText(Arrays.toString(expectedContent)); + if (offset != NO_OFFSET) { + description.appendText(" starting at byte ") // + .appendValue(offset); + } } @Override @@ -96,10 +114,18 @@ class PathMatcher { } byte[] actualContent = getFileContent(path); + if (offset != NO_OFFSET) { + actualContent = copyOfRange(actualContent, offset, offset + expectedContent.length); + } + if (!Arrays.equals(actualContent, expectedContent)) { mismatchDescription // .appendText("a file with content ") // .appendText(Arrays.toString(actualContent)); + if (offset != NO_OFFSET) { + mismatchDescription.appendText(" starting at byte ") // + .appendValue(offset); + } return false; }