mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-28 14:59:47 +00:00
* test: add withEnv helper + canonical PGLite block JSDoc withEnv(overrides, fn) saves prior values, runs the callback, restores via try/finally — including on throw. Handles delete via undefined override. Nested calls compose. Cross-test safe; explicitly NOT intra-file concurrent-safe (process.env is process-global). 7 unit cases covering sync, async, delete-key, delete-when-prior-unset, restore-on-throw, nested compose, multi-key atomic restore. reset-pglite.ts JSDoc extended with the canonical 4-line PGLite block (beforeAll create + afterAll disconnect + beforeEach reset). The lint script in the next commit enforces this exact shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: add check-test-isolation lint script + wire into verify Grep-based lint enforcing 4 rules on non-serial unit test files: R1: no process.env mutations (use withEnv() or rename to *.serial.test.ts) R2: no mock.module() (rename to *.serial.test.ts) R3: new PGLiteEngine( only inside beforeAll() context R4: PGLiteEngine creators must pair with afterAll{disconnect} Wired into 'bun run verify' and 'bun run check:all' (NOT 'bun run test' which is the parallel runner script with no pre-check chain). Matches the existing scripts/check-*.sh family shape (jsonb, progress, etc). 51 baseline violators captured in scripts/check-test-isolation.allowlist. List MUST shrink over time — entries removed by v0.26.8 (env sweep) and v0.26.9 (PGLite sweep). New files cannot be added. CLAUDE.md ## Testing section extended with R1-R4 rules table, the canonical 4-line PGLite block, withEnv pattern, and when-to-quarantine guidance. 16 fixture-driven test cases for the lint: clean, R1 (5 patterns + 1 negative), R2, R3 (top-level vs in-beforeAll), R4 (missing disconnect), *.serial.test.ts skip, test/e2e/ skip, allowlist (3 cases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: quarantine cycle and embed mock.module test files Both files use mock.module(...) at top level — leaks across files in the same shard process. The check-test-isolation lint (R2) bans this pattern in non-serial files; quarantine is the escape hatch. Per v0.26.7 plan D5: prefer quarantine over DI on runCycle/runEmbed. Production signatures stay frozen; tests run at --max-concurrency=1 in the serial post-pass (the existing pattern shipped in v0.26.4 for brain-registry and reconcile-links). Quarantine count: 2 → 4. Cap raised to 10 informational per D15. Renames: test/core/cycle.test.ts → test/core/cycle.serial.test.ts test/embed.test.ts → test/embed.serial.test.ts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.26.7) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: post-ship documentation sync for v0.26.7 - README.md "Contributing" line: point to bun run test + bun run verify (parallel fast loop) - CONTRIBUTING.md "Running tests": rewrite for the v0.26.4/v0.26.7 test surface (parallel runner, verify, slow/serial/e2e tiers) - CONTRIBUTING.md adds "Writing tests that survive the parallel loop" section: R1-R4 lint, canonical PGLite block, withEnv pattern, when to quarantine - llms-full.txt regenerated to pick up the README + CONTRIBUTING changes Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { withEnv } from './with-env.ts';
|
|
|
|
const KEY = 'GBRAIN_WITH_ENV_TEST_KEY';
|
|
const KEY2 = 'GBRAIN_WITH_ENV_TEST_KEY2';
|
|
|
|
describe('withEnv', () => {
|
|
test('sync callback: sets value, runs, restores prior value', async () => {
|
|
process.env[KEY] = 'original';
|
|
const result = await withEnv({ [KEY]: 'overridden' }, () => {
|
|
expect(process.env[KEY]).toBe('overridden');
|
|
return 42;
|
|
});
|
|
expect(result).toBe(42);
|
|
expect(process.env[KEY]).toBe('original');
|
|
delete process.env[KEY];
|
|
});
|
|
|
|
test('async callback: awaits, then restores', async () => {
|
|
process.env[KEY] = 'before';
|
|
const result = await withEnv({ [KEY]: 'during' }, async () => {
|
|
expect(process.env[KEY]).toBe('during');
|
|
await new Promise(r => setTimeout(r, 5));
|
|
expect(process.env[KEY]).toBe('during');
|
|
return 'done';
|
|
});
|
|
expect(result).toBe('done');
|
|
expect(process.env[KEY]).toBe('before');
|
|
delete process.env[KEY];
|
|
});
|
|
|
|
test('delete-key: undefined override removes the var, restores it after', async () => {
|
|
process.env[KEY] = 'will-be-deleted';
|
|
await withEnv({ [KEY]: undefined }, () => {
|
|
expect(process.env[KEY]).toBeUndefined();
|
|
});
|
|
expect(process.env[KEY]).toBe('will-be-deleted');
|
|
delete process.env[KEY];
|
|
});
|
|
|
|
test('delete-key when prior was unset: stays unset after restore', async () => {
|
|
delete process.env[KEY];
|
|
await withEnv({ [KEY]: 'temp' }, () => {
|
|
expect(process.env[KEY]).toBe('temp');
|
|
});
|
|
expect(process.env[KEY]).toBeUndefined();
|
|
});
|
|
|
|
test('restore-on-throw: callback throws, env still restored', async () => {
|
|
process.env[KEY] = 'safe';
|
|
let caught: unknown = null;
|
|
try {
|
|
await withEnv({ [KEY]: 'wreckage' }, () => {
|
|
expect(process.env[KEY]).toBe('wreckage');
|
|
throw new Error('boom');
|
|
});
|
|
} catch (e) {
|
|
caught = e;
|
|
}
|
|
expect((caught as Error).message).toBe('boom');
|
|
expect(process.env[KEY]).toBe('safe');
|
|
delete process.env[KEY];
|
|
});
|
|
|
|
test('nested compose: inner overrides outer, restore returns to outer value', async () => {
|
|
delete process.env[KEY];
|
|
await withEnv({ [KEY]: 'outer' }, async () => {
|
|
expect(process.env[KEY]).toBe('outer');
|
|
await withEnv({ [KEY]: 'inner' }, () => {
|
|
expect(process.env[KEY]).toBe('inner');
|
|
});
|
|
expect(process.env[KEY]).toBe('outer');
|
|
});
|
|
expect(process.env[KEY]).toBeUndefined();
|
|
});
|
|
|
|
test('multiple keys: sets and restores all atomically', async () => {
|
|
process.env[KEY] = 'A-prior';
|
|
delete process.env[KEY2];
|
|
await withEnv({ [KEY]: 'A-new', [KEY2]: 'B-new' }, () => {
|
|
expect(process.env[KEY]).toBe('A-new');
|
|
expect(process.env[KEY2]).toBe('B-new');
|
|
});
|
|
expect(process.env[KEY]).toBe('A-prior');
|
|
expect(process.env[KEY2]).toBeUndefined();
|
|
delete process.env[KEY];
|
|
});
|
|
});
|