From ef94c1ed40ad69e580f06b029dd45056a5bb6318 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:08:24 +0200 Subject: [PATCH 01/13] Added test: Replace an existing, shortened, empty directory --- ...leSystemProviderInMemoryIntegrationTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index c2822284..066c68ea 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -74,6 +74,23 @@ public void testReplaceExistingShortenedFile() throws IOException { } } + /* //TODO https://github.com/cryptomator/cryptofs/issues/176 + @Test + @DisplayName("Replace an existing, shortened, empty directory") + public void testReplaceExistingShortenedDirEmpty() throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var dirName50Chars = "/target_89_123456789_123456789_123456789_123456789_"; //since filename encryption increases filename length, 50 cleartext chars are sufficient + var source = fs.getPath("/sourceDir"); + var target = fs.getPath(dirName50Chars); + Files.createDirectory(source); + Files.createDirectory(target); + + assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); + assertTrue(Files.notExists(source)); + assertTrue(Files.exists(target)); + } + }*/ + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { byte[] key = new byte[64]; Arrays.fill(key, (byte) 0x55); From 8b615447ceb82496ca9a826f5e602a8b04220385 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:15:03 +0200 Subject: [PATCH 02/13] Added test: Replace an existing, shortened symlink --- ...SystemProviderInMemoryIntegrationTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index 066c68ea..9d0921d1 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -91,6 +91,50 @@ public void testReplaceExistingShortenedDirEmpty() throws IOException { } }*/ + /* //TODO https://github.com/cryptomator/cryptofs/issues/177 + @Test + @DisplayName("Replace an existing, shortened symlink") + public void testReplaceExistingShortenedSymlink() throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var source = fs.getPath("/sourceDir"); + var linkedFromSource = fs.getPath("/linkedFromSource.txt"); + var linkedFromSourceContent = "linkedFromSourceContent!"; + + var targetName50Chars = "/target_89_123456789_123456789_123456789_123456789_"; //since filename encryption increases filename length, 50 cleartext chars are sufficient + var target = fs.getPath(targetName50Chars); + var linkedFromTarget = fs.getPath("/linkedFromTarget.txt"); + var linkedFromTargetContent = "linkedFromTargeContent!"; + + Files.createFile(linkedFromSource); + Files.writeString(linkedFromSource, linkedFromSourceContent, UTF_8); + Files.createFile(linkedFromTarget); + Files.writeString(linkedFromTarget, linkedFromTargetContent, UTF_8); + + Files.createSymbolicLink(source, linkedFromSource); + Files.createSymbolicLink(target, linkedFromTarget); + + assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); + assertTrue(Files.notExists(source)); + assertTrue(Files.exists(target)); + + //Assert linked files haven't been changed + assertTrue(Files.exists(linkedFromSource)); + assertEquals(Files.readString(linkedFromSource, UTF_8), linkedFromSourceContent); + assertFalse(Files.isSymbolicLink(linkedFromSource)); + assertTrue(Files.isRegularFile(linkedFromSource, LinkOption.NOFOLLOW_LINKS)); + + assertTrue(Files.exists(linkedFromTarget)); + assertEquals(Files.readString(linkedFromTarget, UTF_8), linkedFromTargetContent); + assertFalse(Files.isSymbolicLink(linkedFromTarget)); + assertTrue(Files.isRegularFile(linkedFromTarget, LinkOption.NOFOLLOW_LINKS)); + + //Assert link is correct + assertTrue(Files.isSymbolicLink(target)); + assertTrue(Files.isRegularFile(target /* FOLLOW_LINKS */)); + assertEquals(Files.readSymbolicLink(target), linkedFromSource); + } + }*/ + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { byte[] key = new byte[64]; Arrays.fill(key, (byte) 0x55); From 0625d6c34ecd6a66f788463ecf238dbefef2d071 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 8 Aug 2023 18:55:20 +0200 Subject: [PATCH 03/13] Replaced simple tests with parameterized tests --- ...SystemProviderInMemoryIntegrationTest.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index 9d0921d1..968e9734 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -9,7 +9,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; import java.io.IOException; @@ -27,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertTrue; +//For shortening: Since filename encryption increases filename length, 50 cleartext chars are sufficient to reach the threshold public class CryptoFileSystemProviderInMemoryIntegrationTest { private static FileSystem tmpFs; @@ -58,13 +60,14 @@ public static void afterAll() throws IOException { tmpFs.close(); } - @Test - @DisplayName("Replace an existing, shortened file") - public void testReplaceExistingShortenedFile() throws IOException { + @DisplayName("Replace an existing file") + @ParameterizedTest + @ValueSource(strings = {"target50Chars_56789_123456789_123456789_123456789_", "target15Chars__", // + "target50Chars_56789_123456789_123456789_123456.txt", "target15C__.txt"}) + public void testReplaceExistingFile(String targetName) throws IOException { try (var fs = setupCryptoFs(50, 100, false)) { - var fiftyCharName2 = "/50char2_50char2_50char2_50char2_50char2_50char.txt"; //since filename encryption increases filename length, 50 cleartext chars are sufficient var source = fs.getPath("/source.txt"); - var target = fs.getPath(fiftyCharName2); + var target = fs.getPath("/" + targetName); Files.createFile(source); Files.createFile(target); @@ -75,13 +78,14 @@ public void testReplaceExistingShortenedFile() throws IOException { } /* //TODO https://github.com/cryptomator/cryptofs/issues/176 - @Test - @DisplayName("Replace an existing, shortened, empty directory") - public void testReplaceExistingShortenedDirEmpty() throws IOException { + @DisplayName("Replace an existing, empty directory") + @ParameterizedTest + @ValueSource(strings = {"target50Chars_56789_123456789_123456789_123456789_", "target15Chars__", // + "target50Chars_56789_123456789_123456789_123456.txt", "target15C__.txt"}) + public void testReplaceExistingDirEmpty(String targetName) throws IOException { try (var fs = setupCryptoFs(50, 100, false)) { - var dirName50Chars = "/target_89_123456789_123456789_123456789_123456789_"; //since filename encryption increases filename length, 50 cleartext chars are sufficient var source = fs.getPath("/sourceDir"); - var target = fs.getPath(dirName50Chars); + var target = fs.getPath("/" + targetName); Files.createDirectory(source); Files.createDirectory(target); @@ -92,16 +96,17 @@ public void testReplaceExistingShortenedDirEmpty() throws IOException { }*/ /* //TODO https://github.com/cryptomator/cryptofs/issues/177 - @Test - @DisplayName("Replace an existing, shortened symlink") - public void testReplaceExistingShortenedSymlink() throws IOException { + @DisplayName("Replace an existing symlink") + @ParameterizedTest + @ValueSource(strings = {"target50Chars_56789_123456789_123456789_123456789_", "target15Chars__", // + "target50Chars_56789_123456789_123456789_123456.txt", "target15C__.txt"}) + public void testReplaceExistingSymlink(String targetName) throws IOException { try (var fs = setupCryptoFs(50, 100, false)) { var source = fs.getPath("/sourceDir"); var linkedFromSource = fs.getPath("/linkedFromSource.txt"); var linkedFromSourceContent = "linkedFromSourceContent!"; - var targetName50Chars = "/target_89_123456789_123456789_123456789_123456789_"; //since filename encryption increases filename length, 50 cleartext chars are sufficient - var target = fs.getPath(targetName50Chars); + var target = fs.getPath("/" + targetName); var linkedFromTarget = fs.getPath("/linkedFromTarget.txt"); var linkedFromTargetContent = "linkedFromTargeContent!"; From 7e683ed489d0a9536fdfac3d67fdfe2edddd43fa Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 8 Aug 2023 19:01:21 +0200 Subject: [PATCH 04/13] Consolidated parameterized test args --- ...SystemProviderInMemoryIntegrationTest.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index 968e9734..97dcbdda 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -14,6 +14,10 @@ import org.mockito.Mockito; import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; @@ -60,10 +64,21 @@ public static void afterAll() throws IOException { tmpFs.close(); } - @DisplayName("Replace an existing file") @ParameterizedTest - @ValueSource(strings = {"target50Chars_56789_123456789_123456789_123456789_", "target15Chars__", // - "target50Chars_56789_123456789_123456789_123456.txt", "target15C__.txt"}) + @ValueSource(strings = { // + "target50Chars_56789_123456789_123456789_123456789_", // + "target15Chars__", // + "target50Chars_56789_123456789_123456789_123456.txt", // + "target15C__.txt" // + }) + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface ParameterizedFileTest { + + } + + @DisplayName("Replace an existing file") + @ParameterizedFileTest public void testReplaceExistingFile(String targetName) throws IOException { try (var fs = setupCryptoFs(50, 100, false)) { var source = fs.getPath("/source.txt"); @@ -79,9 +94,7 @@ public void testReplaceExistingFile(String targetName) throws IOException { /* //TODO https://github.com/cryptomator/cryptofs/issues/176 @DisplayName("Replace an existing, empty directory") - @ParameterizedTest - @ValueSource(strings = {"target50Chars_56789_123456789_123456789_123456789_", "target15Chars__", // - "target50Chars_56789_123456789_123456789_123456.txt", "target15C__.txt"}) + @ParameterizedFileTest public void testReplaceExistingDirEmpty(String targetName) throws IOException { try (var fs = setupCryptoFs(50, 100, false)) { var source = fs.getPath("/sourceDir"); @@ -97,9 +110,7 @@ public void testReplaceExistingDirEmpty(String targetName) throws IOException { /* //TODO https://github.com/cryptomator/cryptofs/issues/177 @DisplayName("Replace an existing symlink") - @ParameterizedTest - @ValueSource(strings = {"target50Chars_56789_123456789_123456789_123456789_", "target15Chars__", // - "target50Chars_56789_123456789_123456789_123456.txt", "target15C__.txt"}) + @ParameterizedFileTest public void testReplaceExistingSymlink(String targetName) throws IOException { try (var fs = setupCryptoFs(50, 100, false)) { var source = fs.getPath("/sourceDir"); From 941e60eb8f98ba2261c8a4015060080a592d46cf Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:07:40 +0200 Subject: [PATCH 05/13] Added tests: Delete not-existing and regular file --- ...SystemProviderInMemoryIntegrationTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index 7f3de4c1..a8d7a11a 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -22,6 +22,8 @@ import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Arrays; import java.util.Comparator; @@ -30,6 +32,7 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; //For shortening: Since filename encryption increases filename length, 50 cleartext chars are sufficient to reach the threshold @@ -150,6 +153,31 @@ public void testReplaceExistingSymlink(String targetName) throws IOException { } }*/ + @DisplayName("Delete not existing file") + @ParameterizedFileTest + public void testDeleteNotExisting(String targetName) throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var file = fs.getPath("/" + targetName); + + assertThrows(NoSuchFileException.class, () -> Files.delete(file)); //TODO Verify behavior + } + } + + @DisplayName("Delete regular file") + @ParameterizedFileTest + public void testDeleteFile(String targetName) throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var file = fs.getPath("/" + targetName); + Files.createFile(file); + + assertTrue(Files.exists(file, LinkOption.NOFOLLOW_LINKS)); + assertDoesNotThrow(() -> Files.delete(file)); + assertTrue(Files.notExists(file, LinkOption.NOFOLLOW_LINKS)); + + assertThrows(NoSuchFileException.class, () -> Files.delete(file)); //TODO Verify behavior + } + } + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { byte[] key = new byte[64]; Arrays.fill(key, (byte) 0x55); From 4e2eaa0173818074122b5da64361eaaef556f8c2 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:09:05 +0200 Subject: [PATCH 06/13] Added test: Delete empty dir that never contained elements --- ...FileSystemProviderInMemoryIntegrationTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index a8d7a11a..a1083f9b 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -178,6 +178,21 @@ public void testDeleteFile(String targetName) throws IOException { } } + @DisplayName("Delete empty directory that never contained elements") + @ParameterizedFileTest + public void testDeleteDirAlwaysEmpty(String targetName) throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var file = fs.getPath("/" + targetName); + Files.createDirectory(file); + + assertTrue(Files.exists(file, LinkOption.NOFOLLOW_LINKS)); + assertDoesNotThrow(() -> Files.delete(file)); + assertTrue(Files.notExists(file, LinkOption.NOFOLLOW_LINKS)); + + assertThrows(NoSuchFileException.class, () -> Files.delete(file)); //TODO Verify behavior + } + } + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { byte[] key = new byte[64]; Arrays.fill(key, (byte) 0x55); From 50e008d498d5961f724626fbd80a8344ec98bc8c Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:29:28 +0200 Subject: [PATCH 07/13] Added test: Delete dir while and after containing multiple elements --- ...SystemProviderInMemoryIntegrationTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index a1083f9b..e1067f83 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -19,6 +19,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.URI; +import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -193,6 +194,42 @@ public void testDeleteDirAlwaysEmpty(String targetName) throws IOException { } } + @DisplayName("Delete directory while and after containing multiple elements") + @ParameterizedFileTest + public void testDeleteDirMultiple(String targetName) throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var targetDir = fs.getPath("/" + targetName); + Files.createDirectory(targetDir); + + var nestedFile = targetDir.resolve("nestedFile"); + Files.createFile(nestedFile); + var nestedDir = targetDir.resolve("nestedDir"); + Files.createDirectory(nestedDir); + var nestedLink = targetDir.resolve("nestedLink"); + Files.createSymbolicLink(nestedLink, fs.getPath("linkTarget")); + + assertThrows(DirectoryNotEmptyException.class, () -> Files.delete(targetDir)); + + assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.exists(nestedFile, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.exists(nestedDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.exists(nestedLink, LinkOption.NOFOLLOW_LINKS)); + + assertDoesNotThrow(() -> Files.delete(nestedFile)); + assertDoesNotThrow(() -> Files.delete(nestedDir)); + assertDoesNotThrow(() -> Files.delete(nestedLink)); + + assertDoesNotThrow(() -> Files.delete(targetDir)); + + assertTrue(Files.notExists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.notExists(nestedFile, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.notExists(nestedDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.notExists(nestedLink, LinkOption.NOFOLLOW_LINKS)); + + assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); //TODO Verify behavior + } + } + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { byte[] key = new byte[64]; Arrays.fill(key, (byte) 0x55); From 5b3906ff17b7190db792ce1e234b900ce5717c8a Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:32:09 +0200 Subject: [PATCH 08/13] Added test: Delete dir while and after containing one element --- ...SystemProviderInMemoryIntegrationTest.java | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index e1067f83..9572f8d3 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -9,8 +9,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; import java.io.IOException; @@ -29,6 +31,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Set; +import java.util.stream.Stream; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; @@ -68,13 +71,19 @@ public static void afterAll() throws IOException { tmpFs.close(); } - @ParameterizedTest - @ValueSource(strings = { // + private final static String[] targetFileNamesArray = new String[]{ // "target50Chars_56789_123456789_123456789_123456789_", // "target15Chars__", // "target50Chars_56789_123456789_123456789_123456.txt", // "target15C__.txt" // - }) + }; + + static Stream targetFileNames() { + return Arrays.stream(targetFileNamesArray); + } + + @ParameterizedTest + @MethodSource("org.cryptomator.cryptofs.CryptoFileSystemProviderInMemoryIntegrationTest#targetFileNames") @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface ParameterizedFileTest { @@ -230,6 +239,40 @@ public void testDeleteDirMultiple(String targetName) throws IOException { } } + static Stream dirEntries() { + Stream> operations = Stream.of(Files::createFile, // + Files::createDirectory, // + nestedElement -> Files.createSymbolicLink(nestedElement, nestedElement.resolveSibling("linkTarget"))); + return operations.flatMap(elementCreator -> targetFileNames().map( // + s -> Arguments.of(s, elementCreator)) // + ); + } + + @DisplayName("Delete directory while and after containing one element") + @ParameterizedTest + @MethodSource("org.cryptomator.cryptofs.CryptoFileSystemProviderInMemoryIntegrationTest#dirEntries") + public void testDeleteDirSingle(String targetName, ThrowingConsumer entryCreator) throws Throwable /* = IOE from entryCreator */ { + try (var fs = setupCryptoFs(50, 100, false)) { + var targetDir = fs.getPath("/" + targetName); + Files.createDirectory(targetDir); + + var nestedElement = targetDir.resolve("nestedElement"); + entryCreator.accept(nestedElement); + + assertThrows(DirectoryNotEmptyException.class, () -> Files.delete(targetDir)); + assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.exists(nestedElement, LinkOption.NOFOLLOW_LINKS)); + + assertDoesNotThrow(() -> Files.delete(nestedElement)); + assertDoesNotThrow(() -> Files.delete(targetDir)); + + assertTrue(Files.notExists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.notExists(nestedElement, LinkOption.NOFOLLOW_LINKS)); + + assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); //TODO Verify behavior + } + } + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { byte[] key = new byte[64]; Arrays.fill(key, (byte) 0x55); From c626b9fa26ab5d85417349bcba3f38863f096e79 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 10 Aug 2023 15:45:30 +0200 Subject: [PATCH 09/13] Removed "Verify behavior" TODOs Verified that `Files#delete` is supposed to throw a NoSuchFileException if no file can be found. --- ...ryptoFileSystemProviderInMemoryIntegrationTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index 9572f8d3..b2f18ecc 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -169,7 +169,7 @@ public void testDeleteNotExisting(String targetName) throws IOException { try (var fs = setupCryptoFs(50, 100, false)) { var file = fs.getPath("/" + targetName); - assertThrows(NoSuchFileException.class, () -> Files.delete(file)); //TODO Verify behavior + assertThrows(NoSuchFileException.class, () -> Files.delete(file)); } } @@ -184,7 +184,7 @@ public void testDeleteFile(String targetName) throws IOException { assertDoesNotThrow(() -> Files.delete(file)); assertTrue(Files.notExists(file, LinkOption.NOFOLLOW_LINKS)); - assertThrows(NoSuchFileException.class, () -> Files.delete(file)); //TODO Verify behavior + assertThrows(NoSuchFileException.class, () -> Files.delete(file)); } } @@ -199,7 +199,7 @@ public void testDeleteDirAlwaysEmpty(String targetName) throws IOException { assertDoesNotThrow(() -> Files.delete(file)); assertTrue(Files.notExists(file, LinkOption.NOFOLLOW_LINKS)); - assertThrows(NoSuchFileException.class, () -> Files.delete(file)); //TODO Verify behavior + assertThrows(NoSuchFileException.class, () -> Files.delete(file)); } } @@ -235,7 +235,7 @@ public void testDeleteDirMultiple(String targetName) throws IOException { assertTrue(Files.notExists(nestedDir, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.notExists(nestedLink, LinkOption.NOFOLLOW_LINKS)); - assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); //TODO Verify behavior + assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); } } @@ -269,7 +269,7 @@ public void testDeleteDirSingle(String targetName, ThrowingConsumer entryC assertTrue(Files.notExists(targetDir, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.notExists(nestedElement, LinkOption.NOFOLLOW_LINKS)); - assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); //TODO Verify behavior + assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); } } From d1664b6baa3cd6df3abcceb43a7f9849715c3db6 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:22:36 +0200 Subject: [PATCH 10/13] Added test for possible regression in the JDK --- .../cryptomator/cryptofs/RegressionTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/test/java/org/cryptomator/cryptofs/RegressionTest.java diff --git a/src/test/java/org/cryptomator/cryptofs/RegressionTest.java b/src/test/java/org/cryptomator/cryptofs/RegressionTest.java new file mode 100644 index 00000000..9c49be1b --- /dev/null +++ b/src/test/java/org/cryptomator/cryptofs/RegressionTest.java @@ -0,0 +1,30 @@ +package org.cryptomator.cryptofs; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class RegressionTest { + + /** + * For cryptofs to function properly {@link Files#readAttributes(Path, Class, LinkOption...)} must throw a + * {@link NoSuchFileException} if the targeted file does not exist.
+ * This behavior is not guaranteed by the JDK specification, although the JDK itself depends on it. + * Internal discussions concluded that depending on this behavior and testing for any regressions is + * preferable to abiding strictly to the specification. + * + * @see CryptoPathMapper#getCiphertextFileType(CryptoPath) + */ + @Test + public void testNotExistingFile(@TempDir Path dir) { + assertThrows(NoSuchFileException.class, () -> Files.readAttributes(dir.resolve("notExistingFile"), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)); + assertThrows(NoSuchFileException.class, () -> Files.readAttributes(dir.resolve("notExistingFile"), BasicFileAttributes.class)); + } +} From 51ab4898687421f7bf921cafb4604166b7cbb0a9 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 22 Sep 2023 20:02:58 +0200 Subject: [PATCH 11/13] Split tests: Delete dir with elements --- ...SystemProviderInMemoryIntegrationTest.java | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index b2f18ecc..c22e16e0 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -205,7 +205,7 @@ public void testDeleteDirAlwaysEmpty(String targetName) throws IOException { @DisplayName("Delete directory while and after containing multiple elements") @ParameterizedFileTest - public void testDeleteDirMultiple(String targetName) throws IOException { + public void testDeleteDirMultipleNagging(String targetName) throws IOException { try (var fs = setupCryptoFs(50, 100, false)) { var targetDir = fs.getPath("/" + targetName); Files.createDirectory(targetDir); @@ -218,23 +218,61 @@ public void testDeleteDirMultiple(String targetName) throws IOException { Files.createSymbolicLink(nestedLink, fs.getPath("linkTarget")); assertThrows(DirectoryNotEmptyException.class, () -> Files.delete(targetDir)); - assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.exists(nestedFile, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.exists(nestedDir, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.exists(nestedLink, LinkOption.NOFOLLOW_LINKS)); assertDoesNotThrow(() -> Files.delete(nestedFile)); + assertThrows(DirectoryNotEmptyException.class, () -> Files.delete(targetDir)); + assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.notExists(nestedFile, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.exists(nestedDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.exists(nestedLink, LinkOption.NOFOLLOW_LINKS)); + assertDoesNotThrow(() -> Files.delete(nestedDir)); + assertThrows(DirectoryNotEmptyException.class, () -> Files.delete(targetDir)); + assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.notExists(nestedDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.exists(nestedLink, LinkOption.NOFOLLOW_LINKS)); + assertDoesNotThrow(() -> Files.delete(nestedLink)); + assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.notExists(nestedLink, LinkOption.NOFOLLOW_LINKS)); assertDoesNotThrow(() -> Files.delete(targetDir)); - assertTrue(Files.notExists(targetDir, LinkOption.NOFOLLOW_LINKS)); + + assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); + } + } + + @DisplayName("Delete directory after containing multiple elements") + @ParameterizedFileTest + public void testDeleteDirMultiple(String targetName) throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var targetDir = fs.getPath("/" + targetName); + Files.createDirectory(targetDir); + + var nestedFile = targetDir.resolve("nestedFile"); + Files.createFile(nestedFile); + var nestedDir = targetDir.resolve("nestedDir"); + Files.createDirectory(nestedDir); + var nestedLink = targetDir.resolve("nestedLink"); + Files.createSymbolicLink(nestedLink, fs.getPath("linkTarget")); + + assertDoesNotThrow(() -> Files.delete(nestedFile)); + assertDoesNotThrow(() -> Files.delete(nestedDir)); + assertDoesNotThrow(() -> Files.delete(nestedLink)); + + assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.notExists(nestedFile, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.notExists(nestedDir, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.notExists(nestedLink, LinkOption.NOFOLLOW_LINKS)); + assertDoesNotThrow(() -> Files.delete(targetDir)); + assertTrue(Files.notExists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); } } @@ -251,7 +289,7 @@ static Stream dirEntries() { @DisplayName("Delete directory while and after containing one element") @ParameterizedTest @MethodSource("org.cryptomator.cryptofs.CryptoFileSystemProviderInMemoryIntegrationTest#dirEntries") - public void testDeleteDirSingle(String targetName, ThrowingConsumer entryCreator) throws Throwable /* = IOE from entryCreator */ { + public void testDeleteDirSingleNagging(String targetName, ThrowingConsumer entryCreator) throws Throwable /* = IOE from entryCreator */ { try (var fs = setupCryptoFs(50, 100, false)) { var targetDir = fs.getPath("/" + targetName); Files.createDirectory(targetDir); @@ -264,11 +302,34 @@ public void testDeleteDirSingle(String targetName, ThrowingConsumer entryC assertTrue(Files.exists(nestedElement, LinkOption.NOFOLLOW_LINKS)); assertDoesNotThrow(() -> Files.delete(nestedElement)); - assertDoesNotThrow(() -> Files.delete(targetDir)); + assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertTrue(Files.notExists(nestedElement, LinkOption.NOFOLLOW_LINKS)); + assertDoesNotThrow(() -> Files.delete(targetDir)); assertTrue(Files.notExists(targetDir, LinkOption.NOFOLLOW_LINKS)); + + assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); + } + } + + @DisplayName("Delete directory after containing one element") + @ParameterizedTest + @MethodSource("org.cryptomator.cryptofs.CryptoFileSystemProviderInMemoryIntegrationTest#dirEntries") + public void testDeleteDirSingle(String targetName, ThrowingConsumer entryCreator) throws Throwable /* = IOE from entryCreator */ { + try (var fs = setupCryptoFs(50, 100, false)) { + var targetDir = fs.getPath("/" + targetName); + Files.createDirectory(targetDir); + + var nestedElement = targetDir.resolve("nestedElement"); + entryCreator.accept(nestedElement); + + assertDoesNotThrow(() -> Files.delete(nestedElement)); + assertTrue(Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS)); assertTrue(Files.notExists(nestedElement, LinkOption.NOFOLLOW_LINKS)); + assertDoesNotThrow(() -> Files.delete(targetDir)); + assertTrue(Files.notExists(targetDir, LinkOption.NOFOLLOW_LINKS)); + assertThrows(NoSuchFileException.class, () -> Files.delete(targetDir)); } } From a38ddffb8af4529a216d363c7ca4a778c127435a Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 10 Oct 2023 00:01:36 +0200 Subject: [PATCH 12/13] Added test: Delete directly recursive link --- ...FileSystemProviderInMemoryIntegrationTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index c22e16e0..7fb65c22 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -334,6 +334,21 @@ public void testDeleteDirSingle(String targetName, ThrowingConsumer entryC } } + @DisplayName("Delete directly recursive link") + @ParameterizedFileTest + public void testDeleteDirectlyRecursiveSymLink(String targetName) throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var link = fs.getPath("/" + targetName); + Files.createSymbolicLink(link, link); + + assertTrue(Files.exists(link, LinkOption.NOFOLLOW_LINKS)); + assertDoesNotThrow(() -> Files.delete(link)); + assertTrue(Files.notExists(link, LinkOption.NOFOLLOW_LINKS)); + + assertThrows(NoSuchFileException.class, () -> Files.delete(link)); + } + } + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { byte[] key = new byte[64]; Arrays.fill(key, (byte) 0x55); From ef7542c8a7c8d63bddb7d7bf0e35a277f2b59ec5 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 10 Oct 2023 00:02:53 +0200 Subject: [PATCH 13/13] Added test: Delete links --- ...SystemProviderInMemoryIntegrationTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index 7fb65c22..945ecdb6 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -30,7 +30,9 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Comparator; +import java.util.List; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Stream; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; @@ -38,6 +40,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; //For shortening: Since filename encryption increases filename length, 50 cleartext chars are sufficient to reach the threshold public class CryptoFileSystemProviderInMemoryIntegrationTest { @@ -334,6 +337,57 @@ public void testDeleteDirSingle(String targetName, ThrowingConsumer entryC } } + /** + * Creates the Cartesian product of {@link #targetFileNames} with itself as stream, excluding entries where both elements match. + */ + static Stream fileNamePairs() { + return targetFileNames().mapMulti((name0, intoStreamConsumer) -> { // + targetFileNames().filter(Predicate.isEqual(name0).negate()) // //Don't create pairs with the same name + .map(name1 -> Arguments.of(name0, name1)) // + .forEach(intoStreamConsumer); + }); + } + + /** + * Creates the Cartesian product of {@link #fileNamePairs} with a list of "targetCreators" as stream. + */ + static Stream linksWithCreator() { + List> operations = List.of(unused -> {}, // + Files::createFile, // + Files::createDirectory, // + nestedElement -> Files.createSymbolicLink(nestedElement, nestedElement.resolveSibling("linkTarget"))); + + return fileNamePairs().mapMulti((names, intoStreamConsumer) -> { // + operations.stream().map(operation -> { + var argValues = names.get(); + return Arguments.of(argValues[0], argValues[1], operation); + }).forEach(intoStreamConsumer); + }); + } + + @DisplayName("Delete links") + @ParameterizedTest + @MethodSource("org.cryptomator.cryptofs.CryptoFileSystemProviderInMemoryIntegrationTest#linksWithCreator") + public void testDeleteSymLink(String linkName, String targetName, ThrowingConsumer targetCreator) throws Throwable /* = IOE from entryCreator */ { + try (var fs = setupCryptoFs(50, 100, false)) { + var link = fs.getPath("/" + linkName); + var target = fs.getPath("/" + targetName); + targetCreator.accept(target); + var targetCreated = Files.exists(target, LinkOption.NOFOLLOW_LINKS); //Allow for no-op targetCreator + Files.createSymbolicLink(link, target); + + assertDoesNotThrow(() -> Files.delete(link)); + assertTrue(Files.notExists(link, LinkOption.NOFOLLOW_LINKS)); + assertThrows(NoSuchFileException.class, () -> Files.delete(link)); + + assumeTrue(targetCreated); + assertTrue(Files.exists(target, LinkOption.NOFOLLOW_LINKS)); + + assertDoesNotThrow(() -> Files.delete(target)); + assertTrue(Files.notExists(target, LinkOption.NOFOLLOW_LINKS)); + } + } + @DisplayName("Delete directly recursive link") @ParameterizedFileTest public void testDeleteDirectlyRecursiveSymLink(String targetName) throws IOException {