Merge pull request #4566 from open-gsd/test/4514-batch3-git-adjacent

test(#4514): migrate git-adjacent workflow checks to named timeout constants
This commit is contained in:
Tom Boucher
2026-09-09 09:15:25 -04:00
committed by GitHub
9 changed files with 82 additions and 24 deletions

View File

@@ -21,7 +21,6 @@
"tests/check-glossary-refs.test.cjs",
"tests/check-predicate.test.cjs",
"tests/check-tdd-review-checkpoint-e2e.test.cjs",
"tests/ci-rebase-check.test.cjs",
"tests/cjs-command-router-adapter.test.cjs",
"tests/code-review-pipeline-regression.test.cjs",
"tests/code-review.test.cjs",
@@ -50,7 +49,6 @@
"tests/gsd-mcp-server-bin.test.cjs",
"tests/gsd-secret-read-guard.test.cjs",
"tests/gsd-statusline.test.cjs",
"tests/gsd-validate-commit-crash-policy.test.cjs",
"tests/gsd-write-guard.test.cjs",
"tests/health-validation.test.cjs",
"tests/hooks-commonjs-marker.test.cjs",
@@ -87,7 +85,6 @@
"tests/plan-review-convergence.test.cjs",
"tests/plugin-manifest.test.cjs",
"tests/policy-160-route0-resume.test.cjs",
"tests/pr-branch-planning-filter.test.cjs",
"tests/prohibition-enforcement.test.cjs",
"tests/prompt-injection-scan.security.test.cjs",
"tests/qa/tdd-walk.cjs",
@@ -95,7 +92,6 @@
"tests/read-guard.test.cjs",
"tests/read-injection-scanner.property.test.cjs",
"tests/read-injection-scanner.security.test.cjs",
"tests/reapply-verify-hunks.test.cjs",
"tests/release-tarball-smoke.install.test.cjs",
"tests/representative-corpus.test.cjs",
"tests/review-lane-invocation.test.cjs",
@@ -108,14 +104,11 @@
"tests/security.test.cjs",
"tests/shared-hooks-dir-resolution.test.cjs",
"tests/shell-command-projection-dispatch.test.cjs",
"tests/ship-notes-wedged-pr.test.cjs",
"tests/slug-derivation-drift-guard.test.cjs",
"tests/state-document.test.cjs",
"tests/state-todos-render.test.cjs",
"tests/task-command-router-resolve-content.test.cjs",
"tests/task-content-resolution.test.cjs",
"tests/task-content-resolver-grammar-parity.test.cjs",
"tests/teams-status.test.cjs",
"tests/windsurf-hooks-bridge.test.cjs",
"tests/worktree-safety.test.cjs"
"tests/windsurf-hooks-bridge.test.cjs"
]

View File

