Add Android animation library browsing
This commit is contained in:
+68
@@ -0,0 +1,68 @@
|
||||
package org.qimiaoscreen.core
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlin.test.*
|
||||
import java.util.Base64
|
||||
import java.security.MessageDigest
|
||||
|
||||
class AnimationDownloadTest {
|
||||
private val item = buildJsonObject { put("type", "animation"); put("id", "a"); put("revision", "r") }
|
||||
private class Cache : LibraryCache {
|
||||
val images = mutableMapOf<String, String>(); val records = mutableMapOf<String, JsonObject>()
|
||||
override suspend fun loadCatalog(deviceId: String): CachedLibrary? = null
|
||||
override suspend fun saveCatalog(deviceId: String, catalog: CachedLibrary) {}
|
||||
override suspend fun readImage(deviceId: String, item: JsonObject, tier: String) = images[tier]
|
||||
override suspend fun writeImage(deviceId: String, item: JsonObject, tier: String, base64: String) { images[tier] = base64 }
|
||||
override suspend fun readRecord(deviceId: String, item: JsonObject, name: String) = records[name]
|
||||
override suspend fun writeRecord(deviceId: String, item: JsonObject, name: String, value: JsonObject) { records[name] = value }
|
||||
override suspend fun writeAnimationFrame(deviceId: String, item: JsonObject, tier: String, chunks: List<String>, sha256: String) {
|
||||
val bytes = chunks.flatMap { Base64.getDecoder().decode(it).toList() }.toByteArray()
|
||||
require(MessageDigest.getInstance("SHA-256").digest(bytes).joinToString("") { "%02x".format(it) } == sha256)
|
||||
images[tier] = Base64.getEncoder().encodeToString(bytes)
|
||||
}
|
||||
override suspend fun retain(deviceId: String, items: List<JsonObject>) {}
|
||||
override suspend fun sizeBytes() = 0L
|
||||
override suspend fun clear() { images.clear(); records.clear() }
|
||||
}
|
||||
@Test fun resumeAndUpgradePreserveTiming() = runBlocking {
|
||||
val cache = Cache(); val offsets = mutableListOf<Int>()
|
||||
val bytes = ByteArray(5000) { (it % 251).toByte() }
|
||||
val hash = MessageDigest.getInstance("SHA-256").digest(bytes).joinToString("") { "%02x".format(it) }
|
||||
val rpc: suspend (String, JsonObject) -> JsonObject = { method, params ->
|
||||
if (method.endsWith(".info")) buildJsonObject {
|
||||
put("revision", "r"); put("frame_count", 2); put("next_offset", JsonNull)
|
||||
put("frames", JsonArray(listOf(75L, 250L).mapIndexed { i, duration -> buildJsonObject { put("index", i); put("duration_ms", duration) } }))
|
||||
} else {
|
||||
val offset = params.getValue("offset").jsonPrimitive.int; offsets += offset
|
||||
val end = minOf(offset + 4096, bytes.size)
|
||||
buildJsonObject {
|
||||
put("revision", "r"); put("tier", params.getValue("tier")); put("frame_index", params.getValue("frame_index")); put("offset", offset)
|
||||
put("total_bytes", bytes.size); put("sha256", hash); put("next_offset", if (end == bytes.size) JsonNull else JsonPrimitive(end))
|
||||
put("data_base64", Base64.getEncoder().encodeToString(bytes.copyOfRange(offset, end)))
|
||||
}
|
||||
}
|
||||
}
|
||||
val first = AnimationDownload(item, cache)
|
||||
assertNull(first.step("device", "preview", rpc))
|
||||
assertNull(first.step("device", "preview", rpc)) // first chunk persisted
|
||||
val resumed = AnimationDownload(item, cache)
|
||||
var clip: AnimationClip? = null
|
||||
repeat(5) { resumed.step("device", "preview", rpc)?.let { clip = it } }
|
||||
assertEquals(listOf(0, 4096, 0, 4096), offsets)
|
||||
assertEquals(listOf(75L, 250L), clip!!.durations)
|
||||
assertEquals("preview", clip!!.tier)
|
||||
assertEquals(2, cache.images.size)
|
||||
repeat(5) { resumed.step("device", "full", rpc)?.let { clip = it } }
|
||||
assertEquals("full", clip!!.tier)
|
||||
assertEquals(setOf("preview", "full"), resumed.complete)
|
||||
assertEquals(4, cache.images.size)
|
||||
}
|
||||
@Test fun mismatchedRevisionDoesNotCommit() = runBlocking {
|
||||
val cache = Cache()
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
AnimationDownload(item, cache).step("device", "preview") { _, _ -> buildJsonObject { put("revision", "other") } }
|
||||
}
|
||||
assertTrue(cache.images.isEmpty()); assertTrue(cache.records.isEmpty())
|
||||
}
|
||||
}
|
||||
+13
-1
@@ -62,7 +62,7 @@ private class RpcFixture : WifiPlatform {
|
||||
val r = Protocol.json.parseToJsonElement(fragment.decodeToString()).jsonObject
|
||||
val method = r.getValue("method").jsonPrimitive.content
|
||||
if (method == "content.play") { wifiWrites++; if (failWrite) throw DeviceTransportFailure("lost reply") }
|
||||
if (method == "status.get") { wifiReads++; if (failRead) throw DeviceTransportFailure("lost read") }
|
||||
if (method == "status.get" || method.startsWith("library.animation.")) { wifiReads++; if (failRead) throw DeviceTransportFailure("lost read") }
|
||||
replies.send(buildJsonObject { put("id", r.getValue("id")); put("ok", true); put("result", buildJsonObject { put("wifi", true) }) }.toString().encodeToByteArray())
|
||||
}
|
||||
override suspend fun receive() = replies.receive()
|
||||
@@ -72,6 +72,18 @@ private class RpcFixture : WifiPlatform {
|
||||
|
||||
class HybridRpcTest {
|
||||
private suspend fun awaitTrue(predicate: () -> Boolean) = withTimeout(3_000) { while (!predicate()) delay(10) }
|
||||
@Test fun animationReadsRetryOnBle() = runBlocking {
|
||||
for (method in listOf("library.animation.info", "library.animation.chunk")) {
|
||||
val f = RpcFixture(); val ble = DeviceRpc(f.ble, PlainCrypto()); ble.open("test")
|
||||
var transport = "ble"
|
||||
val router = HybridRpc(ble, f.ble, f, this, "test", { t, _ -> transport = t }, { fail(it) })
|
||||
try {
|
||||
awaitTrue { transport == "wifi" }; f.failRead = true
|
||||
assertTrue(router.call(method).containsKey("ble"))
|
||||
assertEquals(1, f.wifiReads); assertEquals("ble", transport)
|
||||
} finally { router.close() }
|
||||
}
|
||||
}
|
||||
@Test fun bleFailureDuringNetworkFallbackIsHandled() = runBlocking {
|
||||
val f = RpcFixture(); val ble = DeviceRpc(f.ble, PlainCrypto()); ble.open("test")
|
||||
var mode = "ble"; var lost = false; var uncaught = false
|
||||
|
||||
Reference in New Issue
Block a user