iterate-nodes-by-kind.test.ts 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. /**
  2. * `QueryBuilder.iterateNodesByKind` — the streaming scan that fixes the #610
  3. * OOM. The dynamic-edge synthesizers used to `getNodesByKind('function')` /
  4. * `('method')`, materializing every symbol into one array (gigabytes on a
  5. * symbol-dense project → JS-heap OOM). They now iterate. These tests pin the
  6. * two properties that refactor relies on: the streamed set equals the eager
  7. * set, and an open iterator cursor coexists with other queries on the same
  8. * connection.
  9. */
  10. import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  11. import * as fs from 'fs';
  12. import * as path from 'path';
  13. import * as os from 'os';
  14. import CodeGraph from '../src/index';
  15. describe('iterateNodesByKind (#610 streaming)', () => {
  16. let dir: string;
  17. let cg: CodeGraph;
  18. beforeEach(async () => {
  19. dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-iter-'));
  20. fs.mkdirSync(path.join(dir, 'src'));
  21. fs.writeFileSync(
  22. path.join(dir, 'src', 'a.ts'),
  23. 'export function foo() { return 1; }\n' +
  24. 'export function bar() { return 2; }\n' +
  25. 'export class C { m() { return 3; } n() { return 4; } }\n'
  26. );
  27. cg = CodeGraph.initSync(dir, { config: { include: ['**/*.ts'], exclude: [] } });
  28. await cg.indexAll();
  29. });
  30. afterEach(() => {
  31. try { cg.close(); } catch { /* ignore */ }
  32. fs.rmSync(dir, { recursive: true, force: true });
  33. });
  34. it('yields exactly the same nodes as the eager getNodesByKind', () => {
  35. const q = (cg as unknown as { queries: any }).queries;
  36. for (const kind of ['function', 'method', 'class'] as const) {
  37. const eager = q.getNodesByKind(kind).map((n: any) => n.id).sort();
  38. const streamed = [...q.iterateNodesByKind(kind)].map((n: any) => n.id).sort();
  39. expect(streamed).toEqual(eager);
  40. }
  41. // sanity: the fixture actually produced functions + methods to stream
  42. expect([...q.iterateNodesByKind('function')].length).toBeGreaterThan(0);
  43. expect([...q.iterateNodesByKind('method')].length).toBeGreaterThan(0);
  44. });
  45. it('keeps the cursor valid while other queries run mid-iteration', () => {
  46. const q = (cg as unknown as { queries: any }).queries;
  47. let seen = 0;
  48. for (const n of q.iterateNodesByKind('function')) {
  49. // A different prepared statement stepped on the same connection while the
  50. // iterator's cursor is open must not corrupt it.
  51. const again = q.getNodeById(n.id);
  52. expect(again?.id).toBe(n.id);
  53. seen++;
  54. }
  55. expect(seen).toBe(q.getNodesByKind('function').length);
  56. });
  57. });