diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2024-11-10 15:47:01 +0100 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2024-11-10 15:47:01 +0100 |
| commit | 71e9a88cca01050200489ca716928f3b9c3177b7 (patch) | |
| tree | 8e7df664153a73c28df975d7b568dcdf6dc8e9ad /libs/test-utils/src/main/java/org/the_jk/cleversync | |
| parent | 7b82ec0afe0049dfad85e89f3d42f64176c0c9fa (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.kt | 230 |
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)!! } + } +} |
