diff options
Diffstat (limited to 'libs/test-utils/src')
| -rw-r--r-- | libs/test-utils/src/main/java/org/the_jk/cleversync/io/BaseSingleMergeTest.kt | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/libs/test-utils/src/main/java/org/the_jk/cleversync/io/BaseSingleMergeTest.kt b/libs/test-utils/src/main/java/org/the_jk/cleversync/io/BaseSingleMergeTest.kt new file mode 100644 index 0000000..65d870b --- /dev/null +++ b/libs/test-utils/src/main/java/org/the_jk/cleversync/io/BaseSingleMergeTest.kt @@ -0,0 +1,709 @@ +package org.the_jk.cleversync.io + +import com.google.common.truth.Correspondence +import com.google.common.truth.Truth.assertThat +import org.junit.Assert +import org.junit.Assume +import org.junit.Before +import org.junit.Test +import kotlin.time.Duration + +abstract class BaseSingleMergeTest { + private lateinit var src: ModifiableTree + private lateinit var tgt: ModifiableTree + + @Before + fun setUp() { + src = source() + tgt = target() + } + + @Test + fun empty() { + assertThat(SingleMerge.calculate(tgt, src)).isEmpty() + } + + @Test + fun oneFile() { + val srcFile = src.createFile("foo") + srcFile.write().writer().use { it.write("Hello World") } + + val actions = SingleMerge.calculate(tgt, src) + assertThat(actions).containsExactly(Action.Copy("foo")) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtFile = tgt.openFile("foo") + assertThat(tgtFile?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + } + + @Test + fun oneFileAlreadyExistsSameContent() { + val srcFile = src.createFile("foo") + srcFile.write().writer().use { it.write("Hello World") } + val tgtFile = tgt.createFile("foo") + tgtFile.write().writer().use { it.write("Hello World") } + + assertThat(SingleMerge.calculate(tgt, src)).isEmpty() + } + + @Test + fun oneFileAlreadyExistsDifferentContent() { + val srcFile = src.createFile("foo") + srcFile.write().writer().use { it.write("Hello World") } + val tgtFile = tgt.createFile("foo") + tgtFile.write().writer().use { it.write("Bye bye!") } + + val actions = SingleMerge.calculate(tgt, src) + assertThat(actions).containsExactly(Action.Copy("foo", overwrite = true)) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val newTgtFile = tgt.openFile("foo") + assertThat(newTgtFile?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + } + + @Test + fun oneFileAlreadyExistsSameSizeContentOlder() { + val tgtFile = tgt.createFile("foo") + tgtFile.write().writer().use { it.write("Cruel World") } + Thread.sleep(minModificationTime().inWholeMilliseconds) + val srcFile = src.createFile("foo") + srcFile.write().writer().use { it.write("Hello World") } + + val actions = SingleMerge.calculate(tgt, src) + assertThat(actions).containsExactly(Action.Copy("foo", overwrite = true)) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val newTgtFile = tgt.openFile("foo") + assertThat(newTgtFile?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + } + + @Test + fun oneFileAlreadyExistsSameSizeContentNewer() { + val srcFile = src.createFile("foo") + srcFile.write().writer().use { it.write("Hello World") } + Thread.sleep(minModificationTime().inWholeMilliseconds) + val tgtFile = tgt.createFile("foo") + tgtFile.write().writer().use { it.write("hello world") } + + assertThat(SingleMerge.calculate(tgt, src)).isEmpty() + } + + @Test + fun filesInDirectories() { + val srcFoo = src.createFile("foo") + srcFoo.write().writer().use { it.write("Hello World") } + val srcDir = src.createDirectory("bar") + val srcBarFoo = srcDir.createFile("foo") + srcBarFoo.write().writer().use { it.write("World Hello") } + + val actions = SingleMerge.calculate(tgt, src) + assertThat(actions).containsExactly( + Action.Copy("foo"), + Action.ChangeDir("bar", listOf(Action.Copy("foo")), create = true), + ) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtFoo = tgt.openFile("foo") + assertThat(tgtFoo?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + val tgtBarFoo = tgt.openFile("bar/foo") + assertThat(tgtBarFoo?.read()?.reader()?.use { it.readText() }).isEqualTo("World Hello") + } + + @Test + fun dirInDirInDir() { + val srcFoo = src.createDirectory("foo") + val srcFooBar = srcFoo.createDirectory("bar") + srcFooBar.createDirectory("fum") + + val actions = SingleMerge.calculate(tgt, src) + assertThat(actions).containsExactly( + Action.ChangeDir( + "foo", + listOf( + Action.ChangeDir( + "bar", + listOf( + Action.ChangeDir( + "fum", + emptyList(), + create = true, + ), + ), + create = true, + ), + ), + create = true, + ), + ) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtFooBarFum = tgt.openDir("foo/bar/fum") + assertThat(tgtFooBarFum).isNotNull() + assertThat(tgtFooBarFum?.list()?.directories).isEmpty() + } + + @Test + fun symlink() { + Assume.assumeTrue(sourceSupportsSymlinks() && targetSupportsSymlinks()) + + val srcFoo = src.createFile("foo") + srcFoo.write().writer().use { it.write("Hello World") } + + src.createLink("link1", srcFoo) + src.createLink("link2", "does-not-exist") + src.createLink("link3", "../../escaped") + + val actions = SingleMerge.calculate(tgt, src) + assertThat(actions).containsExactly( + Action.Copy("foo"), + Action.Link("link1", "foo"), + Action.Link("link2", "does-not-exist"), + ) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtFoo = tgt.openFile("foo") + assertThat(tgtFoo?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + val tgtLink1 = tgt.openLink("link1") + when (val linkTarget = tgtLink1?.resolve()) { + is Link.FileTarget -> assertThat(linkTarget.file).isEqualTo(tgtFoo) + else -> Assert.fail() + } + val tgtLink2 = tgt.openLink("link2") + when (val linkTarget = tgtLink2?.resolve()) { + is Link.NoTarget -> assertThat(linkTarget.path).isEqualTo("does-not-exist") + else -> Assert.fail() + } + assertThat(tgt.openLink("link3")).isNull() + } + + @Test + fun resolveSymlink() { + Assume.assumeTrue(sourceSupportsSymlinks()) + + val srcFoo = src.createFile("foo") + srcFoo.write().writer().use { it.write("Hello World") } + val srcBar = src.createDirectory("bar") + + src.createLink("link1", srcFoo) + src.createLink("link2", srcBar) + src.createLink("link3", "does-not-exist") + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options( + resolveLinks = true, + )) + assertThat(actions).containsExactly( + Action.Copy("foo"), + Action.Copy("link1"), + Action.ChangeDir("bar", emptyList(), create = true), + Action.ChangeDir("link2", emptyList(), create = true), + ) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtFoo = tgt.openFile("foo") + assertThat(tgtFoo?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + val tgtLink1 = tgt.openFile("link1") + assertThat(tgtLink1?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + assertThat(tgt.openDir("bar")).isNotNull() + assertThat(tgt.openDir("link2")).isNotNull() + } + + @Test + fun symlinkInDir() { + Assume.assumeTrue(sourceSupportsSymlinks() && targetSupportsSymlinks()) + + val srcFoo = src.createDirectory("foo") + srcFoo.createLink("link", srcFoo) + + val actions = SingleMerge.calculate(tgt, src) + assertThat(actions).containsExactly( + Action.ChangeDir( + "foo", + listOf(Action.Link("link", "../foo")), + create = true, + ), + ) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtFoo = tgt.openDir("foo") + val tgtLink = tgtFoo?.openLink("link") + when (val linkTarget = tgtLink?.resolve()) { + is Link.DirectoryTarget -> assertThat(linkTarget.directory).isEqualTo(tgtFoo) + else -> Assert.fail() + } + } + + @Test + fun symlinkUpdate() { + Assume.assumeTrue(sourceSupportsSymlinks() && targetSupportsSymlinks()) + + src.createLink("link", "does-not-exist") + tgt.createLink("link", "also-does-not-exist") + + val actions = SingleMerge.calculate(tgt, src) + assertThat(actions).containsExactly( + Action.Link( + "link", + target = "does-not-exist", + overwrite = true, + ), + ) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtLink = tgt.openLink("link") + when (val linkTarget = tgtLink?.resolve()) { + is Link.NoTarget -> assertThat(linkTarget.path).isEqualTo("does-not-exist") + else -> Assert.fail() + } + } + + @Test + fun symlinkNoUpdate() { + Assume.assumeTrue(sourceSupportsSymlinks() && targetSupportsSymlinks()) + + src.createLink("link", "does-not-exist") + tgt.createLink("link", "does-not-exist") + + assertThat(SingleMerge.calculate(tgt, src)).isEmpty() + } + + @Test + fun deleteInTarget() { + val tgtFoo = tgt.createFile("foo") + tgtFoo.write().writer().use { it.write("aaa") } + val tgtBar = tgt.createDirectory("bar") + val tgtBarFoo = tgtBar.createFile("foo") + tgtBarFoo.write().writer().use { it.write("aaa") } + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options( + deleteFilesOnlyInTarget = true, + )) + assertThat(actions).containsExactly( + Action.RemoveFile("foo"), + Action.RemoveDir("bar"), + ) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val content = tgt.list() + assertThat(content.directories).isEmpty() + assertThat(content.files).isEmpty() + assertThat(content.links).isEmpty() + } + + @Test + fun deleteInTargetMixed() { + val srcFoo = src.createFile("foo") + srcFoo.write().writer().use { it.write("Hello World") } + val srcDir = src.createDirectory("bar") + val srcBarFoo = srcDir.createFile("foo") + srcBarFoo.write().writer().use { it.write("World Hello") } + + val tgtFile = tgt.createFile("file") + tgtFile.write().writer().use { it.write("aaa") } + val tgtBar = tgt.createDirectory("bar") + val tgtBarFile = tgtBar.createFile("file") + tgtBarFile.write().writer().use { it.write("aaa") } + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options( + deleteFilesOnlyInTarget = true, + )) + assertThat(actions).containsExactly( + Action.Copy("foo"), + Action.RemoveFile("file"), + Action.ChangeDir( + "bar", + listOf( + Action.Copy("foo"), + Action.RemoveFile("file"), + ), + ), + ) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtFoo = tgt.openFile("foo") + assertThat(tgtFoo?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + assertThat(tgt.openFile("file")).isNull() + val tgtBarFoo = tgt.openFile("bar/foo") + assertThat(tgtBarFoo?.read()?.reader()?.use { it.readText() }).isEqualTo("World Hello") + assertThat(tgt.openFile("bar/file")).isNull() + } + + @Test + fun deleteInTargetLink() { + Assume.assumeTrue(targetSupportsSymlinks()) + + tgt.createLink("foo", "does-not-exist") + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options( + deleteFilesOnlyInTarget = true, + )) + assertThat(actions).containsExactly(Action.RemoveLink("foo")) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun mixedTypes() { + Assume.assumeTrue(targetSupportsSymlinks()) + + val srcFoo = src.createFile("foo") + srcFoo.write().writer().use { it.write("Hello World") } + src.createDirectory("bar") + + val tgtBar = tgt.createFile("bar") + tgtBar.write().writer().use { it.write("Remove me") } + tgt.createLink("foo", tgtBar) + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtBar2 = tgt.openDir("bar") + assertThat(tgtBar2).isNotNull() + val tgtFoo = tgt.openFile("foo") + assertThat(tgtFoo?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + } + + @Test + fun mixedTypes2() { + Assume.assumeTrue(targetSupportsSymlinks()) + + src.createDirectory("foo") + + tgt.createLink("foo", "does-not-exist") + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + assertThat(tgt.openDir("foo")).isNotNull() + } + + @Test + fun mixedTypes3() { + val srcFoo = src.createFile("foo") + srcFoo.write().writer().use { it.write("Hello World") } + src.createDirectory("bar") + + val tgtBar = tgt.createFile("bar") + tgtBar.write().writer().use { it.write("Remove me") } + tgt.createDirectory("foo") + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + assertThat(tgt.openFile("foo")?.read()?.reader()?.use { it.readText() }).isEqualTo("Hello World") + assertThat(tgt.openDir("bar")).isNotNull() + } + + @Test + fun mixedTypes4() { + Assume.assumeTrue(sourceSupportsSymlinks() && targetSupportsSymlinks()) + + src.createLink("foo", "does-not-exist") + src.createLink("bar", "also-does-not-exist") + + tgt.createDirectory("foo") + tgt.createFile("bar").write().writer().use { it.write("Foo") } + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + + val tgtFoo = tgt.openLink("foo") + when (val linkTarget = tgtFoo?.resolve()) { + is Link.NoTarget -> assertThat(linkTarget.path).isEqualTo("does-not-exist") + else -> Assert.fail() + } + val tgtBar = tgt.openLink("bar") + when (val linkTarget = tgtBar?.resolve()) { + is Link.NoTarget -> assertThat(linkTarget.path).isEqualTo("also-does-not-exist") + else -> Assert.fail() + } + } + + @Test + fun errorSourceDirGone() { + val foo = src.createDirectory("foo") + foo.createFile("bar").write().use { it.write(1) } + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly( + Action.ChangeDir("foo", actions = listOf(Action.Copy("bar", overwrite = false)), create = true), + ) + + assertThat(src.removeDirectory("foo")).isTrue() + + assertThat(Modifier.apply(tgt, src, actions)).containsExactly("/foo: Source directory does not exist") + assertThat(tgt.list().directories).isEmpty() + assertThat(tgt.list().files).isEmpty() + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun errorTargetDirGone() { + val foo = src.createDirectory("foo") + foo.createFile("bar").write().use { it.write(1) } + tgt.createDirectory("foo") + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly( + Action.ChangeDir("foo", actions = listOf(Action.Copy("bar", overwrite = false)), create = false), + ) + + assertThat(tgt.removeDirectory("foo")).isTrue() + + assertThat(Modifier.apply(tgt, src, actions)).containsExactly("foo: Target directory does not exist") + assertThat(tgt.list().directories).isEmpty() + assertThat(tgt.list().files).isEmpty() + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun errorUnableToCreateTargetDir() { + val foo = src.createDirectory("foo") + foo.createFile("bar").write().use { it.write(1) } + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly( + Action.ChangeDir("foo", actions = listOf(Action.Copy("bar", overwrite = false)), create = true), + ) + + val tgtFoo = tgt.createFile("foo") + tgtFoo.write().use { it.write(2) } + + assertThat(Modifier.apply(tgt, src, actions)).containsExactly("foo: Unable to create directory") + assertThat(tgt.list().directories).isEmpty() + assertThat(tgt.list().files).containsExactly(tgtFoo) + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun errorSourceFileGone() { + src.createFile("foo").write().use { it.write(1) } + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly(Action.Copy("foo", overwrite = false)) + + assertThat(src.removeFile("foo")).isTrue() + + assertThat(Modifier.apply(tgt, src, actions)).containsExactly("/foo: Unable to open file") + assertThat(tgt.list().directories).isEmpty() + assertThat(tgt.list().files).isEmpty() + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun errorSourceFileIsNowADirectory() { + src.createFile("foo").write().use { it.write(1) } + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly(Action.Copy("foo", overwrite = false)) + + assertThat(src.removeFile("foo")).isTrue() + src.createDirectory("foo") + + assertThat(Modifier.apply(tgt, src, actions)).containsExactly("/foo: Unable to open file") + assertThat(tgt.list().directories).isEmpty() + assertThat(tgt.list().files).isEmpty() + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun errorTargetFileExists() { + src.createFile("foo").write().use { it.write(1) } + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly(Action.Copy("foo", overwrite = false)) + + val tgtFoo = tgt.createFile("foo") + tgtFoo.write().use { it.write(2) } + + assertThat(Modifier.apply(tgt, src, actions)).comparingElementsUsing( + Correspondence.from({ a: String, b: String -> a.startsWith(b) }, "startsWith"), + ).containsExactly("/foo: Unable to create file: ") + assertThat(tgt.list().directories).isEmpty() + assertThat(tgt.list().files).containsExactly(tgtFoo) + assertThat(tgtFoo.read().use { it.readAllBytes() }).asList().containsExactly(2.toByte()) + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun errorTargetFileExists2() { + src.createFile("foo").write().use { it.write(1) } + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly(Action.Copy("foo", overwrite = false)) + + val tgtFoo = tgt.createDirectory("foo") + + assertThat(Modifier.apply(tgt, src, actions)).comparingElementsUsing( + Correspondence.from({ a: String, b: String -> a.startsWith(b) }, "startsWith"), + ).containsExactly("/foo: Unable to create file: ") + assertThat(tgt.list().directories).containsExactly(tgtFoo) + assertThat(tgt.list().files).isEmpty() + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun errorTargetLinkExists() { + Assume.assumeTrue(sourceSupportsSymlinks() && targetSupportsSymlinks()) + + src.createLink("foo", "bar") + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly(Action.Link("foo", "bar", overwrite = false)) + + val tgtFoo = tgt.createFile("foo") + tgtFoo.write().use { it.write(2) } + + assertThat(Modifier.apply(tgt, src, actions)).comparingElementsUsing( + Correspondence.from({ a: String, b: String -> a.startsWith(b) }, "startsWith"), + ).containsExactly("/foo: Unable to create link: ") + assertThat(tgt.list().directories).isEmpty() + assertThat(tgt.list().files).containsExactly(tgtFoo) + assertThat(tgtFoo.read().use { it.readAllBytes() }).asList().containsExactly(2.toByte()) + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun errorTargetLinkExists2() { + Assume.assumeTrue(sourceSupportsSymlinks() && targetSupportsSymlinks()) + + src.createLink("foo", "bar") + + val actions = SingleMerge.calculate(tgt, src) + + assertThat(actions).containsExactly(Action.Link("foo", "bar", overwrite = false)) + + val tgtFoo = tgt.createDirectory("foo") + + assertThat(Modifier.apply(tgt, src, actions)).comparingElementsUsing( + Correspondence.from({ a: String, b: String -> a.startsWith(b) }, "startsWith"), + ).containsExactly("/foo: Unable to create link: ") + assertThat(tgt.list().directories).containsExactly(tgtFoo) + assertThat(tgt.list().files).isEmpty() + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun targetDirAlreadyGone() { + tgt.createDirectory("foo") + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options(deleteFilesOnlyInTarget = true)) + + assertThat(actions).containsExactly(Action.RemoveDir("foo")) + + assertThat(tgt.removeDirectory("foo")).isTrue() + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + } + + @Test + fun errorTargetDirIsNowAFile() { + tgt.createDirectory("foo") + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options(deleteFilesOnlyInTarget = true)) + + assertThat(actions).containsExactly(Action.RemoveDir("foo")) + + assertThat(tgt.removeDirectory("foo")).isTrue() + val foo = tgt.createFile("foo") + foo.write().use { it.write(1) } + + assertThat(Modifier.apply(tgt, src, actions)).containsExactly("/foo: Unable to remove dir") + assertThat(tgt.list().directories).isEmpty() + assertThat(tgt.list().files).containsExactly(foo) + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun targetFileAlreadyGone() { + tgt.createFile("foo").write().use { it.write(1) } + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options(deleteFilesOnlyInTarget = true)) + + assertThat(actions).containsExactly(Action.RemoveFile("foo")) + + assertThat(tgt.removeFile("foo")).isTrue() + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + } + + @Test + fun errorTargetFileIsNowADir() { + tgt.createFile("foo").write().use { it.write(1) } + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options(deleteFilesOnlyInTarget = true)) + + assertThat(actions).containsExactly(Action.RemoveFile("foo")) + + assertThat(tgt.removeFile("foo")).isTrue() + val foo = tgt.createDirectory("foo") + + assertThat(Modifier.apply(tgt, src, actions)).containsExactly("/foo: Unable to remove file") + assertThat(tgt.list().directories).containsExactly(foo) + assertThat(tgt.list().files).isEmpty() + assertThat(tgt.list().links).isEmpty() + } + + @Test + fun targetLinkAlreadyGone() { + Assume.assumeTrue(targetSupportsSymlinks()) + tgt.createLink("foo", "bar") + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options(deleteFilesOnlyInTarget = true)) + + assertThat(actions).containsExactly(Action.RemoveLink("foo")) + + assertThat(tgt.removeLink("foo")).isTrue() + + assertThat(Modifier.apply(tgt, src, actions)).isEmpty() + } + + @Test + fun errorTargetLinkIsNowADir() { + Assume.assumeTrue(targetSupportsSymlinks()) + tgt.createLink("foo", "bar") + + val actions = SingleMerge.calculate(tgt, src, options = SingleMerge.Options(deleteFilesOnlyInTarget = true)) + + assertThat(actions).containsExactly(Action.RemoveLink("foo")) + + assertThat(tgt.removeLink("foo")).isTrue() + val foo = tgt.createDirectory("foo") + + assertThat(Modifier.apply(tgt, src, actions)).containsExactly("/foo: Unable to remove link") + assertThat(tgt.list().directories).containsExactly(foo) + assertThat(tgt.list().files).isEmpty() + assertThat(tgt.list().links).isEmpty() + } + + abstract fun source(): ModifiableTree + abstract fun sourceSupportsSymlinks(): Boolean + abstract fun target(): ModifiableTree + abstract fun targetSupportsSymlinks(): Boolean + + abstract fun minModificationTime(): Duration +} |