@@ -27,7 +27,16 @@ const NODE = process.execPath;
const { cleanup } = require('./helpers.cjs');
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
// #3145: class-norm timeout, not a per-suite value — see helpers/timeouts.cjs.
const { GIT_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const { GIT_TIMEOUT_MS, QUICK_SPAWN_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
/**
* The real ci-rebase-check.cjs script run below (fetch+merge against a real
* git fixture remote) is heavier than a QUICK_SPAWN_TIMEOUT_MS-class call —
* it does real git network/merge work, not a trivial mocked/in-process
* operation. Kept as its own pre-existing value, unchanged by this
* migration (#4514).
*/
const CI_REBASE_SCRIPT_TIMEOUT_MS = 20000;
// ---------------------------------------------------------------------------
// Helper: run a small inline Node snippet that requires the run() helper
@@ -54,7 +63,7 @@ function evalRunHelper(stmts) {
}
${stmts}
`;
const r = spawnSync(NODE, ['-e', code], { encoding: 'utf8', timeout: 10_000 });
const r = spawnSync(NODE, ['-e', code], { encoding: 'utf8', timeout: QUICK_SPAWN_TIMEOUT_MS });
return { status: r.status ?? 1, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
}
@@ -157,7 +166,7 @@ describe('ci-rebase-check: fetch-retry loop resolves when git fetch succeeds', (
const r = spawnSync(NODE, [SCRIPT], {
cwd: workDir,
encoding: 'utf8',
timeout: 20_000,
timeout: CI_REBASE_SCRIPT_TIMEOUT_MS,
env: {
...process.env,
GITHUB_BASE_REF: 'main',

View File

@@ -31,6 +31,17 @@ const path = require('node:path');
const { createTempDir, cleanup, TEST_ENV_BASE } = require('./helpers.cjs');
const { runHook, OUTCOME } = require('./helpers/process-seam.cjs');
/**
* gsd-validate-commit.sh is invoked via bash and internally fans out to
* nested `node -e` calls (the classifier/config-read/JSON-extraction
* sites this file's own header docblock describes) -- conceptually the
* same shape `HOOK_FANOUT_TIMEOUT_MS` (60000ms) describes, but this hook
* sits at a much lighter pre-existing value. Not reclassified/raised: no
* bench citation exists for whether this specific hook needs more.
* Pre-existing value, unchanged by this migration (#4514).
*/
const VALIDATE_COMMIT_HOOK_TIMEOUT_MS = 15000;
const HOOK_PATH = path.join(__dirname, '..', 'hooks', 'gsd-validate-commit.sh');
const CONFORMING_COMMIT_PAYLOAD = JSON.stringify({
@@ -94,7 +105,7 @@ function runValidateCommit({ payload, cwd, env } = {}) {
cwd,
env: { ...process.env, ...TEST_ENV_BASE, ...env },
input: payload,
timeoutMs: 15000,
timeoutMs: VALIDATE_COMMIT_HOOK_TIMEOUT_MS,
});
}

View File

@@ -110,6 +110,23 @@ const INSTALL_TIMEOUT_MS = 120000;
*/
const SEAM_DEFAULT_TIMEOUT_MS = 60000;
/**
* A cheap, lightweight subprocess/hook invocation whose own work is
* trivial -- a synchronous in-process guard hook, a fully-mocked shell
* harness (git/gh functions stubbed out), a small `node -e` snippet, or a
* lint script scanning a tiny temp fixture -- with no real git/network/
* fan-out work inside. 10000ms leaves generous headroom over each call
* site's sub-second observed worst case (#4514, batch 3 of the ad hoc
* timeout literal migration, epic #4445 -- consolidates 5 call sites
* across 4 files that had all independently arrived at this exact value).
*
* Deliberately NOT the same as `PROBE_TIMEOUT_MS` (15000ms): consolidating
* onto that would RAISE these sites' bound with no bench citation, which
* this migration's scope (naming, not tuning) does not permit. This norm
* is for the even-lighter end of the spectrum PROBE_TIMEOUT_MS occupies.
*/
const QUICK_SPAWN_TIMEOUT_MS = 10000;
module.exports = {
PROBE_TIMEOUT_MS,
HOOK_FANOUT_TIMEOUT_MS,
@@ -118,4 +135,5 @@ module.exports = {
BUILD_TIMEOUT_MS,
INSTALL_TIMEOUT_MS,
SEAM_DEFAULT_TIMEOUT_MS,
QUICK_SPAWN_TIMEOUT_MS,
};

View File

@@ -84,6 +84,14 @@ const CONFIG_SCHEMA_MANIFEST_PATH = path.join(
REPO_ROOT, 'gsd-core', 'bin', 'shared', 'config-schema.manifest.json',
);
/**
* The cherry-pick recipe below runs the REAL create_pr_branch loop
* extracted from pr-branch.md against a real git fixture (rev-list,
* checkout, cherry-pick) -- genuine git work, not a mocked/trivial
* operation. Pre-existing value, unchanged by this migration (#4514).
*/
const CHERRY_PICK_RECIPE_TIMEOUT_MS = 30000;
// ── Shared git-fixture primitives (L2 + L4) ────────────────────────────────
function git(args, cwd) {
@@ -379,7 +387,7 @@ describe('#2971 — pr-branch.md planning.pr_strict filter (failing-first)', ()
const filterPaths = strict ? ['.planning/'] : transientDirs.map((d) => `.planning/${d}/`);
const script = buildRecipeScript(filterPaths);
try {
const stdout = execFileSync('sh', ['-c', script], { cwd: repoDir, encoding: 'utf8', timeout: 30000 });
const stdout = execFileSync('sh', ['-c', script], { cwd: repoDir, encoding: 'utf8', timeout: CHERRY_PICK_RECIPE_TIMEOUT_MS });
return { status: 0, stdout, stderr: '' };
} catch (err) {
return {

View File

@@ -1099,6 +1099,12 @@ let savedHome;
let savedUserProfile;
let restoreConfigEnv;
/**
* Runs the real verify-reapply-patches.cjs CLI against patches+config
* fixture dirs. Pre-existing value, unchanged by this migration (#4514).
*/
const VERIFY_REAPPLY_TIMEOUT_MS = 60000;
function writeFile(absPath, content) {
fs.mkdirSync(path.dirname(absPath), { recursive: true });
fs.writeFileSync(absPath, content);
@@ -1110,7 +1116,7 @@ function runVerifier() {
'--patches-dir', patchesDir,
'--config-dir', configDir,
'--json',
], { timeoutMs: 60_000 });
], { timeoutMs: VERIFY_REAPPLY_TIMEOUT_MS });
return {
status: r.exitCode,
report: r.stdout && r.stdout.length ? JSON.parse(r.stdout) : null,
@@ -1238,6 +1244,14 @@ function resetFixture() {
fs.mkdirSync(pristineDir);
}
/**
* Runs the real verify-reapply-patches.cjs CLI against patches+config+
* pristine fixture dirs -- a distinct describe-block scope from the other
* runVerifier() above (different fixture, different pre-existing budget).
* Pre-existing value, unchanged by this migration (#4514).
*/
const VERIFY_REAPPLY_PRISTINE_TIMEOUT_MS = 30000;
function runVerifier() {
const r = runNode([
SCRIPT,
@@ -1245,7 +1259,7 @@ function runVerifier() {
'--config-dir', configDir,
'--pristine-dir', pristineDir,
'--json',
], { timeoutMs: 30_000 });
], { timeoutMs: VERIFY_REAPPLY_PRISTINE_TIMEOUT_MS });
return {
status: r.exitCode,
report: r.stdout && r.stdout.length ? JSON.parse(r.stdout) : null,

View File

@@ -5,6 +5,7 @@ const fs = require('node:fs');
const path = require('node:path');
const { stripFencedCode, scanFencedBlocks } = require('../gsd-core/bin/lib/markdown-sectionizer.cjs');
const { cleanup, createTempDir, readFileNormalized } = require('./helpers.cjs');
const { QUICK_SPAWN_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const SHIP_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'ship.md');
@@ -76,7 +77,7 @@ function runTrackShipping(responses) {
const result = spawnSync('bash', ['-c', `${preamble}\n${extractTrackShippingScript()}`], {
cwd: tmpDir,
encoding: 'utf8',
timeout: 10000,
timeout: QUICK_SPAWN_TIMEOUT_MS,
env: {
...process.env,
CURRENT_BRANCH: 'fix/ship-note',

View File

@@ -34,6 +34,7 @@ const { generateSlugInternal } = require(path.join(ROOT, 'gsd-core', 'bin', 'lib
const { getPhaseDirFromPhaseId } = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phase-id.cjs'));
const { slugify: qaSmellRatchetSlugify } = require(path.join(ROOT, 'scripts', 'qa-smell-ratchet.cjs'));
const { createTempDir, cleanup } = require('./helpers.cjs');
const { QUICK_SPAWN_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const { splitLines } = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'text-lines.cjs'));
const { MAX_REGEX_LITERAL_LEN, resetRegexScanStats, getRegexScanStats } = require(path.join(ROOT, 'scripts', 'lib', 'drift-scan.cjs'));
@@ -439,7 +440,7 @@ describe('CLI (main()) — the process.exitCode/stderr surface scanRepo alone do
const res = spawnSync(process.execPath, [path.join(tmpRoot, 'scripts', 'lint-slug-derivation-drift.cjs')], {
encoding: 'utf8',
timeout: 10000,
timeout: QUICK_SPAWN_TIMEOUT_MS,
});
assert.equal(res.status, 1, 'main() must set a non-zero process.exitCode when a violation is found');
@@ -470,7 +471,7 @@ describe('CLI (main()) — the process.exitCode/stderr surface scanRepo alone do
const res = spawnSync(process.execPath, [path.join(tmpRoot, 'scripts', 'lint-slug-derivation-drift.cjs')], {
encoding: 'utf8',
timeout: 10000,
timeout: QUICK_SPAWN_TIMEOUT_MS,
});
assert.equal(res.status, 0);

View File

@@ -4980,6 +4980,7 @@ const path = require('node:path');
const { cleanup } = require('./helpers.cjs');
const { runHook: seamRunHook } = require('./helpers/process-seam.cjs');
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
const { QUICK_SPAWN_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const HOOK_PATH = path.join(__dirname, '..', 'hooks', 'gsd-worktree-path-guard.js');
const INSTALL_SRC = path.join(__dirname, '..', 'bin', 'install.js');
@@ -5036,15 +5037,17 @@ function makeWorktree(mainRepo, branchName) {
* Run the hook with a given payload, returning the spawnSync result.
*/
function runHook(cwd, payload) {
// 10000ms: previously UNBOUNDED (no `timeout` option passed to spawnSync).
// gsd-worktree-path-guard.js is a synchronous, in-process path-guard hook
// (fs/path checks against a JSON stdin payload) — no subprocess or network
// work of its own. 10s leaves generous headroom over its sub-second
// worst case even on a heavily contended CI runner.
// QUICK_SPAWN_TIMEOUT_MS (10000ms): previously UNBOUNDED (no `timeout`
// option passed to spawnSync). gsd-worktree-path-guard.js is a
// synchronous, in-process path-guard hook (fs/path checks against a JSON
// stdin payload) — no subprocess or network work of its own. 10s leaves
// generous headroom over its sub-second worst case even on a heavily
// contended CI runner. See tests/helpers/timeouts.cjs for the shared
// norm this value was promoted to (#4514).
const r = seamRunHook(HOOK_PATH, [], {
cwd,
input: JSON.stringify(payload),
timeoutMs: 10_000,
timeoutMs: QUICK_SPAWN_TIMEOUT_MS,
});
return { status: r.exitCode, stdout: r.stdout, stderr: r.stderr };
}