mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 14:38:59 +02:00
- Tripwire scans execFileSync too (ceiling 8: two more grep-needle string exemptions); merge-introduced timeout-less spawnSync in question-preference-hook fixed — the tripwire caught a site that landed on main AFTER the sweep, on its first day. - gstack-detach gains TWO watchdog kill regression tests: TERM-immune grandchild (the killpg-after-grace escalation) and the leader-dies variant (the pgid-at-spawn fix — the case the first test cannot see). - eval-flake-rank gets its unit suite (final-attempt accounting, artifact exclusion, shard recursion, recency bound). - Groupkill/startup-grace shim markers are per-run unique (pid-suffixed sleep durations): sibling Conductor worktrees run free suites with no machine lock, and fixed markers let one run pgrep/pkill the other's shims — a cross-run flake inside the anti-flake tests. - flake-ledger test pins the project-scoped local default; stale empty section headers in touchfiles-data deleted (they invited entries under deliberately retired categories). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
3.0 KiB
TypeScript
65 lines
3.0 KiB
TypeScript
/**
|
|
* WS1 flake-ledger unit tests: the free runner's FLAKY-PASS events become a
|
|
* durable JSONL series (single writer: the parent runner). Fail-open is the
|
|
* contract — a broken ledger warns loudly but must never turn a real verdict
|
|
* into a failure on the only required lane.
|
|
*/
|
|
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { appendFlakeLedger, flakeLedgerPath, type FlakeLedgerEntry } from '../scripts/test-free-shards';
|
|
|
|
const entry = (file: string): FlakeLedgerEntry => ({
|
|
ts: '2026-08-31T00:00:00.000Z',
|
|
runner: 'free',
|
|
kind: 'flaky-pass',
|
|
file,
|
|
shard: 2,
|
|
});
|
|
|
|
describe('flake ledger', () => {
|
|
test('appends one JSONL line per entry, creating parent dirs', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'flake-ledger-'));
|
|
const ledger = path.join(dir, 'nested', 'ledger.jsonl');
|
|
expect(appendFlakeLedger([entry('test/a.test.ts')], ledger)).toBe(true);
|
|
expect(appendFlakeLedger([entry('test/b.test.ts'), entry('test/c.test.ts')], ledger)).toBe(true);
|
|
const lines = fs.readFileSync(ledger, 'utf-8').trim().split('\n');
|
|
expect(lines).toHaveLength(3);
|
|
expect(JSON.parse(lines[0])).toMatchObject({ runner: 'free', kind: 'flaky-pass', file: 'test/a.test.ts', shard: 2 });
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('empty entry list is a no-op success (no file created)', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'flake-ledger-'));
|
|
const ledger = path.join(dir, 'ledger.jsonl');
|
|
expect(appendFlakeLedger([], ledger)).toBe(true);
|
|
expect(fs.existsSync(ledger)).toBe(false);
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('FAIL-OPEN: an unwritable path warns and returns false, never throws', () => {
|
|
const warnings: string[] = [];
|
|
// A path whose parent is a FILE cannot be mkdir'd — deterministic EEXIST/ENOTDIR.
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'flake-ledger-'));
|
|
const blocker = path.join(dir, 'blocker');
|
|
fs.writeFileSync(blocker, 'not a dir');
|
|
const ledger = path.join(blocker, 'ledger.jsonl');
|
|
const ok = appendFlakeLedger([entry('test/a.test.ts')], ledger, (l) => warnings.push(l));
|
|
expect(ok).toBe(false);
|
|
expect(warnings).toHaveLength(1);
|
|
expect(warnings[0]).toContain('verdict unaffected');
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('env override wins; local default is project-scoped, never machine-global', () => {
|
|
expect(flakeLedgerPath({ GSTACK_FLAKE_LEDGER: '/x/y.jsonl' } as NodeJS.ProcessEnv)).toBe('/x/y.jsonl');
|
|
// Without the env override, the default lives under the PROJECT dir
|
|
// (sibling worktrees of different repos must not interleave one series);
|
|
// tmpdir is only the last-resort fallback when slug detection fails.
|
|
const local = flakeLedgerPath({} as NodeJS.ProcessEnv);
|
|
expect(local).toMatch(/flake-ledger\.jsonl$/);
|
|
expect(local.includes(path.join('.gstack', 'projects')) || local.startsWith(os.tmpdir())).toBe(true);
|
|
});
|
|
});
|