train S2: merge #3804 at 7dd4185fe3 v1.92.78.0

This commit is contained in:
garrytan committed 2000-01-01 00:00:00 +00:00
commit be487a9ba2
21 files changed
+162 -26

No files matched your search

+2 -1
View File
@@ -111,7 +111,8 @@ test.each([
expect(facts.receipts.filter((receipt: any) => receipt.outcome === 'timeout')).toHaveLength(3 - scenario.passed);
expect(facts.receipts.every((receipt: any) => receipt.timeoutMs === 240_000 && !receipt.answered)).toBe(true);
expect(facts.active).toBe(0); expect(facts.answers).toBe(0); expect(facts.pendingTimers).toBe(0);
expect(facts.leftovers).toEqual([]); expect(facts.ownedStateLeftovers).toEqual([]);
expect(facts.leftovers).toEqual([]);
expect(facts.ownedStateLeftovers, JSON.stringify({ fixtures: facts.fixtures, inputs: facts.inputs.map((input: any) => [input.id, input.config, input.state]), events: facts.events.filter((event: any) => ['query-close', 'capture-settle', 'cleanup'].includes(event.kind)) })).toEqual([]);
expect(facts.events.filter((event: any) => event.kind === 'query-close')).toHaveLength(scenario.queries);
expect(facts.events.filter((event: any) => event.kind === 'cleanup')).toHaveLength(3);
}, 20_000);
+3 -1
View File
@@ -1326,7 +1326,9 @@ describe('Autoplan phase entry through the gbrain :user render (#2569)', () => {
edit(rewriteSectionBase(fs.readFileSync(path.join(ROOT, 'autoplan/sections', name), 'utf8'), linkRoot)));
return root;
}
function withEnv<T>(env: Record<string, string | undefined>, work: () => T): T {
/** The state-root chain the case does not name is cleared, so an ambient GSTACK_STATE_ROOT (every gstack launch file exports one) cannot pick the root. */
function withEnv<T>(given: Record<string, string | undefined>, work: () => T): T {
const env = { GSTACK_STATE_ROOT: undefined, GSTACK_STATE_DIR: undefined, CLAUDE_PLUGIN_DATA: undefined, ...given };
const previous = Object.fromEntries(Object.keys(env).map(k => [k, process.env[k]]));
for (const [k, v] of Object.entries(env)) if (v === undefined) delete process.env[k]; else process.env[k] = v;
try { return work(); }
+1 -1
View File
@@ -62,7 +62,7 @@ describe.skipIf(process.platform !== 'linux')('bootstrap paid-shard cleanup inte
fs.writeFileSync(path.join(root, 'controller.stdout.log'), controller.stdout ?? '', { mode: 0o600 });
fs.writeFileSync(path.join(root, 'controller.stderr.log'), controller.stderr ?? '', { mode: 0o600 });
expect(controller.error).toBeUndefined();
expect(controller.status).toBe(0);
expect(controller.status, `controller exited ${controller.status} (signal ${controller.signal}); stderr tail: ${(controller.stderr ?? '').slice(-600).replace(/\s*\n\s*/g, ' | ')}`).toBe(0);
const outcome = JSON.parse(fs.readFileSync(outcomePath, 'utf8'));
if (scenario === 'ack-failure') {
expect(controller.stdout).toContain('(fail) qa-bootstrap');
+25
View File
@@ -247,6 +247,31 @@ describe('probe lock recovery and failure', () => {
expect(calls(f)).toBe(1);
}));
test('a lock released while a waiter reads its age is not abandoned; the next owner\'s fresh lock survives', withFixture((f) => {
const sig = sigOf(f);
const lock = path.join(f.locks, sig);
const once = path.join(f.home, 'vanish-once');
const real = JSON.stringify(Bun.which('stat'));
fs.mkdirSync(lock, { recursive: true });
fs.writeFileSync(path.join(f.home, 'bin/stat'), `#!/usr/bin/env bash
if [ "$3" = "$RACE_LOCK" ] && [ -f "$RACE_ONCE" ]; then
case "$1" in
-c) rmdir "$RACE_LOCK"; [ "$RACE_PHASE" = early ] && mkdir "$RACE_LOCK"; exit 1 ;;
-f) ${real} "$@"; rc=$?; rm -f "$RACE_ONCE"; [ -d "$RACE_LOCK" ] || mkdir "$RACE_LOCK"; exit "$rc" ;;
esac
fi
exec ${real} "$@"
`, { mode: 0o755 });
for (const phase of ['early', 'late']) {
fs.writeFileSync(once, 'armed');
const r = probe(f, { _GSTACK_CODEX_LOCK_WAIT: '1', RACE_LOCK: lock, RACE_ONCE: once, RACE_PHASE: phase });
expect(fs.existsSync(once)).toBe(false);
expect(r.stdout).toContain('MODEL_PROBE_INCONCLUSIVE (another probe of this model still holds its lock)');
expect(fs.existsSync(lock)).toBe(true);
expect(calls(f)).toBe(0);
}
}));
test('an unreadable cache or uncreatable lock is unverified and launches no paid probe', withFixture((f) => {
fs.mkdirSync(f.cache);
const dir = probe(f);
+8
View File
@@ -74,6 +74,14 @@ describe('gstack-evidence run', () => {
expect(fs.readFileSync(rec.log_path, 'utf-8')).toContain('ok');
});
test('the lane command sees its own log path as GSTACK_EVIDENCE_LOG (the free runner\'s home tripwire excludes exactly that file)', () => {
const r = run(['run', '--label', 'focused', '--', 'printf "%s" "$GSTACK_EVIDENCE_LOG"']);
expect(r.status).toBe(0);
const rec = records().pop();
expect(path.isAbsolute(rec.log_path)).toBe(true);
expect(r.stdout).toBe(rec.log_path);
});
test('propagates a failing exit code and records it', () => {
const r = run(['run', '--label', 'tests', '--', 'exit 3']);
expect(r.status).toBe(3);
+28
View File
@@ -78,6 +78,34 @@ describe('free-suite home-write tripwire (#2895)', () => {
expect(failure).toContain('only');
});
test.skipIf(process.platform === 'win32')('the wrapping gstack-evidence lane log is excluded by its exact path; a foreign write beside it still trips', async () => {
const home = fakeHome();
const logs = path.join(home, '.gstack/projects/p/logs');
fs.mkdirSync(logs, { recursive: true });
const laneLog = path.join(logs, 'focused.log');
fs.writeFileSync(laneLog, 'lane start\n');
const unexcluded = guardFreeHome(['test/a.test.ts'], { HOME: home });
const options = (script: string, name: string) => ({ env: { ...process.env, HOME: home, GSTACK_EVIDENCE_LOG: laneLog }, quiet: true, log: () => {},
logFilePath: path.join(home, `${name}.log`),
commandFor: () => ({ command: process.execPath, args: ['-e', `${script}console.log('Ran 1 tests across 1 files. [1.00ms]')`] }) });
const grow = `require('fs').appendFileSync(${JSON.stringify(laneLog)}, 'lane output\\n');`;
const errors: string[] = [];
const spy = spyOn(console, 'error').mockImplementation((...args: unknown[]) => { errors.push(args.join(' ')); });
try {
expect((await runFreeShard(['test/a.test.ts'], 1, 1, options(grow, 'lane'))).status).toBe('passed');
expect(unexcluded.verify()).toContain('~/.gstack/projects/p/logs/focused.log');
const foreign = `${grow}require('fs').writeFileSync(${JSON.stringify(path.join(logs, 'other.log'))}, 'x');`;
expect((await runFreeShard(['test/a.test.ts'], 1, 1, options(foreign, 'foreign'))).status).toBe('failed');
expect(errors.join('\n')).toContain('~/.gstack/projects/p/logs/other.log');
expect(errors.join('\n')).not.toContain('focused.log');
} finally {
spy.mockRestore();
for (const retained of errors.flatMap(line => /retained (\S+)$/.exec(line)?.[1] ?? [])) {
if (/^gstack-free-shard-/.test(path.basename(retained))) fs.rmSync(retained, { recursive: true, force: true });
}
}
});
test('a concurrent phase guard names no shard and points at attribution', () => {
const home = fakeHome();
const guard = guardFreeHome(['test/a.test.ts', 'test/b.test.ts'], { HOME: home }, { kind: 'concurrent', shards: 3 });
+6 -1
View File
@@ -1460,8 +1460,13 @@ describe('scan: option-like bases and page-controlled inline ignores', () => {
fs.mkdirSync(many, { recursive: true });
for (let i = 0; i < 1100; i++) fs.writeFileSync(path.join(many, `f${i}.css`), 'a{}');
try {
const r = run(['scan', '--format', 'gstack', 'src/many'], { env: { IMPECCABLE_BIN: FAKE, IMPECCABLE_FAKE_SLEEP_MS: '1000', GSTACK_DESIGN_DETECT_TIMEOUT_MS: '1500' } });
const log = path.join(SANDBOX, 'argv-many.log');
fs.rmSync(log, { force: true });
const r = run(['scan', '--format', 'gstack', 'src/many'], { env: { IMPECCABLE_BIN: FAKE, IMPECCABLE_FAKE_LOG: log } });
expect(r.code).toBe(2); // a directory target is one batch (the fake reports findings): the budget test needs files
const calls = fs.readFileSync(log, 'utf-8').trim().split('\n').map(l => JSON.parse(l).argv as string[]);
expect(calls).toHaveLength(1);
expect(calls[0].some(x => x.endsWith(path.join('src', 'many')))).toBe(true);
const files = fs.readdirSync(many).map(f => path.join('src', 'many', f));
const r2 = run(['scan', '--format', 'gstack', ...files], { env: { IMPECCABLE_BIN: FAKE, IMPECCABLE_FAKE_SLEEP_MS: '1000', GSTACK_DESIGN_DETECT_TIMEOUT_MS: '1500' } });
expect(r2.err).toMatch(/DETECT_TIMEOUT: whole-scan budget 7500ms exceeded, \d+ of 11 batches not run/);
+2 -1
View File
@@ -312,7 +312,8 @@ describe('Office Hours real session runner with fake processes', () => {
expect(captured.output).toBe('captured output');
const diagnostic = JSON.parse(fs.readFileSync(path.join(dir, '.gstack/test-transcripts/drain-failure.json'), 'utf8'));
expect(diagnostic.stderr).toContain('fixture auth failure');
expect(running(Number(fs.readFileSync(path.join(dir, 'child.pid'), 'utf8')))).toBe(false);
const child = fs.readFileSync(path.join(dir, 'child.pid'), 'utf8').trim();
expect(running(Number(child)), `held-pipe child after the runner returned (group ${fs.readFileSync(path.join(dir, 'parent.pid'), 'utf8').trim()}): ${spawnSync('ps', ['-o', 'pid=,ppid=,pgid=,stat=,etime=', '-p', child], { encoding: 'utf8', timeout: 5_000 }).stdout.trim() || 'gone now'}`).toBe(false);
});
}, 10_000);
}
+44 -7
View File
@@ -6,11 +6,12 @@
* the base branch and every other open PR, and a lookup that cannot finish is
* `unknown`, which withholds `REGATE: not-needed`.
*/
import { afterEach, describe, expect, test } from 'bun:test';
import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { EXIT } from '../lib/headless-artifacts';
import { BASE_POLICY, cleanup, makeFixtureRepo, makePr, recordAndBundle, restamp, runBin, type FixtureRepo } from './helpers/restamp-fixture';
import { gitFixtureTemplate, rebindFile, rebindGitDir, type GitFixtureTemplate } from './helpers/git-fixture-template';
import { BASE_POLICY, cleanup, fixtureRepoAt, makePr, recordAndBundle, restamp, runBin, seedFixtureRepo, type FixtureRepo } from './helpers/restamp-fixture';
const repos: FixtureRepo[] = [];
afterEach(() => { while (repos.length) cleanup(repos.pop()!); });
@@ -39,10 +40,14 @@ console.error("gh shim: unsupported "+a.join(" ")); process.exit(1)' "${file}" "
}
const ghCalls = (repo: FixtureRepo) => fs.readFileSync(path.join(repo.root, 'gh-regate', 'calls.log'), 'utf8');
type Lanes = Array<{ label: string; command: string }>;
const TESTS_LANE: Lanes = [{ label: 'tests', command: 'true' }];
const DIGEST_LANES: Lanes = [...TESTS_LANE, { label: 'digest', command: 'cat generated/digest.md' }];
/** pr-a adds migration v240, is stamped 1.2.4.0 and gated (lanes recorded and bundled); then restamped to 1.2.5.0. */
function gatedThenRestamped(lanes: Array<{ label: string; command: string }> = [{ label: 'tests', command: 'true' }]) {
const repo = makeFixtureRepo({ prefix: 'pregate-regate-', policy: POLICY });
repos.push(repo);
function buildGated(root: string, lanes: Lanes) {
seedFixtureRepo(root, { policy: POLICY });
const repo = fixtureRepoAt(root);
makePr(repo, 'pr-a', { expects: '1.2.4.0' });
fs.mkdirSync(path.join(repo.work, 'migrations'), { recursive: true });
fs.writeFileSync(path.join(repo.work, 'migrations', 'v240-add-index.sql'), 'create index a;\n');
@@ -54,7 +59,39 @@ function gatedThenRestamped(lanes: Array<{ label: string; command: string }> = [
const head = repo.git('rev-parse', 'HEAD');
expect(restamp(repo, ['--version', '1.2.5.0']).status).toBe(EXIT.ok);
repo.git('commit', '-qam', 'stamp 1.2.5.0');
return { repo, head, bundle, current: repo.git('rev-parse', 'HEAD') };
return { head, current: repo.git('rev-parse', 'HEAD') };
}
// Each fixture costs four gstack bin runs (two restamps, an evidence run and a bundle); build each lane set once
// and give every test its own copy instead of rebuilding it per test.
const templates = new Map<Lanes, GitFixtureTemplate<ReturnType<typeof buildGated>>>();
beforeAll(() => {
for (const lanes of [TESTS_LANE, DIGEST_LANES]) {
const template = gitFixtureTemplate({
prefix: 'pregate-regate-',
build: root => buildGated(root, lanes),
rebind: (copy, tpl) => {
rebindGitDir(path.join(copy, 'work', '.git'), tpl, copy);
rebindFile(path.join(copy, 'bundle.json'), tpl, copy);
const projects = path.join(copy, 'home', 'projects');
for (const slug of fs.existsSync(projects) ? fs.readdirSync(projects) : []) {
const dir = path.join(projects, slug.split(path.basename(tpl)).join(path.basename(copy)));
if (dir !== path.join(projects, slug)) fs.renameSync(path.join(projects, slug), dir);
for (const file of fs.readdirSync(dir, { recursive: true, withFileTypes: true })) if (file.isFile()) rebindFile(path.join(file.parentPath, file.name), tpl, copy);
}
fs.rmSync(path.join(copy, 'home', '.capy'), { recursive: true, force: true });
},
});
template.copy().root && templates.set(lanes, template);
}
}, 60_000);
afterAll(() => { for (const t of templates.values()) t.dispose(); });
function gatedThenRestamped(lanes: Lanes = TESTS_LANE) {
const { root, meta: { head, current } } = templates.get(lanes)!.copy();
const repo = fixtureRepoAt(root);
repos.push(repo);
return { repo, head, bundle: path.join(repo.root, 'bundle.json'), current };
}
const regate = (repo: FixtureRepo, args: string[], env: Record<string, string>) => runBin(repo, 'gstack-pregate', ['regate', '--base', 'main', ...args], env);
@@ -97,7 +134,7 @@ describe('gstack-pregate regate: evidence, stamps and lane verdicts', () => {
});
test('a stamp-sensitive lane reruns, so a regate is needed; a code edit is not stamp-only and names the file and line', () => {
const { repo, head, bundle } = gatedThenRestamped([{ label: 'tests', command: 'true' }, { label: 'digest', command: 'cat generated/digest.md' }]);
const { repo, head, bundle } = gatedThenRestamped(DIGEST_LANES);
const env = ghShim(repo, { list: [] });
const r = regate(repo, ['--against', head, '--bundle', bundle], env);
expect(r.status).toBe(EXIT.fail);
+2 -1
View File
@@ -284,7 +284,8 @@ describe('gstack-session-update lock identity + TTL (#2613)', () => {
const r = runScript(base, install, state);
expect(r.status).toBe(0);
const log = await waitForLog(state, /UP_TO_DATE|UPDATING/);
expect(log).toMatch(/UP_TO_DATE|UPDATING/);
const lockPid = fs.existsSync(path.join(lockDir, 'pid')) ? fs.readFileSync(path.join(lockDir, 'pid'), 'utf8').trim() : 'no lock';
expect(log, `planted dead pid ${dead.pid} (alive now: ${isAlive(dead.pid)}); lock pid after 15 s: ${lockPid} (alive: ${isAlive(Number(lockPid))}); script exit ${r.status}, stderr: ${r.stderr}`).toMatch(/UP_TO_DATE|UPDATING/);
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
+4 -1
View File
@@ -171,7 +171,10 @@ describe('shard engine: process lifecycle', () => {
});
expect(paid.status).toBe('timed-out');
expect(summaryExitCode(summarize([paid]))).toBe(1);
expect(await waitDead(Number(fs.readFileSync(pidFile, 'utf8')))).toBe(true);
const paidChild = Number(fs.readFileSync(pidFile, 'utf8'));
const paidChildDead = await waitDead(paidChild);
const procStat = (pid: number) => { try { return fs.readFileSync(`/proc/${pid}/stat`, 'utf8').trim(); } catch { return 'no /proc entry'; } };
expect(paidChildDead, `paid grandchild ${paidChild} of group ${paid.groupPid} 2 s after the timeout kill: ${procStat(paidChild)}`).toBe(true);
}, 30_000);
test('win32 semantics: no process group and never a negative-pid kill', async () => {
+1 -1
View File
@@ -308,7 +308,7 @@ describe('Claude Code child launch', () => {
const file = ho.out.match(/^HANDOFF: (\S+)/m)![1]!;
const argsFile = path.join(h.root, 'claude-args.txt');
const fake = path.join(h.root, 'fake-claude');
fs.writeFileSync(fake, `#!/bin/sh\nprintf '%s\\n' "$@" > "${argsFile}"\n`, { mode: 0o755 });
fs.writeFileSync(fake, `#!/bin/sh\nprintf '%s\\n' "$@" > "${argsFile}.tmp" && mv "${argsFile}.tmp" "${argsFile}"\n`, { mode: 0o755 });
const r = h.sb(['launch', '--in', file], { GSTACK_CLAUDE_BIN: fake });
expect(r.code).toBe(0);
const session = h.field(r.out, 'CHILD', 'session');
+5 -2
View File
@@ -35,13 +35,16 @@ const result = (out: string) => line(out, 'GSTACK_RESULT:');
const next = (out: string) => line(out, 'NEXT:');
const remoteRefs = (fx: TrainFixture) => fx.repo.git('ls-remote', 'origin');
// fails_when=coordinatorClone copies origin.git/objects file by file again, so an entry that appears or vanishes there mid-copy (the detached maintenance.lock every push into the fixture origin leaves) fails the clone, as in two Free Tests reds on 2026-10-28 (GSTA-139 ledger); why_new=no other test clones while origin's objects directory changes; seam=a dangling symlink planted under objects/, which a local copy refuses on every run; no_claim=which process removed the loose object those runs named
// fails_when=coordinatorClone copies origin.git/objects file by file again, so an entry that appears or vanishes there mid-copy (the detached maintenance.lock every push into the fixture origin leaves) fails the clone, as in two Free Tests reds on 2026-10-28 (GSTA-139 ledger); why_new=no other test clones while origin's objects directory changes; seam=a dangling symlink planted under objects/ beside a held maintenance.lock, which a local copy refuses on every run; the seam has its own name because the fixture push's own detached maintenance can hold objects/maintenance.lock at that moment (EEXIST in Free Tests 41288253305 and 41288417894, 2026-11-01); no_claim=which process removed the loose object those runs named
test('the coordinator clone reads origin through git, never by copying its objects directory (GSTA-139)', () => {
const fx = make();
fs.symlinkSync(path.join(fx.repo.root, 'gone'), path.join(fx.repo.origin, 'objects', 'maintenance.lock'));
const objects = path.join(fx.repo.origin, 'objects');
fs.writeFileSync(path.join(objects, 'maintenance.lock'), '');
fs.symlinkSync(path.join(fx.repo.root, 'gone'), path.join(objects, 'gstack-dangling-seam'));
const coord = coordinatorClone(fx.repo);
expect(spawnSync('git', ['-C', coord, 'rev-parse', 'origin/main'], { encoding: 'utf8', timeout: 30_000, env: { ...process.env, ...fx.repo.env } }).stdout.trim()).toBe(fx.repo.git('rev-parse', 'origin/main'));
expect(fs.existsSync(path.join(coord, '.git', 'objects', 'maintenance.lock'))).toBe(false);
expect(fs.readdirSync(path.join(coord, '.git', 'objects'))).not.toContain('gstack-dangling-seam');
});
describe('the train command surface', () => {