Files
gstack/test/gbrain-source-worktree-advance.test.ts
Garry TanandClaude Fable 5 3e674e4c01 fix: sweep — every sync spawn in the test trees carries a timeout (436 sites, 157 files)
spawnSync/execSync/Bun.spawnSync BLOCK the main thread, so bun's in-process
per-test timeout can never fire while one waits — a hung child (stdin read,
network probe, dead daemon) wedges the whole shard until the runner's
external wall-clock SIGKILL. This exact class reached main: free-tests run
33262077256, test/gstack-memory-ingest.test.ts (normally 2.3s) held shard 2
at the 360s wall while its five siblings finished in ~65s.

Mechanical sweep in two waves (12 + 4 fan-out agents, every edit verified
against its call site): default timeout: 30_000 (matches the free runner's
per-test budget), 120_000 for genuinely slow ops (installs, builds,
playwright, provider CLIs), helper wrappers fixed ONCE where call sites
route through them. Sites that only LOOK like calls (string fixtures, grep
needles, comments) were skipped with reasons — the enforcement commit that
follows marks them exempt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 04:49:57 +00:00

159 lines
6.6 KiB
TypeScript

/**
* #2516: the brain worktree gbrain indexes must advance on the daily sync —
* and the unattended advance path must be SAFE: refuse dirty worktrees,
* refuse anything that is not a worktree of the artifacts repo, and never
* force-remove. (Pre-fix, the worktree only moved when setup-gbrain /
* sync-gbrain / brain-restore ran, so brains silently served stale pages.)
*/
import { describe, test as _test, expect, beforeEach, afterEach } from 'bun:test';
const test = (name: string, fn: any) => _test(name, fn, 30000);
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { spawnSync } from 'child_process';
const ROOT = path.resolve(import.meta.dir, '..');
const BIN = path.join(ROOT, 'bin');
let tmpHome: string;
function run(argv: string[], env: Record<string, string> = {}) {
const full = path.join(BIN, argv[0]);
const res = spawnSync(full, argv.slice(1), {
env: { ...process.env, HOME: tmpHome, GSTACK_HOME: tmpHome, ...env },
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
}
function git(args: string[], cwd: string) {
const res = spawnSync('git', args, { cwd, encoding: 'utf-8', timeout: 30_000 });
return { stdout: (res.stdout || '').trim(), status: res.status ?? -1 };
}
function commit(cwd: string, msg: string): string {
fs.appendFileSync(path.join(cwd, 'artifact.md'), `${msg}\n`);
git(['add', 'artifact.md'], cwd);
git(['commit', '-q', '-m', msg], cwd);
return git(['rev-parse', 'HEAD'], cwd).stdout;
}
const worktreePath = () => path.join(tmpHome, '.gstack-brain-worktree');
function makeArtifactsRepoWithWorktree(): { head: string } {
git(['init', '-q', '-b', 'main'], tmpHome);
git(['config', 'user.email', 't@t'], tmpHome);
git(['config', 'user.name', 't'], tmpHome);
const head = commit(tmpHome, 'seed');
git(['worktree', 'add', '--detach', worktreePath(), head], tmpHome);
return { head };
}
beforeEach(() => {
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'wtree-adv-home-'));
});
afterEach(() => {
fs.rmSync(tmpHome, { recursive: true, force: true });
});
describe('gstack-gbrain-source-wireup --advance-only (#2516)', () => {
test('advances a clean, behind worktree to the parent HEAD', () => {
makeArtifactsRepoWithWorktree();
const newHead = commit(tmpHome, 'second');
const r = run(['gstack-gbrain-source-wireup', '--advance-only']);
expect(r.status).toBe(0);
expect(r.stdout + r.stderr).toContain('advanced');
expect(git(['rev-parse', 'HEAD'], worktreePath()).stdout).toBe(newHead);
});
test('up-to-date worktree is a no-op', () => {
const { head } = makeArtifactsRepoWithWorktree();
const r = run(['gstack-gbrain-source-wireup', '--advance-only']);
expect(r.status).toBe(0);
expect(r.stdout + r.stderr).toContain('up-to-date');
expect(git(['rev-parse', 'HEAD'], worktreePath()).stdout).toBe(head);
});
test('REFUSES a dirty worktree — local changes are never advanced away', () => {
const { head } = makeArtifactsRepoWithWorktree();
commit(tmpHome, 'second');
fs.writeFileSync(path.join(worktreePath(), 'artifact.md'), 'local edit\n');
const r = run(['gstack-gbrain-source-wireup', '--advance-only']);
expect(r.status).toBe(0); // benign skip, not a hard failure
expect(r.stderr).toContain('local changes');
expect(git(['rev-parse', 'HEAD'], worktreePath()).stdout).toBe(head); // untouched
expect(fs.readFileSync(path.join(worktreePath(), 'artifact.md'), 'utf-8')).toBe('local edit\n');
});
test('REFUSES a path that is not a worktree of the artifacts repo', () => {
makeArtifactsRepoWithWorktree();
// A standalone user repo masquerading as the brain worktree.
const userRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'wtree-adv-user-'));
try {
git(['init', '-q', '-b', 'main'], userRepo);
git(['config', 'user.email', 't@t'], userRepo);
git(['config', 'user.name', 't'], userRepo);
const userHead = commit(userRepo, 'user work');
const r = run(['gstack-gbrain-source-wireup', '--advance-only'], {
GSTACK_BRAIN_WORKTREE: userRepo,
});
expect(r.status).toBe(0);
expect(r.stderr).toContain('not a worktree of');
expect(git(['rev-parse', 'HEAD'], userRepo).stdout).toBe(userHead); // untouched
} finally {
fs.rmSync(userRepo, { recursive: true, force: true });
}
});
test('missing worktree is a benign skip', () => {
git(['init', '-q', '-b', 'main'], tmpHome);
git(['config', 'user.email', 't@t'], tmpHome);
git(['config', 'user.name', 't'], tmpHome);
commit(tmpHome, 'seed');
const r = run(['gstack-gbrain-source-wireup', '--advance-only']);
expect(r.status).toBe(0);
expect(r.stderr).toContain('no managed worktree');
});
test('never contains a force-remove on the advance-only path (static pin)', () => {
// The unattended path must not be able to delete local worktree changes:
// do_advance_only may not call safe_rm_worktree, `worktree remove`, or rm -rf.
const src = fs.readFileSync(path.join(BIN, 'gstack-gbrain-source-wireup'), 'utf-8');
const fn = src.slice(src.indexOf('do_advance_only()'), src.indexOf('do_uninstall()'));
expect(fn.length).toBeGreaterThan(100);
expect(fn).not.toContain('safe_rm_worktree');
expect(fn).not.toContain('worktree remove');
expect(fn).not.toContain('rm -rf');
});
});
describe('brain-sync --once daily advance wiring (#2516)', () => {
test('once advances the worktree behind a 24h attempt stamp', () => {
makeArtifactsRepoWithWorktree();
run(['gstack-config', 'set', 'artifacts_sync_mode', 'artifacts-only']);
const second = commit(tmpHome, 'second');
const r1 = run(['gstack-brain-sync', '--once']);
expect(r1.status).toBe(0);
const stamp = path.join(tmpHome, '.brain-worktree-last-advance');
expect(fs.existsSync(stamp)).toBe(true);
expect(git(['rev-parse', 'HEAD'], worktreePath()).stdout).toBe(second);
// Within the 24h window: parent advances again, --once does NOT re-advance.
const third = commit(tmpHome, 'third');
const r2 = run(['gstack-brain-sync', '--once']);
expect(r2.status).toBe(0);
expect(git(['rev-parse', 'HEAD'], worktreePath()).stdout).toBe(second);
expect(git(['rev-parse', 'HEAD'], worktreePath()).stdout).not.toBe(third);
// Expire the stamp → the next --once advances again.
fs.writeFileSync(stamp, String(Math.floor(Date.now() / 1000) - 90000));
const r3 = run(['gstack-brain-sync', '--once']);
expect(r3.status).toBe(0);
expect(git(['rev-parse', 'HEAD'], worktreePath()).stdout).toBe(third);
});
});