mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
Resolves v0.34.0.0 (W1-W8 code intelligence) with master's v0.33.2.1 + search-lite work (query cache + intent weighting + token budget + drift watch + metric glossary + search modes). Conflict resolutions: - VERSION / package.json: kept 0.34.0.0 (mine; higher than master's 0.33.2.1) - CHANGELOG.md: both entries preserved; reordered so v0.33.2.1 sits above v0.33.2.0 (semver order) - src/cli.ts CLI_ONLY: union of both — `edges-backfill` (mine) + `cache` (master) - src/core/migrate.ts: renumbered my migrations to avoid collision with master's query_cache_search_lite (v55), query_cache_knobs_hash (v56), search_telemetry_rollup (v57). My `edges_backfilled_at_v0_33_2` moves v55 → v58; my `code_traversal_cache_v0_34` moves v56 → v59. Code refs in `src/core/code-intel/traversal-cache.ts` and the paired test updated to match. - src/core/operations.ts query op: kept master's `hybridSearchCached` routing (search-lite cache integration) AND my `sourceId` resolution block (D4 source-routing fix from v0.34 STEP 0). Both apply. Verification: - `bun run typecheck` clean - `bun run verify` clean (includes check-cli-executable, check-jsonb, check-system-of-record, check-eval-glossary-fresh, etc.) - Migration v50→v59 apply cleanly on PGLite in isolated test runs - Individual test files pass (e.g. test/search-lang-symbol-kind.test.ts: 9 pass / 0 fail in 913ms) Known follow-up: the parallel test shard runner times out some beforeAll hooks at the default 7s budget. Tests pass when run sequentially (`--max-concurrency=1`); 27/0 confirmed across 3 sample files in 2.4s sequential vs timeouts under parallel-shard contention. Master added 4 new migrations (v55-v57 + search-lite related) increasing per-test-file PGLite init cost; on 8 shards racing for OS resources, some shards hit the 7s ceiling. This is a test-infrastructure issue (shard isolation under heavier migrations), not a code-correctness issue. Fix is a follow-up: either raise shard test timeout, reduce shard count, or migrate to fixture-based engine setup for hot tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
184 lines
6.1 KiB
TypeScript
184 lines
6.1 KiB
TypeScript
/**
|
|
* v0.34 W3b — code_traversal_cache module tests.
|
|
*
|
|
* Hermetic PGLite test suite covering:
|
|
* - cache hit returns memoized response (after migration v59)
|
|
* - cache miss triggers compute
|
|
* - D3: cluster_generation bump invalidates cached rows
|
|
* - clearTraversalCache: source-scoped clear deletes the right rows
|
|
* - clearTraversalCache: --all-sources gate requires explicit opt-out
|
|
* - getCachedOrCompute: try-cache-then-compute happy path
|
|
*/
|
|
import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test';
|
|
import { PGLiteEngine } from '../../src/core/pglite-engine.ts';
|
|
import { resetPgliteState } from '../helpers/reset-pglite.ts';
|
|
import {
|
|
getCachedTraversal,
|
|
putCachedTraversal,
|
|
getClusterGeneration,
|
|
bumpClusterGeneration,
|
|
clearTraversalCache,
|
|
getCachedOrCompute,
|
|
type CacheKey,
|
|
} from '../../src/core/code-intel/traversal-cache.ts';
|
|
|
|
let engine: PGLiteEngine;
|
|
|
|
beforeAll(async () => {
|
|
engine = new PGLiteEngine();
|
|
await engine.connect({});
|
|
await engine.initSchema();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await engine.disconnect();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await resetPgliteState(engine);
|
|
});
|
|
|
|
const baseKey = (over: Partial<CacheKey> = {}): CacheKey => ({
|
|
symbol_qualified: 'src/foo::bar',
|
|
depth: 5,
|
|
source_id: 'default',
|
|
cluster_generation: 0,
|
|
...over,
|
|
});
|
|
|
|
describe('W3b: getClusterGeneration / bumpClusterGeneration', () => {
|
|
test('defaults to 0 when never set', async () => {
|
|
const g = await getClusterGeneration(engine);
|
|
expect(g).toBe(0);
|
|
});
|
|
|
|
test('bump increments by 1 and persists', async () => {
|
|
const next = await bumpClusterGeneration(engine);
|
|
expect(next).toBe(1);
|
|
const read = await getClusterGeneration(engine);
|
|
expect(read).toBe(1);
|
|
});
|
|
|
|
test('multiple bumps are monotonic', async () => {
|
|
await bumpClusterGeneration(engine);
|
|
await bumpClusterGeneration(engine);
|
|
const final = await bumpClusterGeneration(engine);
|
|
expect(final).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('W3b: putCachedTraversal / getCachedTraversal', () => {
|
|
test('cache miss returns null', async () => {
|
|
const hit = await getCachedTraversal(engine, baseKey());
|
|
expect(hit).toBeNull();
|
|
});
|
|
|
|
test('cache hit returns the response after put', async () => {
|
|
const key = baseKey();
|
|
const payload = { result: 'ok', depth_groups: [{ depth: 1, nodes: [] }] };
|
|
await putCachedTraversal(engine, key, payload, new Date().toISOString(), 0);
|
|
const hit = await getCachedTraversal(engine, key);
|
|
expect(hit).not.toBeNull();
|
|
expect(hit?.response).toEqual(payload);
|
|
});
|
|
|
|
test('D3: cluster_generation mismatch returns null (cache miss)', async () => {
|
|
const key = baseKey({ cluster_generation: 1 });
|
|
await putCachedTraversal(engine, key, { v: 'fresh' }, new Date().toISOString(), 0);
|
|
// Now look up with a stale generation
|
|
const staleKey = baseKey({ cluster_generation: 0 });
|
|
const hit = await getCachedTraversal(engine, staleKey);
|
|
expect(hit).toBeNull();
|
|
});
|
|
|
|
test('UPSERT on conflict replaces older row', async () => {
|
|
const key = baseKey();
|
|
await putCachedTraversal(engine, key, { v: 1 }, new Date().toISOString(), 0);
|
|
await putCachedTraversal(engine, key, { v: 2 }, new Date().toISOString(), 0);
|
|
const hit = await getCachedTraversal<{ v: number }>(engine, key);
|
|
expect(hit?.response).toEqual({ v: 2 });
|
|
});
|
|
});
|
|
|
|
describe('W3b: clearTraversalCache', () => {
|
|
test('refuses without source_id or all_sources', async () => {
|
|
await expect(clearTraversalCache(engine, {})).rejects.toThrow(/specify source_id/);
|
|
});
|
|
|
|
test('source-scoped clear deletes only that source', async () => {
|
|
await putCachedTraversal(engine, baseKey({ source_id: 'src-a' }), { v: 1 }, new Date().toISOString(), 0);
|
|
await putCachedTraversal(engine, baseKey({ source_id: 'src-b' }), { v: 2 }, new Date().toISOString(), 0);
|
|
const deleted = await clearTraversalCache(engine, { sourceId: 'src-a' });
|
|
expect(deleted).toBe(1);
|
|
const a = await getCachedTraversal(engine, baseKey({ source_id: 'src-a' }));
|
|
const b = await getCachedTraversal(engine, baseKey({ source_id: 'src-b' }));
|
|
expect(a).toBeNull();
|
|
expect(b).not.toBeNull();
|
|
});
|
|
|
|
test('all_sources clears everything', async () => {
|
|
await putCachedTraversal(engine, baseKey({ source_id: 'src-a' }), { v: 1 }, new Date().toISOString(), 0);
|
|
await putCachedTraversal(engine, baseKey({ source_id: 'src-b' }), { v: 2 }, new Date().toISOString(), 0);
|
|
const deleted = await clearTraversalCache(engine, { allSources: true });
|
|
expect(deleted).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('W3b: getCachedOrCompute', () => {
|
|
test('miss: runs compute and caches result', async () => {
|
|
let computeCalls = 0;
|
|
const result = await getCachedOrCompute(
|
|
engine,
|
|
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
|
|
async () => {
|
|
computeCalls += 1;
|
|
return { x: 42 };
|
|
},
|
|
);
|
|
expect(result).toEqual({ x: 42 });
|
|
expect(computeCalls).toBe(1);
|
|
});
|
|
|
|
test('hit: skips compute on second call', async () => {
|
|
let computeCalls = 0;
|
|
const compute = async () => {
|
|
computeCalls += 1;
|
|
return { x: 42 };
|
|
};
|
|
await getCachedOrCompute(
|
|
engine,
|
|
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
|
|
compute,
|
|
);
|
|
await getCachedOrCompute(
|
|
engine,
|
|
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
|
|
compute,
|
|
);
|
|
expect(computeCalls).toBe(1);
|
|
});
|
|
|
|
test('D3: bumping cluster_generation invalidates the cache', async () => {
|
|
let computeCalls = 0;
|
|
const compute = async () => {
|
|
computeCalls += 1;
|
|
return { x: computeCalls };
|
|
};
|
|
await getCachedOrCompute(
|
|
engine,
|
|
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
|
|
compute,
|
|
);
|
|
expect(computeCalls).toBe(1);
|
|
// Bump generation — next call should recompute.
|
|
await bumpClusterGeneration(engine);
|
|
const second = await getCachedOrCompute(
|
|
engine,
|
|
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
|
|
compute,
|
|
);
|
|
expect(computeCalls).toBe(2);
|
|
expect(second).toEqual({ x: 2 });
|
|
});
|
|
});
|