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
This commit is contained in:
Markus Kreusch
2016-01-03 20:08:52 +01:00
parent a05fa19de4
commit 801253aa27
35 changed files with 3450 additions and 1632 deletions
+7
View File
@@ -12,5 +12,12 @@
<artifactId>commons</artifactId>
<name>Cryptomator common</name>
<description>Shared utilities</description>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons-test</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,34 @@
package org.cryptomator.common;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class Holder<V> implements Supplier<V>, Consumer<V> {
private final V initial;
private V value;
public <W extends V> 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);
}
}
@@ -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<Object> 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));
}
}
@@ -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();
}
}
@@ -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 = "..";
+5
View File
@@ -36,6 +36,11 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
</dependency>
<!-- Tests -->
<dependency>
@@ -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<NioFolder> parent, Path path, NioAccess nioAccess) {
return new NioFolder(parent, path, nioAccess, this);
}
@Override
public NioFile nioFile(Optional<NioFolder> 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);
}
}
@@ -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<Path> 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();
}
}
@@ -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<InstanceFactory> DEFAULT = new Holder<>(new DefaultInstanceFactory());
NioFolder nioFolder(Optional<NioFolder> parent, Path path, NioAccess nioAccess);
NioFile nioFile(Optional<NioFolder> 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);
}
@@ -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<NioAccess> 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<Path> 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();
}
@@ -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<NioFolder> parent, Path path) {
super(parent, path);
sharedChannel = new SharedFileChannel(path);
}
SharedFileChannel channel() {
return sharedChannel;
}
public ReadWriteLock lock() {
return lock;
public NioFile(Optional<NioFolder> 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;
}
}
@@ -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();
}
@@ -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<Path, NioFolder> folders = WeakValuedCache.usingLoader(this::folderFromPath);
private final WeakValuedCache<Path, NioFile> files = WeakValuedCache.usingLoader(this::fileFromPath);
public NioFolder(Optional<NioFolder> parent, Path path) {
super(parent, path);
public NioFolder(Optional<NioFolder> parent, Path path, NioAccess nioAccess, InstanceFactory instanceFactory) {
super(parent, path, nioAccess, instanceFactory);
}
@Override
public Stream<? extends Node> 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);
}
}
}
@@ -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<NioFolder> parent;
protected final Path path;
private NioFileSystem fileSystem;
protected final NioAccess nioAccess;
protected final InstanceFactory instanceFactory;
public NioNode(Optional<NioFolder> parent, Path path) {
public NioNode(Optional<NioFolder> 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);
}
@@ -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);
}
}
@@ -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<Thread, Thread> 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;
}
}
@@ -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);
}
}
@@ -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<NioFolder> 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();
}
}
@@ -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<Path> 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()));
}
}
@@ -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 {
}
}
@@ -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)));
}
}
@@ -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)));
}
}
@@ -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();
}
}
@@ -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<NioFolder> 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<Runnable> 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<Runnable> 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<Runnable> 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<Runnable> 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> T computeInThread(Supplier<T> computation) {
CompletableFuture<T> 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;
}
}
}
}
@@ -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<NioFolder> 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<Path> childrenPaths = Stream.<Path>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<Path> childrenPaths = Stream.<Path>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<Path> childrenPaths = Stream.<Path>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 <T> Answer<T> answerFrom(Supplier<T> supplier) {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return supplier.get();
}
};
}
}
@@ -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)));
}
}
@@ -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 <T> Matcher<Class<T>> aClassThatDoesNotDeclareMethod(String name, Class<?>... parameterTypes) {
return new TypeSafeDiagnosingMatcher<Class<T>>(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<T> 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<T> item) {
try {
return item.getDeclaredMethod(name, parameterTypes) != null;
} catch (NoSuchMethodException e) {
return false;
} catch (SecurityException e) {
throw new RuntimeException(e);
}
}
};
}
public static <T> Matcher<Class<T>> aClassThatDoesDeclareMethod(String name, Class<?>... parameterTypes) {
return new TypeSafeDiagnosingMatcher<Class<T>>(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<T> 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<T> item) {
try {
return item.getDeclaredMethod(name, parameterTypes) != null;
} catch (NoSuchMethodException e) {
return false;
} catch (SecurityException e) {
throw new RuntimeException(e);
}
}
};
}
}
@@ -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<Integer>() {
@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<Integer> simulateRead(int amount, ByteBuffer target) {
return new Answer<Integer>() {
@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<Integer> simulateWrite(int amount, ByteBuffer target) {
return new Answer<Integer>() {
@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<RuntimeException> holder = new Holder<>();
private void inThreadRethrowingException(Runnable task) {
Holder<Throwable> 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);
}
}
@@ -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)));
}
}
@@ -1,4 +1,4 @@
package org.cryptomator.filesystem.nio;
package org.cryptomator.filesystem.nio.integrationtests;
import java.io.IOException;
import java.io.OutputStream;
@@ -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());
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -1,4 +1,4 @@
package org.cryptomator.filesystem.nio;
package org.cryptomator.filesystem.nio.integrationtests;
import static org.hamcrest.CoreMatchers.equalTo;
@@ -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<Path> withPartialContentAt(int offset, byte[] value) {
return new IsFileWithContentMatcher(offset, value);
}
public Matcher<Path> withContent(byte[] value) {
return new IsFileWithContentMatcher(value);
}
@@ -75,10 +80,19 @@ class PathMatcher {
public static class IsFileWithContentMatcher extends TypeSafeDiagnosingMatcher<Path> {
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;
}