summaryrefslogtreecommitdiff
path: root/libs/test-utils/src/main/java/org/the_jk/cleversync
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2024-11-10 15:47:01 +0100
committerJoel Klinghed <the_jk@spawned.biz>2024-11-10 15:47:01 +0100
commit71e9a88cca01050200489ca716928f3b9c3177b7 (patch)
tree8e7df664153a73c28df975d7b568dcdf6dc8e9ad /libs/test-utils/src/main/java/org/the_jk/cleversync
parent7b82ec0afe0049dfad85e89f3d42f64176c0c9fa (diff)
Add verifier
Used to check if target files have the expected hash. Using a memory cache to not have to read source each time but falls back to reading source if needed.
Diffstat (limited to 'libs/test-utils/src/main/java/org/the_jk/cleversync')
-rw-r--r--libs/test-utils/src/main/java/org/the_jk/cleversync/io/BaseVerifierTest.kt230
1 files changed, 230 insertions, 0 deletions
diff --git a/libs/test-utils/src/main/java/org/the_jk/cleversync/io/BaseVerifierTest.kt b/libs/test-utils/src/main/java/org/the_jk/cleversync/io/BaseVerifierTest.kt
new file mode 100644
index 0000000..f72788b
--- /dev/null
+++ b/libs/test-utils/src/main/java/org/the_jk/cleversync/io/BaseVerifierTest.kt
@@ -0,0 +1,230 @@
+package org.the_jk.cleversync.io
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+
+abstract class BaseVerifierTest {
+ private lateinit var src: ModifiableTree
+ private lateinit var tgt: ModifiableTree
+ private val memory = FakeMemory()
+
+ @Before
+ fun setUp() {
+ src = source()
+ tgt = target()
+ }
+
+ @Test
+ fun empty() {
+ assertThat(Verifier.calculate(tgt, src, memory)).isEmpty()
+ assertThat(memory.isEmpty()).isTrue()
+ }
+
+ @Test
+ fun contentMatchNoMemory() {
+ val sourceFoo = src.createFile("foo")
+ sourceFoo.write().use { it.write(CONTENT1) }
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT1) }
+
+ assertThat(Verifier.calculate(tgt, src, memory)).isEmpty()
+ assertThat(memory.get("/foo")).isEqualTo(HASH1)
+ }
+
+ @Test
+ fun contentMatchMemory() {
+ memory.put("/foo", HASH1)
+ val sourceFoo = src.createFile("foo")
+ // Intentionally wrong, to show that source is not read when memory hash matches
+ sourceFoo.write().use { it.write(0) }
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT1) }
+
+ assertThat(Verifier.calculate(tgt, src, memory)).isEmpty()
+ }
+
+ @Test
+ fun contentMatchMemoryOldFormat() {
+ Assume.assumeTrue(Hasher.DEFAULT != Hasher.Format.SHA160)
+
+ memory.put("/foo", HASH1_OLD)
+ val sourceFoo = src.createFile("foo")
+ // Intentionally wrong, to show that source is not read when memory hash matches
+ sourceFoo.write().use { it.write(0) }
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT1) }
+
+ assertThat(Verifier.calculate(tgt, src, memory)).isEmpty()
+ }
+
+ @Test
+ fun contentMismatchNoMemory() {
+ val sourceFoo = src.createFile("foo")
+ sourceFoo.write().use { it.write(CONTENT1) }
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ val actions = Verifier.calculate(tgt, src, memory)
+ assertThat(actions).containsExactly(Action.Copy(name = "foo", overwrite = true))
+ assertThat(Modifier.apply(tgt, src, actions, memory = memory)).isEmpty()
+ assertThat(memory.get("/foo")).isEqualTo(HASH1)
+ assertThat(targetFoo.read().use { it.readBytes() })
+ .isEqualTo(CONTENT1)
+ }
+
+ @Test
+ fun contentMismatchMemory() {
+ memory.put("/foo", HASH1)
+ val sourceFoo = src.createFile("foo")
+ sourceFoo.write().use { it.write(CONTENT1) }
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ val actions = Verifier.calculate(tgt, src, memory)
+ assertThat(actions).containsExactly(Action.Copy(name = "foo", overwrite = true))
+ assertThat(Modifier.apply(tgt, src, actions, memory = memory)).isEmpty()
+ assertThat(memory.get("/foo")).isEqualTo(HASH1)
+ assertThat(targetFoo.read().use { it.readBytes() })
+ .isEqualTo(CONTENT1)
+ }
+
+ @Test
+ fun contentMismatchMemoryOldFormat() {
+ memory.put("/foo", HASH1_OLD)
+ val sourceFoo = src.createFile("foo")
+ sourceFoo.write().use { it.write(CONTENT1) }
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ val actions = Verifier.calculate(tgt, src, memory)
+ assertThat(actions).containsExactly(Action.Copy(name = "foo", overwrite = true))
+ assertThat(Modifier.apply(tgt, src, actions, memory = memory)).isEmpty()
+ assertThat(memory.get("/foo")).isEqualTo(HASH1)
+ assertThat(targetFoo.read().use { it.readBytes() })
+ .isEqualTo(CONTENT1)
+ }
+
+ @Test
+ fun contentMismatchBadMemory() {
+ memory.put("/foo", "".toByteArray().inputStream().use { Hasher.hash(it)!! })
+ val sourceFoo = src.createFile("foo")
+ sourceFoo.write().use { it.write(CONTENT1) }
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ val actions = Verifier.calculate(tgt, src, memory)
+ assertThat(actions).containsExactly(Action.Copy(name = "foo", overwrite = true))
+ assertThat(Modifier.apply(tgt, src, actions, memory = memory)).isEmpty()
+ assertThat(memory.get("/foo")).isEqualTo(HASH1)
+ assertThat(targetFoo.read().use { it.readBytes() })
+ .isEqualTo(CONTENT1)
+ }
+
+ @Test
+ fun contentMismatchBadMemory2() {
+ memory.put("/foo", HASH2)
+ val sourceFoo = src.createFile("foo")
+ sourceFoo.write().use { it.write(CONTENT1) }
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ // We trust memory, so source will not be read and the difference
+ // will not be noticed. Remember that this is used together with merge.
+ assertThat(Verifier.calculate(tgt, src, memory)).isEmpty()
+ }
+
+ @Test
+ fun contentMismatchNoSource() {
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ assertThat(Verifier.calculate(tgt, src, memory)).isEmpty()
+ }
+
+ @Test
+ fun contentMismatchMemoryNoSource() {
+ memory.put("/foo", HASH1)
+ val targetFoo = tgt.createFile("foo")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ assertThat(Verifier.calculate(tgt, src, memory)).isEmpty()
+ }
+
+ @Test
+ fun dirContentMatchMemory() {
+ memory.put("/foo/bar", HASH1)
+ val sourceFoo = src.createDirectory("foo").createFile("bar")
+ // Intentionally wrong, to show that source is not read when memory hash matches
+ sourceFoo.write().use { it.write(0) }
+ val targetFoo = tgt.createDirectory("foo").createFile("bar")
+ targetFoo.write().use { it.write(CONTENT1) }
+
+ assertThat(Verifier.calculate(tgt, src, memory)).isEmpty()
+ }
+
+ @Test
+ fun dirContentMismatchNoMemory() {
+ val sourceFoo = src.createDirectory("foo").createFile("bar")
+ sourceFoo.write().use { it.write(CONTENT1) }
+ val targetFoo = tgt.createDirectory("foo").createFile("bar")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ val actions = Verifier.calculate(tgt, src, memory)
+ assertThat(actions).containsExactly(
+ Action.ChangeDir(
+ name = "foo",
+ actions = listOf(Action.Copy(name = "bar", overwrite = true)),
+ ),
+ )
+ assertThat(Modifier.apply(tgt, src, actions, memory = memory)).isEmpty()
+ assertThat(memory.get("/foo/bar")).isEqualTo(HASH1)
+ assertThat(targetFoo.read().use { it.readBytes() })
+ .isEqualTo(CONTENT1)
+ }
+ @Test
+ fun dirContentMismatchMemory() {
+ memory.put("/foo/bar", HASH1)
+ val sourceFoo = src.createDirectory("foo").createFile("bar")
+ sourceFoo.write().use { it.write(CONTENT1) }
+ val targetFoo = tgt.createDirectory("foo").createFile("bar")
+ targetFoo.write().use { it.write(CONTENT2) }
+
+ val actions = Verifier.calculate(tgt, src, memory)
+ assertThat(actions).containsExactly(
+ Action.ChangeDir(
+ name = "foo",
+ actions = listOf(Action.Copy(name = "bar", overwrite = true)),
+ ),
+ )
+ assertThat(Modifier.apply(tgt, src, actions, memory = memory)).isEmpty()
+ assertThat(memory.get("/foo/bar")).isEqualTo(HASH1)
+ assertThat(targetFoo.read().use { it.readBytes() })
+ .isEqualTo(CONTENT1)
+ }
+
+ abstract fun source(): ModifiableTree
+ abstract fun target(): ModifiableTree
+
+ private class FakeMemory : Verifier.Memory {
+ private val data = mutableMapOf<String, String>()
+
+ override fun get(path: String) = data[path]
+
+ override fun put(path: String, hash: String) {
+ data[path] = hash
+ }
+
+ fun isEmpty() = data.isEmpty()
+ }
+
+ private companion object {
+ val CONTENT1 = "Hello world".toByteArray()
+ val HASH1 = CONTENT1.inputStream().use { Hasher.hash(it)!! }
+ val HASH1_OLD = CONTENT1.inputStream().use { Hasher.hash(it, Hasher.Format.SHA160)!! }
+
+ val CONTENT2 = "Goodbye world".toByteArray()
+ val HASH2 = CONTENT2.inputStream().use { Hasher.hash(it)!! }
+ }
+}