Files
codegraph/__tests__/synthesis-progress.test.ts
T
ad5300a601 fix(ui): show synthesis as a 'Linking dynamic dispatch' phase; mute node:sqlite warning spam (#1299)
Two first-run UX bugs surfaced by indexing a real 1,342-file C repo:

1. After 'Resolving refs' hit 100%, the ~40 dynamic-dispatch synthesis
   passes ran with no progress surface, so the bar sat frozen at 100%
   long enough to read as a hang (the C fn-pointer pass alone can hold
   for a while on C-heavy repos). Synthesis now reports per-pass
   progress through a new 'linking' IndexProgress phase, rendered as
   'Linking dynamic dispatch'. The step total is pinned by a test to
   the synthesizer's actual __mark() count so adding a pass without
   bumping it fails loudly.

2. node:sqlite's ExperimentalWarning is emitted once per THREAD, so the
   main process plus every parse worker printed it mid-index,
   interleaved with the progress UI. All launch paths now pass
   --disable-warning=ExperimentalWarning: both bundle launchers, the
   Windows npm-shim invocation, and the CLI self-relaunch
   (NODE_RUNTIME_FLAGS, deliberately excluded from the re-exec gate so
   an older installed launcher never triggers a pointless re-exec, and
   version-gated off nodes older than the flag).

Verified end-to-end on the same repo: zero warnings, live linking bar,
byte-identical graph (50,520 nodes / 148,232 edges). Full suite green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 19:44:09 -05:00

68 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Progress reporting for the callback-edge synthesis tail.
*
* Synthesis runs AFTER the resolution bar reaches 100%, so before this it had
* no progress surface at all — on synthesizer-heavy repos (e.g. large C
* codebases hitting the fn-pointer pass) the CLI sat frozen at
* "Resolving refs 100%" long enough that users concluded the index hung and
* killed it. These tests pin (a) that indexing emits the dedicated 'linking'
* phase with monotonic per-pass progress, and (b) that the advertised step
* total stays in sync with the synthesizer's actual pass list.
*/
import { describe, it, expect } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph, IndexProgress } from '../src/index';
import { SYNTH_PROGRESS_STEPS } from '../src/resolution/callback-synthesizer';
describe('synthesis progress ("Linking dynamic dispatch" phase)', () => {
it('SYNTH_PROGRESS_STEPS matches the synthesizers actual __mark() step count', () => {
// The constant is cosmetic (progress denominator), but drift makes the bar
// end early or jump to 100% — adding a pass must bump it. Every step site
// calls __mark('<label>') with a string literal, so count those.
const src = fs.readFileSync(
path.join(__dirname, '../src/resolution/callback-synthesizer.ts'),
'utf8'
);
const stepSites = (src.match(/__mark\('/g) ?? []).length;
expect(SYNTH_PROGRESS_STEPS).toBe(stepSites);
});
it('indexing emits a monotonic linking phase ending at the full step count', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-synth-progress-'));
try {
fs.writeFileSync(path.join(dir, 'a.ts'), 'export function helper() { return 1; }\n');
fs.writeFileSync(
path.join(dir, 'b.ts'),
"import { helper } from './a';\nexport function main() { return helper(); }\n"
);
const events: IndexProgress[] = [];
const cg = await CodeGraph.init(dir, {
index: true,
onProgress: (p) => events.push(p),
});
await cg.close();
const linking = events.filter((e) => e.phase === 'linking');
expect(linking.length).toBeGreaterThan(0);
// Emitted up-front so the phase label flips as soon as synthesis starts…
expect(linking[0]!.current).toBe(0);
// …and every step reports against the same total, monotonically.
expect(linking.every((e) => e.total === SYNTH_PROGRESS_STEPS)).toBe(true);
for (let i = 1; i < linking.length; i++) {
expect(linking[i]!.current).toBeGreaterThanOrEqual(linking[i - 1]!.current);
}
expect(linking[linking.length - 1]!.current).toBe(SYNTH_PROGRESS_STEPS);
// The linking phase comes after resolution has finished.
const lastResolving = events.map((e) => e.phase).lastIndexOf('resolving');
const firstLinking = events.map((e) => e.phase).indexOf('linking');
expect(firstLinking).toBeGreaterThan(lastResolving);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});