cli-unlock.test.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { afterEach, beforeEach, describe, expect, it } from 'vitest';
  2. import { execFile, execFileSync } from 'child_process';
  3. import * as fs from 'fs';
  4. import * as net from 'net';
  5. import * as os from 'os';
  6. import * as path from 'path';
  7. import { CodeGraph } from '../src';
  8. import { getDaemonPidPath, getDaemonSocketPath } from '../src/mcp/daemon-paths';
  9. import { CodeGraphPackageVersion } from '../src/mcp/version';
  10. const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js');
  11. function runCodegraph(args: string[], cwd: string): string {
  12. return execFileSync(process.execPath, [BIN, ...args], {
  13. cwd,
  14. encoding: 'utf8',
  15. env: { ...process.env, CODEGRAPH_NO_DAEMON: '1' },
  16. stdio: ['ignore', 'pipe', 'pipe'],
  17. });
  18. }
  19. function runCodegraphAsync(args: string[], cwd: string): Promise<string> {
  20. return new Promise((resolve, reject) => {
  21. execFile(
  22. process.execPath,
  23. [BIN, ...args],
  24. { cwd, encoding: 'utf8', env: { ...process.env, CODEGRAPH_NO_DAEMON: '1' } },
  25. (error, stdout, stderr) => {
  26. if (error) reject(new Error(`${error.message}\n${stderr}`));
  27. else resolve(stdout);
  28. },
  29. );
  30. });
  31. }
  32. describe('codegraph unlock — daemon artifact recovery (#1553)', () => {
  33. let tempDir: string;
  34. beforeEach(() => {
  35. tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-unlock-'));
  36. const cg = CodeGraph.initSync(tempDir);
  37. cg.close();
  38. });
  39. afterEach(() => {
  40. fs.rmSync(tempDir, { recursive: true, force: true });
  41. });
  42. it('removes indexing and phantom-daemon artifacts, then permits indexing', () => {
  43. const graphDir = path.join(tempDir, '.codegraph');
  44. const pidPath = getDaemonPidPath(tempDir);
  45. const socketPath = getDaemonSocketPath(tempDir);
  46. fs.writeFileSync(path.join(graphDir, 'codegraph.lock'), 'stale\n');
  47. fs.writeFileSync(pidPath, JSON.stringify({
  48. pid: process.pid,
  49. version: CodeGraphPackageVersion,
  50. socketPath,
  51. startedAt: Date.now() - 60_000,
  52. }));
  53. if (process.platform !== 'win32') fs.writeFileSync(socketPath, 'stale\n');
  54. const output = runCodegraph(['unlock', tempDir], tempDir);
  55. expect(output).toContain('Removed stale lock artifacts');
  56. expect(fs.existsSync(path.join(graphDir, 'codegraph.lock'))).toBe(false);
  57. expect(fs.existsSync(pidPath)).toBe(false);
  58. if (process.platform !== 'win32') expect(fs.existsSync(socketPath)).toBe(false);
  59. expect(() => process.kill(process.pid, 0)).not.toThrow();
  60. expect(() => runCodegraph(['index', '--quiet', tempDir], tempDir)).not.toThrow();
  61. });
  62. it('preserves artifacts when the recorded live daemon answers the socket hello', async () => {
  63. const pidPath = getDaemonPidPath(tempDir);
  64. const socketPath = getDaemonSocketPath(tempDir);
  65. const server = net.createServer((socket) => {
  66. socket.end(JSON.stringify({
  67. codegraph: CodeGraphPackageVersion,
  68. pid: process.pid,
  69. socketPath,
  70. protocol: 1,
  71. }) + '\n');
  72. });
  73. await new Promise<void>((resolve, reject) => {
  74. server.once('error', reject);
  75. server.listen(socketPath, resolve);
  76. });
  77. fs.writeFileSync(pidPath, JSON.stringify({
  78. pid: process.pid,
  79. version: CodeGraphPackageVersion,
  80. socketPath,
  81. startedAt: Date.now(),
  82. }));
  83. try {
  84. const output = await runCodegraphAsync(['unlock', tempDir], tempDir);
  85. expect(output).toContain('No stale lock files found');
  86. expect(fs.existsSync(pidPath)).toBe(true);
  87. } finally {
  88. await new Promise<void>((resolve) => server.close(() => resolve()));
  89. }
  90. });
  91. });