1
0

synthesis-progress.test.ts 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. /**
  2. * Progress reporting for the callback-edge synthesis tail.
  3. *
  4. * Synthesis runs AFTER the resolution bar reaches 100%, so before this it had
  5. * no progress surface at all — on synthesizer-heavy repos (e.g. large C
  6. * codebases hitting the fn-pointer pass) the CLI sat frozen at
  7. * "Resolving refs 100%" long enough that users concluded the index hung and
  8. * killed it. These tests pin (a) that indexing emits the dedicated 'linking'
  9. * phase with monotonic per-pass progress, and (b) that the advertised step
  10. * total stays in sync with the synthesizer's actual pass list.
  11. */
  12. import { describe, it, expect } from 'vitest';
  13. import * as fs from 'fs';
  14. import * as os from 'os';
  15. import * as path from 'path';
  16. import { CodeGraph, IndexProgress } from '../src/index';
  17. import { SYNTH_PASSES, SYNTH_PROGRESS_STEPS } from '../src/resolution/callback-synthesizer';
  18. describe('synthesis progress ("Linking dynamic dispatch" phase)', () => {
  19. it('SYNTH_PROGRESS_STEPS matches the synthesizer’s actual step count', () => {
  20. // The constant is cosmetic (progress denominator), but drift makes the bar
  21. // end early or jump to 100%. Steps = one per SYNTH_PASSES registry entry
  22. // (each marks exactly once, run or gated-out, sequential or pooled) plus
  23. // the fixed literal __mark('<label>') sites (the ordered Go pre-passes and
  24. // the merge/insert tail). Adding a pass = adding a registry entry, so the
  25. // constant tracks automatically; this pins the fixed-site count.
  26. const src = fs.readFileSync(
  27. path.join(__dirname, '../src/resolution/callback-synthesizer.ts'),
  28. 'utf8'
  29. );
  30. const fixedSites = (src.match(/__mark\('/g) ?? []).length;
  31. expect(SYNTH_PROGRESS_STEPS).toBe(SYNTH_PASSES.length + fixedSites);
  32. });
  33. it('indexing emits a monotonic linking phase ending at the full step count', async () => {
  34. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-synth-progress-'));
  35. try {
  36. fs.writeFileSync(path.join(dir, 'a.ts'), 'export function helper() { return 1; }\n');
  37. fs.writeFileSync(
  38. path.join(dir, 'b.ts'),
  39. "import { helper } from './a';\nexport function main() { return helper(); }\n"
  40. );
  41. const events: IndexProgress[] = [];
  42. const cg = await CodeGraph.init(dir, {
  43. index: true,
  44. onProgress: (p) => events.push(p),
  45. });
  46. await cg.close();
  47. const linking = events.filter((e) => e.phase === 'linking');
  48. expect(linking.length).toBeGreaterThan(0);
  49. // Emitted up-front so the phase label flips as soon as synthesis starts…
  50. expect(linking[0]!.current).toBe(0);
  51. // …and every step reports against the same total, monotonically.
  52. expect(linking.every((e) => e.total === SYNTH_PROGRESS_STEPS)).toBe(true);
  53. for (let i = 1; i < linking.length; i++) {
  54. expect(linking[i]!.current).toBeGreaterThanOrEqual(linking[i - 1]!.current);
  55. }
  56. expect(linking[linking.length - 1]!.current).toBe(SYNTH_PROGRESS_STEPS);
  57. // The linking phase comes after resolution has finished.
  58. const lastResolving = events.map((e) => e.phase).lastIndexOf('resolving');
  59. const firstLinking = events.map((e) => e.phase).indexOf('linking');
  60. expect(firstLinking).toBeGreaterThan(lastResolving);
  61. } finally {
  62. fs.rmSync(dir, { recursive: true, force: true });
  63. }
  64. });
  65. });