mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-30 03:12:32 +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>
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
/**
|
|
* Run a callback with `process.env` mutations applied, then restore the prior
|
|
* values via try/finally. The canonical pattern for env-touching tests in this
|
|
* repo.
|
|
*
|
|
* Why this exists: `process.env` is process-global. Tests that mutate it
|
|
* leak state across files in the same bun test process (the parallel runner
|
|
* loads multiple files into one process per shard). `withEnv` saves the
|
|
* prior value of every key it touches, runs the callback, and restores via
|
|
* try/finally — including when the callback throws.
|
|
*
|
|
* Important caveat: `withEnv` is cross-test-safe but NOT intra-file
|
|
* concurrent-safe. Two `test.concurrent()` calls in the same file both
|
|
* calling withEnv on the same key will race — the global is only one
|
|
* variable. Files that mutate env stay outside the `test.concurrent()`
|
|
* codemod's eligibility filter (the `*.serial.test.ts` quarantine + the
|
|
* codemod's `grep -L "process\.env\."` exclusion handle this).
|
|
*
|
|
* Use:
|
|
* import { withEnv } from './helpers/with-env.ts';
|
|
*
|
|
* test('reads OPENAI_API_KEY', async () => {
|
|
* await withEnv({ OPENAI_API_KEY: 'sk-test' }, async () => {
|
|
* expect(loadConfig().openai_key).toBe('sk-test');
|
|
* });
|
|
* });
|
|
*
|
|
* // Delete a var (override is undefined):
|
|
* await withEnv({ GBRAIN_HOME: undefined }, async () => {
|
|
* expect(process.env.GBRAIN_HOME).toBeUndefined();
|
|
* });
|
|
*
|
|
* // Multiple keys:
|
|
* await withEnv({ A: '1', B: '2', C: undefined }, fn);
|
|
*
|
|
* // Nested compose: inner restores to outer's value, not original.
|
|
* await withEnv({ K: 'outer' }, async () => {
|
|
* await withEnv({ K: 'inner' }, async () => {
|
|
* expect(process.env.K).toBe('inner');
|
|
* });
|
|
* expect(process.env.K).toBe('outer');
|
|
* });
|
|
*/
|
|
export async function withEnv<T>(
|
|
overrides: Record<string, string | undefined>,
|
|
fn: () => T | Promise<T>,
|
|
): Promise<T> {
|
|
const keys = Object.keys(overrides);
|
|
const prior: Record<string, string | undefined> = {};
|
|
for (const key of keys) {
|
|
prior[key] = process.env[key];
|
|
}
|
|
try {
|
|
for (const [key, value] of Object.entries(overrides)) {
|
|
if (value === undefined) {
|
|
delete process.env[key];
|
|
} else {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
return await fn();
|
|
} finally {
|
|
for (const [key, value] of Object.entries(prior)) {
|
|
if (value === undefined) {
|
|
delete process.env[key];
|
|
} else {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|