Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 70 additions & 20 deletions scripts/playground-command-timeout-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,45 @@ import assert from "node:assert/strict"
import { createRuntime } from "../packages/runtime-core/src/index.js"
import { createPlaygroundRuntimeBackend, type PlaygroundCliModule } from "../packages/runtime-playground/src/index.js"

let runCalled = false
const commandTimeoutMs = 25
let runCalls = 0
let runtimeDisposed = false
let timedRunCompleted = false
let resolveRunEntered!: () => void
let resolveTimedRun!: () => void
let resolveTimedRunSettled!: () => void
const runEntered = new Promise<void>((resolve) => {
resolveRunEntered = resolve
})
const timedRun = new Promise<{ text: string; exitCode: number }>((resolve) => {
resolveTimedRun = () => resolve({ text: "", exitCode: 0 })
})
const timedRunSettled = new Promise<void>((resolve) => {
resolveTimedRunSettled = resolve
})

const fakeCliModule: PlaygroundCliModule = {
runCLI: async () => ({
serverUrl: "http://127.0.0.1:9400",
playground: {
run: async () => {
runCalled = true
return await new Promise<never>(() => undefined)
runCalls += 1
if (runCalls === 1) {
return { text: "warm", exitCode: 0 }
}

resolveRunEntered()
try {
return await timedRun
} finally {
timedRunCompleted = true
resolveTimedRunSettled()
}
},
},
async [Symbol.asyncDispose]() {
return undefined
runtimeDisposed = true
resolveTimedRun()
},
}),
}
Expand All @@ -31,26 +57,50 @@ const runtime = await createRuntime({
},
}, createPlaygroundRuntimeBackend({ cliModule: fakeCliModule }))

await assert.rejects(
() => runtime.execute({
try {
// Keep runtime startup outside the deliberately tiny in-flight command budget.
await runtime.execute({
command: "wordpress.run-php",
args: ["code=echo 'warm';"],
})

const startedAt = Date.now()
const execution = runtime.execute({
command: "wordpress.run-php",
args: ["code=echo 'never';"],
timeoutMs: 25,
}),
(error) => {
assert.ok(error instanceof Error)
assert.match(error.message, /Runtime command wordpress\.run-php exceeded timeoutMs=25/)
return true
},
)
timeoutMs: commandTimeoutMs,
})

assert.equal(runCalled, true)
const enteredBeforeSettlement = await Promise.race([
runEntered.then(() => true),
execution.then(() => false, () => false),
])
assert.equal(enteredBeforeSettlement, true, "the timed command must enter Playground run() before settling")

const observation = await runtime.observe({ type: "command-result" })
const commandResult = observation.data as { exitCode?: number; stderr?: string }
assert.equal(commandResult.exitCode, 1)
assert.match(commandResult.stderr ?? "", /timeoutMs=25/)
await assert.rejects(
() => execution,
(error) => {
assert.ok(error instanceof Error)
assert.match(error.message, /Runtime command wordpress\.run-php exceeded timeoutMs=25/)
return true
},
)

const elapsedMs = Date.now() - startedAt
assert.ok(elapsedMs >= commandTimeoutMs, `timeout fired early after ${elapsedMs}ms`)
assert.ok(elapsedMs < 1_000, `timeout was not bounded: ${elapsedMs}ms`)
assert.equal(timedRunCompleted, false, "timeout must cancel execution without waiting for the backend run to settle")

const observation = await runtime.observe({ type: "command-result" })
const commandResult = observation.data as { exitCode?: number; stderr?: string }
assert.equal(commandResult.exitCode, 1)
assert.match(commandResult.stderr ?? "", /timeoutMs=25/)
} finally {
await runtime.destroy()
}

await runtime.destroy()
await timedRunSettled
assert.equal(timedRunCompleted, true)
assert.equal(runtimeDisposed, true, "runtime teardown must terminate the in-flight fake backend run")

console.log("playground command timeout smoke passed")