Files
tspm/test/test.crashlog.ts

203 lines
6.5 KiB
TypeScript

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 { execSync } from 'child_process';
// 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);
`;
/**
* Helper to run a CLI command and capture output
*/
function runCli(cmd: string): { stdout: string; stderr: string; exitCode: number } {
try {
const stdout = execSync(cmd, { encoding: 'utf-8', stdio: 'pipe' });
return { stdout, stderr: '', exitCode: 0 };
} catch (e: any) {
return {
stdout: e.stdout?.toString() || '',
stderr: e.stderr?.toString() || '',
exitCode: e.status ?? 1,
};
}
}
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);
// Stop any existing daemon first
runCli('tsx ts/cli.ts daemon stop');
await tools.delayFor(1000);
// Start the daemon
console.log('Starting daemon...');
const daemonResult = runCli('tsx ts/cli.ts daemon start');
console.log('Daemon start output:', daemonResult.stdout, daemonResult.stderr);
// Wait for daemon to be ready
await tools.delayFor(3000);
// Add a process that will crash
console.log('Adding crash test process...');
const addResult = runCli(`tsx ts/cli.ts add "node ${crashScriptPath}" --name crash-test`);
console.log('Add output:', addResult.stdout, addResult.stderr);
// Extract process ID from output
const idMatch = addResult.stdout.match(/Assigned ID:\s*(\d+)/);
if (!idMatch) {
console.log('Could not extract process ID from output, skipping integration test');
runCli('tsx ts/cli.ts daemon stop');
await fs.unlink(crashScriptPath).catch(() => {});
return;
}
const processId = parseInt(idMatch[1]);
console.log(`Process ID: ${processId}`);
// Start the process
console.log('Starting process that will crash...');
runCli(`tsx ts/cli.ts start ${processId}`);
// Wait for the process to crash (it crashes after 3 seconds)
console.log('Waiting for process to crash...');
await tools.delayFor(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).toInclude('CRASH REPORT');
expect(crashLogContent).toInclude('Exit Code');
expect(crashLogContent).toInclude('About to crash');
// Stop the process and daemon
console.log('Cleaning up...');
runCli(`tsx ts/cli.ts delete ${processId}`);
runCli('tsx ts/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);
// Stop any existing daemon
runCli('tsx ts/cli.ts daemon stop');
await tools.delayFor(1000);
// Start the daemon
console.log('Starting daemon...');
runCli('tsx ts/cli.ts daemon start');
// Wait for daemon to be ready
await tools.delayFor(3000);
// Add a process that we'll kill
console.log('Adding kill test process...');
const addResult = runCli(`tsx ts/cli.ts add "node ${killScriptPath}" --name kill-test`);
console.log('Add output:', addResult.stdout, addResult.stderr);
// Extract process ID
const idMatch = addResult.stdout.match(/Assigned ID:\s*(\d+)/);
if (!idMatch) {
console.log('Could not extract process ID from output, skipping integration test');
runCli('tsx ts/cli.ts daemon stop');
await fs.unlink(killScriptPath).catch(() => {});
return;
}
const processId = parseInt(idMatch[1]);
// Start the process
console.log('Starting process to be killed...');
runCli(`tsx ts/cli.ts start ${processId}`);
// Wait for process to run a bit
await tools.delayFor(2000);
// Get the actual PID of the running process
const statusResult = runCli(`tsx ts/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
runCli(`kill -TERM ${pid}`);
// Wait for crash log to be created
await tools.delayFor(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);
expect(crashLogContent).toInclude('SIGTERM');
}
}
// Clean up
console.log('Cleaning up...');
runCli(`tsx ts/cli.ts delete ${processId}`);
runCli('tsx ts/cli.ts daemon stop');
await fs.unlink(killScriptPath).catch(() => {});
});
export default tap.start();