JSONLineServerSmokeTest.kt
src/jvmMain/kotlin/afield/examples/JSONLineServerSmokeTest.kt · 161 lines
package afield.examples
import afield.document.graph.Box
import afield.runtime.execution.ReturnValueRegistry
import afield.io.process.JSONLineServer
import java.io.OutputStreamWriter
import java.net.ServerSocket
import java.net.Socket
object JSONLineServerSmokeTest
FIELD_TEST id: examples.json-line-server.smoke runner: jvm-main entrypoint: afield.examples.JSONLineServerSmokeTest tags: [smoke, examples, json, socket] timeoutSec: 20 expect: exitCode: 0 stdoutContains: - "JSONLineServer smoke tests passed"
object JSONLineServerSmokeTest {
fun main(args: Array<String>)
@JvmStatic
fun main(args: Array<String>) {
check(runTests()) { "JSONLineServer smoke tests failed" }
}
fun runTests(): Boolean
body — 132 lines, from line 29
fun runTests(): Boolean {
return try {
testCapturesLatestJsonLine()
testFixedPortConstructionIsIdempotent()
testAcceptsReconnects()
testStopsWithBoxLifecycle()
println("JSONLineServer smoke tests passed")
true
} catch (t: Throwable) {
t.printStackTrace()
false
}
}
private fun testCapturesLatestJsonLine() {
val server = JSONLineServer(0, bindHost = "127.0.0.1")
try {
sendLines(
server.port,
"""{"value": 1}""",
"not json",
"""{"value": 2, "name": "latest"}"""
)
waitFor("latest JSON line") {
server.output.optInt("value", -1) == 2
}
require(server.output.optInt("value", -1) == 2) { "Expected value=2, got ${server.output}" }
require(server.output.optString("name") == "latest") { "Expected latest object, got ${server.output}" }
require(server.lineCount == 2L) { "Expected 2 valid JSON lines, got ${server.lineCount}" }
require(server.invalidLineCount == 1L) { "Expected 1 invalid JSON line, got ${server.invalidLineCount}" }
require(server.latestLine == """{"value": 2, "name": "latest"}""") { "Unexpected latest line ${server.latestLine}" }
} finally {
server.close()
}
}
private fun testFixedPortConstructionIsIdempotent() {
val port = freePort()
val first = JSONLineServer(port, bindHost = "127.0.0.1")
try {
val second = JSONLineServer(port, bindHost = "127.0.0.1")
require(first === second) { "Expected fixed-port JSONLineServer constructor to return cached instance" }
sendLines(port, """{"idempotent": true}""")
waitFor("cached server output") {
second.output.optBoolean("idempotent", false)
}
} finally {
first.close()
}
val fresh = JSONLineServer(port, bindHost = "127.0.0.1")
try {
require(fresh !== first) { "Expected close() to remove cached JSONLineServer instance" }
} finally {
fresh.close()
}
}
private fun testAcceptsReconnects() {
val server = JSONLineServer(0, bindHost = "127.0.0.1")
try {
sendLines(server.port, """{"connection": 1}""")
waitFor("first connection output") {
server.output.optInt("connection", -1) == 1 && server.connectionCount == 1L
}
waitFor("first connection to close") {
server.activeConnectionCount == 0L && server.latestDisconnectedAtMillis > 0L
}
sendLines(server.port, """{"connection": 2}""")
waitFor("second connection output") {
server.output.optInt("connection", -1) == 2 && server.connectionCount == 2L
}
require(server.output.optInt("connection", -1) == 2) { "Expected reconnected client to update output, got ${server.output}" }
require(server.latestConnectedAtMillis > 0L) { "Expected connection timestamp to be set" }
require(server.latestConnectionRemoteAddress != null) { "Expected connection remote address to be set" }
} finally {
server.close()
}
}
private fun testStopsWithBoxLifecycle() {
val box = Box("json_line_server_stop")
val server = JSONLineServer(0, bindHost = "127.0.0.1", autoStart = false)
ReturnValueRegistry.launch(box, server, "default") { _, _, _ -> }
waitFor("server to start") {
server.running && server.port != 0
}
sendLines(server.port, """{"tick": 1}""")
waitFor("server output") {
server.output.optInt("tick", -1) == 1
}
box.ke.stop(box)
waitFor("server to stop") {
!server.running
}
}
private fun freePort(): Int {
return ServerSocket(0).use { it.localPort }
}
private fun sendLines(port: Int, vararg lines: String) {
Socket("127.0.0.1", port).use { socket ->
OutputStreamWriter(socket.getOutputStream(), Charsets.UTF_8).buffered().use { out ->
lines.forEach { line ->
out.write(line)
out.newLine()
}
out.flush()
}
}
}
private fun waitFor(description: String, predicate: () -> Boolean) {
val deadline = System.currentTimeMillis() + 5000
while (System.currentTimeMillis() < deadline) {
if (predicate()) return
Thread.sleep(25)
}
error("Timed out waiting for $description")
}
}