test(#4523): migrate config/env/locking/perf batch to named timeout constants (#4673)

Batch 12 of the ad hoc timeout literal migration (epic #4445). Replaces
every bare numeric timeout/timeoutMs object-literal property in
tests/check-env.test.cjs, tests/config-get-default.test.cjs,
tests/federated-config.test.cjs,
tests/gsd-check-update-worker-platform-gate.test.cjs,
tests/gsd-mcp-server-bin.test.cjs, tests/health-validation.test.cjs,
tests/locking-bugs-1909-1916-1925-1927.test.cjs,
tests/perf-316-state-lock-buffer-alloc.test.cjs,
tests/perf-317-context-monitor-fs.test.cjs, and
tests/pi-config-dir-env-override.test.cjs with a named constant, per
eslint-rules/no-adhoc-timeout-literal.cjs. Removes these 10 files from the
rule's allowlist.

Ground truth via eslint matched the issue's stated 26 sites across 10 files
exactly. Reuses PROBE_TIMEOUT_MS, GENERATOR_SCRIPT_TIMEOUT_MS,
GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS, and INSTALL_TIMEOUT_MS across 4 files.
No new shared constants needed -- every recurring value across files was
independently verified to be a genuinely different operation class, per
this migration's standing rule that numeric coincidence is never identity.
Adds 11 new file-local constants, three of which are not real subprocess
timeouts at all (a config-merge fixture value, and two node:test per-test
timeout options bounding ReDoS/lock-retry regression backstops).

Per the issue's explicit mandate, gsd-check-update-worker-platform-gate.test.cjs
now imports (read-only) NPM_VIEW_TIMEOUT_MS from
gsd-core/bin/check-latest-version.cjs for disclosure -- this file and that
production module once independently guessed the same 15000ms value,
causing the PR #4428 Windows double-SIGKILL collision. No site in this
file's current bare literals actually wraps a live npm-view call needing
margin arithmetic, so the import documents the historical relationship
honestly rather than fabricating a computation. No src/bin file edited
(only a read-only import added), no numeric value changed anywhere.

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-09-12 21:33:26 -04:00
committed by GitHub
parent d1d9ee85a5
commit d6788a6805
11 changed files with 153 additions and 37 deletions

View File

@@ -1,24 +1,14 @@
[
"tests/assumption-delta-checkpoint-e2e.test.cjs",
"tests/assumption-delta.test.cjs",
"tests/check-env.test.cjs",
"tests/check-predicate.test.cjs",
"tests/cjs-command-router-adapter.test.cjs",
"tests/code-review-pipeline-regression.test.cjs",
"tests/code-review.test.cjs",
"tests/config-get-default.test.cjs",
"tests/dispatcher.test.cjs",
"tests/federated-config.test.cjs",
"tests/gate-predicate-evaluator.test.cjs",
"tests/gsd-check-update-worker-platform-gate.test.cjs",
"tests/gsd-mcp-server-bin.test.cjs",
"tests/gsd-statusline.test.cjs",
"tests/health-validation.test.cjs",
"tests/locking-bugs-1909-1916-1925-1927.test.cjs",
"tests/perf-316-state-lock-buffer-alloc.test.cjs",
"tests/perf-317-context-monitor-fs.test.cjs",
"tests/phase6-capstone-conformance.test.cjs",
"tests/pi-config-dir-env-override.test.cjs",
"tests/plan-phase-stall-detection.test.cjs",
"tests/plan-review-convergence.test.cjs",
"tests/policy-160-route0-resume.test.cjs",

View File

@@ -23,6 +23,7 @@ const assert = require('node:assert/strict');
const path = require('node:path');
const fs = require('node:fs');
const { spawnSync } = require('node:child_process');
const { GENERATOR_SCRIPT_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const SCRIPT = path.resolve(__dirname, '..', 'scripts', 'check-env.cjs');
const FIXTURE_ROOT = path.resolve(__dirname, 'fixtures', 'check-env');
@@ -41,7 +42,7 @@ function runScript(cwd, args = [], envOverrides = {}) {
const result = spawnSync(process.execPath, [SCRIPT, ...args], {
cwd,
encoding: 'utf8',
timeout: 30_000,
timeout: GENERATOR_SCRIPT_TIMEOUT_MS,
env: { ...process.env, ...envOverrides },
});
return {

View File

@@ -16,6 +16,14 @@ const path = require('path');
const os = require('os');
const { cleanup, captureFdSync } = require('./helpers.cjs');
/**
* A `config-get` CLI call with a deliberately pathological 50KB key,
* proving the call fails safely without hanging -- distinct from
* PROBE_TIMEOUT_MS's plain-query class (15000ms) despite the shared file,
* since this site is specifically testing pathological-input tolerance.
*/
const LONG_KEY_CLI_TIMEOUT_MS = 8000;
// In-process invocation, not execFileSync: cmdConfigGet is a pure CJS
// function reachable without spawning `node` as a child. The prior
// execFileSync(..., { timeout: 5000 }) raced a real subprocess's startup
@@ -767,6 +775,7 @@ const { execFileSync } = require('node:child_process');
const GSD_TOOLS = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs');
const { ERROR_REASON } = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'io.cjs'));
const { cleanup } = require('./helpers.cjs');
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
describe('bug-2943: config-get returns schema default for context_window', () => {
let tmpDir;
@@ -798,7 +807,7 @@ describe('bug-2943: config-get returns schema default for context_window', () =>
stdout = execFileSync(process.execPath, args, {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 15000,
timeout: PROBE_TIMEOUT_MS,
});
} catch (err) {
exitCode = err.status ?? 1;
@@ -1108,7 +1117,7 @@ test('config-get with a very long key (50KB) fails safely without hanging', (t)
const projectDir = createTempProject('cli-neg-config-13-');
t.after(() => cleanup(projectDir));
const longKey = 'x'.repeat(50000);
const result = runCli(['config-get', longKey], { cwd: projectDir, timeoutMs: 8000 });
const result = runCli(['config-get', longKey], { cwd: projectDir, timeoutMs: LONG_KEY_CLI_TIMEOUT_MS });
assert.equal(result.signal, null, 'long input must not trigger the harness timeout');
assert.equal(result.hasStackTrace, false, 'long input must not crash');
assert.notEqual(result.status, 0, 'unknown 50KB key must fail');

View File

@@ -67,6 +67,15 @@ const neverCentral = (_key) => false;
/** An always-central isCentralKey. */
const alwaysCentral = (_key) => true;
/**
* NOT a subprocess spawn timeout. Fixture DATA -- the test value for a
* fictional `mytool.timeout` config key in a mergeFederatedConfig()
* schema-merge test, proving a user-supplied override for a numeric config
* key is honored. Never passed into a real spawnSync/execFileSync options
* object.
*/
const FIXTURE_CONFIG_TIMEOUT_VALUE = 99;
// ─── 1. Empty configSchema ────────────────────────────────────────────────────
describe('empty configSchema', () => {
@@ -279,9 +288,9 @@ describe('valid federated key — user override with correct type', () => {
const result = mergeFederatedConfig({
configSchema: { 'mytool.timeout': NUMBER_SLICE },
isCentralKey: neverCentral,
userConfig: { mytool: { timeout: 99 } },
userConfig: { mytool: { timeout: FIXTURE_CONFIG_TIMEOUT_VALUE } },
});
assert.strictEqual(result.values['mytool.timeout'], 99);
assert.strictEqual(result.values['mytool.timeout'], FIXTURE_CONFIG_TIMEOUT_VALUE);
assert.deepEqual(result.warnings, []);
});

View File

@@ -29,6 +29,35 @@ const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { scanFencedBlocks } = require('../gsd-core/bin/lib/markdown-sectionizer.cjs');
const { NPM_VIEW_TIMEOUT_MS } = require('../gsd-core/bin/check-latest-version.cjs');
/**
* NOT a margin above NPM_VIEW_TIMEOUT_MS (imported above for disclosure,
* per this batch's epic issue -- this file and check-latest-version.cjs
* once independently guessed the same 15000ms value, causing a Windows
* double-SIGKILL collision fixed in PR #4428). This specific scenario
* (missing compiled runtime library) degrades BEFORE reaching the wrapped
* `npm view` call at all -- see this test's own #3582 comment
* ("checkLatestVersion -> not ok" on the degrade, never invoked) -- so its
* bound has no margin relationship to NPM_VIEW_TIMEOUT_MS to preserve.
* Kept as its own independent, pre-existing value.
*/
const WORKER_DEGRADED_PATH_TIMEOUT_MS = 8000;
/**
* gsd-update-banner.js only reads a small cache file from disk and prints
* JSON -- no subprocess/network work -- so this leaves generous headroom
* over its sub-second worst case even on a contended CI runner. NOT
* related to NPM_VIEW_TIMEOUT_MS or WORKER_DEGRADED_PATH_TIMEOUT_MS above
* (a different hook script entirely).
*/
const UPDATE_BANNER_HOOK_TIMEOUT_MS = 10_000;
// Imported for disclosure only (see WORKER_DEGRADED_PATH_TIMEOUT_MS's own
// doc comment above): confirms this file references the collision-prone
// constant from check-latest-version.cjs without deriving any of this
// file's own timeout values from it.
void NPM_VIEW_TIMEOUT_MS;
const WORKER_PATH = path.join(__dirname, '..', 'hooks', 'gsd-check-update-worker.js');
const PROJECTION_PATH = path.join(
@@ -134,7 +163,7 @@ describe('worker delegates the npm spawn (does not re-open the gate, #498)', ()
};
const r = runHookSeam(path.join(cold.hooksDir, 'gsd-check-update-worker.js'), [], {
env,
timeoutMs: 8000,
timeoutMs: WORKER_DEGRADED_PATH_TIMEOUT_MS,
});
assert.equal(r.exitCode, 0, `worker must exit 0 on a build failure; stderr: ${r.stderr}`);
assert.ok(fs.existsSync(cacheFile), 'worker must still reach the end and write a cache record');
@@ -863,7 +892,7 @@ describe('gsd-update-banner.js end-to-end', () => {
// contended CI runner.
const r = seamRunHook(HOOK_PATH, [], {
env: { ...process.env, HOME: home, USERPROFILE: home },
timeoutMs: 10_000,
timeoutMs: UPDATE_BANNER_HOOK_TIMEOUT_MS,
});
return { status: r.exitCode, stdout: r.stdout, stderr: r.stderr };
}
@@ -1017,7 +1046,7 @@ describe('gsd-update-banner.js: #3582 cold tree — degrades to silent, never cr
const r = seamRunHook(path.join(cold.hooksDir, 'gsd-update-banner.js'), [], {
env: { ...process.env, HOME: home, USERPROFILE: home },
timeoutMs: 10_000,
timeoutMs: UPDATE_BANNER_HOOK_TIMEOUT_MS,
});
assert.equal(r.exitCode, 0, `hook must exit 0 on a build failure; stderr: ${r.stderr}`);

View File

@@ -11,6 +11,7 @@ const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const path = require('node:path');
const { PROTOCOL_VERSION } = require('../gsd-core/bin/lib/mcp-server.cjs');
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const SHIM = path.join(__dirname, '..', 'bin', 'gsd-mcp-server.js');
@@ -18,7 +19,7 @@ function run(stdin) {
return spawnSync(process.execPath, [SHIM], {
input: stdin,
encoding: 'utf-8',
timeout: 15000,
timeout: PROBE_TIMEOUT_MS,
env: { ...process.env, GSD_TEST_MODE: '1' },
});
}

View File

@@ -1492,6 +1492,19 @@ const {
buildNotStartedPhaseVariants,
} = require('../gsd-core/bin/lib/validate.cjs');
/**
* NOT a subprocess spawn timeout. This is `node:test`'s own per-test
* `{ timeout }` option (the second positional argument to `test(name,
* options, fn)`), used as a hang BACKSTOP for two ReDoS (#663) adversarial-
* input regression tests -- catches any regression that re-introduces a
* catastrophically-backtracking pattern. Coincides numerically with
* tests/helpers/timeouts.cjs's SCAN_USAGE_ERROR_TIMEOUT_MS and
* MALFORMED_INPUT_HOOK_TIMEOUT_MS but is a completely different mechanism
* (a node:test option, not a subprocess spawn bound) -- disclosed, not
* merged.
*/
const REDOS_BACKSTOP_TEST_TIMEOUT_MS = 5000;
// ─── Part A: behavior preservation ───────────────────────────────────────────
describe('buildRoadmapPhaseVariants — behavior preservation (#663)', () => {
@@ -1588,7 +1601,7 @@ describe('buildRoadmapPhaseVariants — ReDoS adversarial fixture (#663)', () =>
// yields NO match (empty roadmapPhases Set) — the correct behavior when the
// terminating colon is absent. The { timeout: 5000 } backstop catches any
// regression that re-introduces a slow pattern.
test('malformed heading without colon yields no phase match (adversarial input)', { timeout: 5000 }, () => {
test('malformed heading without colon yields no phase match (adversarial input)', { timeout: REDOS_BACKSTOP_TEST_TIMEOUT_MS }, () => {
const pathological = '## Phase a' + '-a'.repeat(32) + ' ';
const { roadmapPhases } = buildRoadmapPhaseVariants(pathological);
assert.strictEqual(roadmapPhases.size, 0,
@@ -1601,7 +1614,7 @@ describe('buildNotStartedPhaseVariants — ReDoS adversarial fixture (#663)', ()
// A checklist-style line with many "-a" segments and no colon triggers the
// same catastrophic backtracking. Assert the correct structured result:
// the malformed line yields an empty notStarted Set.
test('malformed unchecked-item without terminator yields no phase match (adversarial input)', { timeout: 5000 }, () => {
test('malformed unchecked-item without terminator yields no phase match (adversarial input)', { timeout: REDOS_BACKSTOP_TEST_TIMEOUT_MS }, () => {
// No trailing colon or whitespace: the regex terminator [:\s*] cannot match,
// so the engine must backtrack through all '-a' repetitions and conclude no
// match. Under the old nested-quantifier pattern this was exponential;

View File

@@ -23,6 +23,14 @@ const { spawn } = require('child_process');
const { runGsdTools, createTempProject, cleanup, waitFor, TOOLS_PATH } = require('./helpers.cjs');
/**
* NOT a subprocess spawn timeout. This is the file's own `waitFor` busy-
* poll helper's giving-up bound, waiting for two racing subprocesses to
* each write a "ready" sentinel file before the test drops their shared
* synchronization barrier.
*/
const BARRIER_WAIT_TIMEOUT_MS = 10000;
// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────
@@ -234,7 +242,7 @@ describe('#1925 TOCTOU: state commands use readModifyWriteStateMd', () => {
// ── Orchestrate: wait for both ready-signals, then drop the barrier ───────
try {
await waitFor(() => fs.existsSync(readyA) && fs.existsSync(readyB), {
timeoutMs: 10000,
timeoutMs: BARRIER_WAIT_TIMEOUT_MS,
stepMs: 10,
message: 'Timed out waiting for both subprocesses to reach barrier',
});
@@ -373,7 +381,7 @@ describe('#1925 TOCTOU: state commands use readModifyWriteStateMd', () => {
// ── Orchestrate: wait for both ready-signals, then drop the barrier ───────
try {
await waitFor(() => fs.existsSync(readyA) && fs.existsSync(readyB), {
timeoutMs: 10000,
timeoutMs: BARRIER_WAIT_TIMEOUT_MS,
stepMs: 10,
message: 'Timed out waiting for both subprocesses to reach barrier',
});
@@ -501,7 +509,7 @@ describe('#1927 config.json: setConfigValue must hold planning lock', () => {
// ── Wait for both to reach barrier, then release ──────────────────────────
try {
await waitFor(() => fs.existsSync(readyA) && fs.existsSync(readyB), {
timeoutMs: 10000,
timeoutMs: BARRIER_WAIT_TIMEOUT_MS,
stepMs: 10,
message: 'Timed out waiting for both config-set subprocesses to reach barrier',
});

View File

@@ -38,6 +38,17 @@ const { cleanup } = require('./helpers.cjs');
// Constants
// ─────────────────────────────────────────────────────────────────────────────
/**
* NOT a subprocess spawn timeout. This is `node:test`'s own per-test
* `{ timeout }` option (the second positional argument to `test(name,
* options, fn)`), bounding a worker_threads + SharedArrayBuffer
* lock-contention regression test (sabCount retry assertion). Coincides
* numerically with tests/helpers/timeouts.cjs's PROBE_TIMEOUT_MS but is a
* completely different mechanism (a node:test option, not a subprocess
* spawn bound) -- disclosed, not merged.
*/
const LOCK_RETRY_TEST_TIMEOUT_MS = 15000;
const STATE_CJS_PATH = path.join(
__dirname, '..', 'gsd-core', 'bin', 'lib', 'state.cjs'
);
@@ -193,7 +204,7 @@ describe('perf #316: acquireStateLock hoists sleep buffer — exactly one SAB pe
test(
'sabCount === 1 after a call that undergoes >= 1 retry (post-fix assertion)',
{ timeout: 15000 },
{ timeout: LOCK_RETRY_TEST_TIMEOUT_MS },
async () => {
// ── Worker A: hold the lock until the writer signals contention ─────────
// The holder releases the lock only when the writer signals its first

View File

@@ -27,10 +27,42 @@ const os = require('node:os');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const { cleanup } = require('./helpers.cjs');
const { GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const MONITOR_PATH = path.join(__dirname, '..', 'hooks', 'gsd-context-monitor.js');
const tmpDir = os.tmpdir();
/**
* A plain single-invocation query-style spawn of hooks/gsd-context-monitor.js
* via the file's MONITOR_PATH constant -- no fan-out, no detached child
* writer triggered.
*/
const CONTEXT_MONITOR_QUERY_TIMEOUT_MS = 5000;
/**
* The #1974 fold: this hook invocation additionally triggers a DETACHED
* `state record-session` writer internally -- heavier than
* CONTEXT_MONITOR_QUERY_TIMEOUT_MS's plain-query class despite the
* coincidentally-matching value with sites in other files this batch.
*/
const CONTEXT_MONITOR_CRITICAL_RECORD_TIMEOUT_MS = 10000;
/**
* A tighter bound chosen for one specific quick-probe scenario in this
* file -- distinct tier from the other three context-monitor constants in
* this file, not equalized without bench data.
*/
const CONTEXT_MONITOR_QUICK_PROBE_TIMEOUT_MS = 4000;
/**
* The #2289/#3709 folds: a hook spawn sometimes carrying an extra
* `--require <preload-script>` flag ahead of the hook path itself --
* still one process, no fan-out. Coincides numerically with other files'
* timeout constants in this same batch (a CLI pathological-input query, a
* worker's degraded fast path) but describes neither -- kept local.
*/
const CONTEXT_MONITOR_PRELOAD_HOOK_TIMEOUT_MS = 8000;
/**
* Spawn the context-monitor hook with the given options.
*
@@ -100,7 +132,7 @@ function runMonitorRaw(opts) {
stdout = execFileSync(process.execPath, [MONITOR_PATH], {
input,
encoding: 'utf-8',
timeout: 5000,
timeout: CONTEXT_MONITOR_QUERY_TIMEOUT_MS,
});
} catch (e) {
exitCode = e.status ?? 1;
@@ -220,7 +252,7 @@ describe('perf #317: config.json absent (exercises config-missing → defaults p
stdout = execFileSync(process.execPath, [MONITOR_PATH], {
input: JSON.stringify({ session_id: sessionId, cwd: testCwd, hook_event_name: 'PostToolUse' }),
encoding: 'utf-8',
timeout: 5000,
timeout: CONTEXT_MONITOR_QUERY_TIMEOUT_MS,
});
} catch (e) {
exitCode = e.status ?? 1;
@@ -395,7 +427,7 @@ function runHook(sessionId, remainingPct, cwd) {
const result = runHookSeam(HOOK_PATH, [], {
input,
timeoutMs: 10000,
timeoutMs: CONTEXT_MONITOR_CRITICAL_RECORD_TIMEOUT_MS,
env: { ...process.env, HOME: process.env.HOME },
});
@@ -412,7 +444,7 @@ function runRecordSession(cwd, stoppedAt) {
const result = spawnSync(
process.execPath,
[GSD_TOOLS, 'state', 'record-session', '--stopped-at', stoppedAt, '--cwd', cwd],
{ encoding: 'utf-8', timeout: 30000 }
{ encoding: 'utf-8', timeout: GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS }
);
return {
exitCode: result.status,
@@ -652,7 +684,7 @@ function runStatuslineHook(remainingPct, totalTokens = 1_000_000, acwEnv = null)
execFileSync(process.execPath, [HOOK_PATH], {
input: payload,
env,
timeout: 4000,
timeout: CONTEXT_MONITOR_QUICK_PROBE_TIMEOUT_MS,
});
} catch { /* non-zero exit is fine; we only need the bridge file */ }
@@ -685,7 +717,7 @@ function runMonitorHook(remainingPct, usedPct) {
stdout = execFileSync(process.execPath, [MONITOR_PATH], {
input,
encoding: 'utf-8',
timeout: 5000,
timeout: CONTEXT_MONITOR_QUERY_TIMEOUT_MS,
});
} catch (e) {
stdout = e.stdout || '';
@@ -843,7 +875,7 @@ function runMonitor({ hookEventName, sessionId, remainingPct = 30, usedPct = 70,
stdout = execFileSync(process.execPath, [MONITOR_PATH], {
input: JSON.stringify(payload),
encoding: 'utf-8',
timeout: 5000,
timeout: CONTEXT_MONITOR_QUERY_TIMEOUT_MS,
env: { ...process.env, ...env },
});
} catch (e) {
@@ -1069,7 +1101,7 @@ function runMonitor(opts) {
input: JSON.stringify(payload),
env,
encoding: 'utf8',
timeout: 8000,
timeout: CONTEXT_MONITOR_PRELOAD_HOOK_TIMEOUT_MS,
});
} catch (e) {
stdout = e.stdout || '';
@@ -1359,7 +1391,7 @@ describe('#3709 context-monitor: PreCompact resets the warn sentinel', () => {
stdout = execFileSync(process.execPath, argv, {
input: JSON.stringify({ session_id: id, cwd, hook_event_name: event }),
encoding: 'utf8',
timeout: 8000,
timeout: CONTEXT_MONITOR_PRELOAD_HOOK_TIMEOUT_MS,
env,
});
} catch (e) { stdout = e.stdout || ''; exitCode = e.status ?? 1; }
@@ -2302,7 +2334,7 @@ describe('#3709 round 3: DEBOUNCE_CALLS and STALE_SECONDS at their limits', () =
stdout = execFileSync(process.execPath, ['--require', NOW_PRELOAD, HOOK], {
input: JSON.stringify({ session_id: id, cwd: os.tmpdir(), hook_event_name: 'PostToolUse' }),
encoding: 'utf8',
timeout: 8000,
timeout: CONTEXT_MONITOR_PRELOAD_HOOK_TIMEOUT_MS,
env: { ...process.env, GSD_TEST_NOW_MS: String(nowMs) },
});
} catch (e) { stdout = e.stdout || ''; exitCode = e.status ?? 1; }

View File

@@ -41,13 +41,26 @@ const { spawnSync } = require('node:child_process');
const { INSTALL_SCRIPT, installerEnv, BUILD_SCRIPT } = require('./helpers/install-shared.cjs');
const { cleanup, createTempDir } = require('./helpers.cjs');
const { INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
/**
* Spawns scripts/build-hooks.js (the same script BUILD_TIMEOUT_MS
* documents, at 30000ms) but at four times that constant's bound, used
* here as a prep step before this file's own real installer run below.
* Not equalized to the lighter shared norm without bench data -- the
* identical pattern (and identical value) to epic #4445 batch 9's
* file-local BUILD_HOOKS_UNDER_LOAD_TIMEOUT_MS in a different file
* (tests/emitted-attribution.test.cjs, not importable) -- disclosed as
* coincidence, not merged.
*/
const BUILD_HOOKS_PREP_TIMEOUT_MS = 120_000;
// hooks/dist is gitignored and built (DEFECT.HOOKS-DIST-SCOPED-CI): a scoped CI
// lane does not run build:hooks first, so a real --pi --global install there
// would emit no gsd-hooks/ dir. Build idempotently before spawning, exactly as
// the golden/emitted-attribution harnesses do.
function ensureHooksBuilt() {
spawnSync(process.execPath, [BUILD_SCRIPT], { encoding: 'utf-8', stdio: 'pipe', timeout: 120_000 });
spawnSync(process.execPath, [BUILD_SCRIPT], { encoding: 'utf-8', stdio: 'pipe', timeout: BUILD_HOOKS_PREP_TIMEOUT_MS });
}
/** Spawn the real installer for --pi --global against a fresh sandbox HOME,
@@ -57,7 +70,7 @@ function runPiGlobalInstall(home, extraEnv = {}) {
const result = spawnSync(process.execPath, [INSTALL_SCRIPT, '--pi', '--global'], {
cwd: home,
encoding: 'utf8',
timeout: 120_000,
timeout: INSTALL_TIMEOUT_MS,
env: installerEnv({ HOME: home, USERPROFILE: home, ...extraEnv }),
});
assert.strictEqual(result.status, 0,