1
0

wasm-runtime-flags.test.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. /**
  2. * WASM runtime flags — the workaround for the V8 turboshaft WASM Zone OOM
  3. * (`Fatal process out of memory: Zone`) that crashed `codegraph index` on large
  4. * polyglot repos under Node >= 22. See issues #293 and #298.
  5. *
  6. * The crash was reproduced with the real indexer on the bundled Node 24 runtime;
  7. * empirically only `--liftoff-only` prevents it (`--no-wasm-tier-up` /
  8. * `--no-wasm-dynamic-tiering` do not), and the flag must be on node's command
  9. * line — `setFlagsFromString`, worker `execArgv`, and `NODE_OPTIONS` all fail.
  10. * These tests pin that contract so it can't silently regress.
  11. */
  12. import { describe, it, expect } from 'vitest';
  13. import { spawnSync } from 'child_process';
  14. import * as fs from 'fs';
  15. import * as os from 'os';
  16. import * as path from 'path';
  17. import {
  18. WASM_RUNTIME_FLAGS,
  19. processHasWasmRuntimeFlags,
  20. buildRelaunchArgv,
  21. } from '../src/extraction/wasm-runtime-flags';
  22. describe('WASM_RUNTIME_FLAGS', () => {
  23. it('pins --liftoff-only (the only flag shown to stop the turboshaft Zone OOM)', () => {
  24. // On Node 24, --no-wasm-tier-up and --no-wasm-dynamic-tiering both still
  25. // crash; only --liftoff-only forces grammars onto the Liftoff baseline and
  26. // off the optimizing tier. Pin it so it can't be swapped for an ineffective
  27. // flag.
  28. expect(WASM_RUNTIME_FLAGS).toContain('--liftoff-only');
  29. });
  30. it('every flag is a real, accepted flag on the running Node/V8 runtime', () => {
  31. // node rejects unknown CLI flags at startup, so a renamed/removed flag would
  32. // break the bundled launcher and make the relaunch guard a silent no-op.
  33. // Prove each flag actually launches node here.
  34. const res = spawnSync(
  35. process.execPath,
  36. [...WASM_RUNTIME_FLAGS, '-e', 'process.exit(0)'],
  37. { encoding: 'utf8' }
  38. );
  39. expect(res.status, `node rejected ${WASM_RUNTIME_FLAGS.join(' ')}:\n${res.stderr}`).toBe(0);
  40. });
  41. });
  42. describe('processHasWasmRuntimeFlags', () => {
  43. it('is true only when every required flag is present', () => {
  44. expect(processHasWasmRuntimeFlags(['--liftoff-only'])).toBe(true);
  45. expect(processHasWasmRuntimeFlags(['--liftoff-only', '--enable-source-maps'])).toBe(true);
  46. });
  47. it('is false when the flags are absent', () => {
  48. expect(processHasWasmRuntimeFlags([])).toBe(false);
  49. expect(processHasWasmRuntimeFlags(['--max-old-space-size=4096'])).toBe(false);
  50. });
  51. });
  52. describe('buildRelaunchArgv', () => {
  53. it('places the wasm flags first, then the script and its args', () => {
  54. expect(buildRelaunchArgv('/x/codegraph.js', ['index', '/repo'], [])).toEqual([
  55. '--liftoff-only',
  56. '/x/codegraph.js',
  57. 'index',
  58. '/repo',
  59. ]);
  60. });
  61. it('preserves other existing node flags without duplicating ours', () => {
  62. expect(
  63. buildRelaunchArgv('/x/codegraph.js', ['status'], ['--liftoff-only', '--enable-source-maps'])
  64. ).toEqual(['--liftoff-only', '--enable-source-maps', '/x/codegraph.js', 'status']);
  65. });
  66. it('produces an argv that actually launches node WITH the flag applied', () => {
  67. // End-to-end proof of the delivery mechanism without needing the crash:
  68. // run the constructed argv and confirm the child sees the flag in execArgv.
  69. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-relaunch-'));
  70. try {
  71. const harness = path.join(dir, 'harness.cjs');
  72. fs.writeFileSync(harness, 'process.stdout.write(JSON.stringify(process.execArgv));');
  73. const res = spawnSync(process.execPath, buildRelaunchArgv(harness, []), { encoding: 'utf8' });
  74. expect(res.status, res.stderr).toBe(0);
  75. expect(JSON.parse(res.stdout)).toContain('--liftoff-only');
  76. } finally {
  77. fs.rmSync(dir, { recursive: true, force: true });
  78. }
  79. });
  80. });