large-corpus-regressions.test.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  2. import * as fs from 'node:fs';
  3. import * as os from 'node:os';
  4. import * as path from 'node:path';
  5. import CodeGraph from '../src/index';
  6. import { QueryBuilder } from '../src/db/queries';
  7. describe('large-corpus regression fixes', () => {
  8. it('collects a dense unresolved-reference chunk without spreading it onto the V8 stack (#1558)', () => {
  9. const row = {
  10. id: 1,
  11. from_node_id: 'source',
  12. reference_name: 'target',
  13. reference_kind: 'calls',
  14. line: 1,
  15. col: 1,
  16. candidates: null,
  17. file_path: 'dense.c',
  18. language: 'c',
  19. status: 'pending',
  20. name_tail: 'target',
  21. };
  22. const denseRows = new Array(200_000).fill(row);
  23. const db = { prepare: () => ({ all: () => denseRows }) };
  24. const queries = new QueryBuilder(db as any);
  25. expect(queries.getUnresolvedReferencesByFiles(['dense.c'])).toHaveLength(200_000);
  26. });
  27. it('records an oversized file during a fresh index so sync does not retry it (#1557)', async () => {
  28. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-skipped-file-'));
  29. try {
  30. fs.writeFileSync(path.join(dir, 'oversized.py'), 'value = 1\n'.repeat(120_000));
  31. const cg = await CodeGraph.init(dir, { silent: true });
  32. const indexed = await cg.indexAll();
  33. expect(indexed.filesSkipped).toBe(1);
  34. expect(cg.getFiles().find((f) => f.path === 'oversized.py')?.errors?.[0]?.code).toBe('size_exceeded');
  35. const synced = await cg.sync();
  36. expect(synced.filesAdded).toBe(0);
  37. cg.close();
  38. } finally {
  39. fs.rmSync(dir, { recursive: true, force: true });
  40. }
  41. });
  42. it('records an oversized file through the single-file indexing path (#1557)', async () => {
  43. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-single-skipped-file-'));
  44. try {
  45. fs.writeFileSync(path.join(dir, 'oversized.py'), 'value = 1\n'.repeat(120_000));
  46. const cg = await CodeGraph.init(dir, { silent: true });
  47. const indexed = await cg.indexFiles(['oversized.py']);
  48. expect(indexed.filesSkipped).toBe(1);
  49. expect(cg.getFiles().find((f) => f.path === 'oversized.py')?.errors?.[0]?.code).toBe('size_exceeded');
  50. const synced = await cg.sync();
  51. expect(synced.filesAdded).toBe(0);
  52. expect(synced.filesModified).toBe(0);
  53. cg.close();
  54. } finally {
  55. fs.rmSync(dir, { recursive: true, force: true });
  56. }
  57. });
  58. });
  59. describe('JSX synthesis language boundary (#1560)', () => {
  60. let dir: string;
  61. beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-jsx-gate-')); });
  62. afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); });
  63. it('does not create jsx-render edges from JSX-looking text in a C-only project', async () => {
  64. fs.writeFileSync(
  65. path.join(dir, 'only.c'),
  66. 'void Foo(void) {}\nvoid parent(void) { const char *s = "<Foo/>"; }\n'
  67. );
  68. const cg = await CodeGraph.init(dir, { silent: true });
  69. await cg.indexAll();
  70. const rows = (cg as any).db.db.prepare(
  71. "SELECT count(*) AS c FROM edges WHERE json_extract(metadata, '$.synthesizedBy') = 'jsx-render'"
  72. ).get() as { c: number };
  73. cg.close();
  74. expect(rows.c).toBe(0);
  75. });
  76. it('runs for JavaScript while excluding C parents in the same project', async () => {
  77. fs.writeFileSync(
  78. path.join(dir, 'native.c'),
  79. 'void Widget(void) {}\nvoid native_parent(void) { const char *s = "<Widget/>"; }\n'
  80. );
  81. fs.writeFileSync(
  82. path.join(dir, 'ui.jsx'),
  83. 'export function Widget() { return <span/>; }\nexport function App() { return <Widget/>; }\n'
  84. );
  85. const cg = await CodeGraph.init(dir, { silent: true });
  86. await cg.indexAll();
  87. const rows = (cg as any).db.db.prepare(`
  88. SELECT source.file_path AS source_file, target.name AS target_name
  89. FROM edges e
  90. JOIN nodes source ON source.id = e.source
  91. JOIN nodes target ON target.id = e.target
  92. WHERE json_extract(e.metadata, '$.synthesizedBy') = 'jsx-render'
  93. `).all() as Array<{ source_file: string; target_name: string }>;
  94. cg.close();
  95. expect(rows).toContainEqual({ source_file: 'ui.jsx', target_name: 'Widget' });
  96. expect(rows.some((row) => row.source_file === 'native.c')).toBe(false);
  97. });
  98. });
  99. describe('failure markers vs later real results (#1557 × #1541)', () => {
  100. it('a failure marker never blocks storing a later successful parse of the same bytes', async () => {
  101. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-marker-override-'));
  102. try {
  103. const rel = 'flaky.py';
  104. const content = 'def real_fn():\n return 1\n\nclass RealClass:\n def m(self):\n return 2\n';
  105. fs.writeFileSync(path.join(dir, rel), content);
  106. const cg = await CodeGraph.init(dir, { silent: true });
  107. const { initGrammars, loadGrammarsForLanguages } = await import('../src/extraction/grammars');
  108. await initGrammars();
  109. await loadGrammarsForLanguages(['python']);
  110. const orch = (cg as any).orchestrator;
  111. const stats = fs.statSync(path.join(dir, rel));
  112. // What recordParseFailure persists when a parse worker dies: a marker
  113. // row under the SAME content hash the retry will store with.
  114. await orch.storeExtractionResult(rel, content, 'python', stats, {
  115. nodes: [], edges: [], unresolvedReferences: [],
  116. errors: [{ message: 'Worker exited with code 1', filePath: rel, severity: 'error', code: 'parse_error' }],
  117. durationMs: 0,
  118. });
  119. expect(cg.getFile(rel)?.nodeCount).toBe(0);
  120. // The retry pass succeeds with identical bytes — the marker must be
  121. // replaced, not treated as "no changes".
  122. const { extractFromSource } = await import('../src/extraction/tree-sitter');
  123. const real = extractFromSource(rel, content, 'python');
  124. expect(real.nodes.length).toBeGreaterThan(0);
  125. await orch.storeExtractionResult(rel, content, 'python', stats, real);
  126. expect(cg.getFile(rel)?.nodeCount).toBe(real.nodes.length);
  127. expect(cg.getNodesInFile(rel).map((n: { name: string }) => n.name)).toContain('real_fn');
  128. cg.close();
  129. } finally {
  130. fs.rmSync(dir, { recursive: true, force: true });
  131. }
  132. });
  133. });