Changes to filesystem API and nio implementation

* Partial implementation of nio filesystem
* Addded some tests
* Added project for common test dependencies
* Removed default implementation of Folder#delete
** reason: didn't work because empty folders were not deleted and this
cannot be done in the default implementation
This commit is contained in:
Markus Kreusch
2015-12-28 16:28:51 +01:00
parent 0254569826
commit 157839c32f
22 changed files with 819 additions and 64 deletions

1
main/commons-test/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target/

23
main/commons-test/pom.xml Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2015 Markus Kreusch This file is licensed under the terms
of the MIT license. See the LICENSE.txt file for more info. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0-SNAPSHOT</version>
</parent>
<artifactId>commons-test</artifactId>
<name>Cryptomator common test dependencies</name>
<description>Shared utilities for tests</description>
<dependencies>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,27 @@
package org.cryptomator.commons.test.matcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
/**
* Wraps hamcrest contains and containsInAny order matcher factory methods to
* avoid problems due to incorrect / inconsistent handling of generics by the
* several java compilers.
*
* @author Markus Kreusch
*/
public class ContainsMatcher {
@SuppressWarnings({ "unchecked" })
@SafeVarargs
public static <T> Matcher<Iterable<? super T>> containsInAnyOrder(Matcher<? extends T>... matchers) {
return Matchers.containsInAnyOrder((Matcher[]) matchers);
}
@SuppressWarnings({ "unchecked" })
@SafeVarargs
public static <T> Matcher<Iterable<? super T>> contains(Matcher<? extends T>... matchers) {
return Matchers.contains((Matcher[]) matchers);
}
}

View File

@@ -0,0 +1,55 @@
/*******************************************************************************
* Copyright (c) 2015 Markus Kreusch
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
******************************************************************************/
package org.cryptomator.commons.test.matcher;
import java.util.function.Function;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
public class PropertyMatcher<T, P> extends TypeSafeDiagnosingMatcher<T> {
private final Class<T> expectedType;
private final Function<T, P> getter;
private final String name;
private final Matcher<? super P> subMatcher;
public PropertyMatcher(Class<T> type, Function<T, P> getter, String name, Matcher<? super P> subMatcher) {
super(type);
this.expectedType = type;
this.getter = getter;
this.name = name;
this.subMatcher = subMatcher;
}
@Override
public void describeTo(Description description) {
description.appendText("a ") //
.appendText(expectedType.getSimpleName()) //
.appendText(" with a ") //
.appendText(name) //
.appendText(" that is ") //
.appendDescriptionOf(subMatcher);
}
@Override
protected boolean matchesSafely(T item, Description mismatchDescription) {
P propertyValue = getter.apply(item);
if (subMatcher.matches(propertyValue)) {
return true;
} else {
mismatchDescription.appendText("a ") //
.appendText(expectedType.getSimpleName()) //
.appendText(" with a ") //
.appendText(name) //
.appendText(" that was ");
subMatcher.describeMismatch(propertyValue, mismatchDescription);
return false;
}
}
}

View File

@@ -0,0 +1,86 @@
package org.cryptomator.filesystem;
import java.util.function.Consumer;
public class FileSystemVisitor {
private final Consumer<Folder> beforeFolderVisitor;
private final Consumer<Folder> afterFolderVisitor;
private final Consumer<File> fileVisitor;
private FileSystemVisitor(Consumer<Folder> beforeFolderVisitor, Consumer<Folder> afterFolderVisitor, Consumer<File> fileVisitor) {
this.beforeFolderVisitor = beforeFolderVisitor;
this.afterFolderVisitor = afterFolderVisitor;
this.fileVisitor = fileVisitor;
}
public static FileSystemVisitorBuilder fileSystemVisitor() {
return new FileSystemVisitorBuilder();
}
public FileSystemVisitor visit(Folder folder) {
beforeFolderVisitor.accept(folder);
folder.folders().forEach(this::visit);
folder.files().forEach(this::visit);
afterFolderVisitor.accept(folder);
return this;
}
public FileSystemVisitor visit(File file) {
fileVisitor.accept(file);
return this;
}
public static class FileSystemVisitorBuilder {
private Consumer<Folder> beforeFolderVisitor = noOp();
private Consumer<Folder> afterFolderVisitor = noOp();
private Consumer<File> fileVisitor = noOp();
private FileSystemVisitorBuilder() {
}
public FileSystemVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
if (beforeFolderVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
this.beforeFolderVisitor = beforeFolderVisitor;
return this;
}
public FileSystemVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
if (afterFolderVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
this.afterFolderVisitor = afterFolderVisitor;
return this;
}
public FileSystemVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
if (fileVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
this.fileVisitor = fileVisitor;
return this;
}
public FileSystemVisitor visit(Folder folder) {
return build().visit(folder);
}
public FileSystemVisitor visit(File file) {
return build().visit(file);
}
public FileSystemVisitor build() {
return new FileSystemVisitor(beforeFolderVisitor, afterFolderVisitor, fileVisitor);
}
private static <T> Consumer<T> noOp() {
return ignoredParameter -> {
};
}
}
}

