summaryrefslogtreecommitdiffhomepage
path: root/packages/app/e2e/backend.ts
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-01 11:58:11 -0400
committerGitHub <[email protected]>2026-04-01 15:58:11 +0000
commit38d22765920ef5047db4d9f1bdc0bdf602e6906f (patch)
tree378eb3358326fb91bef194fea04aeea64cb75279 /packages/app/e2e/backend.ts
parentd58004a864ee04a34a30fbbcdde9336d477fc8fa (diff)
downloadopencode-38d22765920ef5047db4d9f1bdc0bdf602e6906f.tar.gz
opencode-38d22765920ef5047db4d9f1bdc0bdf602e6906f.zip
test(e2e): isolate prompt tests with per-worker backend (#20464)
Diffstat (limited to 'packages/app/e2e/backend.ts')
-rw-r--r--packages/app/e2e/backend.ts119
1 files changed, 119 insertions, 0 deletions
diff --git a/packages/app/e2e/backend.ts b/packages/app/e2e/backend.ts
new file mode 100644
index 000000000..2acbe7179
--- /dev/null
+++ b/packages/app/e2e/backend.ts
@@ -0,0 +1,119 @@
+import { spawn } from "node:child_process"
+import fs from "node:fs/promises"
+import net from "node:net"
+import os from "node:os"
+import path from "node:path"
+import { fileURLToPath } from "node:url"
+
+type Handle = {
+ url: string
+ stop: () => Promise<void>
+}
+
+function freePort() {
+ return new Promise<number>((resolve, reject) => {
+ const server = net.createServer()
+ server.once("error", reject)
+ server.listen(0, () => {
+ const address = server.address()
+ if (!address || typeof address === "string") {
+ server.close(() => reject(new Error("Failed to acquire a free port")))
+ return
+ }
+ server.close((err) => {
+ if (err) reject(err)
+ else resolve(address.port)
+ })
+ })
+ })
+}
+
+async function waitForHealth(url: string, probe = "/global/health") {
+ const end = Date.now() + 120_000
+ let last = ""
+ while (Date.now() < end) {
+ try {
+ const res = await fetch(`${url}${probe}`)
+ if (res.ok) return
+ last = `status ${res.status}`
+ } catch (err) {
+ last = err instanceof Error ? err.message : String(err)
+ }
+ await new Promise((resolve) => setTimeout(resolve, 250))
+ }
+ throw new Error(`Timed out waiting for backend health at ${url}${probe}${last ? ` (${last})` : ""}`)
+}
+
+const LOG_CAP = 100
+
+function cap(input: string[]) {
+ if (input.length > LOG_CAP) input.splice(0, input.length - LOG_CAP)
+}
+
+function tail(input: string[]) {
+ return input.slice(-40).join("")
+}
+
+export async function startBackend(label: string): Promise<Handle> {
+ const port = await freePort()
+ const sandbox = await fs.mkdtemp(path.join(os.tmpdir(), `opencode-e2e-${label}-`))
+ const appDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
+ const repoDir = path.resolve(appDir, "../..")
+ const opencodeDir = path.join(repoDir, "packages", "opencode")
+ const env = {
+ ...process.env,
+ OPENCODE_DISABLE_SHARE: process.env.OPENCODE_DISABLE_SHARE ?? "true",
+ OPENCODE_DISABLE_LSP_DOWNLOAD: "true",
+ OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
+ OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true",
+ OPENCODE_TEST_HOME: path.join(sandbox, "home"),
+ XDG_DATA_HOME: path.join(sandbox, "share"),
+ XDG_CACHE_HOME: path.join(sandbox, "cache"),
+ XDG_CONFIG_HOME: path.join(sandbox, "config"),
+ XDG_STATE_HOME: path.join(sandbox, "state"),
+ OPENCODE_CLIENT: "app",
+ OPENCODE_STRICT_CONFIG_DEPS: "true",
+ } satisfies Record<string, string | undefined>
+ const out: string[] = []
+ const err: string[] = []
+ const proc = spawn(
+ "bun",
+ ["run", "--conditions=browser", "./src/index.ts", "serve", "--port", String(port), "--hostname", "127.0.0.1"],
+ {
+ cwd: opencodeDir,
+ env,
+ stdio: ["ignore", "pipe", "pipe"],
+ },
+ )
+ proc.stdout?.on("data", (chunk) => { out.push(String(chunk)); cap(out) })
+ proc.stderr?.on("data", (chunk) => { err.push(String(chunk)); cap(err) })
+
+ const url = `http://127.0.0.1:${port}`
+ try {
+ await waitForHealth(url)
+ } catch (error) {
+ proc.kill("SIGTERM")
+ await fs.rm(sandbox, { recursive: true, force: true }).catch(() => undefined)
+ throw new Error(
+ [
+ `Failed to start isolated e2e backend for ${label}`,
+ error instanceof Error ? error.message : String(error),
+ tail(out),
+ tail(err),
+ ]
+ .filter(Boolean)
+ .join("\n"),
+ )
+ }
+
+ return {
+ url,
+ async stop() {
+ if (proc.exitCode === null) {
+ proc.kill("SIGTERM")
+ await new Promise((resolve) => proc.once("exit", () => resolve(undefined))).catch(() => undefined)
+ }
+ await fs.rm(sandbox, { recursive: true, force: true }).catch(() => undefined)
+ },
+ }
+}