mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 17:04:27 +01:00
screen #3948: merge 1ee952242b onto main b3e9e5d32c
This commit is contained in:
commit
d7977bdeb0
4 files changed
+107
-9
No files matched your search
@@ -19,6 +19,8 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { hookChildBudgetMs } from '../hosts/claude/hooks/spawn-bin';
|
||||
import { launchSkipReason } from '../hosts/claude/hooks/question-log-hook.ts';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const HOOK = path.join(ROOT, 'hosts', 'claude', 'hooks', 'question-log-hook');
|
||||
@@ -394,3 +396,59 @@ describe('PostToolUse hook (crash safety)', () => {
|
||||
expect(fs.readFileSync(errLog, 'utf-8')).toContain('stdin parse failed');
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// One budget for every child (Claude Code kills the hook at 5 s)
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
describe('PostToolUse hook (budget)', () => {
|
||||
test('every child shares the registered 5 s budget, less the exit reserve', () => {
|
||||
expect(hookChildBudgetMs(1_000, 1_000)).toBe(4_500);
|
||||
expect(hookChildBudgetMs(1_000, 3_000)).toBe(2_500);
|
||||
expect(hookChildBudgetMs(1_000, 6_000)).toBeLessThan(0);
|
||||
});
|
||||
|
||||
test('a call launches only with at least 250 ms of budget left', () => {
|
||||
expect(launchSkipReason(4_500)).toBeNull();
|
||||
expect(launchSkipReason(250)).toBeNull();
|
||||
expect(launchSkipReason(249)).toBe("the hook's 5000 ms budget is spent (249 ms left, under the 250 ms a call needs)");
|
||||
expect(launchSkipReason(-40)).toBe("the hook's 5000 ms budget is spent (0 ms left, under the 250 ms a call needs)");
|
||||
});
|
||||
|
||||
test('slow children stop at the hook budget and the skipped questions are logged', () => {
|
||||
const fakeBin = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-slow-bun-'));
|
||||
try {
|
||||
fs.writeFileSync(path.join(fakeBin, 'bun'), `#!/bin/sh\nsleep 5\nexec "${process.execPath}" "$@"\n`, { mode: 0o755 });
|
||||
const env: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(process.env)) {
|
||||
if (v !== undefined) env[k] = v;
|
||||
}
|
||||
env.GSTACK_STATE_ROOT = stateRoot;
|
||||
delete env.GSTACK_HOME;
|
||||
env.GSTACK_QUESTION_LOG_NO_DERIVE = '1';
|
||||
env.PATH = `${fakeBin}${path.delimiter}${env.PATH ?? ''}`;
|
||||
const questions = Array.from({ length: 6 }, (_, i) => ({ question: `Budget question ${i}`, options: ['A', 'B'] }));
|
||||
const res = spawnSync(process.execPath, [path.join(ROOT, 'hosts', 'claude', 'hooks', 'question-log-hook.ts')], {
|
||||
env,
|
||||
input: JSON.stringify({
|
||||
session_id: 'budget',
|
||||
tool_name: 'AskUserQuestion',
|
||||
tool_use_id: 'tu-budget',
|
||||
tool_input: { questions },
|
||||
tool_response: { answers: questions.map(() => ({ option_label: 'A' })) },
|
||||
cwd: ROOT,
|
||||
}),
|
||||
encoding: 'utf-8',
|
||||
cwd: ROOT,
|
||||
timeout: 30_000,
|
||||
});
|
||||
expect(res.status).toBe(0);
|
||||
expect(readLog().length).toBeLessThan(questions.length);
|
||||
const errors = fs.readFileSync(path.join(stateRoot, 'hook-errors.log'), 'utf-8');
|
||||
expect(errors).toMatch(/exited null \(SIG\w+ after \d+ of \d+ ms\)/);
|
||||
expect(errors).toContain('budget is spent');
|
||||
} finally {
|
||||
fs.rmSync(fakeBin, { recursive: true, force: true });
|
||||
}
|
||||
}, 30_000);
|
||||
});
|
||||
Reference in new issue
Block a user