View File

@@ -88,17 +88,7 @@ public interface Folder extends Node {
* <p>
* If the directory does not exist this method does nothing.
*/
default void delete() throws UncheckedIOException {
if (!exists()) {
return;
}
folders().forEach(Folder::delete);
files().forEach(file -> {
try (WritableFile writableFile = file.openWritable()) {
writableFile.delete();
}
});
}
void delete();
/**
* Moves this directory and its contents to the given destination. If the

View File

@@ -1,10 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2015 Markus Kreusch
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- Copyright (c) 2015 Markus Kreusch This file is licensed under the terms
of the MIT license. See the LICENSE.txt file for more info. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
@@ -38,6 +36,19 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons-test</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -1,18 +0,0 @@
package org.cryptomator.filesystem.nio;
import java.nio.file.Path;
import java.util.Optional;
class DefaultNioNodeFactory implements NioNodeFactory {
@Override
public NioFile file(Optional<NioFolder> parent, Path path) {
return new NioFile(parent, path, this);
}
@Override
public NioFolder folder(Optional<NioFolder> parent, Path path) {
return new NioFolder(parent, path, this);
}
}

View File

@@ -1,5 +1,7 @@
package org.cryptomator.filesystem.nio;
import static java.lang.String.format;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
@@ -15,8 +17,8 @@ class NioFile extends NioNode implements File {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
public NioFile(Optional<NioFolder> parent, Path path, NioNodeFactory nodeFactory) {
super(parent, path, nodeFactory);
public NioFile(Optional<NioFolder> parent, Path path) {
super(parent, path);
}
@Override
@@ -99,4 +101,9 @@ class NioFile extends NioNode implements File {
}
}
@Override
public String toString() {
return format("NioFile(%s)", path);
}
}

View File

@@ -1,5 +1,7 @@
package org.cryptomator.filesystem.nio;
import static org.cryptomator.filesystem.FolderCreateMode.INCLUDING_PARENTS;
import java.nio.file.Path;
import java.util.Optional;
@@ -8,11 +10,12 @@ import org.cryptomator.filesystem.FileSystem;
public class NioFileSystem extends NioFolder implements FileSystem {
public static NioFileSystem rootedAt(Path root) {
return new NioFileSystem(root, new DefaultNioNodeFactory());
return new NioFileSystem(root);
}
NioFileSystem(Path root, NioNodeFactory nodeFactory) {
super(Optional.empty(), root, nodeFactory);
private NioFileSystem(Path root) {
super(Optional.empty(), root);
create(INCLUDING_PARENTS);
}
}

View File

@@ -1,5 +1,7 @@
package org.cryptomator.filesystem.nio;
import static java.lang.String.format;
import static org.cryptomator.filesystem.FileSystemVisitor.fileSystemVisitor;
import static org.cryptomator.filesystem.FolderCreateMode.INCLUDING_PARENTS;
import java.io.IOException;
@@ -13,14 +15,15 @@ import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.FolderCreateMode;
import org.cryptomator.filesystem.Node;
import org.cryptomator.filesystem.WritableFile;
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, NioNodeFactory nodeFactory) {
super(parent, path, nodeFactory);
public NioFolder(Optional<NioFolder> parent, Path path) {
super(parent, path);
}
@Override
@@ -41,11 +44,11 @@ class NioFolder extends NioNode implements Folder {
}
private NioFile fileFromPath(Path path) {
return nodeFactory.file(Optional.of(this), path);
return new NioFile(Optional.of(this), path);
}
private NioFolder folderFromPath(Path path) {
return nodeFactory.folder(Optional.of(this), path);
return new NioFolder(Optional.of(this), path);
}
@Override
@@ -82,4 +85,31 @@ class NioFolder extends NioNode implements Folder {
}
}
@Override
public String toString() {
return format("NioFolder(%s)", path);
}
@Override
public void delete() {
fileSystemVisitor() //
.forEachFile(NioFolder::deleteFile) //
.afterFolder(NioFolder::deleteEmptyFolder) //
.visit(this);
}
private static final void deleteFile(File file) {
try (WritableFile writableFile = file.openWritable()) {
writableFile.delete();
}
}
private static final void deleteEmptyFolder(Folder folder) {
try {
Files.delete(((NioFolder) folder).path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

View File

@@ -14,17 +14,26 @@ class NioNode implements Node {
protected final Optional<NioFolder> parent;
protected final Path path;
protected final NioNodeFactory nodeFactory;
public NioNode(Optional<NioFolder> parent, Path path, NioNodeFactory nodeFactory) {
private NioFileSystem fileSystem;
public NioNode(Optional<NioFolder> parent, Path path) {
this.path = path.toAbsolutePath();
this.nodeFactory = nodeFactory;
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).nodeFactory == nodeFactory;
&& ((NioNode) other).fileSystem() == fileSystem();
}
@Override

View File

@@ -1,12 +0,0 @@
package org.cryptomator.filesystem.nio;
import java.nio.file.Path;
import java.util.Optional;
interface NioNodeFactory {
NioFile file(Optional<NioFolder> parent, Path path);
NioFolder folder(Optional<NioFolder> parent, Path path);
}

View File

@@ -6,6 +6,8 @@ import java.util.function.Function;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ExecutionError;
import com.google.common.util.concurrent.UncheckedExecutionException;
class WeakValuedCache<Key, Value> {
@@ -30,7 +32,11 @@ class WeakValuedCache<Key, Value> {
try {
return delegate.get(key);
} catch (ExecutionException e) {
throw new RuntimeException(e);
throw new IllegalStateException("No checked exception can be thrown by loader", e);
} catch (UncheckedExecutionException e) {
throw (RuntimeException) e.getCause();
} catch (ExecutionError e) {
throw (Error) e.getCause();
}
}

View File

@@ -0,0 +1,89 @@
package org.cryptomator.filesystem.nio;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.io.IOUtils;
class FilesystemSetupUtils {
public static Path emptyFilesystem() {
try {
return Files.createTempDirectory("test-filesystem");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static Path testFilesystem(Entry firstEntry, Entry... entries) {
try {
Path root = Files.createTempDirectory("test-filesystem");
firstEntry.create(root);
for (Entry entry : entries) {
entry.create(root);
}
return root;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static FileEntry file(String path) {
return new FileEntry(Paths.get(path));
}
public static FolderEntry folder(String path) {
return new FolderEntry(Paths.get(path));
}
interface Entry {
void create(Path root) throws IOException;
}
public static class FileEntry implements Entry {
private Path relativePath;
private byte[] data = new byte[0];
public FileEntry(Path relativePath) {
this.relativePath = relativePath;
}
public FileEntry withData(byte[] data) {
this.data = data;
return this;
}
public FileEntry withData(String data) {
return withData(data.getBytes());
}
@Override
public void create(Path root) throws IOException {
Path filePath = root.resolve(relativePath);
Files.createDirectories(filePath.getParent());
try (OutputStream out = Files.newOutputStream(filePath)) {
IOUtils.write(data, out);
}
}
}
public static class FolderEntry implements Entry {
private Path relativePath;
public FolderEntry(Path relativePath) {
this.relativePath = relativePath;
}
@Override
public void create(Path root) throws IOException {
Files.createDirectories(root.resolve(relativePath));
}
}
}

View File

@@ -0,0 +1,52 @@
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 java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class NioFileSystemTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testParentIsEmpty() throws IOException {
NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem());
assertThat(fileSystem.parent().isPresent(), is(false));
}
@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);
}
}

View File

@@ -0,0 +1,174 @@
package org.cryptomator.filesystem.nio;
import static java.util.stream.Collectors.toList;
import static org.cryptomator.commons.test.matcher.ContainsMatcher.containsInAnyOrder;
import static org.cryptomator.filesystem.FolderCreateMode.FAIL_IF_PARENT_IS_MISSING;
import static org.cryptomator.filesystem.FolderCreateMode.INCLUDING_PARENTS;
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.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 org.cryptomator.filesystem.Folder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class NioFolderTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testNameIsNameOfFolder() throws IOException {
final String folderName = "folderNameABC";
NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem());
Folder folder = fileSystem.folder(folderName);
assertThat(folder, folderWithName(folderName));
}
@Test
public void testCreateWithOptionFailIfParentIsMissingFailsIfParentIsMissing() throws IOException {
NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem());
Folder folderWithNonExistingParent = fileSystem.folder("a").folder("b");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(fileSystem.path.resolve("a/b").toString());
folderWithNonExistingParent.create(FAIL_IF_PARENT_IS_MISSING);
}
@Test
public void testCreateWithOptionIncludingParentsSucceedsIfParentIsMissing() throws IOException {
Path emptyFilesystemPath = emptyFilesystem();
NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystemPath);
Folder folderWithNonExistingParent = fileSystem.folder("a").folder("b");
folderWithNonExistingParent.create(INCLUDING_PARENTS);
assertThat(Files.isDirectory(emptyFilesystemPath.resolve("a/b")), is(true));
}
@Test
public void testCreateWithOptionFailIfParentIsMissingSucceedsIfParentIsPresent() throws IOException {
Path emptyFilesystemPath = emptyFilesystem();
NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystemPath);
Folder nonExistingFolder = fileSystem.folder("a");
nonExistingFolder.create(FAIL_IF_PARENT_IS_MISSING);
assertThat(Files.isDirectory(emptyFilesystemPath.resolve("a")), is(true));
}
@Test
public void testChildrenOfEmptyNioFolderAreEmpty() throws IOException {
NioFolder folder = NioFileSystem.rootedAt(emptyFilesystem());
assertThat(folder.children().collect(toList()), is(empty()));
}
@Test
public void testChildrenOfNonExistingFolderThrowsUncheckedIOExceptionWithAbolutePathOfFolderInMessage() throws IOException {
Path emptyFolderPath = emptyFilesystem();
NioFolder folder = NioFileSystem.rootedAt(emptyFolderPath);
Files.delete(emptyFolderPath);
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(emptyFolderPath.toString());
folder.children();
}
@Test
public void testChildrenOfFolderAreCorrect() 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.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();
}
}

View File

@@ -0,0 +1,20 @@
package org.cryptomator.filesystem.nio;
import static org.hamcrest.CoreMatchers.equalTo;
import org.cryptomator.commons.test.matcher.PropertyMatcher;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.hamcrest.Matcher;
class NioNodeMatcher {
public static Matcher<Folder> folderWithName(String name) {
return new PropertyMatcher<>(Folder.class, Folder::name, "name", equalTo(name));
}
public static Matcher<File> fileWithName(String name) {
return new PropertyMatcher<>(File.class, File::name, "name", equalTo(name));
}
}

View File

@@ -0,0 +1,22 @@
package org.cryptomator.filesystem.nio;
import static org.hamcrest.CoreMatchers.is;
import java.nio.file.Files;
import java.nio.file.Path;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
class PathMatcher {
public static Matcher<Path> isDirectory() {
return new FeatureMatcher<Path, Boolean>(is(true), "a path for which Files.isDirectory", "Files.isDirectory") {
@Override
protected Boolean featureValueOf(Path actual) {
return Files.isDirectory(actual);
}
};
}
}

View File

@@ -0,0 +1,15 @@
package org.cryptomator.filesystem.nio;
import org.apache.commons.lang3.StringUtils;
public enum SampleFilesystem {
EMPTY_FILESYSTEM, SOME_FOLDERS_AND_FILES
;
public String directoryName() {
return StringUtils.removeEnd(name(), "_FILESYSTEM").toLowerCase();
}
}

View File

@@ -0,0 +1,138 @@
package org.cryptomator.filesystem.nio;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.function.Function;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
public class WeakValuedCacheTest {
private final String A_KEY = "aKey";
private final String ANOTHER_KEY = "anotherKey";
private WeakValuedCache<String, Value> inTest;
private Function<String, Value> loader;
@SuppressWarnings("unchecked")
@Before
public void setup() {
loader = Mockito.mock(Function.class);
inTest = WeakValuedCache.usingLoader(loader);
}
@Test
public void testResultOfGetIsResultOfLoaderForTheSameKey() {
Value theValue = new Value();
Value theOtherValue = new Value();
when(loader.apply(A_KEY)).thenReturn(theValue);
when(loader.apply(ANOTHER_KEY)).thenReturn(theOtherValue);
Value result = inTest.get(A_KEY);
Value anotherResult = inTest.get(ANOTHER_KEY);
assertThat(result, is(sameInstance(theValue)));
assertThat(anotherResult, is(sameInstance(theOtherValue)));
}
@Test
public void testCachedResultIsResultOfLoaderForTheSameKey() {
Value theValue = new Value();
Value theOtherValue = new Value();
when(loader.apply(A_KEY)).thenReturn(theValue);
when(loader.apply(ANOTHER_KEY)).thenReturn(theOtherValue);
inTest.get(A_KEY);
inTest.get(ANOTHER_KEY);
Value result = inTest.get(A_KEY);
Value anotherResult = inTest.get(ANOTHER_KEY);
assertThat(result, is(sameInstance(theValue)));
assertThat(anotherResult, is(sameInstance(theOtherValue)));
}
@Test
public void testTwiceInvocationOfGetDoesNotInvokeLoaderTwice() {
Value theValue = new Value();
when(loader.apply(A_KEY)).thenReturn(theValue);
inTest.get(A_KEY);
inTest.get(A_KEY);
verify(loader).apply(A_KEY);
}
@Test
public void testSecondInvocationOfGetReturnsTheSameResult() {
Value theValue = new Value();
when(loader.apply(A_KEY)).thenReturn(theValue);
inTest.get(A_KEY);
Value result = inTest.get(A_KEY);
assertThat(result, is(sameInstance(theValue)));
}
@Test
public void testCacheDoesNotPreventGarbageCollectionOfValues() {
when(loader.apply(A_KEY)).thenAnswer(this::createValueUsingMoreThanHalfTheJvmMemory);
inTest.get(A_KEY);
// force garbage collection of previously created value by creating an
// object so large it can not coexist with the value
createByteArrayUsingMoreThanHalfTheJvmMemory();
}
@Test(expected = RuntimeExceptionThrownInLoader.class)
public void testCacheRethrowsRuntimeExceptionsFromLoader() {
when(loader.apply(A_KEY)).thenThrow(new RuntimeExceptionThrownInLoader());
inTest.get(A_KEY);
}
@Test(expected = ErrorThrownInLoader.class)
public void testCacheRethrowsErrorsFromLoader() {
when(loader.apply(A_KEY)).thenThrow(new ErrorThrownInLoader());
inTest.get(A_KEY);
}
private Value createValueUsingMoreThanHalfTheJvmMemory(InvocationOnMock invocation) {
byte[] data = createByteArrayUsingMoreThanHalfTheJvmMemory();
Value value = new Value();
value.setPayload(data);
return value;
}
private byte[] createByteArrayUsingMoreThanHalfTheJvmMemory() {
int max = (int) Runtime.getRuntime().maxMemory();
byte[] data = new byte[max / 2 + 1];
return data;
}
private static class RuntimeExceptionThrownInLoader extends RuntimeException {
}
private static class ErrorThrownInLoader extends Error {
}
private static class Value {
@SuppressWarnings("unused")
private Object payload;
public void setPayload(Object payload) {
this.payload = payload;
}
}
}

View File

@@ -35,6 +35,7 @@
<log4j.version>2.1</log4j.version>
<slf4j.version>1.7.7</slf4j.version>
<junit.version>4.12</junit.version>
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
<commons-io.version>2.4</commons-io.version>
<commons-collections.version>4.0</commons-collections.version>
<commons-lang3.version>3.3.2</commons-lang3.version>
@@ -43,7 +44,7 @@
<jackson-databind.version>2.4.4</jackson-databind.version>
<mockito.version>1.10.19</mockito.version>
</properties>
<repositories>
<repository>
<id>jitpack.io</id>
@@ -54,6 +55,13 @@
<dependencyManagement>
<dependencies>
<!-- modules -->
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons-test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>filesystem-api</artifactId>
@@ -74,7 +82,7 @@
<artifactId>filesystem-crypto</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>core</artifactId>
@@ -173,18 +181,36 @@
<version>${jackson-databind.version}</version>
</dependency>
<!-- JUnit / Mockito -->
<!-- JUnit / Mockito / Hamcrest -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
@@ -223,6 +249,7 @@
<module>core</module>
<module>ui</module>
<module>jackrabbit-filesystem-adapter</module>
<module>commons-test</module>
</modules>
<profiles>