GitNexus/gitnexus/test/unit/v8-sidecar.test.ts
Gergő Magyar 72edf40087
perf(store): V8 sidecars plus hardlinked ParsedFile restore (#3099)
* perf(store): add best-effort V8 sidecars beside canonical JSON caches

Warm ParsedFile and parse-cache loads skip JSON.parse when a sidecar is present. JSON remains authoritative: envelope validation plus v8.deserialize decide the hit, and any failure falls back without reparsing.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(store): require generation bind or sidecar drop before cache overwrite

A same-length JSON rewrite could accept a leftover V8 sidecar if both
generation rotation and unlink failed. Refuse the new generation unless
at least one of those invalidations succeeds; skip publishing a sidecar
when only the drop succeeded.

detect_changes --scope all: 7 files, risk low, no affected processes.
tsc --noEmit clean; 115/115 relevant unit tests; cache-related
integration tests pass. parse-impl-env-reads worker-ready timeout is
pre-existing (same 5 failures with this change set stashed). ESLint 0
errors; remaining warnings are pre-existing and not on changed lines.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(autofix): apply prettier + eslint fixes via /autofix command

* refactor(store): share V8 overwrite invalidation across persist paths

The bind-or-drop gate lived in five writers. One helper keeps the
protocol in a single place and lets bind/drop run together on the
async path.

detect_changes --scope all: 3 files, risk low, no affected processes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(store): hardlink durable ParsedFile shards into the run store

Warm restore of parsedfile-cache into parsedfile-store now publishes all four shard files via fs.link, falling back to copy-into-tmp + rename so a leftover dest hardlink can never be written through. JSON remains the canonical cache; V8 sidecars ride the same path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(store): load immutable V8 shards in place, drop JSON fallback

Warm analyze was still paying JSON.parse plus a restore copy. One .v8 envelope per shard and SCHEMA_BUMP 81 make a miss re-extract instead of serving a stale JSON twin.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(store): validate durable V8 warm-cache restores

Reject incomplete or corrupt durable generations and snapshot valid shards before skipping parse workers, preserving ParsedFiles when persistence fails.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(store): drop unused durable load path

Load ParsedFiles only from the run-store snapshot and share one checksummed payload reader so inspect and deserialize stay consistent.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-30 20:50:20 +00:00

204 lines
7.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { mkdtemp, rm, writeFile, readFile } from 'fs/promises';
import { tmpdir } from 'os';
import path from 'path';
import {
inspectV8Cache,
internGraphStrings,
parseCachePathListing,
tryLoadV8Cache,
writeV8CacheFile,
V8_CACHE_FORMAT,
} from '../../src/storage/v8-sidecar.js';
describe('v8 cache envelope', () => {
it('interns duplicate strings in place and keeps Map identity', () => {
const pool = new Map<string, string>();
const m = new Map([['k', 'dup']]);
const graph = { a: 'dup', b: 'dup', m };
internGraphStrings(graph, pool);
expect(graph.a).toBe(graph.b);
expect(graph.m).toBe(m);
expect(graph.m.get('k')).toBe(graph.a);
expect([...pool.keys()].sort()).toEqual(['dup', 'k']);
expect(pool.get('dup')).toBe('dup');
});
it('round-trips a live graph including Maps', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-'));
try {
const filePath = path.join(dir, 'shard.v8');
const graph = { n: 1, nested: { s: 'x' }, map: new Map([['a', 1]]) };
expect(await writeV8CacheFile(filePath, graph)).toBe(true);
const hit = await tryLoadV8Cache(filePath);
expect(hit?.kind).toBe('hit');
if (hit?.kind !== 'hit') return;
const value = hit.value as typeof graph;
expect(value.n).toBe(1);
expect(value.map).toBeInstanceOf(Map);
expect(value.map.get('a')).toBe(1);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('skips deserialize when a valid path listing misses wantPaths', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-skip-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, [{ filePath: 'a.c' }], ['a.c']);
const skip = await tryLoadV8Cache(filePath, undefined, new Set(['other.c']));
expect(skip?.kind).toBe('skip');
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('misses when the path listing bytes are corrupted', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-failclosed-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, [{ filePath: 'a.c' }], ['a.c']);
const buf = await readFile(filePath);
const v8len = buf.readUInt16LE(14);
const pathBytesOff = 16 + v8len + 4;
const pathBytes = buf.readUInt32LE(pathBytesOff);
const pathsOff = 16 + v8len + 12;
buf.fill(0x00, pathsOff, pathsOff + Math.min(pathBytes, 1));
await writeFile(filePath, buf);
const hit = await tryLoadV8Cache(filePath, undefined, new Set(['other.c']));
expect(hit).toBeUndefined();
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('misses when a same-length path listing is rewritten without the digest', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-list-tamper-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, [{ filePath: 'a.c' }], ['a.c']);
const buf = await readFile(filePath);
const v8len = buf.readUInt16LE(14);
const pathsOff = 16 + v8len + 12;
const listing = Buffer.from('1\nb.c\n');
listing.copy(buf, pathsOff);
await writeFile(filePath, buf);
expect(await inspectV8Cache(filePath)).toBeUndefined();
expect(await tryLoadV8Cache(filePath, undefined, new Set(['other.c']))).toBeUndefined();
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('treats invalid UTF-8 path listing bytes as untrusted', () => {
expect(parseCachePathListing(Buffer.from([0x31, 0x0a, 0xff, 0x0a]))).toBeNull();
});
it('misses when the path listing is invalid UTF-8 instead of skipping', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-utf8-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, [{ filePath: 'a.c' }], ['a.c']);
const buf = await readFile(filePath);
const v8len = buf.readUInt16LE(14);
const pathBytes = buf.readUInt32LE(16 + v8len + 4);
const pathsOff = 16 + v8len + 12;
buf.fill(0xff, pathsOff, pathsOff + Math.min(pathBytes, 1));
await writeFile(filePath, buf);
const hit = await tryLoadV8Cache(filePath, undefined, new Set(['other.c']));
expect(hit).toBeUndefined();
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('deserializes when the envelope path count disagrees with the listing', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-count-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, [{ filePath: 'a.c' }], ['a.c']);
const buf = await readFile(filePath);
const v8len = buf.readUInt16LE(14);
buf.writeUInt32LE(2, 16 + v8len);
await writeFile(filePath, buf);
expect(await inspectV8Cache(filePath)).toBeUndefined();
const hit = await tryLoadV8Cache(filePath, undefined, new Set(['other.c']));
expect(hit?.kind).toBe('hit');
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('misses when internGraphStrings throws during materialization', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-intern-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, { s: 'x' });
const pool = new Map<string, string>();
pool.set = () => {
throw new Error('intern boom');
};
expect(await tryLoadV8Cache(filePath, pool)).toBeUndefined();
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('misses when the recorded Node major does not match this runtime', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-compat-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, { ok: true });
const buf = await readFile(filePath);
buf.writeUInt16LE(1, 12);
await writeFile(filePath, buf);
expect(await tryLoadV8Cache(filePath)).toBeUndefined();
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('misses when the recorded V8 version does not match this runtime', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-v8-compat-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, { ok: true });
const buf = await readFile(filePath);
buf[16] ^= 1;
await writeFile(filePath, buf);
expect(await tryLoadV8Cache(filePath)).toBeUndefined();
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('misses when the payload checksum rejects same-size corruption', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-payload-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeV8CacheFile(filePath, { ok: true });
const buf = await readFile(filePath);
expect(buf.readUInt32LE(8)).toBe(V8_CACHE_FORMAT);
const v8len = buf.readUInt16LE(14);
const pathBytes = buf.readUInt32LE(16 + v8len + 4);
const payloadOff = 16 + v8len + 12 + pathBytes;
buf[payloadOff] ^= 0xff;
await writeFile(filePath, buf);
expect(await tryLoadV8Cache(filePath)).toBeUndefined();
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('misses a garbage magic without throwing', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'v8cf-magic-'));
try {
const filePath = path.join(dir, 'shard.v8');
await writeFile(filePath, Buffer.alloc(64, 7));
expect(await tryLoadV8Cache(filePath)).toBeUndefined();
} finally {
await rm(dir, { recursive: true, force: true });
}
});
});