mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-20 20:00:45 +02:00
fix(browse): honest probe budget, bounded daemon log, single refusal source, liveness + reinstall coverage
Five hardening items in the browse CLI and its tests: - probeHealthWithBackoff's advertised ~8s budget could really run ~10s: the final 2s probe could start 1ms before the deadline, and every call site had JUST run a failed probe yet the loop re-probed immediately. Iterations now start with the sleep and each probe's timeout clamps to the remaining budget (isServerHealthy takes an injectable timeout). - browse-daemon.log is append-mode across every respawn with no size cap, so a crash-respawn loop fills the disk. The path is now built in one place (daemonLogPath — the Unix fd path and the Windows launcher string had two spellings) and daemon start rotates a >10MB log to browse-daemon.log.1, single generation, matching the repo's 10MB rotation convention. Rotation is exported + injectable and behaviorally unit-tested. - The two "healthy daemon already running" refusal blocks in connect had already drifted (one lost the tabs/cookies/logins explainer) — extracted refuseHeadedOverLiveDaemon as the single source. - process-liveness: pinned the EPERM-means-alive contract (PID 1 on POSIX, PID 4 on Windows — signalable-or-EPERM, both alive). A probe that reads EPERM as dead is the false negative that leaked agents. - runBoundedChromiumReinstall had zero coverage: now exercised end-to-end against a stub bunx on a prepended PATH — exit 0, install-exit-N with stderr tail, the detached group-kill timeout path (child of the child dies too), and spawn-error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
412ad5c1f9
commit
aa6c73821f
@@ -40,10 +40,50 @@ describe('#2461 daemon log wiring', () => {
|
||||
|
||||
test('log sink is append-mode (accumulates across respawns)', () => {
|
||||
const cli = SRC('cli.ts');
|
||||
expect(cli).toMatch(/openSync\(path\.join\(config\.stateDir, 'browse-daemon\.log'\), 'a'\)/);
|
||||
// Both spawn paths open through the single daemonLogPath() source (M4),
|
||||
// which itself must build from the state dir.
|
||||
expect(cli).toMatch(/openSync\(daemonLogPath\(\), 'a'\)/);
|
||||
expect(cli).toMatch(/path\.join\(config\.stateDir, 'browse-daemon\.log'\)/);
|
||||
expect(cli).toMatch(/openSync\(\$\{daemonLogPathStr\},'a'\)/);
|
||||
});
|
||||
|
||||
test('append-mode log is growth-bounded: rotated at 10MB before daemon start', () => {
|
||||
const cli = SRC('cli.ts');
|
||||
expect(cli).toMatch(/DAEMON_LOG_MAX_BYTES = 10 \* 1024 \* 1024/);
|
||||
expect(cli).toMatch(/rotateDaemonLogIfOversized\(\);/);
|
||||
// Single generation: rename to .1, matching the repo's 10MB conventions.
|
||||
expect(cli).toMatch(/renameSync\(p, `\$\{p\}\.1`\)/);
|
||||
});
|
||||
|
||||
test('rotation behavior: oversized rotates to a single .1 generation, small/missing are no-ops', () => {
|
||||
const os = require('os');
|
||||
const { rotateDaemonLogIfOversized } = require('../src/cli');
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-daemon-log-'));
|
||||
try {
|
||||
const p = path.join(tmp, 'browse-daemon.log');
|
||||
// Missing log: no throw (first launch).
|
||||
rotateDaemonLogIfOversized(p, 1024);
|
||||
// Under the cap: untouched, no generation created.
|
||||
fs.writeFileSync(p, 'x'.repeat(10));
|
||||
rotateDaemonLogIfOversized(p, 1024);
|
||||
expect(fs.existsSync(p)).toBe(true);
|
||||
expect(fs.existsSync(`${p}.1`)).toBe(false);
|
||||
// Over the cap: rotated out of the way so the daemon starts fresh.
|
||||
fs.writeFileSync(p, 'y'.repeat(2048));
|
||||
rotateDaemonLogIfOversized(p, 1024);
|
||||
expect(fs.existsSync(p)).toBe(false);
|
||||
expect(fs.readFileSync(`${p}.1`, 'utf-8')).toContain('y');
|
||||
// Single generation: the next rotation REPLACES .1 (bounded at ~2x cap
|
||||
// total, never a .2).
|
||||
fs.writeFileSync(p, 'z'.repeat(2048));
|
||||
rotateDaemonLogIfOversized(p, 1024);
|
||||
expect(fs.readFileSync(`${p}.1`, 'utf-8')).toContain('z');
|
||||
expect(fs.existsSync(`${p}.2`)).toBe(false);
|
||||
} finally {
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('bun-polyfill routes Windows spawns through cross-spawn (ENOENT + cmd.exe injection fix)', () => {
|
||||
const polyfill = SRC('bun-polyfill.cjs');
|
||||
expect(polyfill).toContain("require('cross-spawn')");
|
||||
|
||||
Reference in New Issue
Block a user