diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index 392885b640c1..596fe095b1ff 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -1,4 +1,7 @@ import { assert, describe, it } from "@effect/vitest"; +import { afterAll } from "vite-plus/test"; +// @effect-diagnostics-next-line nodeBuiltinImport:off - the executed suite runs the generated launch script through a real POSIX shell. +import * as NodeChildProcess from "node:child_process"; import * as NodeServices from "@effect/platform-node/NodeServices"; import * as NetService from "@t3tools/shared/Net"; import * as Duration from "effect/Duration"; @@ -447,3 +450,114 @@ describe("ssh tunnel scripts", () => { }).pipe(Effect.provide(layer), Effect.scoped); }); }); + +// The launch script's failure diagnostic only misbehaves when a real shell runs +// the failure branch against a log left over from a previous run, so the suite +// below executes the real generated script. Find a shell that has the tools it +// needs; anywhere else the executed suite skips. +const REQUIRED_SHELL_TOOLS = ["nohup", "mktemp", "cmp", "tail"] as const; + +const posixShellRunner = (() => { + // Candidates rather than a platform switch: wsl.exe simply fails to spawn + // where it does not exist, which is the same answer as a missing tool. + const candidates = [ + { file: "bash", args: [] as ReadonlyArray }, + { file: "wsl.exe", args: ["-e", "bash"] as ReadonlyArray }, + ]; + const probe = REQUIRED_SHELL_TOOLS.map((tool) => `command -v ${tool} >/dev/null || exit 1`).join( + "\n", + ); + return ( + candidates.find((candidate) => { + const result = NodeChildProcess.spawnSync(candidate.file, [...candidate.args, "-c", probe], { + encoding: "utf8", + }); + return result.status === 0; + }) ?? null + ); +})(); + +const runShell = (script: string, args: ReadonlyArray = []) => { + if (posixShellRunner === null) throw new Error("no POSIX shell runner available"); + // The launch script arrives on stdin with the state key as $1 in production too. + const result = NodeChildProcess.spawnSync( + posixShellRunner.file, + [...posixShellRunner.args, "-s", "--", ...args], + { input: script, encoding: "utf8" }, + ); + return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" }; +}; + +const sh = (value: string) => `'${value.replaceAll("'", "'\\''")}'`; + +// Reading the generated script proves what it says, not what it does. An +// append-mode launch satisfied every text assertion and still blamed a silent +// server's failure on the previous run's log, because [ -s "$LOG_FILE" ] stayed +// true forever once any run had logged. So run the real script in a throwaway +// HOME seeded with a stale log, with a fake `node` that picks a port, fails +// readiness immediately, and lets the runner exit without writing a byte. +describe.skipIf(posixShellRunner === null)("remote launch script (executed)", () => { + const fixtures: Array = []; + + afterAll(() => { + for (const work of fixtures) runShell(`set -eu\nrm -rf ${sh(work)}`); + fixtures.length = 0; + }); + + it("reports a silent launch instead of tailing the previous run's log", () => { + const setup = runShell( + [ + "set -eu", + "work=$(mktemp -d)", + 'mkdir -p "$work/bin" "$work/home/.t3/ssh-launch/launch-test"', + // One fake node serves the launch script's three contracts, told apart + // by argv: `node - /port ...` picks a port, `node - ` finds no external server, `node - ...` probes readiness, + // and `node