voyage/tests/helpers/hook-helper.mjs
Kjell Tore Guttormsen ed6699da58
refactor(simplify): one source per rule, shared test helpers, a faster isolation test [skip-docs]
/simplify over a97efb6..HEAD (the plan_version 1.9 / trial-4 delivery).

- coordinator-contract: engine names come from engine-findings ENGINES;
  one markedMajorCodeReview predicate under both lowering checks;
  isEngineFinding everywhere; the unreachable branch (c) of
  illegalBriefLowering folded into (e).
- armB workflow: RULE_KEYS = Object.keys(RULE_TIERS); the engine map is a
  literal pinned equal to ENGINES by the workflow test.
- plan-schema: usesFilesGrammar() is the one 1.9 check (validator, sweep,
  testFirstFiles, stepTestsOnlyFromFields); opensEntry built on opensChunk.
- plan-validator: one issue builder for the Files and Test first grammar.
- plan-scope-sweep: the 1.9 and the 6.2.0 reader are one if/else.
- artifact-commit: TRAILER_RE/trailerProblem no longer exported (no
  importer); the order comment says the CLI finds the repo root first.
- tests: the lock drivers in session-id-isolation (d) run side by side per
  mode (~100 s -> ~30 s); a shared artifact-repo helper; severityRank and
  extractFindingsBlock reused; childEnv(env) without a spread; one copy of
  the PLAN_VERSION_CURRENT check.

Plan readers: validator errors/warnings, plannedPaths, fieldPaths,
testFirstFiles and stepTestsOnlyFromFields compared byte for byte against
HEAD over 510 plans / 1692 steps plus 210 synthetic 1.7-1.9 cases: same.
Suite 2384 (2382/0/2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:06:47 +02:00

56 lines
2.3 KiB
JavaScript

// hook-helper.mjs — Shared test helper for hook scripts.
// Spawns a hook as a child process and feeds it JSON via stdin.
//
// Source: ../../../llm-security/tests/hooks/hook-helper.mjs (copied; since order 10
// the kill limit is HOOK_TIMEOUT_MS and a call can set its own)
// Provenance: borrowed within the same marketplace (same author, MIT).
import { execFile } from 'node:child_process';
import { childEnv } from './child-env.mjs';
// How long a hook may run before the helper kills it. The tests check a
// hook's answer, not its speed (SC #14 times itself, tests/helpers/timing.mjs),
// so the limit only has to catch a hook that hangs. 5 s killed P7
// (post-edit-check) at load 95-177 in four wave sessions while it was 8/8
// green alone (order 10, 2026-10-09). hooks/hooks.json sets no timeout for
// these hooks, so 5 s was stricter than anything the hook meets in use.
export const HOOK_TIMEOUT_MS = 30_000;
/**
* Run a hook script by spawning `node <scriptPath>` and piping `input` to stdin.
*
* @param {string} scriptPath - Absolute path to the hook .mjs file
* @param {object|string} input - JSON payload (object will be stringified)
* @returns {Promise<{ code: number, stdout: string, stderr: string }>}
*/
export function runHook(scriptPath, input) {
return runHookWithEnv(scriptPath, input, {});
}
/**
* Run a hook script with custom environment variables.
*
* @param {string} scriptPath - Absolute path to the hook .mjs file
* @param {object|string} input - JSON payload (object will be stringified)
* @param {Record<string, string>} envOverrides - Extra env vars to set
* @param {{ timeoutMs?: number }} [opts] - kill limit for this call (default HOOK_TIMEOUT_MS)
* @returns {Promise<{ code: number, stdout: string, stderr: string }>}
*/
export function runHookWithEnv(scriptPath, input, envOverrides, opts = {}) {
return new Promise((resolve) => {
const env = childEnv(envOverrides);
const child = execFile(
'node',
[scriptPath],
{ timeout: opts.timeoutMs ?? HOOK_TIMEOUT_MS, env },
(err, stdout, stderr) => {
resolve({
code: child.exitCode ?? (err && err.code === 'ERR_CHILD_PROCESS_STDIO_FINAL' ? 0 : 1),
stdout: stdout || '',
stderr: stderr || '',
});
}
);
child.stdin.end(typeof input === 'string' ? input : JSON.stringify(input));
});
}