test(audio-rooms): -DnestsInteropExternal bypasses Docker
Adds a "bring your own stack" path to NostrNestsHarness so the interop
tests can run without a Docker daemon. With `-DnestsInteropExternal=true`:
- Skip the `docker compose up` that builds + boots
moq-auth + moq-relay
- Port-probe + /health-check the same 8090 / 4443 endpoints the
Docker path uses
- close() is a no-op — the caller owns the lifecycle
Useful in two situations:
1. Sandboxes / restricted CI without a Docker daemon (just run
`cargo install moq-relay` + `node moq-auth/dist/index.js`
yourself, then run gradle with the flag)
2. Fast iteration — the Docker path takes ~30 s to compile
moq-relay on first run; with this, you keep both processes
alive across many test invocations
Forward `nestsInteropExternal` (and `nestsInteropDebug`,
`nestsInteropMoqRev`) through the Test task so the property reaches
test workers; without that, the gate stays off in the worker JVM.
Also: `assertSpeakerReached` / `assertListenerReached` now log a "✘"
checkpoint with the rich state description before calling fail().
JUnit captures the assertion message in a separate section, but the
"standard output" tab is what most people read first when scanning
for a cause — so the chained-cause string now lands in both places.
This commit is contained in:
@@ -95,4 +95,7 @@ kotlin {
|
|||||||
tasks.withType<Test>().configureEach {
|
tasks.withType<Test>().configureEach {
|
||||||
System.getProperty("nestsInterop")?.let { systemProperty("nestsInterop", it) }
|
System.getProperty("nestsInterop")?.let { systemProperty("nestsInterop", it) }
|
||||||
System.getProperty("nestsInteropRev")?.let { systemProperty("nestsInteropRev", it) }
|
System.getProperty("nestsInteropRev")?.let { systemProperty("nestsInteropRev", it) }
|
||||||
|
System.getProperty("nestsInteropMoqRev")?.let { systemProperty("nestsInteropMoqRev", it) }
|
||||||
|
System.getProperty("nestsInteropExternal")?.let { systemProperty("nestsInteropExternal", it) }
|
||||||
|
System.getProperty("nestsInteropDebug")?.let { systemProperty("nestsInteropDebug", it) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,10 @@ object InteropDebug {
|
|||||||
else -> error("unsupported expectedClass=$expectedClass")
|
else -> error("unsupported expectedClass=$expectedClass")
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
// Land the rich state dump on stdout too — JUnit captures the
|
||||||
|
// fail message separately, but the standard-output section is
|
||||||
|
// what people glance at first when debugging.
|
||||||
|
checkpoint(scope, "✘ speaker NOT @ $expectedClass — actual=${describe(actual)}")
|
||||||
fail("[$scope] speaker did not reach $expectedClass — was ${describe(actual)}")
|
fail("[$scope] speaker did not reach $expectedClass — was ${describe(actual)}")
|
||||||
}
|
}
|
||||||
checkpoint(scope, "speaker @ $expectedClass — ${describe(actual)}")
|
checkpoint(scope, "speaker @ $expectedClass — ${describe(actual)}")
|
||||||
@@ -183,6 +187,7 @@ object InteropDebug {
|
|||||||
else -> error("unsupported expectedClass=$expectedClass")
|
else -> error("unsupported expectedClass=$expectedClass")
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
checkpoint(scope, "✘ listener NOT @ $expectedClass — actual=${describe(actual)}")
|
||||||
fail("[$scope] listener did not reach $expectedClass — was ${describe(actual)}")
|
fail("[$scope] listener did not reach $expectedClass — was ${describe(actual)}")
|
||||||
}
|
}
|
||||||
checkpoint(scope, "listener @ $expectedClass — ${describe(actual)}")
|
checkpoint(scope, "listener @ $expectedClass — ${describe(actual)}")
|
||||||
|
|||||||
+56
-2
@@ -45,7 +45,7 @@ import java.nio.file.Path
|
|||||||
* across as many cases as possible.
|
* across as many cases as possible.
|
||||||
*/
|
*/
|
||||||
class NostrNestsHarness private constructor(
|
class NostrNestsHarness private constructor(
|
||||||
private val workDir: File,
|
private val workDir: File?,
|
||||||
val authBaseUrl: String,
|
val authBaseUrl: String,
|
||||||
val moqEndpoint: String,
|
val moqEndpoint: String,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
@@ -54,16 +54,37 @@ class NostrNestsHarness private constructor(
|
|||||||
override fun close() {
|
override fun close() {
|
||||||
if (stopped) return
|
if (stopped) return
|
||||||
stopped = true
|
stopped = true
|
||||||
runDocker(workDir, "down", "-v", "--remove-orphans")
|
// External mode: caller owns the lifecycle of moq-auth +
|
||||||
|
// moq-relay. Nothing for us to tear down.
|
||||||
|
val dir = workDir ?: return
|
||||||
|
runDocker(dir, "down", "-v", "--remove-orphans")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** System property gate. Tests should call [assumeNestsInterop] first. */
|
/** System property gate. Tests should call [assumeNestsInterop] first. */
|
||||||
const val ENABLE_PROPERTY = "nestsInterop"
|
const val ENABLE_PROPERTY = "nestsInterop"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bypass-Docker mode. When `-DnestsInteropExternal=true`, the
|
||||||
|
* harness doesn't try to bring up containers — it assumes the
|
||||||
|
* caller has already started `moq-auth` on [AUTH_HOST_PORT] and
|
||||||
|
* `moq-relay` on [MOQ_HOST_PORT] (e.g. via
|
||||||
|
* `nests/dev-config/start-dev.sh`). Useful on machines without a
|
||||||
|
* Docker daemon (sandboxes, restricted CI), and for fast
|
||||||
|
* iteration since each test no longer pays the
|
||||||
|
* `docker compose up` / `docker compose down` cost.
|
||||||
|
*
|
||||||
|
* The harness still port-probes + health-checks both endpoints
|
||||||
|
* before declaring "ready", so a half-started external stack
|
||||||
|
* fails the same way a half-started Docker stack would.
|
||||||
|
*/
|
||||||
|
const val EXTERNAL_PROPERTY = "nestsInteropExternal"
|
||||||
|
|
||||||
/** Tests that depend on the harness call this in `@BeforeTest`. */
|
/** Tests that depend on the harness call this in `@BeforeTest`. */
|
||||||
fun isEnabled(): Boolean = System.getProperty(ENABLE_PROPERTY) == "true"
|
fun isEnabled(): Boolean = System.getProperty(ENABLE_PROPERTY) == "true"
|
||||||
|
|
||||||
|
private fun isExternal(): Boolean = System.getProperty(EXTERNAL_PROPERTY) == "true"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pin the nostrnests revision to a known-good SHA. Bump deliberately
|
* Pin the nostrnests revision to a known-good SHA. Bump deliberately
|
||||||
* — drift on `main` should not silently change interop expectations.
|
* — drift on `main` should not silently change interop expectations.
|
||||||
@@ -138,6 +159,8 @@ class NostrNestsHarness private constructor(
|
|||||||
"Call NostrNestsHarness.assumeNestsInterop() first to skip cleanly."
|
"Call NostrNestsHarness.assumeNestsInterop() first to skip cleanly."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isExternal()) return startExternal()
|
||||||
|
|
||||||
val workDir = ensureRepo()
|
val workDir = ensureRepo()
|
||||||
// The compose file `build:`s `./moq` — the upstream moq-rs
|
// The compose file `build:`s `./moq` — the upstream moq-rs
|
||||||
// sources — but nostrnests does NOT ship that directory; it
|
// sources — but nostrnests does NOT ship that directory; it
|
||||||
@@ -186,6 +209,37 @@ class NostrNestsHarness private constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "External" path: caller has already started moq-auth +
|
||||||
|
* moq-relay (e.g. via `nests/dev-config/start-dev.sh` or a
|
||||||
|
* `cargo install moq-relay` build kicked off by hand). We just
|
||||||
|
* port-probe + health-check, then return a harness whose
|
||||||
|
* [close] is a no-op.
|
||||||
|
*
|
||||||
|
* Skip Docker entirely — useful on machines without a Docker
|
||||||
|
* daemon, and dramatically faster on developer iteration.
|
||||||
|
*/
|
||||||
|
private fun startExternal(): NostrNestsHarness {
|
||||||
|
try {
|
||||||
|
waitForPort("127.0.0.1", AUTH_HOST_PORT, PORT_READY_TIMEOUT_MS)
|
||||||
|
waitForPort("127.0.0.1", MOQ_HOST_PORT, PORT_READY_TIMEOUT_MS)
|
||||||
|
waitForHealth("http://127.0.0.1:$AUTH_HOST_PORT/health", PORT_READY_TIMEOUT_MS)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"external moq-auth / moq-relay not reachable on 127.0.0.1:" +
|
||||||
|
"$AUTH_HOST_PORT and 127.0.0.1:$MOQ_HOST_PORT — " +
|
||||||
|
"did you start the stack? See nests/dev-config/start-dev.sh.\n" +
|
||||||
|
"Original error: ${t.message}",
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return NostrNestsHarness(
|
||||||
|
workDir = null,
|
||||||
|
authBaseUrl = "http://127.0.0.1:$AUTH_HOST_PORT",
|
||||||
|
moqEndpoint = "https://127.0.0.1:$MOQ_HOST_PORT/",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience for `@BeforeTest`: throws JUnit's `AssumptionViolated`
|
* Convenience for `@BeforeTest`: throws JUnit's `AssumptionViolated`
|
||||||
* (via `org.junit.Assume`) when the property isn't set, which JUnit
|
* (via `org.junit.Assume`) when the property isn't set, which JUnit
|
||||||
|
|||||||
Reference in New Issue
Block a user