import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../ts/plugins.js'; import * as paths from '../ts/paths.js'; import * as fs from 'fs/promises'; // Import tspm client import { tspmIpcClient } from '../ts/client/tspm.ipcclient.js'; // Test process that will crash const CRASH_SCRIPT = ` setInterval(() => { console.log('[test] Process is running...'); }, 1000); setTimeout(() => { console.error('[test] About to crash with non-zero exit code!'); process.exit(42); }, 3000); `; tap.test('should create crash logs when process crashes', async (tools) => { const crashScriptPath = plugins.path.join(paths.tspmDir, 'test-crash-script.js'); const crashLogsDir = plugins.path.join(paths.tspmDir, 'crashlogs'); // Clean up any existing crash logs try { await fs.rm(crashLogsDir, { recursive: true, force: true }); } catch {} // Write the crash script await fs.writeFile(crashScriptPath, CRASH_SCRIPT); // Start the daemon console.log('Starting daemon...'); const daemonResult = await tools.runCommand('tsx ts/cli/tspm.cli.ts daemon start'); expect(daemonResult.exitCode).toEqual(0); // Wait for daemon to be ready await tools.wait(2000); // Add a process that will crash console.log('Adding crash test process...'); const addResult = await tools.runCommand(`tsx ts/cli/tspm.cli.ts add "node ${crashScriptPath}" --name crash-test`); expect(addResult.exitCode).toEqual(0); // Extract process ID from output const idMatch = addResult.stdout.match(/Process added with ID: (\d+)/); expect(idMatch).toBeTruthy(); const processId = parseInt(idMatch![1]); // Start the process console.log('Starting process that will crash...'); const startResult = await tools.runCommand(`tsx ts/cli/tspm.cli.ts start ${processId}`); expect(startResult.exitCode).toEqual(0); // Wait for the process to crash (it crashes after 3 seconds) console.log('Waiting for process to crash...'); await tools.wait(5000); // Check if crash log was created console.log('Checking for crash log...'); const crashLogFiles = await fs.readdir(crashLogsDir).catch(() => []); console.log(`Found ${crashLogFiles.length} crash log files:`, crashLogFiles); // Should have at least one crash log expect(crashLogFiles.length).toBeGreaterThan(0); // Find the crash log for our test process const testCrashLog = crashLogFiles.find(file => file.includes('crash-test')); expect(testCrashLog).toBeTruthy(); // Read and verify crash log content const crashLogPath = plugins.path.join(crashLogsDir, testCrashLog!); const crashLogContent = await fs.readFile(crashLogPath, 'utf-8'); console.log('Crash log content:'); console.log(crashLogContent); // Verify crash log contains expected information expect(crashLogContent).toIncludeIgnoreCase('crash report'); expect(crashLogContent).toIncludeIgnoreCase('exit code: 42'); expect(crashLogContent).toIncludeIgnoreCase('About to crash'); // Stop the process console.log('Cleaning up...'); await tools.runCommand(`tsx ts/cli/tspm.cli.ts delete ${processId}`); // Stop the daemon await tools.runCommand('tsx ts/cli/tspm.cli.ts daemon stop'); // Clean up test file await fs.unlink(crashScriptPath).catch(() => {}); }); tap.test('should create crash logs when process is killed', async (tools) => { const killScriptPath = plugins.path.join(paths.tspmDir, 'test-kill-script.js'); const crashLogsDir = plugins.path.join(paths.tspmDir, 'crashlogs'); // Write a script that runs indefinitely const KILL_SCRIPT = ` setInterval(() => { console.log('[test] Process is running and will be killed...'); }, 500); `; await fs.writeFile(killScriptPath, KILL_SCRIPT); // Start the daemon console.log('Starting daemon...'); const daemonResult = await tools.runCommand('tsx ts/cli/tspm.cli.ts daemon start'); expect(daemonResult.exitCode).toEqual(0); // Wait for daemon to be ready await tools.wait(2000); // Add a process that we'll kill console.log('Adding kill test process...'); const addResult = await tools.runCommand(`tsx ts/cli/tspm.cli.ts add "node ${killScriptPath}" --name kill-test`); expect(addResult.exitCode).toEqual(0); // Extract process ID const idMatch = addResult.stdout.match(/Process added with ID: (\d+)/); expect(idMatch).toBeTruthy(); const processId = parseInt(idMatch![1]); // Start the process console.log('Starting process to be killed...'); const startResult = await tools.runCommand(`tsx ts/cli/tspm.cli.ts start ${processId}`); expect(startResult.exitCode).toEqual(0); // Wait for process to run a bit await tools.wait(2000); // Get the actual PID of the running process const statusResult = await tools.runCommand(`tsx ts/cli/tspm.cli.ts describe ${processId}`); const pidMatch = statusResult.stdout.match(/pid:\s+(\d+)/); if (pidMatch) { const pid = parseInt(pidMatch[1]); console.log(`Killing process with PID ${pid}...`); // Kill the process with SIGTERM await tools.runCommand(`kill -TERM ${pid}`); // Wait for crash log to be created await tools.wait(3000); // Check for crash log console.log('Checking for crash log from killed process...'); const crashLogFiles = await fs.readdir(crashLogsDir).catch(() => []); const killCrashLog = crashLogFiles.find(file => file.includes('kill-test')); if (killCrashLog) { const crashLogPath = plugins.path.join(crashLogsDir, killCrashLog); const crashLogContent = await fs.readFile(crashLogPath, 'utf-8'); console.log('Kill crash log content:'); console.log(crashLogContent); // Verify it contains signal information expect(crashLogContent).toIncludeIgnoreCase('signal: SIGTERM'); } } // Clean up console.log('Cleaning up...'); await tools.runCommand(`tsx ts/cli/tspm.cli.ts delete ${processId}`); await tools.runCommand('tsx ts/cli/tspm.cli.ts daemon stop'); await fs.unlink(killScriptPath).catch(() => {}); }); export default tap.start();