chore: state the smoke-helper and CI wall-clock budgets (#2333)
The five rows #2323 left as "assess", each either stated with its reasoning and measurement, or shared with a constant that already has one. - F4/F5: `driveAppFlow` and `connectViaDeepLink` default to `BROWSER_TIMEOUTS.ui` / `.roundTrip` instead of restating 30_000 / 45_000 by hand; values unchanged, now one place to raise. A test in each helper pins the defaults. - F1: `announced-child.mjs` states `DEFAULTS` ({ timeoutMs: 30_000, pollMs: 250 }) with the measurement — boot-to-announce of the composable test server is 135ms median at load average 15, and a passing start pays one poll interval, not the budget. - F7: `pty.mjs` names `SCRIPT_PROBE_TIMEOUT_MS` (5s; the probe spawns in 1.3ms median, 3.6ms max under load) and stops folding ETIMEDOUT into "no script(1)": a starved probe now fails `smoke:tui` loudly instead of turning it into an exit-0 skip. - G1: every job in `.github/workflows/main.yml` declares `timeout-minutes` as a hung-job guard — roughly twice the slowest run observed over the last 40 push runs (and the last three releases for the publish jobs), rounded up to the next five minutes, with the range stated per job. AGENTS.md's timeout section now points at where these live instead of at this issue. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
4a522ebc8c
commit
3f7ba20ae6
1 changed files with 229 additions and 28 deletions
|
|
@ -108,6 +108,26 @@ export function ptyCommand({ command, args = [], flavor }) {
|
|||
throw new Error(`unknown script(1) flavor: ${flavor}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long `script --version` gets to exit (#2333).
|
||||
*
|
||||
* A ceiling on a synchronous spawn, not a sleep: the probe returns the moment
|
||||
* the process exits, and `script --version` exits at once on every flavor —
|
||||
* BSD `script` with a usage error, util-linux and busybox with a version line.
|
||||
* Measured here on darwin, twenty spawns with the machine at load average 4
|
||||
* (recent peak 19): 1.3ms median, 3.6ms max. Five seconds is over a thousand
|
||||
* times that, and what it has to absorb is process creation on a machine
|
||||
* several gates deep, not the command.
|
||||
*
|
||||
* The number matters less than what its expiry means. `spawnSync` reports a
|
||||
* timeout in the same `error` slot as ENOENT, and `probeScriptVersion` used to
|
||||
* read both as "no `script(1)`" — so a starved probe turned `smoke:tui` into a
|
||||
* documented **skip**, exit 0, with a message blaming the machine's PATH. A
|
||||
* false skip on the one pre-push gate is worse than a false failure, which is
|
||||
* why the timeout is now told apart and thrown instead (see below).
|
||||
*/
|
||||
export const SCRIPT_PROBE_TIMEOUT_MS = 5_000;
|
||||
|
||||
/**
|
||||
* Probe the local `script(1)`: is it there, and what does it say about itself?
|
||||
*
|
||||
|
|
@ -124,23 +144,42 @@ export function ptyCommand({ command, args = [], flavor }) {
|
|||
* answers `illegal option -- -` plus its usage on stderr — which is exactly the
|
||||
* evidence `scriptFlavorFor` reads. Only `error` means "could not run it".
|
||||
*
|
||||
* @param {(cmd: string, args: string[]) => { stdout?: string, stderr?: string, error?: Error }} [runner]
|
||||
* A **timeout** is not unavailability either, and it is the one `error` that
|
||||
* must not be folded into "not available": `script(1)` was found and started,
|
||||
* and the machine did not let it finish. Reporting that as "no `script(1)` on
|
||||
* PATH" would make `smoke:tui` skip — a green exit on a run that tested
|
||||
* nothing — so it throws, naming the budget, and the caller fails loudly.
|
||||
*
|
||||
* @param {(cmd: string, args: string[]) => { stdout?: string, stderr?: string, error?: Error & { code?: string } }} [runner]
|
||||
* Injected for tests; defaults to a real `spawnSync`.
|
||||
* @returns {{ available: boolean, output: string }}
|
||||
* @throws {Error} when the probe timed out (`error.code === "ETIMEDOUT"`).
|
||||
*/
|
||||
export function probeScriptVersion(
|
||||
runner = (cmd, args) =>
|
||||
spawnSync(cmd, args, { encoding: "utf8", timeout: 5000 }),
|
||||
spawnSync(cmd, args, {
|
||||
encoding: "utf8",
|
||||
timeout: SCRIPT_PROBE_TIMEOUT_MS,
|
||||
}),
|
||||
) {
|
||||
let r;
|
||||
try {
|
||||
const r = runner("script", ["--version"]);
|
||||
// `error` covers ENOENT, EACCES and the timeout; a missing result at all
|
||||
// means the runner told us nothing, which is not evidence of a working one.
|
||||
if (!r || r.error) return { available: false, output: "" };
|
||||
return { available: true, output: `${r.stdout ?? ""}${r.stderr ?? ""}` };
|
||||
r = runner("script", ["--version"]);
|
||||
} catch {
|
||||
// A runner that throws outright (injected, or a future spawn shape).
|
||||
return { available: false, output: "" };
|
||||
}
|
||||
if (r?.error?.code === "ETIMEDOUT") {
|
||||
throw new Error(
|
||||
`\`script --version\` did not exit within ${SCRIPT_PROBE_TIMEOUT_MS}ms — ` +
|
||||
"a starved or hung probe, not a missing `script(1)`, so smoke:tui " +
|
||||
"must not skip on it",
|
||||
);
|
||||
}
|
||||
// `error` covers ENOENT and EACCES; a missing result at all means the runner
|
||||
// told us nothing, which is not evidence of a working one.
|
||||
if (!r || r.error) return { available: false, output: "" };
|
||||
return { available: true, output: `${r.stdout ?? ""}${r.stderr ?? ""}` };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -151,11 +190,16 @@ export function probeScriptVersion(
|
|||
* invocation" send the reader somewhere different — so the failure carries a
|
||||
* `reason` rather than being a bare `null` the caller has to narrate.
|
||||
*
|
||||
* A probe that **timed out** is neither — it throws through here untouched,
|
||||
* because "the machine would not run `script`" is a failure to report, not a
|
||||
* reason to skip (see `probeScriptVersion`).
|
||||
*
|
||||
* @param {object} [opts]
|
||||
* @param {string} [opts.platform]
|
||||
* @param {() => { available: boolean, output: string }} [opts.probe]
|
||||
* @returns {{ ok: true, flavor: string, wrap: (spec: { command: string, args?: string[] }) => { command: string, args: string[] } }
|
||||
* | { ok: false, reason: string }}
|
||||
* @throws {Error} when the probe timed out.
|
||||
*/
|
||||
export function resolvePtyWrapper({
|
||||
platform = process.platform,
|
||||
|
|
|
|||
Loading…
Reference in a new issue