mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 06:28:59 +02:00
test(wireup): make gbrain-missing PATH fixture hermetic
The gbrain-missing test appended the host PATH (and a hardcoded /opt/homebrew/bin) to the fixture PATH, so on any machine with a real gbrain installed the 'missing' case saw it, exited 0 instead of 2, and could never fail where the bug exists — a false green for a whole machine class. The fixture now keeps only root-owned OS dirs on the child PATH, and a new determinism check plants a host-like gbrain to prove it is unreachable. Absorbed from PR #2615 with authorship preserved; the PR-thread liveness screenshot (docs/images/gstack-pr-liveness-2255.png) is dropped — referenced by nothing in the tree. Fixes #2255 Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
committed by
Garry Tan
co-authored by
CommandCodeBot
parent
85fd9db554
commit
2010b345e6
@@ -20,6 +20,18 @@ const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const BIN_DIR = path.join(ROOT, 'bin');
|
||||
const WIREUP_BIN = path.join(BIN_DIR, 'gstack-gbrain-source-wireup');
|
||||
|
||||
// Hermetic PATH base (#2255). The missing-gbrain fixtures must not see a
|
||||
// user-installed gbrain on the host (e.g. macOS /opt/homebrew/bin), or the
|
||||
// "missing" case exits 0 instead of 2. Base is root-owned OS dirs only
|
||||
// (/usr/bin:/bin:/usr/sbin:/sbin — where git/python3/jq/coreutils resolve on
|
||||
// macOS and Linux) plus BUN_ONLY_DIR, so no user-installed gbrain can be
|
||||
// present. The scratch dir holds only a bun symlink, mirroring
|
||||
// gbrain-detect-install.test.ts so spawned children can resolve bun on CI
|
||||
// regardless of install dir.
|
||||
const BUN_ONLY_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'wireup-bun-only-'));
|
||||
fs.symlinkSync(process.execPath, path.join(BUN_ONLY_DIR, 'bun'));
|
||||
const HERMETIC_PATH = `/usr/bin:/bin:/usr/sbin:/sbin:${BUN_ONLY_DIR}`;
|
||||
|
||||
let tmpHome: string;
|
||||
let gstackHome: string;
|
||||
let worktreeDir: string;
|
||||
@@ -113,7 +125,7 @@ function run(
|
||||
opts: { env?: Record<string, string> } = {}
|
||||
) {
|
||||
const env = {
|
||||
PATH: `${fakeBinDir}:${process.env.PATH || '/usr/bin:/bin:/opt/homebrew/bin'}`,
|
||||
PATH: `${fakeBinDir}:${HERMETIC_PATH}`,
|
||||
HOME: tmpHome,
|
||||
GSTACK_HOME: gstackHome,
|
||||
GSTACK_BRAIN_WORKTREE: worktreeDir,
|
||||
@@ -229,14 +241,52 @@ describe('gstack-gbrain-source-wireup — wireup mode', () => {
|
||||
|
||||
test('--strict + gbrain missing on PATH: exits 2', () => {
|
||||
setupGstackRepo('git@github.com:user/gstack-brain-user.git');
|
||||
// Don't make a fake gbrain — fakeBinDir is empty. Keep system dirs on PATH
|
||||
// so basic commands (git, awk, sed, etc.) work; only `gbrain` is absent.
|
||||
const r = run(['--strict'], {
|
||||
env: { PATH: `${fakeBinDir}:/usr/bin:/bin:/opt/homebrew/bin` },
|
||||
});
|
||||
// Don't make a fake gbrain — fakeBinDir is empty. run() applies the
|
||||
// hermetic PATH base; only `gbrain` is absent.
|
||||
const r = run(['--strict']);
|
||||
expect(r.status).toBe(2);
|
||||
});
|
||||
|
||||
test('--strict + gbrain present in controlled dir: exits 0 (positive control)', () => {
|
||||
setupGstackRepo('git@github.com:user/gstack-brain-user.git');
|
||||
// Positive control for hermeticity: a gbrain stub in the test-controlled
|
||||
// fakeBinDir (first on the hermetic PATH) IS found, so --strict proceeds
|
||||
// (exit 0). This proves the fixture CAN supply gbrain when present; the
|
||||
// determinism test below proves the host cannot leak one in (#2255).
|
||||
makeFakeGbrain({});
|
||||
const r = run(['--strict'], { env: { GSTACK_BRAIN_NO_SYNC: '1' } });
|
||||
expect(r.status).toBe(0);
|
||||
expect(gbrainCalls().some((c) => c.startsWith('gbrain sources add'))).toBe(true);
|
||||
});
|
||||
|
||||
test('--strict + gbrain present in a host-like dir: still exits 2 (determinism)', () => {
|
||||
setupGstackRepo('git@github.com:user/gstack-brain-user.git');
|
||||
// Determinism check (#2255, plan TS2): plant a real-looking gbrain stub in
|
||||
// a dir that the OLD fixture would have leaked via process.env.PATH or the
|
||||
// hardcoded /opt/homebrew/bin list. The root-owned-only hermetic base
|
||||
// excludes user-writable dirs, so the child never sees the stub and the
|
||||
// missing case stays deterministic across dev machines. This test fails on
|
||||
// the unpatched fixture (stub found -> exit 0) and passes on the fixed one.
|
||||
const hostLikeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'wireup-host-like-'));
|
||||
fs.writeFileSync(
|
||||
path.join(hostLikeDir, 'gbrain'),
|
||||
'#!/bin/bash\necho "gbrain 0.18.2"\n',
|
||||
{ mode: 0o755 }
|
||||
);
|
||||
// No env PATH override: run() applies the hermetic base. The stub exists
|
||||
// only in a user-writable dir the base excludes.
|
||||
const r = run(['--strict']);
|
||||
expect(r.status).toBe(2);
|
||||
// Sanity: the stub IS visible to a shell using the host-like PATH, so this
|
||||
// test would catch the old leak if the base ever regressed.
|
||||
const check = spawnSync('bash', ['-c', `command -v gbrain && gbrain --version`], {
|
||||
env: { PATH: `${hostLikeDir}:${process.env.PATH || '/usr/bin:/bin'}` },
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
expect(check.status).toBe(0);
|
||||
expect(check.stdout).toContain('gbrain 0.18.2');
|
||||
});
|
||||
|
||||
test('source-id derived from origin URL', () => {
|
||||
setupGstackRepo('git@github.com:user/gstack-brain-alice.git');
|
||||
makeFakeGbrain({});
|
||||
@@ -388,9 +438,7 @@ describe('gstack-gbrain-source-wireup — uninstall mode', () => {
|
||||
expect(fs.existsSync(worktreeDir)).toBe(true);
|
||||
// Now remove the fake gbrain so uninstall sees gbrain missing
|
||||
fs.rmSync(path.join(fakeBinDir, 'gbrain'), { force: true });
|
||||
const r = run(['--uninstall'], {
|
||||
env: { PATH: `${fakeBinDir}:/usr/bin:/bin:/opt/homebrew/bin` },
|
||||
});
|
||||
const r = run(['--uninstall']);
|
||||
expect(r.status).toBe(0); // best-effort, never fails on gbrain absence
|
||||
expect(fs.existsSync(worktreeDir)).toBe(false); // worktree still cleaned up
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user