Files
utun2/tools/chatgui-android/tests/test_config_provider.kt
T

120 lines
6.1 KiB
Kotlin

import android.content.Context
import android.content.ContextWrapper
import com.utun.chat.data.AutoSocketProtocols
import com.utun.chat.data.ConfigProvider
import com.utun.chat.data.ServerEntry
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.launch
import kotlinx.coroutines.cancelAndJoin
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.io.File
import kotlin.system.exitProcess
/* Запуск через app_process с собранным APK в CLASSPATH: настоящие ConfigProvider/DataStore,
* отдельный каталог без доступа к профилю приложения. Полученные .cfg проверяет test_node_config --validate. */
fun main(args: Array<String>): Unit = runBlocking {
val directory = File(args.single())
check(directory.mkdirs()) { "Use a new output directory: $directory" }
val context = object : ContextWrapper(null) {
override fun getApplicationContext(): Context = this
override fun getFilesDir(): File = directory
}
val provider = ConfigProvider(context)
provider.setValue("node.name", "Android config probe")
provider.setValue("node.private_key", "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f")
provider.setValue("node.public_key", "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01")
provider.saveServersToConfig(emptyList()).await()
val defaults = File(provider.configFilePath()).readText()
check("storage_unit_size=100M\n" in defaults && "storage_total_size=1G\n" in defaults)
File(directory, "default.cfg").writeText(defaults)
provider.setValue("chat.storage_autoload_maxsize_mb", "23")
provider.setValue("chat.storage_maxsize_gb", "7")
provider.setValue("debug.categories", "config=debug,chat_sync=info,stream=debug")
provider.setConnectionMode("manual")
for (protocols in AutoSocketProtocols.values()) {
provider.setAutoSocketProtocols(protocols)
val servers = protocols.createServers("probe", "127.0.0.1", emptyList()) +
protocols.createServers("probe", "::1", emptyList())
val inactive = listOf(ServerEntry("inactive", "offline", "192.0.2.1", "tcp", 4567))
for (utun in 0..1) {
provider.setValue("gui.utun_enabled", "$utun")
provider.saveServersToConfig(servers, inactive).await()
val saved = File(provider.configFilePath()).readText()
check("#[server:offline_v4_tcp]" in saved)
val text = provider.buildConfigText().await()
check("storage_unit_size=23M\n" in text && "storage_total_size=7G\n" in text)
check("storage_autoload_maxsize_mb=" !in text && "storage_maxsize_gb=" !in text)
check("auto_socket_protocols=${protocols.configValue}\n" in text && "utun_enabled=$utun\n" in text)
check("radio_vad_enabled=" !in text && "radio_auto_enabled=" in text)
check(!text.contains("[server:offline_v4_tcp]"))
val name = "${protocols.configValue.replace('+', '-')}-utun$utun"
File(directory, "$name-saved.cfg").writeText(saved)
File(directory, "$name-start.cfg").writeText(text)
}
}
fun values(text: String): Map<String, String> = text.lineSequence().map { it.trim() }
.filter { !it.startsWith("#") && it.contains('=') }.associate { it.substringBefore('=') to it.substringAfter('=') }
val copied = mutableListOf(ServerEntry("copy", "probe", "127.0.0.1", "tcp", 3344))
val save = provider.saveServersToConfig(copied)
copied.clear()
save.await()
check(provider.loadServersFromConfig().await().single().port == 3344)
lateinit var intent: Deferred<Unit>
val screen = launch(start = CoroutineStart.UNDISPATCHED) {
intent = provider.saveServersToConfig(listOf(ServerEntry("intent", "probe", "127.0.0.1", "tcp", 4455)))
intent.await()
}
screen.cancelAndJoin()
intent.await()
check(provider.loadServersFromConfig().await().single().port == 4455)
provider.setValue("gui.utun_enabled", "0")
provider.saveStreamSettings(8, 0)
val stop = AtomicBoolean()
val mutations = AtomicInteger()
val editor = launch(Dispatchers.IO) {
while (!stop.get() && mutations.get() < 512) {
val index = mutations.getAndIncrement()
if (index % 2 == 0) provider.saveStreamSettings(8, 0) else provider.saveStreamSettings(16, 2)
}
}
try {
repeat(24) { index ->
val text = provider.buildConfigText().await()
val runtime = values(text)
val pair = runtime["stream_subblocks"] to runtime["stream_rs_redundant"]
check(pair in listOf("8" to "0", "16" to "2")) { "Mixed preferences snapshot: $pair" }
val saved = values(File(provider.configFilePath()).readText())
check(runtime == saved) { "Saved and runtime config differ at snapshot $index" }
File(directory, "concurrent-$index.cfg").writeText(text)
}
} finally { stop.set(true); editor.cancelAndJoin() }
check(mutations.get() > 0)
val cfg = File(provider.configFilePath())
val previous = File(directory, "previous.cfg")
check(cfg.renameTo(previous))
check(cfg.mkdir())
val sentinel = File(cfg, "old")
sentinel.writeText("preserve")
try {
check(runCatching { provider.buildConfigText().await() }.isFailure)
check(runCatching { provider.saveServersToConfig(emptyList()).await() }.isFailure)
check(sentinel.readText() == "preserve")
check(directory.listFiles()!!.none { it.name.startsWith(".utun.cfg.") })
} finally {
check(sentinel.delete())
check(cfg.delete())
check(previous.renameTo(cfg))
}
provider.buildConfigText().await()
println("PASS: real ConfigProvider/DataStore defaults, protocols, IPv4/IPv6, inactive sockets, UTUN, " +
"input ownership, cancelled waiter, 24 coherent snapshots, ${mutations.get()} edits and IO failure recovery")
exitProcess(0)
}