mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-02 17:40:02 +02:00
* feat: add surface-aware exploratory QA and ship documentation gates * test: preserve delegated QA setup authority after main integration * fix(qa): clarify exploration order and preserve report artifacts * test(qa): follow the shared setup reference directly * refactor(ship): make verification and recovery routes explicit * test(ship): align evidence and review guards with explicit routes * fix(workflows): clarify ship recovery and functional QA evidence * fix(workflows): clarify approval recovery and full QA coverage * refactor(workflows): order review transactions and clarify ship state * fix(ship): clarify final verification and fail closed at publication * fix(evals): attribute native atomic documentation writes * fix(ship): clarify recovery and documentation lifecycle guidance * fix(test): preserve observed native placeholder styling in CI * fix(codex): report watchdog timeouts without a process-exit race * Checkpoint functional QA implementation and workflow validation repairs * Fix documentation and shared-review fixture contracts * docs: clarify judge reuse and evaluation supervision * test: align review evidence and selected case contracts * test: verify append-only documentation checkpoints and recovery * fix: qualify QA workflows and CI validation repairs * fix: launch shared-libs fixture scripts on Windows * fix: qualify QA deadlines, fixture isolation, and shard cleanup * fix: preserve qualified QA and cancellation repairs * fix: enforce functional fixture authority and share strict event decoding * fix: retain free-test evidence and explain recovery * fix: reject malformed native evidence after decoder consolidation * test: use reliable capture for telemetry privacy filters * test: refresh measured quick coverage and document validation costs * Fix native fixture receipts and preserve VM validation evidence * Align negative judge controls with upstream clarity policy * Fix report-only QA preparation and public evidence handling * Clarify QA-only preparation and current-report preservation * Stream Ship quality judgments with an explicit 64k response contract * Validate compact judge reasoning locally with supported wire schema * Align functional QA fixture instructions with evidence acceptance * Bind native browser diagnostics to execution evidence and align review verdicts * Preserve native diagnostic line boundaries * Serialize functional QA evidence from native captures * Keep large QA evidence fixture payload out of Windows argv
101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
/**
|
|
* Direct pins for runShardChild (scripts/test-strict-output.ts) — the shared
|
|
* spawn/detached/group-kill/wall-timer/reap lifecycle extracted from the paid
|
|
* runner's runPaidShard, designed for scripts/test-free-shards.ts to migrate
|
|
* onto next. test/paid-shards.test.ts pins the paid runner end-to-end; these
|
|
* pin the helper's own contract so the free-runner migration has a floor.
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import type { ChildProcess } from 'child_process';
|
|
import { runShardChild } from '../scripts/test-strict-output';
|
|
|
|
/** Collect the child's full stdout+stderr, resolving only when drained. */
|
|
function collectingHook(chunks: string[]) {
|
|
return (child: ChildProcess): Array<Promise<void>> => {
|
|
const consume = (stream: NodeJS.ReadableStream | null): Promise<void> =>
|
|
stream
|
|
? new Promise((resolve, reject) => {
|
|
stream.on('data', (chunk: Buffer | string) => chunks.push(chunk.toString()));
|
|
stream.on('end', resolve);
|
|
stream.on('error', reject);
|
|
})
|
|
: Promise.resolve();
|
|
return [consume(child.stdout), consume(child.stderr)];
|
|
};
|
|
}
|
|
|
|
describe('runShardChild', () => {
|
|
test('clean exit: exitCode 0, not timed out, output drained before resolve', async () => {
|
|
const chunks: string[] = [];
|
|
const result = await runShardChild({
|
|
command: process.execPath,
|
|
args: ['-e', 'console.log("hello-from-child")'],
|
|
cwd: process.cwd(),
|
|
env: process.env,
|
|
timeoutMs: 30_000,
|
|
hookStreams: collectingHook(chunks),
|
|
});
|
|
expect(result.exitCode).toBe(0);
|
|
expect(result.timedOut).toBe(false);
|
|
expect(result.groupPid).toBeGreaterThan(0);
|
|
// The hookStreams promises are awaited AFTER close — trailing output is
|
|
// fully drained before callers read their classifier/log state.
|
|
expect(chunks.join('')).toContain('hello-from-child');
|
|
}, 30_000);
|
|
|
|
test('non-zero exit code propagates untouched', async () => {
|
|
const result = await runShardChild({
|
|
command: process.execPath,
|
|
args: ['-e', 'process.exit(7)'],
|
|
cwd: process.cwd(),
|
|
env: process.env,
|
|
timeoutMs: 30_000,
|
|
hookStreams: () => [],
|
|
});
|
|
expect(result.exitCode).toBe(7);
|
|
expect(result.timedOut).toBe(false);
|
|
}, 30_000);
|
|
|
|
test('a spinning child is group-SIGKILLed at the wall deadline and reported timedOut', async () => {
|
|
const startedAt = Date.now();
|
|
const result = await runShardChild({
|
|
command: process.execPath,
|
|
// A real busy loop: an in-process timer could never fire in this child.
|
|
args: ['-e', 'const end = Date.now() + 600000; while (Date.now() < end) {}'],
|
|
cwd: process.cwd(),
|
|
env: process.env,
|
|
timeoutMs: 1_200,
|
|
hookStreams: () => [],
|
|
});
|
|
expect(result.timedOut).toBe(true);
|
|
expect(Date.now() - startedAt).toBeLessThan(2_200);
|
|
if (process.platform !== 'win32') {
|
|
const reaped = (pid = result.groupPid as number) => {
|
|
try { process.kill(pid, 0); return false; }
|
|
catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === 'ESRCH') return true;
|
|
throw error;
|
|
}
|
|
};
|
|
const observationDeadline = Date.now() + 1_000;
|
|
while (!reaped() && Date.now() < observationDeadline) await Bun.sleep(10);
|
|
expect(reaped()).toBe(true);
|
|
expect(reaped(-(result.groupPid as number))).toBe(true);
|
|
}
|
|
}, 30_000);
|
|
|
|
test('a spawn failure THROWS so callers keep their could-not-run handling', async () => {
|
|
await expect(runShardChild({
|
|
command: path.join(os.tmpdir(), 'definitely-not-a-real-binary-8b1f'),
|
|
args: [],
|
|
cwd: process.cwd(),
|
|
env: process.env,
|
|
timeoutMs: 5_000,
|
|
hookStreams: () => [],
|
|
})).rejects.toThrow();
|
|
}, 30_000);
|
|
});
|