Port Android server runtime to GaCy Go
This commit is contained in:
1 parent
92a4b4f545
commit
3c1186c781
20 files changed
+830
-798
No files matched your search
@@ -0,0 +1,119 @@
|
||||
package com.gacy.android
|
||||
|
||||
import com.gacy.android.data.GoRuntimeInstaller
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class GoRuntimeInstallerTest {
|
||||
@get:Rule val tmp = TemporaryFolder()
|
||||
|
||||
private fun archive(resource: String, hotfix: String, extra: Pair<String, String>? = null, versions: String = "{}"): ByteArray {
|
||||
val entries = linkedMapOf(
|
||||
"persistent" to "original-save", "freesr-data.json" to "original-roster",
|
||||
"server.json" to "server-config", "hotfix.json" to hotfix, "versions.json" to versions,
|
||||
"res.json" to resource, "teleports.json" to "{}", "resources/MapEntrance.json" to "{}"
|
||||
)
|
||||
if (extra != null) entries[extra.first] = extra.second
|
||||
val output = ByteArrayOutputStream()
|
||||
ZipOutputStream(output).use { zip ->
|
||||
entries.forEach { (name, content) ->
|
||||
zip.putNextEntry(ZipEntry(name)); zip.write(content.toByteArray()); zip.closeEntry()
|
||||
}
|
||||
}
|
||||
return output.toByteArray()
|
||||
}
|
||||
|
||||
@Test fun updatePreservesUserDataAndRepairsMissingResources() {
|
||||
val directory = tmp.newFolder("runtime")
|
||||
val old = archive("old", """{"existing":{"asset":"bundled"}}""")
|
||||
GoRuntimeInstaller(directory) { ByteArrayInputStream(old) }.prepare()
|
||||
File(directory, "persistent").writeText("user-save")
|
||||
File(directory, "freesr-data.json").writeText("user-roster")
|
||||
File(directory, "hotfix.json").writeText("""{"existing":{"asset":"user"}}""")
|
||||
File(directory, "versions.json").writeText("""{"existing":{"asset":"user"}}""")
|
||||
val updated = archive("new", """{"existing":{"asset":"new"},"added":{"asset":"new"}}""", versions = """{"existing":{"asset":"new"},"added":{"asset":"new"}}""")
|
||||
val installer = GoRuntimeInstaller(directory) { ByteArrayInputStream(updated) }
|
||||
installer.prepare()
|
||||
assertEquals("user-save", File(directory, "persistent").readText())
|
||||
assertEquals("user-roster", File(directory, "freesr-data.json").readText())
|
||||
assertEquals("new", File(directory, "res.json").readText())
|
||||
val hotfix = JSONObject(File(directory, "hotfix.json").readText())
|
||||
assertEquals("user", hotfix.getJSONObject("existing").getString("asset"))
|
||||
assertEquals("new", hotfix.getJSONObject("added").getString("asset"))
|
||||
val versions = JSONObject(File(directory, "versions.json").readText())
|
||||
assertEquals("user", versions.getJSONObject("existing").getString("asset"))
|
||||
assertEquals("new", versions.getJSONObject("added").getString("asset"))
|
||||
File(directory, "resources/MapEntrance.json").delete()
|
||||
installer.prepare()
|
||||
assertTrue(File(directory, "resources/MapEntrance.json").isFile)
|
||||
assertEquals("user-save", File(directory, "persistent").readText())
|
||||
}
|
||||
|
||||
@Test fun rejectsPathsOutsideRuntimeDirectory() {
|
||||
val directory = tmp.newFolder("runtime")
|
||||
val zip = archive("data", "{}", "../outside" to "bad")
|
||||
try {
|
||||
GoRuntimeInstaller(directory) { ByteArrayInputStream(zip) }.prepare()
|
||||
fail("Path traversal should be rejected")
|
||||
} catch (_: IllegalArgumentException) { }
|
||||
assertFalse(File(directory.parentFile, "outside").exists())
|
||||
assertFalse(File(directory, ".asset-version").exists())
|
||||
}
|
||||
|
||||
@Test fun installerUpgradeMergesVersionsEvenWhenArchiveIsUnchanged() {
|
||||
val directory = tmp.newFolder("migration")
|
||||
val zip = archive("data", "{}", versions = """{"new":{"asset":"bundle"}}""")
|
||||
val installer = GoRuntimeInstaller(directory) { ByteArrayInputStream(zip) }
|
||||
installer.prepare()
|
||||
File(directory, "versions.json").writeText("""{"custom":{"asset":"user"}}""")
|
||||
val marker = File(directory, ".asset-version")
|
||||
marker.writeText(marker.readText().removePrefix("2:"))
|
||||
installer.prepare()
|
||||
val versions = JSONObject(File(directory, "versions.json").readText())
|
||||
assertEquals("user", versions.getJSONObject("custom").getString("asset"))
|
||||
assertEquals("bundle", versions.getJSONObject("new").getString("asset"))
|
||||
}
|
||||
|
||||
@Test fun simultaneousStartsSerializeExtraction() {
|
||||
val directory = tmp.newFolder("runtime")
|
||||
val zip = archive("data", "{}")
|
||||
val firstOpened = CountDownLatch(1)
|
||||
val release = CountDownLatch(1)
|
||||
val secondOpened = CountDownLatch(1)
|
||||
val opens = AtomicInteger()
|
||||
val installer = GoRuntimeInstaller(directory) {
|
||||
if (opens.incrementAndGet() == 1) {
|
||||
firstOpened.countDown()
|
||||
check(release.await(5, TimeUnit.SECONDS))
|
||||
} else {
|
||||
secondOpened.countDown()
|
||||
}
|
||||
ByteArrayInputStream(zip)
|
||||
}
|
||||
val workers = Executors.newFixedThreadPool(2)
|
||||
try {
|
||||
val first = workers.submit { installer.prepare() }
|
||||
assertTrue(firstOpened.await(5, TimeUnit.SECONDS))
|
||||
val second = workers.submit { installer.prepare() }
|
||||
assertFalse("a second install entered before the first was released", secondOpened.await(200, TimeUnit.MILLISECONDS))
|
||||
release.countDown()
|
||||
first.get(10, TimeUnit.SECONDS); second.get(10, TimeUnit.SECONDS)
|
||||
assertEquals("data", File(directory, "res.json").readText())
|
||||
} finally {
|
||||
release.countDown()
|
||||
workers.shutdownNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.gacy.android
|
||||
|
||||
import com.gacy.android.service.LogBuffer
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class LogBufferTest {
|
||||
@After fun cleanup() = LogBuffer.clear()
|
||||
|
||||
@Test fun preservesBoundedDiagnosticsWhenRecentLogRollsOver() {
|
||||
LogBuffer.clear()
|
||||
repeat(101) { LogBuffer.append("[gateway] diagnostic-$it") }
|
||||
repeat(501) { LogBuffer.append("ordinary-$it") }
|
||||
val copied = LogBuffer.getAllText()
|
||||
assertFalse(copied.contains("diagnostic-0\n"))
|
||||
assertTrue(copied.contains("diagnostic-1\n"))
|
||||
assertTrue(copied.contains("ordinary-500"))
|
||||
LogBuffer.clear()
|
||||
assertTrue(LogBuffer.getAllText().isEmpty())
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,37 @@
|
||||
package com.gacy.android
|
||||
|
||||
import com.gacy.android.model.SceneConfig
|
||||
import com.gacy.android.model.ServerConfig
|
||||
import com.gacy.android.model.ServerExecutionMode
|
||||
import com.gacy.android.model.TrailblazerConfig
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
class ModelAndConfigTest {
|
||||
|
||||
@Test
|
||||
fun networkSettingsRejectInvalidAddressesAndPorts() {
|
||||
val original = ServerConfig(serverKey = "keep")
|
||||
assertNull(original.withNetworkInput(ServerExecutionMode.LOCAL_EMBEDDED, "0.0.0.0", "21000", "23301"))
|
||||
assertNull(original.withNetworkInput(ServerExecutionMode.LOCAL_EMBEDDED, "example.com/path", "21000", "23301"))
|
||||
assertNull(original.withNetworkInput(ServerExecutionMode.LOCAL_EMBEDDED, "example.com", "65536", "23301"))
|
||||
assertNull(original.withNetworkInput(ServerExecutionMode.LOCAL_EMBEDDED, "example.com", "abc", "23301"))
|
||||
val valid = original.withNetworkInput(ServerExecutionMode.REMOTE_SERVER, " example.com ", "1234", "5678")!!
|
||||
assertEquals("example.com", valid.remoteHost)
|
||||
assertEquals(1234, valid.dispatchPort)
|
||||
assertEquals("keep", valid.serverKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localApiUsesLoopbackWhileRemoteApiUsesConfiguredHost() {
|
||||
val local = ServerConfig(remoteHost = "192.168.1.42", dispatchPort = 24567)
|
||||
assertEquals("http://127.0.0.1:24567", local.dispatchUrl)
|
||||
assertEquals("http://192.168.1.42:24567", local.copy(executionMode = ServerExecutionMode.REMOTE_SERVER).dispatchUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSceneConfigSerialization() {
|
||||
val config = SceneConfig(
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gacy.android
|
||||
|
||||
import com.gacy.android.data.parseUnstuckResponse
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class UnstuckResponseTest {
|
||||
@Test fun confirmsOnlyQueuedRequestsWithAnId() {
|
||||
assertTrue(parseUnstuckResponse("""{"state":"queued","request_id":"abc"}""").success)
|
||||
assertFalse(parseUnstuckResponse("""{"retcode":0,"message":"OK"}""").success)
|
||||
assertFalse(parseUnstuckResponse("""{"state":"queued"}""").success)
|
||||
assertFalse(parseUnstuckResponse("""{"state":"queued","request_id":null}""").success)
|
||||
assertFalse(parseUnstuckResponse("""{"state":"failed","request_id":"abc"}""").success)
|
||||
assertFalse(parseUnstuckResponse("not json").success)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user