mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-19 09:06:54 -04:00
Test filesystem api
* Invariant tests for the File interface * Invariant tests for reading / writing files ** Due to missing features currently ignoring CryptoFileSystem
This commit is contained in:
@@ -77,7 +77,16 @@ public interface File extends Node, Comparable<File> {
|
||||
Mover.move(this, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Deletes the file if it exists.
|
||||
* <p>
|
||||
* Does nothign if the file does not exist.
|
||||
*/
|
||||
default void delete() {
|
||||
if (!exists()) {
|
||||
return;
|
||||
}
|
||||
try (WritableFile writableFile = openWritable()) {
|
||||
writableFile.delete();
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ class InMemoryNode implements Node {
|
||||
|
||||
@Override
|
||||
public Instant lastModified() {
|
||||
if (!exists()) {
|
||||
throw new UncheckedIOException(new IOException("File does not exist"));
|
||||
}
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.cryptomator.filesystem.invariants;
|
||||
|
||||
import static org.cryptomator.filesystem.invariants.matchers.NodeMatchers.hasContent;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assume.assumeThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory;
|
||||
import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile;
|
||||
import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(Theories.class)
|
||||
public class FileReadWriteTests {
|
||||
|
||||
@DataPoints
|
||||
public static final Iterable<FileSystemFactory> FILE_SYSTEM_FACTORIES = new FileSystemFactories();
|
||||
|
||||
@DataPoints
|
||||
public static final Iterable<WayToObtainAFolder> WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder();
|
||||
|
||||
@DataPoints
|
||||
public static final Iterable<WayToObtainAFile> WAYS_TO_OBTAIN_A_FILE = new WaysToObtainAFile();
|
||||
|
||||
private static final String FILE_NAME = "fileName";
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Theory
|
||||
public void testWriteToNonExistingFileCreatesFileWithContent(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile) {
|
||||
assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false));
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME);
|
||||
byte[] dataToWrite = new byte[] {42, -43, 111, 104, -3, 83, -99, 30};
|
||||
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
writable.write(ByteBuffer.wrap(dataToWrite));
|
||||
}
|
||||
|
||||
assertThat(file, hasContent(dataToWrite));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testWriteToExistingFileOverwritesContent(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) {
|
||||
assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true));
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
byte[] originalData = new byte[] {32, 44, 1, -3, 4, 66, 4};
|
||||
File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData);
|
||||
byte[] dataToWrite = new byte[] {42, -43, 111, 104, -3, 83, -99, 30};
|
||||
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
writable.write(ByteBuffer.wrap(dataToWrite));
|
||||
}
|
||||
|
||||
assertThat(file, hasContent(dataToWrite));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testPartialWriteAtStartOfExistingFileOverwritesOnlyPartOfContents(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) {
|
||||
assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true));
|
||||
|
||||
// TODO implement partial writes in CryptoFileSystem
|
||||
assumeThat(fileSystemFactory.toString(), not(containsString("Crypto")));
|
||||
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
byte[] originalData = new byte[] {32, 44, 1, -3, 4, 66, 4};
|
||||
byte[] dataToWrite = new byte[] {1, 2, 3, 4};
|
||||
byte[] expectedData = new byte[] {1, 2, 3, 4, 4, 66, 4};
|
||||
File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData);
|
||||
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
writable.write(ByteBuffer.wrap(dataToWrite));
|
||||
}
|
||||
|
||||
assertThat(file, hasContent(expectedData));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testPartialWriteInTheMiddleOfExistingFileOverwritesOnlyPartOfContents(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) {
|
||||
assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true));
|
||||
|
||||
// TODO implement partial writes in CryptoFileSystem
|
||||
assumeThat(fileSystemFactory.toString(), not(containsString("Crypto")));
|
||||
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
byte[] originalData = new byte[] {32, 44, 1, -3, 4, 66, 4};
|
||||
byte[] dataToWrite = new byte[] {3, 4, 5, 6};
|
||||
byte[] expectedData = new byte[] {32, 44, 3, 4, 5, 6, 4};
|
||||
File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData);
|
||||
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
writable.position(2);
|
||||
writable.write(ByteBuffer.wrap(dataToWrite));
|
||||
}
|
||||
|
||||
assertThat(file, hasContent(expectedData));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testPartialWriteAtEndOfExistingFileOverwritesOnlyPartOfContents(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) {
|
||||
assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true));
|
||||
|
||||
// TODO implement partial writes in CryptoFileSystem
|
||||
assumeThat(fileSystemFactory.toString(), not(containsString("Crypto")));
|
||||
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
byte[] originalData = new byte[] {-1, 44, 1, -3, 4, 66, 4};
|
||||
byte[] dataToWrite = new byte[] {4, 5, 6, 7};
|
||||
byte[] expectedData = new byte[] {-1, 44, 1, 4, 5, 6, 7};
|
||||
File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData);
|
||||
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
writable.position(3);
|
||||
writable.write(ByteBuffer.wrap(dataToWrite));
|
||||
}
|
||||
|
||||
assertThat(file, hasContent(expectedData));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate;
|
||||
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
|
||||
import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory;
|
||||
import org.cryptomator.filesystem.nio.NioFileSystem;
|
||||
import org.cryptomator.filesystem.shortening.ShorteningFileSystem;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
@@ -36,6 +37,8 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
add("InMemoryFileSystem", this::createInMemoryFileSystem);
|
||||
add("CryptoFileSystem(NioFileSystem)", this::createCryptoFileSystemNio);
|
||||
add("CryptoFileSystem(InMemoryFileSystem)", this::createCryptoFileSystemInMemory);
|
||||
add("ShorteningFileSystem(NioFileSystem)", this::createShorteningFileSystemNio);
|
||||
add("ShorteningFileSystem(InMemoryFileSystem)", this::createShorteningFileSystemInMemory);
|
||||
}
|
||||
|
||||
private FileSystem createNioFileSystem() {
|
||||
@@ -58,6 +61,16 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
return new CryptoFileSystem(createNioFileSystem(), createCryptor(), Mockito.mock(CryptoFileSystemDelegate.class), "aPassphrase");
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemNio() {
|
||||
FileSystem delegate = createNioFileSystem();
|
||||
return new ShorteningFileSystem(delegate.folder("d"), delegate.folder("m"), 3);
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemInMemory() {
|
||||
FileSystem delegate = createInMemoryFileSystem();
|
||||
return new ShorteningFileSystem(delegate.folder("d"), delegate.folder("m"), 3);
|
||||
}
|
||||
|
||||
private Cryptor createCryptor() {
|
||||
Cryptor cryptor = new CryptorImpl(RANDOM_MOCK);
|
||||
cryptor.randomizeMasterkey();
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package org.cryptomator.filesystem.invariants;
|
||||
|
||||
import static org.cryptomator.filesystem.invariants.matchers.NodeMatchers.hasContent;
|
||||
import static org.cryptomator.common.test.matcher.OptionalMatcher.presentOptionalWithValueThat;
|
||||
import static org.cryptomator.filesystem.invariants.matchers.InstantMatcher.inRangeInclusiveWithTolerance;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assume.assumeThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory;
|
||||
import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile;
|
||||
import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder;
|
||||
@@ -34,21 +36,126 @@ public class FileTests {
|
||||
|
||||
private static final String FILE_NAME = "fileName";
|
||||
|
||||
private static final String FOLDER_NAME = "folderName";
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Theory
|
||||
public void testWriteToNonExistingFileCreatesFileWithContent(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile) {
|
||||
public void testNonExistingFileDoesNotExist(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile) {
|
||||
assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false));
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME);
|
||||
byte[] dataToWrite = new byte[] {42, -43, 111, 104, -3, 83, -99, 30};
|
||||
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
writable.write(ByteBuffer.wrap(dataToWrite));
|
||||
}
|
||||
assertThat(file.exists(), is(false));
|
||||
}
|
||||
|
||||
assertThat(file, hasContent(dataToWrite));
|
||||
@Theory
|
||||
public void testExistingFileExist(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) {
|
||||
assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true));
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainAnExistingFile.fileWithName(fileSystem, FILE_NAME);
|
||||
|
||||
assertThat(file.exists(), is(true));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testNameOfFileIsFileName(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) {
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME);
|
||||
|
||||
assertThat(file.name(), is(FILE_NAME));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testDeletedFileDoesNotExist(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) {
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME);
|
||||
file.delete();
|
||||
|
||||
assertThat(file.exists(), is(false));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testParentOfFileInFilesystemIsFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) {
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME);
|
||||
|
||||
assertThat(file.parent(), is(presentOptionalWithValueThat(is(fileSystem))));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testParentOfFileInFolderIsFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAFolder, WayToObtainAFile wayToObtainAFile) {
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
Folder folder = wayToObtainAFolder.folderWithName(fileSystem, FOLDER_NAME);
|
||||
File file = wayToObtainAFile.fileWithName(folder, FILE_NAME);
|
||||
|
||||
assertThat(file.parent(), is(presentOptionalWithValueThat(is(folder))));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testFilesystemOfFileInFilesystemInFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) {
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME);
|
||||
|
||||
assertThat(file.fileSystem(), is(fileSystem));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testFilesystemOfFileInFolderIsFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAFolder, WayToObtainAFile wayToObtainAFile) {
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
Folder folder = wayToObtainAFolder.folderWithName(fileSystem, FOLDER_NAME);
|
||||
File file = wayToObtainAFile.fileWithName(folder, FILE_NAME);
|
||||
|
||||
assertThat(file.fileSystem(), is(fileSystem));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testFilesFromTwoFileSystemsDoNotBelongToSameFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) {
|
||||
File file = wayToObtainAFile.fileWithName(fileSystemFactory.create(), FILE_NAME);
|
||||
File otherFile = wayToObtainAFile.fileWithName(fileSystemFactory.create(), FILE_NAME);
|
||||
|
||||
assertThat(file.belongsToSameFilesystem(otherFile), is(false));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testFilesFromSameFileSystemsDoBelongToSameFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) {
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME);
|
||||
File otherFile = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME);
|
||||
|
||||
assertThat(file.belongsToSameFilesystem(otherFile), is(true));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testFilesBelongToSameFilesystemAsItself(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) {
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME);
|
||||
|
||||
assertThat(file.belongsToSameFilesystem(file), is(true));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testLastModifiedIsInCorrectSecondsRange(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) {
|
||||
assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true));
|
||||
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
Instant min = Instant.now();
|
||||
File file = wayToObtainAnExistingFile.fileWithName(fileSystem, FILE_NAME);
|
||||
Instant max = Instant.now();
|
||||
|
||||
assertThat(file.lastModified(), is(inRangeInclusiveWithTolerance(min, max, 2000)));
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testLastModifiedThrowsUncheckedIoExceptionForNonExistingFile(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile) {
|
||||
assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false));
|
||||
|
||||
FileSystem fileSystem = fileSystemFactory.create();
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
|
||||
System.out.println(wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME).lastModified());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,19 +16,60 @@ class WaysToObtainAFile implements Iterable<WayToObtainAFile> {
|
||||
|
||||
public WaysToObtainAFile() {
|
||||
addNonExisting("invoke file", this::invokeFile);
|
||||
addNonExisting("delete file created by writing to it", this::deleteFileCreatedByWritingToIt);
|
||||
|
||||
addExisting("create file by writing to it", this::createFileByWritingToIt);
|
||||
addExisting("create file by copying", this::createFileByCopying);
|
||||
addExisting("create file by moving", this::createFileByMoving);
|
||||
}
|
||||
|
||||
private File invokeFile(Folder parent, String name, byte[] content) {
|
||||
return parent.file(name);
|
||||
}
|
||||
|
||||
private File createFileByWritingToIt(Folder parent, String name, byte[] content) {
|
||||
private File deleteFileCreatedByWritingToIt(Folder parent, String name, byte[] content) {
|
||||
boolean deleteParent = !parent.exists();
|
||||
parent.create();
|
||||
File result = parent.file(name);
|
||||
try (WritableFile writable = result.openWritable()) {
|
||||
writable.write(ByteBuffer.wrap(content));
|
||||
}
|
||||
result.delete();
|
||||
if (deleteParent) {
|
||||
parent.delete();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private File createFileByWritingToIt(Folder parent, String name, byte[] content) {
|
||||
parent.create();
|
||||
File result = parent.file(name);
|
||||
try (WritableFile writable = result.openWritable()) {
|
||||
writable.write(ByteBuffer.wrap(content));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private File createFileByCopying(Folder parent, String name, byte[] content) {
|
||||
parent.create();
|
||||
File tmp = parent.file(name + ".createFileByCopying.tmp");
|
||||
try (WritableFile writable = tmp.openWritable()) {
|
||||
writable.write(ByteBuffer.wrap(content));
|
||||
}
|
||||
File result = parent.file(name);
|
||||
tmp.copyTo(result);
|
||||
tmp.delete();
|
||||
return result;
|
||||
}
|
||||
|
||||
private File createFileByMoving(Folder parent, String name, byte[] content) {
|
||||
parent.create();
|
||||
File tmp = parent.file(name + ".createFileByCopying.tmp");
|
||||
try (WritableFile writable = tmp.openWritable()) {
|
||||
writable.write(ByteBuffer.wrap(content));
|
||||
}
|
||||
File result = parent.file(name);
|
||||
tmp.moveTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.cryptomator.filesystem.invariants.matchers;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher;
|
||||
|
||||
public class InstantMatcher {
|
||||
|
||||
public static Matcher<Instant> inRangeInclusiveWithTolerance(Instant min, Instant max, int toleranceInMilliseconds) {
|
||||
return inRangeInclusive(min.minusMillis(toleranceInMilliseconds), max.plusMillis(toleranceInMilliseconds));
|
||||
}
|
||||
|
||||
public static Matcher<Instant> inRangeInclusive(Instant min, Instant max) {
|
||||
return new TypeSafeDiagnosingMatcher<Instant>(Instant.class) {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText(format("an Instant in [%s,%s]", min, max));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(Instant item, Description mismatchDescription) {
|
||||
if (item.isBefore(min) || item.isAfter(max)) {
|
||||
mismatchDescription.appendText("the instant ").appendValue(item);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user