fix(audio-rooms): share Docker harness across all interop test classes

Each interop test class was running its own NostrNestsHarness.start()
in @BeforeClass and harnessOrNull?.close() in @AfterClass — meaning
the Docker stack tore down + spun back up between every class. That
sequence was both slow (~30 s Cargo build for moq-relay each time)
and unreliable: leftover network state from the prior `down -v` was
racing the next `up -d`, leaving moq-auth either unreachable
(SocketException) or producing truncated 401 responses (EOFException
on body.string()) for tests that ran after the first.

Symptoms before this fix (first class wins; everything else fails):
  - NostrNestsAuthEndpointsInteropTest                       
  - NostrNestsAuthInteropTest          → SocketException     
  - NostrNestsAuthFailureInteropTest   → EOFException        
  - NostrNestsRoundTripInteropTest     → "Failed to reach"   
  - NostrNestsMultiPeerInteropTest     → "Failed to reach"   

Fix: NostrNestsHarness.shared() returns a process-singleton. First
caller does the docker compose up + port-probe; every subsequent
caller reuses the same containers. Teardown is registered once via
Runtime.addShutdownHook so the stack lives for the JVM's lifetime
and dies cleanly at test process exit.

All five test classes now call shared() in @BeforeClass; the
@AfterClass blocks no longer call close() on the singleton (clearing
the local reference is enough — the shutdown hook handles the real
teardown). The original start() entry point is preserved for callers
that want a per-call harness.
This commit is contained in:
Claude
2026-04-26 19:30:25 +00:00
parent 8b5af5d496
commit 7e67c4655f
6 changed files with 45 additions and 11 deletions
@@ -103,14 +103,13 @@ class NostrNestsAuthEndpointsInteropTest {
@JvmStatic
fun setUpHarness() {
if (NostrNestsHarness.isEnabled()) {
harnessOrNull = NostrNestsHarness.start()
harnessOrNull = NostrNestsHarness.shared()
}
}
@AfterClass
@JvmStatic
fun tearDownHarness() {
harnessOrNull?.close()
harnessOrNull = null
}
}
@@ -217,14 +217,13 @@ class NostrNestsAuthFailureInteropTest {
@JvmStatic
fun setUpHarness() {
if (NostrNestsHarness.isEnabled()) {
harnessOrNull = NostrNestsHarness.start()
harnessOrNull = NostrNestsHarness.shared()
}
}
@AfterClass
@JvmStatic
fun tearDownHarness() {
harnessOrNull?.close()
harnessOrNull = null
}
}
@@ -72,14 +72,13 @@ class NostrNestsAuthInteropTest {
@JvmStatic
fun setUpHarness() {
if (NostrNestsHarness.isEnabled()) {
harnessOrNull = NostrNestsHarness.start()
harnessOrNull = NostrNestsHarness.shared()
}
}
@AfterClass
@JvmStatic
fun tearDownHarness() {
harnessOrNull?.close()
harnessOrNull = null
}
}
@@ -89,11 +89,50 @@ class NostrNestsHarness private constructor(
private const val PORT_READY_TIMEOUT_MS = 90_000L
private const val PORT_PROBE_INTERVAL_MS = 500L
/**
* Process-level singleton instance. Once a test class brings the
* stack up, every other test class in the same JVM run shares
* the same containers — bringing the moq-relay Cargo build down
* and back up between every `@BeforeClass` is both slow (~30 s
* compile) and flaky (port-bind / network-create races leave
* the second start in a half-broken state). Container teardown
* is registered once with the JVM as a shutdown hook.
*/
@Volatile private var sharedInstance: NostrNestsHarness? = null
private val sharedLock = Any()
/**
* Bring the stack up if not already running, returning the
* shared instance for this JVM run. Subsequent callers reuse
* the same containers. Use this in @BeforeClass.
*/
fun shared(): NostrNestsHarness {
sharedInstance?.let { return it }
synchronized(sharedLock) {
sharedInstance?.let { return it }
val instance = doStart()
Runtime
.getRuntime()
.addShutdownHook(
Thread({
runCatching { instance.close() }
}, "NostrNestsHarness-shutdown"),
)
sharedInstance = instance
return instance
}
}
/**
* Bring the stack up. Caller must `close()` to tear it down — use
* `try-with-resources` / Kotlin's `use { … }`.
*
* Most callers should use [shared] instead so the stack is
* reused across test classes.
*/
fun start(): NostrNestsHarness {
fun start(): NostrNestsHarness = doStart()
private fun doStart(): NostrNestsHarness {
check(isEnabled()) {
"NostrNestsHarness.start called without -D$ENABLE_PROPERTY=true. " +
"Call NostrNestsHarness.assumeNestsInterop() first to skip cleanly."
@@ -392,14 +392,13 @@ class NostrNestsMultiPeerInteropTest {
@JvmStatic
fun setUpHarness() {
if (NostrNestsHarness.isEnabled()) {
harnessOrNull = NostrNestsHarness.start()
harnessOrNull = NostrNestsHarness.shared()
}
}
@AfterClass
@JvmStatic
fun tearDownHarness() {
harnessOrNull?.close()
harnessOrNull = null
}
}
@@ -268,14 +268,13 @@ class NostrNestsRoundTripInteropTest {
@JvmStatic
fun setUpHarness() {
if (NostrNestsHarness.isEnabled()) {
harnessOrNull = NostrNestsHarness.start()
harnessOrNull = NostrNestsHarness.shared()
}
}
@AfterClass
@JvmStatic
fun tearDownHarness() {
harnessOrNull?.close()
harnessOrNull = null
}
}