Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more tests #178

Draft
wants to merge 14 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,40 @@
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.api.function.ThrowingConsumer;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
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.DirectoryNotEmptyException;
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;
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;
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;
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 {

private static FileSystem tmpFs;
Expand Down Expand Up @@ -58,34 +74,332 @@ public static void afterAll() throws IOException {
tmpFs.close();
}

@Test
@DisplayName("Replace an existing, shortened, empty directory")
public void testReplaceExistingShortenedDirEmpty() throws IOException {
private final static String[] targetFileNamesArray = new String[]{ //
"target50Chars_56789_123456789_123456789_123456789_", //
"target15Chars__", //
"target50Chars_56789_123456789_123456789_123456.txt", //
"target15C__.txt" //
};

static Stream<String> targetFileNames() {
return Arrays.stream(targetFileNamesArray);
}

@ParameterizedTest
@MethodSource("org.cryptomator.cryptofs.CryptoFileSystemProviderInMemoryIntegrationTest#targetFileNames")
@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");
var target = fs.getPath("/" + targetName);
Files.createFile(source);
Files.createFile(target);

assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING));
assertTrue(Files.notExists(source));
assertTrue(Files.exists(target));
}
}

@DisplayName("Replace an existing, empty directory")
@ParameterizedFileTest
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);

assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING));
assertTrue(Files.notExists(source));
assertTrue(Files.exists(target));
}
}

@Test
@DisplayName("Replace an existing, shortened file")
public void testReplaceExistingShortenedFile() throws IOException {
/* //TODO https://github.com/cryptomator/cryptofs/issues/177
@DisplayName("Replace an existing symlink")
@ParameterizedFileTest
public void testReplaceExistingSymlink(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);
Files.createFile(source);
Files.createFile(target);
var source = fs.getPath("/sourceDir");
var linkedFromSource = fs.getPath("/linkedFromSource.txt");
var linkedFromSourceContent = "linkedFromSourceContent!";

var target = fs.getPath("/" + targetName);
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 *<remove angle brackets when enabling test>/));
assertEquals(Files.readSymbolicLink(target), linkedFromSource);
}
}*/

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

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

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

@DisplayName("Delete directory while and after containing multiple elements")
@ParameterizedFileTest
public void testDeleteDirMultipleNagging(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));
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));
}
}
Comment on lines +209 to +251
Copy link
Member

@infeo infeo Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the rationale behind this test?


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

static Stream<Arguments> dirEntries() {
Stream<ThrowingConsumer<Path>> 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 testDeleteDirSingleNagging(String targetName, ThrowingConsumer<Path> 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));
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<Path> 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));
}
}

/**
* Creates the Cartesian product of {@link #targetFileNames} with itself as stream, excluding entries where both elements match.
*/
static Stream<Arguments> 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<Arguments> linksWithCreator() {
List<ThrowingConsumer<Path>> 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<Path> 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 {
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));
}
}

Expand Down
Loading