diff --git a/scripts/lib/fork-engine.ts b/scripts/lib/fork-engine.ts index 8e548e94..12a704a5 100644 --- a/scripts/lib/fork-engine.ts +++ b/scripts/lib/fork-engine.ts @@ -76,7 +76,7 @@ export function forkWorker(scriptPath, envKey, workerName, argv = [], timeoutMs console.error(`\n[fork] Spawning ${workerName} worker (pid isolation)...`); const child = fork(scriptPath, argv, { - env: { ...process.env, [envKey]: workerName }, + env: { ...process.env, [envKey]: workerName, CODEGRAPH_ENGINE: workerName }, stdio: ['ignore', 'pipe', 'inherit', 'ipc'], }); diff --git a/src/db/connection.ts b/src/db/connection.ts index 333bc6ce..bc8ef827 100644 --- a/src/db/connection.ts +++ b/src/db/connection.ts @@ -360,7 +360,7 @@ function openRepoNative(customDbPath?: string): { repo: Repository; close(): voi */ export function openRepo( customDbPath?: string, - opts: { repo?: Repository } = {}, + opts: { repo?: Repository; engine?: 'native' | 'wasm' | 'auto' } = {}, ): { repo: Repository; close(): void } { if (opts.repo != null) { if (!(opts.repo instanceof Repository)) { @@ -371,8 +371,12 @@ export function openRepo( return { repo: opts.repo, close() {} }; } + // Respect explicit engine selection: opts.engine > CODEGRAPH_ENGINE env > auto. + // This ensures --engine wasm and benchmark workers bypass the native path. + const engine = opts.engine || process.env.CODEGRAPH_ENGINE || 'auto'; + // Try native rusqlite path first (Phase 6.14) - if (isNativeAvailable()) { + if (engine !== 'wasm' && isNativeAvailable()) { try { return openRepoNative(customDbPath); } catch (e) { @@ -412,8 +416,11 @@ export function openReadonlyWithNative(customPath?: string): { } { const db = openReadonlyOrFail(customPath); + // Respect explicit engine selection, consistent with openRepo(). + const engine = process.env.CODEGRAPH_ENGINE || 'auto'; + let nativeDb: NativeDatabase | undefined; - if (isNativeAvailable()) { + if (engine !== 'wasm' && isNativeAvailable()) { try { const dbPath = findDbPath(customPath); const native = getNative(); diff --git a/tests/benchmarks/regression-guard.test.ts b/tests/benchmarks/regression-guard.test.ts index 918de7fa..92b0780c 100644 --- a/tests/benchmarks/regression-guard.test.ts +++ b/tests/benchmarks/regression-guard.test.ts @@ -65,8 +65,19 @@ const SKIP_VERSIONS = new Set(['3.8.0']); * - 3.9.0:1-file rebuild — native incremental path re-runs graph-wide phases * (structureMs, AST, CFG, dataflow) on single-file rebuilds. Documented in * BUILD-BENCHMARKS.md Notes section with phase-level breakdown. + * + * - 3.9.0:fnDeps depth {1,3,5} — openRepo() always routed queries through the + * native NAPI path regardless of engine selection, so both "wasm" and "native" + * benchmark workers measured native rusqlite open/close overhead (~27ms vs + * ~10ms with direct better-sqlite3). Fixed by wiring CODEGRAPH_ENGINE through + * openRepo(); v3.10.0 benchmarks will reflect the corrected measurements. */ -const KNOWN_REGRESSIONS = new Set(['3.9.0:1-file rebuild']); +const KNOWN_REGRESSIONS = new Set([ + '3.9.0:1-file rebuild', + '3.9.0:fnDeps depth 1', + '3.9.0:fnDeps depth 3', + '3.9.0:fnDeps depth 5', +]); /** * Maximum minor-version gap allowed for comparison. When the nearest @@ -332,6 +343,38 @@ describe('Benchmark regression guard', () => { 'INCREMENTAL_BENCHMARK_DATA', ); + // Warn when KNOWN_REGRESSIONS entries are stale (more than 1 minor version + // behind the current package version). This makes the stale-exemption + // problem self-detecting rather than requiring manual bookkeeping. + test('KNOWN_REGRESSIONS entries are not stale', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const pkgVersion: string = JSON.parse( + fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'), + ).version; + const stale: string[] = []; + for (const entry of KNOWN_REGRESSIONS) { + const colonIdx = entry.indexOf(':'); + if (colonIdx === -1) continue; + const entryVersion = entry.slice(0, colonIdx); + const gap = minorGap(entryVersion, pkgVersion); + if (gap > 1) { + stale.push( + `${entry} (version ${entryVersion} is ${gap} minor versions behind ${pkgVersion})`, + ); + } + } + if (stale.length > 0) { + console.warn( + `[regression-guard] Stale KNOWN_REGRESSIONS entries — remove after verifying corrected data:\n ${stale.join('\n ')}`, + ); + } + expect( + stale.length, + `KNOWN_REGRESSIONS has ${stale.length} stale entries (>1 minor version behind ${pkgVersion}). ` + + `Remove them after verifying the corrected benchmark data has landed:\n ${stale.join('\n ')}`, + ).toBe(0); + }); + // Validate newest-first ordering assumption for all history arrays test('build history is sorted newest-first', () => { assertNewestFirst(buildHistory, 'Build benchmark');