Przeglądaj źródła

Test filesystem api

* Invariant tests for the File interface
* Invariant tests for reading / writing files
** Due to missing features currently ignoring CryptoFileSystem
Markus Kreusch 9 lat temu
rodzic
commit
56fcb99248

+ 9 - 0
main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java

@@ -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();
 		}

+ 3 - 0
main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java

@@ -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;
 	}
 

+ 133 - 0
main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileReadWriteTests.java

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

+ 13 - 0
main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java

@@ -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();

+ 116 - 9
main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileTests.java

@@ -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));
+	}
+
+	@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);
 
-		assertThat(file, hasContent(dataToWrite));
+		System.out.println(wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME).lastModified());
 	}
 
 }

+ 41 - 0
main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java

@@ -16,15 +16,33 @@ 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 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));
@@ -32,6 +50,29 @@ class WaysToObtainAFile implements Iterable<WayToObtainAFile> {
 		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;
+	}
+
 	private void addExisting(String name, WayToObtainAFileThatExists factory) {
 		values.add(new WayToObtainAFileThatExists() {
 			@Override

+ 35 - 0
main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/InstantMatcher.java

@@ -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;
+			}
+		};
+	}
+
+}