From a15cfde0fcbdb29dafdb9ebe39fe53c8da4073be Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Sun, 25 Aug 2024 02:30:36 +0200 Subject: Combine tests from both local and samba Most the tests test the Tree implementation and thus should work on all such implementations. Current exception is symlinks which Samba backend doesn't (currently?) support. Improve the Samba remove methods to better match the expected behavior. --- libs/local/build.gradle.kts | 2 +- .../org/the_jk/cleversync/local/LocalTreeTest.kt | 309 +------------------ libs/samba/build.gradle.kts | 1 + .../the_jk/cleversync/io/samba/SambaDirectory.kt | 36 ++- .../org/the_jk/cleversync/samba/SambaTreeTest.kt | 178 ++++------- libs/test-utils/build.gradle.kts | 15 + .../java/org/the_jk/cleversync/TreeAbstractTest.kt | 342 +++++++++++++++++++++ libs/utils/build.gradle.kts | 1 + 8 files changed, 455 insertions(+), 429 deletions(-) create mode 100644 libs/test-utils/build.gradle.kts create mode 100644 libs/test-utils/src/main/java/org/the_jk/cleversync/TreeAbstractTest.kt (limited to 'libs') diff --git a/libs/local/build.gradle.kts b/libs/local/build.gradle.kts index 46d8128..6a126d0 100644 --- a/libs/local/build.gradle.kts +++ b/libs/local/build.gradle.kts @@ -8,5 +8,5 @@ android { dependencies { implementation(project(":libs:io")) - testImplementation(project(":libs:utils")) + testImplementation(project(":libs:test-utils")) } diff --git a/libs/local/src/test/java/org/the_jk/cleversync/local/LocalTreeTest.kt b/libs/local/src/test/java/org/the_jk/cleversync/local/LocalTreeTest.kt index a7d0afa..e2d4264 100644 --- a/libs/local/src/test/java/org/the_jk/cleversync/local/LocalTreeTest.kt +++ b/libs/local/src/test/java/org/the_jk/cleversync/local/LocalTreeTest.kt @@ -1,326 +1,23 @@ package org.the_jk.cleversync.local -import com.google.common.truth.Truth.assertThat -import org.junit.Assert import org.junit.Before import org.junit.Rule -import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config -import org.robolectric.shadows.ShadowLooper -import org.the_jk.cleversync.io.Directory -import org.the_jk.cleversync.io.Link -import org.the_jk.cleversync.io.ModifiableLink -import org.the_jk.cleversync.io.ModifiableTree -import org.the_jk.cleversync.safeValue +import org.the_jk.cleversync.TreeAbstractTest @Config(manifest=Config.NONE) @RunWith(RobolectricTestRunner::class) -class LocalTreeTest { +class LocalTreeTest : TreeAbstractTest() { @get:Rule val folder = TemporaryFolder() - private lateinit var tree: ModifiableTree - @Before fun setUp() { tree = LocalTreeFactory.modifiableTree(folder.root.toPath()) } - @Test - fun empty() { - val content = tree.list() - assertThat(content.directories).isEmpty() - assertThat(content.files).isEmpty() - assertThat(content.links).isEmpty() - } - - @Test - fun emptyLive() { - val content = tree.liveList().safeValue() - assertThat(content?.directories).isEmpty() - assertThat(content?.files).isEmpty() - assertThat(content?.links).isEmpty() - } - - @Test - fun createDirectory() { - val foo = tree.createDirectory("foo") - assertThat(foo.name).isEqualTo("foo") - val fooContent = foo.list() - assertThat(fooContent.directories).isEmpty() - assertThat(fooContent.files).isEmpty() - assertThat(fooContent.links).isEmpty() - val content = tree.list() - assertThat(content.directories).contains(foo) - assertThat(content.files).isEmpty() - assertThat(content.links).isEmpty() - } - - @Test - fun observeCreateDirectory() { - val content = tree.liveList() - var dir: Directory? = null - content.observeForever { - if (it.directories.size == 1) dir = it.directories[0] - } - tree.createDirectory("foo") - while (dir == null) { - ShadowLooper.idleMainLooper() - } - assertThat(dir?.name).isEqualTo("foo") - } - - @Test - fun createFile() { - val foo = tree.createFile("foo") - // Files are not created until you write to them. - assertThat(tree.list().files).isEmpty() - foo.write().use { os -> - os.write(byteArrayOf(1, 2, 3, 4)) - } - assertThat(tree.list().files).contains(foo) - assertThat(foo.size).isEqualTo(4.toULong()) - foo.read().use { - assertThat(it.readBytes()).isEqualTo(byteArrayOf(1, 2, 3, 4)) - } - } - - @Test - fun overwriteFile() { - val foo = tree.createFile("foo") - foo.write().use { os -> - os.write(byteArrayOf(1, 2, 3, 4)) - } - foo.write().use { os -> - os.write(127) - os.write(byteArrayOf(1)) - os.write(byteArrayOf(2), 0, 0) - } - assertThat(foo.size).isEqualTo(2.toULong()) - assertThat(tree.list().files).hasSize(1) - foo.read().use { - assertThat(it.readBytes()).isEqualTo(byteArrayOf(127, 1)) - } - } - - @Test - fun removeDir() { - tree.createDirectory("foo") - tree.removeDirectory("foo") - assertThat(tree.list().directories).isEmpty() - } - - @Test - fun removeDirLive() { - tree.createDirectory("foo") - val content = tree.liveList() - var done = false - content.observeForever { - if (it.directories.isEmpty()) done = true - } - tree.removeDirectory("foo") - while (!done) { - ShadowLooper.idleMainLooper() - } - } - - @Test - fun createLink() { - val dir = tree.createDirectory("dir") - val file = tree.createFile("file") - val link = tree.createLink("link", dir.name) - var target = link.resolve() - when (target) { - is Link.DirectoryTarget -> assertThat(target.directory).isEqualTo( - dir - ) - is Link.FileTarget -> Assert.fail() - is Link.NoTarget -> Assert.fail() - } - assertThat(tree.openDir("link")).isEqualTo(dir) - - link.target(file) - target = link.resolve() - when (target) { - is Link.DirectoryTarget -> Assert.fail() - is Link.FileTarget -> Assert.fail() - is Link.NoTarget -> Unit - } - file.write().use { it.write(1) } - target = link.resolve() - when (target) { - is Link.DirectoryTarget -> Assert.fail() - is Link.FileTarget -> assertThat(target.file).isEqualTo(file) - is Link.NoTarget -> Assert.fail() - } - - assertThat(tree.openFile("link")).isEqualTo(file) - } - - @Test - fun createLinkSubdir() { - val foo = tree.createDirectory("foo") - val bar = foo.createDirectory("bar") - val link1 = tree.createLink("link1", "foo/bar") - val link2 = tree.createLink("link2", bar) - assertThat(link1.resolve()).isEqualTo(link2.resolve()) - assertThat((link1.resolve() as Link.DirectoryTarget).directory).isEqualTo(bar) - val link3 = foo.createLink("link3", "../link1") - assertThat((link3.resolve() as Link.DirectoryTarget).directory).isEqualTo(bar) - } - - @Test - fun createLiveLink() { - val content = tree.liveList() - var link: Link? = null - content.observeForever { - if (it.links.size == 1) link = it.links[0] - } - val dir = tree.createDirectory("dir") - tree.createLink("link", "dir") - while (link == null) { - ShadowLooper.idleMainLooper() - } - assertThat((link?.resolve() as Link.DirectoryTarget).directory).isEqualTo(dir) - } - - @Test - fun sameDir() { - val dir1 = tree.createDirectory("dir") - val dir2 = tree.openDir("dir") - assertThat(dir1).isEqualTo(dir2) - assertThat(dir1.hashCode()).isEqualTo(dir2.hashCode()) - assertThat(dir1.toString()).isEqualTo(dir2.toString()) - } - - @Test - fun sameFile() { - val file1 = tree.createFile("file") - file1.write().use { it.write(127) } - val file2 = tree.openFile("file") - assertThat(file1).isEqualTo(file2) - assertThat(file1.hashCode()).isEqualTo(file2.hashCode()) - assertThat(file1.toString()).isEqualTo(file2.toString()) - } - - @Test - fun sameLink() { - val link1 = tree.createLink("link", "foo") - val link2 = tree.openLink("link") - assertThat(link1).isEqualTo(link2) - assertThat(link1.hashCode()).isEqualTo(link2.hashCode()) - assertThat(link1.toString()).isEqualTo(link2.toString()) - } - - @Test - fun removeDirWithContent() { - val foo = tree.createDirectory("foo") - foo.createDirectory("dir") - foo.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) } - foo.createLink("link", "file") - assertThat(tree.list().directories).hasSize(1) - assertThat(tree.removeDirectory("foo")).isTrue() - assertThat(tree.list().directories).isEmpty() - } - - @Test - fun removeWrongType() { - tree.createDirectory("dir") - assertThat(tree.removeFile("dir")).isFalse() - assertThat(tree.removeLink("dir")).isFalse() - tree.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) } - assertThat(tree.removeDirectory("file")).isFalse() - assertThat(tree.removeLink("file")).isFalse() - tree.createLink("link", "doesn't exist") - assertThat(tree.removeDirectory("link")).isFalse() - assertThat(tree.removeFile("link")).isFalse() - val content = tree.list() - assertThat(content.directories).hasSize(1) - assertThat(content.files).hasSize(1) - assertThat(content.links).hasSize(1) - } - - @Test - fun removeFile() { - tree.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) } - assertThat(tree.list().files).hasSize(1) - tree.removeFile("file") - assertThat(tree.list().files).isEmpty() - } - - @Test - fun removeLink() { - val dir = tree.createDirectory("dir") - val file = tree.createFile("file") - file.write().use { it.write(127) } - tree.createLink("link1", dir) - tree.createLink("link2", file) - assertThat(tree.list().links).hasSize(2) - tree.removeLink("link1") - tree.removeLink("link2") - assertThat(tree.list().links).isEmpty() - } - - @Test - fun changeLink() { - val dir = tree.createDirectory("dir") - val file = tree.createFile("file") - file.write().use { it.write(127) } - val link = tree.createLink("link", "doesn't exist") - assertThat(link.resolve() is Link.NoTarget).isTrue() - link.target(file) - assertThat((link.resolve() as Link.FileTarget).file).isEqualTo(file) - link.target(dir) - assertThat((link.resolve() as Link.DirectoryTarget).directory).isEqualTo(dir) - link.target("bad") - assertThat(link.resolve() is Link.NoTarget).isTrue() - } - - @Test - fun changeModifiableLink() { - val dir = tree.createDirectory("dir") - val file = tree.createFile("file") - file.write().use { it.write(127) } - val link = tree.createLink("link", "doesn't exist") - assertThat(link.modifiableResolve() is ModifiableLink.NoTarget).isTrue() - link.target(file) - assertThat((link.modifiableResolve() as ModifiableLink.ModifiableFileTarget).file).isEqualTo(file) - link.target(dir) - assertThat((link.modifiableResolve() as ModifiableLink.ModifiableDirectoryTarget).directory).isEqualTo(dir) - link.target("bad") - assertThat(link.modifiableResolve() is ModifiableLink.NoTarget).isTrue() - } - - @Test - fun recursiveLink() { - val link = tree.createLink("link", "link") - assertThat(link.resolve() is Link.NoTarget).isTrue() - } - - @Test - fun names() { - assertThat(tree.createDirectory("dir").name).isEqualTo("dir") - assertThat(tree.createFile("file").name).isEqualTo("file") - assertThat(tree.createLink("link", "file").name).isEqualTo("link") - } - - @Test - fun openNonExistent() { - assertThat(tree.openDir("dir")).isNull() - assertThat(tree.openFile("file")).isNull() - assertThat(tree.openLink("link")).isNull() - } - - @Test - fun lastModified() { - val file = tree.createFile("foo") - file.write().use { it.write(1) } - val old = file.lastModified - file.write().use { it.write(2); it.flush() } - val new = file.lastModified - assertThat(old.isBefore(new) || old == new).isTrue() - } + override fun supportSymlinks() = true } diff --git a/libs/samba/build.gradle.kts b/libs/samba/build.gradle.kts index 7fab391..bae1ec9 100644 --- a/libs/samba/build.gradle.kts +++ b/libs/samba/build.gradle.kts @@ -47,6 +47,7 @@ android { dependencies { implementation(project(":libs:io")) testImplementation(project(":libs:utils")) + testImplementation(project(":libs:test-utils")) } listOf("Debug", "Release").forEach { buildType -> diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt index f5230b2..fc5b290 100644 --- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt @@ -126,15 +126,45 @@ internal open class SambaDirectory( } override fun removeDirectory(name: String): Boolean { - return conn.removeDir(SambaConnection.join(path, name)) + val removePath = SambaConnection.join(path, name) + val entry = conn.entry(removePath) ?: return false + if (entry.type != NativeSamba.DirEntryType.DIR) return false + return removeRecursive(removePath) + } + + private fun removeRecursive(removePath: String): Boolean { + val dir = conn.openDir(removePath) ?: return false + try { + dir.list().forEach { entry -> + val entryPath = SambaConnection.join(removePath, entry.name) + if (!when (entry.type) { + NativeSamba.DirEntryType.FILE, + NativeSamba.DirEntryType.LINK, + -> conn.unlink(entryPath) + NativeSamba.DirEntryType.DIR + -> removeRecursive(entryPath) + }) { + return false + } + } + return conn.removeDir(removePath) + } finally { + dir.destroy() + } } override fun removeFile(name: String): Boolean { - return conn.unlink(SambaConnection.join(path, name)) + val removePath = SambaConnection.join(path, name) + val entry = conn.entry(removePath) ?: return false + if (entry.type != NativeSamba.DirEntryType.FILE) return false + return conn.unlink(removePath) } override fun removeLink(name: String): Boolean { - return conn.unlink(SambaConnection.join(path, name)) + val removePath = SambaConnection.join(path, name) + val entry = conn.entry(removePath) ?: return false + if (entry.type != NativeSamba.DirEntryType.LINK) return false + return conn.unlink(removePath) } override fun openDir(name: String) = modifiableOpenDir(name) diff --git a/libs/samba/src/test/java/org/the_jk/cleversync/samba/SambaTreeTest.kt b/libs/samba/src/test/java/org/the_jk/cleversync/samba/SambaTreeTest.kt index 61c8da7..fecd746 100644 --- a/libs/samba/src/test/java/org/the_jk/cleversync/samba/SambaTreeTest.kt +++ b/libs/samba/src/test/java/org/the_jk/cleversync/samba/SambaTreeTest.kt @@ -12,9 +12,8 @@ import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.shadows.ShadowLooper -import org.the_jk.cleversync.io.Directory +import org.the_jk.cleversync.TreeAbstractTest import org.the_jk.cleversync.io.samba.SambaCredentials -import org.the_jk.cleversync.safeValue import java.io.File import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -22,14 +21,18 @@ import java.util.concurrent.TimeUnit @Config(manifest=Config.NONE) @RunWith(RobolectricTestRunner::class) -class SambaTreeTest { +class SambaTreeTest : TreeAbstractTest() { @Before fun setUpTest() { assertThat(shareDir.listFiles()).isEmpty() + + tree = SambaTreeFactory.modifiableTree(uri, credentials).getOrThrow() } @After fun tearDownTest() { + tree.close() + for (file in shareDir.listFiles()!!) { if (file.isDirectory) { file.deleteRecursively() @@ -47,157 +50,94 @@ class SambaTreeTest { } } - @Test - fun listEmptyRoot() { - SambaTreeFactory.tree(uri, credentials).getOrThrow().use { root -> - val content = root.list() - assertThat(content.directories).isEmpty() - assertThat(content.files).isEmpty() - assertThat(content.links).isEmpty() - } - } - - @Test - fun listEmptyRootLive() { - SambaTreeFactory.tree(uri, credentials).getOrThrow().use { root -> - val content = root.liveList().safeValue() - assertThat(content?.directories).isEmpty() - assertThat(content?.files).isEmpty() - assertThat(content?.links).isEmpty() - } - } - @Test fun listRootWithSymlink() { File(shareDir, "dir").mkdir() File(shareDir, "file").writeText("foo") Files.createSymbolicLink(File(shareDir, "link").toPath(), File("file").toPath()) - SambaTreeFactory.tree(uri, credentials).getOrThrow().use { root -> - val content = root.list() - - assertThat(content.directories).hasSize(1) - assertThat(content.directories[0].name).isEqualTo("dir") - assertThat(content.files).hasSize(2) - if (content.files[0].name == "file") { - assertThat(content.files[0].name).isEqualTo("file") - assertThat(content.files[0].size).isEqualTo(3UL) - assertThat(content.files[1].name).isEqualTo("link") - assertThat(content.files[1].size).isEqualTo(3UL) - } else { - assertThat(content.files[0].name).isEqualTo("link") - assertThat(content.files[0].size).isEqualTo(3UL) - assertThat(content.files[1].name).isEqualTo("file") - assertThat(content.files[1].size).isEqualTo(3UL) - } - // libsmb uses SMB2/SMB3 and unix extensions are SMB1, so no symlinks for now - assertThat(content.links).isEmpty() + val content = tree.list() + + assertThat(content.directories).hasSize(1) + assertThat(content.directories[0].name).isEqualTo("dir") + assertThat(content.files).hasSize(2) + if (content.files[0].name == "file") { + assertThat(content.files[0].name).isEqualTo("file") + assertThat(content.files[0].size).isEqualTo(3UL) + assertThat(content.files[1].name).isEqualTo("link") + assertThat(content.files[1].size).isEqualTo(3UL) + } else { + assertThat(content.files[0].name).isEqualTo("link") + assertThat(content.files[0].size).isEqualTo(3UL) + assertThat(content.files[1].name).isEqualTo("file") + assertThat(content.files[1].size).isEqualTo(3UL) } + // libsmb uses SMB2/SMB3 and unix extensions are SMB1, so no symlinks for now + assertThat(content.links).isEmpty() } @Test - fun readFile() { + fun readExistingFile() { File(shareDir, "file").writeText("hello world") - SambaTreeFactory.tree(uri, credentials).getOrThrow().use { root -> - val file = root.openFile("file") - assertThat(file?.name).isEqualTo("file") - assertThat(file?.size).isEqualTo(11UL) + val file = tree.openFile("file") + assertThat(file?.name).isEqualTo("file") + assertThat(file?.size).isEqualTo(11UL) - file?.read().use { input -> - assertThat(input?.readAllBytes()?.toString(StandardCharsets.UTF_8)).isEqualTo("hello world") - } + file?.read().use { input -> + assertThat(input?.readAllBytes()?.toString(StandardCharsets.UTF_8)).isEqualTo("hello world") + } - file?.read().use { input -> - assertThat(input?.available()).isEqualTo(11) - assertThat(input?.markSupported()).isTrue() - val buffer = ByteArray(10) - assertThat(input?.read(buffer, 5, 5)).isEqualTo(5) - input?.mark(100) - assertThat(buffer.sliceArray(5..<10).toString(StandardCharsets.UTF_8)).isEqualTo("hello") - assertThat(input?.read(buffer)).isEqualTo(6) - assertThat(buffer.sliceArray(0..<6).toString(StandardCharsets.UTF_8)).isEqualTo(" world") - input?.reset() - assertThat(input?.read(buffer, 3, 5)).isEqualTo(5) - assertThat(buffer.sliceArray(3..<8).toString(StandardCharsets.UTF_8)).isEqualTo(" worl") - } + file?.read().use { input -> + assertThat(input?.available()).isEqualTo(11) + assertThat(input?.markSupported()).isTrue() + val buffer = ByteArray(10) + assertThat(input?.read(buffer, 5, 5)).isEqualTo(5) + input?.mark(100) + assertThat(buffer.sliceArray(5..<10).toString(StandardCharsets.UTF_8)).isEqualTo("hello") + assertThat(input?.read(buffer)).isEqualTo(6) + assertThat(buffer.sliceArray(0..<6).toString(StandardCharsets.UTF_8)).isEqualTo(" world") + input?.reset() + assertThat(input?.read(buffer, 3, 5)).isEqualTo(5) + assertThat(buffer.sliceArray(3..<8).toString(StandardCharsets.UTF_8)).isEqualTo(" worl") } } @Test - fun writeFile() { - SambaTreeFactory.modifiableTree(uri, credentials).getOrThrow().use { root -> - val file = root.createFile("file") - assertThat(file.name).isEqualTo("file") - - file.write().writer().use { output -> - output.write("hello world") - } - - assertThat(file.size).isEqualTo(11UL) - } + override fun createFile() { + super.createFile() - assertThat(File(shareDir, "file").readText()).isEqualTo("hello world") + assertThat(File(shareDir, "foo").readBytes()).isEqualTo(byteArrayOf(1, 2, 3, 4)) } @Test - fun overwriteFile() { - File(shareDir, "file").writeText("hello world") - - SambaTreeFactory.modifiableTree(uri, credentials).getOrThrow().use { root -> - val file = root.modifiableOpenFile("file") - assertThat(file?.name).isEqualTo("file") - assertThat(file?.size).isEqualTo(11UL) - - file?.write().use { output -> - val buffer = "foobar".toByteArray(StandardCharsets.UTF_8) - output?.write(buffer, 0, 1) - output?.write(buffer, 1, 2) - output?.write(buffer, 3, 3) - } - - assertThat(file?.size).isEqualTo(6UL) - } + override fun overwriteFile() { + super.overwriteFile() - assertThat(File(shareDir, "file").readText()).isEqualTo("foobar") + assertThat(File(shareDir, "foo").readBytes()).isEqualTo(byteArrayOf(127, 1)) } @Test - fun createDirectory() { - SambaTreeFactory.modifiableTree(uri, credentials).getOrThrow().use { root -> - val foo = root.createDirectory("foo") - assertThat(foo.name).isEqualTo("foo") - val fooContent = foo.list() - assertThat(fooContent.directories).isEmpty() - assertThat(fooContent.files).isEmpty() - assertThat(fooContent.links).isEmpty() - val content = root.list() - assertThat(content.directories).contains(foo) - assertThat(content.files).isEmpty() - assertThat(content.links).isEmpty() - } + override fun createDirectory() { + super.createDirectory() assertThat(File(shareDir, "foo").isDirectory).isTrue() } @Test(timeout = 10000) - fun observeCreateDirectory() { - SambaTreeFactory.modifiableTree(uri, credentials).getOrThrow().use { root -> - val content = root.liveList() - var dir: Directory? = null - content.observeForever { - if (it.directories.size == 1) dir = it.directories[0] - } - root.createDirectory("foo") - while (dir == null) { - ShadowLooper.idleMainLooper(10, TimeUnit.SECONDS) - } - assertThat(dir?.name).isEqualTo("foo") - } + override fun observeCreateDirectory() { + super.observeCreateDirectory() assertThat(File(shareDir, "foo").isDirectory).isTrue() } + // libsmb uses SMB2/SMB3 and unix extensions are SMB1, so no symlinks for now + override fun supportSymlinks() = false + + override fun idle() { + ShadowLooper.idleMainLooper(10, TimeUnit.SECONDS) + } + companion object { private lateinit var uri: String private lateinit var credentials: SambaCredentials diff --git a/libs/test-utils/build.gradle.kts b/libs/test-utils/build.gradle.kts new file mode 100644 index 0000000..1b796fa --- /dev/null +++ b/libs/test-utils/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "org.the_jk.cleversync.testutils" +} + +dependencies { + implementation(project(":libs:io")) + implementation(libs.junit) + implementation(libs.robolectric) + implementation(libs.truth) + implementation(project(":libs:utils")) +} diff --git a/libs/test-utils/src/main/java/org/the_jk/cleversync/TreeAbstractTest.kt b/libs/test-utils/src/main/java/org/the_jk/cleversync/TreeAbstractTest.kt new file mode 100644 index 0000000..b513503 --- /dev/null +++ b/libs/test-utils/src/main/java/org/the_jk/cleversync/TreeAbstractTest.kt @@ -0,0 +1,342 @@ +package org.the_jk.cleversync + +import com.google.common.truth.Truth.assertThat +import org.junit.Assert +import org.junit.Assume +import org.junit.Test +import org.robolectric.shadows.ShadowLooper +import org.the_jk.cleversync.io.Directory +import org.the_jk.cleversync.io.Link +import org.the_jk.cleversync.io.ModifiableLink +import org.the_jk.cleversync.io.ModifiableTree + +abstract class TreeAbstractTest { + protected lateinit var tree: ModifiableTree + + @Test + open fun empty() { + val content = tree.list() + assertThat(content.directories).isEmpty() + assertThat(content.files).isEmpty() + assertThat(content.links).isEmpty() + } + + @Test + open fun emptyLive() { + val content = tree.liveList().safeValue() + assertThat(content?.directories).isEmpty() + assertThat(content?.files).isEmpty() + assertThat(content?.links).isEmpty() + } + + @Test + open fun createDirectory() { + val foo = tree.createDirectory("foo") + assertThat(foo.name).isEqualTo("foo") + val fooContent = foo.list() + assertThat(fooContent.directories).isEmpty() + assertThat(fooContent.files).isEmpty() + assertThat(fooContent.links).isEmpty() + val content = tree.list() + assertThat(content.directories).contains(foo) + assertThat(content.files).isEmpty() + assertThat(content.links).isEmpty() + } + + @Test(timeout = 10000) + open fun observeCreateDirectory() { + val content = tree.liveList() + var dir: Directory? = null + content.observeForever { + if (it.directories.size == 1) dir = it.directories[0] + } + tree.createDirectory("foo") + while (dir == null) { + idle() + } + assertThat(dir?.name).isEqualTo("foo") + } + + @Test + open fun createFile() { + val foo = tree.createFile("foo") + // Files are not created until you write to them. + assertThat(tree.list().files).isEmpty() + foo.write().use { os -> + os.write(byteArrayOf(1, 2, 3, 4)) + } + assertThat(tree.list().files).contains(foo) + assertThat(foo.size).isEqualTo(4.toULong()) + foo.read().use { + assertThat(it.readBytes()).isEqualTo(byteArrayOf(1, 2, 3, 4)) + } + } + + @Test + open fun overwriteFile() { + val foo = tree.createFile("foo") + foo.write().use { os -> + os.write(byteArrayOf(1, 2, 3, 4)) + } + foo.write().use { os -> + os.write(127) + os.write(byteArrayOf(1)) + os.write(byteArrayOf(2), 0, 0) + } + assertThat(foo.size).isEqualTo(2.toULong()) + assertThat(tree.list().files).hasSize(1) + foo.read().use { + assertThat(it.readBytes()).isEqualTo(byteArrayOf(127, 1)) + } + } + + @Test + open fun removeDir() { + tree.createDirectory("foo") + tree.removeDirectory("foo") + assertThat(tree.list().directories).isEmpty() + } + + @Test(timeout = 10000) + open fun removeDirLive() { + tree.createDirectory("foo") + val content = tree.liveList() + var done = false + content.observeForever { + if (it.directories.isEmpty()) done = true + } + tree.removeDirectory("foo") + while (!done) { + idle() + } + } + + @Test + open fun createLink() { + Assume.assumeTrue(supportSymlinks()) + + val dir = tree.createDirectory("dir") + val file = tree.createFile("file") + val link = tree.createLink("link", dir.name) + var target = link.resolve() + when (target) { + is Link.DirectoryTarget -> assertThat(target.directory).isEqualTo( + dir + ) + is Link.FileTarget -> Assert.fail() + is Link.NoTarget -> Assert.fail() + } + assertThat(tree.openDir("link")).isEqualTo(dir) + + link.target(file) + target = link.resolve() + when (target) { + is Link.DirectoryTarget -> Assert.fail() + is Link.FileTarget -> Assert.fail() + is Link.NoTarget -> Unit + } + file.write().use { it.write(1) } + target = link.resolve() + when (target) { + is Link.DirectoryTarget -> Assert.fail() + is Link.FileTarget -> assertThat(target.file).isEqualTo(file) + is Link.NoTarget -> Assert.fail() + } + + assertThat(tree.openFile("link")).isEqualTo(file) + } + + @Test + open fun createLinkSubdir() { + Assume.assumeTrue(supportSymlinks()) + + val foo = tree.createDirectory("foo") + val bar = foo.createDirectory("bar") + val link1 = tree.createLink("link1", "foo/bar") + val link2 = tree.createLink("link2", bar) + assertThat(link1.resolve()).isEqualTo(link2.resolve()) + assertThat((link1.resolve() as Link.DirectoryTarget).directory) + .isEqualTo(bar) + val link3 = foo.createLink("link3", "../link1") + assertThat((link3.resolve() as Link.DirectoryTarget).directory) + .isEqualTo(bar) + } + + @Test(timeout = 10000) + open fun createLiveLink() { + Assume.assumeTrue(supportSymlinks()) + + val content = tree.liveList() + var link: Link? = null + content.observeForever { + if (it.links.size == 1) link = it.links[0] + } + val dir = tree.createDirectory("dir") + tree.createLink("link", "dir") + while (link == null) { + idle() + } + assertThat((link?.resolve() as Link.DirectoryTarget).directory).isEqualTo(dir) + } + + @Test + open fun sameDir() { + val dir1 = tree.createDirectory("dir") + val dir2 = tree.openDir("dir") + assertThat(dir1).isEqualTo(dir2) + assertThat(dir1.hashCode()).isEqualTo(dir2.hashCode()) + assertThat(dir1.toString()).isEqualTo(dir2.toString()) + } + + @Test + open fun sameFile() { + val file1 = tree.createFile("file") + file1.write().use { it.write(127) } + val file2 = tree.openFile("file") + assertThat(file1).isEqualTo(file2) + assertThat(file1.hashCode()).isEqualTo(file2.hashCode()) + assertThat(file1.toString()).isEqualTo(file2.toString()) + } + + @Test + open fun sameLink() { + Assume.assumeTrue(supportSymlinks()) + + val link1 = tree.createLink("link", "foo") + val link2 = tree.openLink("link") + assertThat(link1).isEqualTo(link2) + assertThat(link1.hashCode()).isEqualTo(link2.hashCode()) + assertThat(link1.toString()).isEqualTo(link2.toString()) + } + + @Test + open fun removeDirWithContent() { + val foo = tree.createDirectory("foo") + foo.createDirectory("dir") + foo.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) } + if (supportSymlinks()) { + foo.createLink("link", "file") + } + assertThat(tree.list().directories).hasSize(1) + assertThat(tree.removeDirectory("foo")).isTrue() + assertThat(tree.list().directories).isEmpty() + } + + @Test + open fun removeWrongType() { + tree.createDirectory("dir") + assertThat(tree.removeFile("dir")).isFalse() + assertThat(tree.removeLink("dir")).isFalse() + tree.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) } + assertThat(tree.removeDirectory("file")).isFalse() + assertThat(tree.removeLink("file")).isFalse() + if (supportSymlinks()) { + tree.createLink("link", "doesn't exist") + assertThat(tree.removeDirectory("link")).isFalse() + assertThat(tree.removeFile("link")).isFalse() + } + val content = tree.list() + assertThat(content.directories).hasSize(1) + assertThat(content.files).hasSize(1) + if (supportSymlinks()) { + assertThat(content.links).hasSize(1) + } + } + + @Test + open fun removeFile() { + tree.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) } + assertThat(tree.list().files).hasSize(1) + tree.removeFile("file") + assertThat(tree.list().files).isEmpty() + } + + @Test + open fun removeLink() { + Assume.assumeTrue(supportSymlinks()) + + val dir = tree.createDirectory("dir") + val file = tree.createFile("file") + file.write().use { it.write(127) } + tree.createLink("link1", dir) + tree.createLink("link2", file) + assertThat(tree.list().links).hasSize(2) + tree.removeLink("link1") + tree.removeLink("link2") + assertThat(tree.list().links).isEmpty() + } + + @Test + open fun changeLink() { + Assume.assumeTrue(supportSymlinks()) + + val dir = tree.createDirectory("dir") + val file = tree.createFile("file") + file.write().use { it.write(127) } + val link = tree.createLink("link", "doesn't exist") + assertThat(link.resolve() is Link.NoTarget).isTrue() + link.target(file) + assertThat((link.resolve() as Link.FileTarget).file).isEqualTo(file) + link.target(dir) + assertThat((link.resolve() as Link.DirectoryTarget).directory).isEqualTo(dir) + link.target("bad") + assertThat(link.resolve() is Link.NoTarget).isTrue() + } + + @Test + open fun changeModifiableLink() { + Assume.assumeTrue(supportSymlinks()) + + val dir = tree.createDirectory("dir") + val file = tree.createFile("file") + file.write().use { it.write(127) } + val link = tree.createLink("link", "doesn't exist") + assertThat(link.modifiableResolve() is ModifiableLink.NoTarget).isTrue() + link.target(file) + assertThat((link.modifiableResolve() as ModifiableLink.ModifiableFileTarget).file).isEqualTo(file) + link.target(dir) + assertThat((link.modifiableResolve() as ModifiableLink.ModifiableDirectoryTarget).directory).isEqualTo(dir) + link.target("bad") + assertThat(link.modifiableResolve() is ModifiableLink.NoTarget).isTrue() + } + + @Test + open fun recursiveLink() { + Assume.assumeTrue(supportSymlinks()) + + val link = tree.createLink("link", "link") + assertThat(link.resolve() is Link.NoTarget).isTrue() + } + + @Test + open fun names() { + assertThat(tree.createDirectory("dir").name).isEqualTo("dir") + assertThat(tree.createFile("file").name).isEqualTo("file") + if (supportSymlinks()) { + assertThat(tree.createLink("link", "file").name).isEqualTo("link") + } + } + + @Test + open fun openNonExistent() { + assertThat(tree.openDir("dir")).isNull() + assertThat(tree.openFile("file")).isNull() + assertThat(tree.openLink("link")).isNull() + } + + @Test + open fun lastModified() { + val file = tree.createFile("foo") + file.write().use { it.write(1) } + val old = file.lastModified + file.write().use { it.write(2); it.flush() } + val new = file.lastModified + assertThat(old.isBefore(new) || old == new).isTrue() + } + + protected abstract fun supportSymlinks(): Boolean + + protected open fun idle() { + ShadowLooper.idleMainLooper() + } +} diff --git a/libs/utils/build.gradle.kts b/libs/utils/build.gradle.kts index b4f0ae5..693a327 100644 --- a/libs/utils/build.gradle.kts +++ b/libs/utils/build.gradle.kts @@ -9,4 +9,5 @@ android { dependencies { api(libs.androidx.livedata) api(libs.androidx.livedata.ktx) + testImplementation(project(":libs:io")) } -- cgit v1.2.3-70-g09d2