/**
* Hot-Reload Race Condition Test
*
* Tests whether concurrent tool calls during infrastructure hot-reload
* create race conditions, dropped requests, or inconsistent state.
*
* Test Strategy:
* 1. Start rapid-fire tool calls (10 calls/second)
* 2. Trigger hot-reload by modifying ConversationManager
* 3. Continue rapid-fire calls during reload window
* 4. Verify all requests complete successfully
* 5. Verify state consistency (no lost approvals, no duplicates)
*/
import { spawn } from 'child_process';
import { writeFileSync, readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Configuration
const TOTAL_CALLS = 50; // Total tool calls to make
const CALLS_PER_SECOND = 10; // Rate of calls
const HOT_RELOAD_AT_CALL = 15; // Trigger hot-reload after this many calls
const CALL_INTERVAL = 1000 / CALLS_PER_SECOND; // ms between calls
// Test state
const results = [];
let callsMade = 0;
let hotReloadTriggered = false;
/**
* Call the example-tool via MCP
*/
async function callTool(action, callNumber) {
return new Promise((resolve) => {
const startTime = Date.now();
// Simulate MCP tool call via claude CLI
const proc = spawn('claude', [
'--dangerously-skip-permissions',
'-p', `Use the mcp__mcp-tool-factory__example-tool with action: "${action}"`,
], {
stdio: ['pipe', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
proc.stdout.on('data', (data) => {
stdout += data.toString();
});
proc.stderr.on('data', (data) => {
stderr += data.toString();
});
proc.on('close', (code) => {
const latency = Date.now() - startTime;
const result = {
callNumber,
action,
code,
latency,
success: code === 0,
hotReloadActive: hotReloadTriggered,
timestamp: Date.now(),
};
results.push(result);
console.log(`[Call ${callNumber}] ${action} → ${code === 0 ? '✅' : '❌'} (${latency}ms) ${hotReloadTriggered ? '🔥' : ''}`);
resolve(result);
});
});
}
/**
* Trigger hot-reload by modifying ConversationManager
*/
function triggerHotReload() {
console.log('\n🔥 TRIGGERING HOT-RELOAD 🔥\n');
const filePath = join(__dirname, 'src', 'core', 'conversation-manager.ts');
const content = readFileSync(filePath, 'utf8');
// Add a comment with timestamp to force file change
const modifiedContent = content.replace(
'class ConversationManager {',
`class ConversationManager {\n // Hot-reload test: ${Date.now()}`
);
writeFileSync(filePath, modifiedContent);
console.log('📝 Modified conversation-manager.ts');
// Trigger build
const buildProc = spawn('npm', ['run', 'build'], {
cwd: __dirname,
stdio: 'inherit',
});
buildProc.on('close', (code) => {
if (code === 0) {
console.log('✅ Build complete - hot-reload should trigger\n');
} else {
console.log('❌ Build failed\n');
}
});
hotReloadTriggered = true;
}
/**
* Revert ConversationManager changes
*/
function revertChanges() {
console.log('\n🔄 Reverting changes...');
const filePath = join(__dirname, 'src', 'core', 'conversation-manager.ts');
const content = readFileSync(filePath, 'utf8');
// Remove the test comment
const cleanContent = content.replace(/\n \/\/ Hot-reload test: \d+/, '');
writeFileSync(filePath, cleanContent);
// Rebuild
const buildProc = spawn('npm', ['run', 'build'], {
cwd: __dirname,
stdio: 'inherit',
});
buildProc.on('close', () => {
console.log('✅ Reverted and rebuilt\n');
});
}
/**
* Analyze test results
*/
function analyzeResults() {
console.log('\n' + '='.repeat(80));
console.log('RACE CONDITION TEST RESULTS');
console.log('='.repeat(80));
const totalCalls = results.length;
const successfulCalls = results.filter(r => r.success).length;
const failedCalls = results.filter(r => !r.success).length;
const callsDuringHotReload = results.filter(r => r.hotReloadActive).length;
const failuresDuringHotReload = results.filter(r => r.hotReloadActive && !r.success).length;
const avgLatency = results.reduce((sum, r) => sum + r.latency, 0) / totalCalls;
const maxLatency = Math.max(...results.map(r => r.latency));
const minLatency = Math.min(...results.map(r => r.latency));
console.log(`\nTotal Calls: ${totalCalls}`);
console.log(`Successful: ${successfulCalls} (${(successfulCalls/totalCalls*100).toFixed(1)}%)`);
console.log(`Failed: ${failedCalls} (${(failedCalls/totalCalls*100).toFixed(1)}%)`);
console.log(`\nCalls During Hot-Reload: ${callsDuringHotReload}`);
console.log(`Failures During Reload: ${failuresDuringHotReload}`);
console.log(`\nLatency:`);
console.log(` Average: ${avgLatency.toFixed(0)}ms`);
console.log(` Min: ${minLatency}ms`);
console.log(` Max: ${maxLatency}ms`);
// Check for race conditions
console.log('\n' + '-'.repeat(80));
console.log('RACE CONDITION ANALYSIS:');
console.log('-'.repeat(80));
if (failuresDuringHotReload > 0) {
console.log('❌ RACE CONDITION DETECTED');
console.log(` ${failuresDuringHotReload} requests failed during hot-reload window`);
console.log(' Recommendation: Implement request barrier or instance lock');
} else if (callsDuringHotReload === 0) {
console.log('⚠️ INCONCLUSIVE - No calls overlapped with hot-reload');
console.log(' Recommendation: Increase call rate or reduce reload time');
} else {
console.log('✅ NO RACE CONDITION');
console.log(` ${callsDuringHotReload} calls succeeded during hot-reload`);
console.log(' Single-threaded event loop provides safety');
}
console.log('\n' + '='.repeat(80) + '\n');
}
/**
* Main test execution
*/
async function runTest() {
console.log('Starting Hot-Reload Race Condition Test');
console.log(`Configuration: ${TOTAL_CALLS} calls @ ${CALLS_PER_SECOND}/sec`);
console.log(`Hot-reload trigger: After call #${HOT_RELOAD_AT_CALL}\n`);
const testSequence = [
// Phase 1: Baseline calls (identity queries)
...Array(5).fill('identity'),
// Phase 2: Approval flow setup
'approve:write-file',
'write-file',
// Phase 3: Rapid calls around hot-reload trigger point
...Array(HOT_RELOAD_AT_CALL - 7).fill('greet'),
// Phase 4: Calls during hot-reload window
...Array(20).fill('identity'),
// Phase 5: Post-reload verification
'write-file', // Should still be approved
...Array(TOTAL_CALLS - HOT_RELOAD_AT_CALL - 21).fill('greet'),
];
for (let i = 0; i < testSequence.length; i++) {
const action = testSequence[i];
// Trigger hot-reload at specific call
if (i === HOT_RELOAD_AT_CALL && !hotReloadTriggered) {
triggerHotReload();
}
// Make the call
callTool(action, i + 1);
// Wait before next call
await new Promise(resolve => setTimeout(resolve, CALL_INTERVAL));
}
// Wait for all calls to complete
console.log('\n⏳ Waiting for all calls to complete...\n');
await new Promise(resolve => setTimeout(resolve, 5000));
// Analyze results
analyzeResults();
// Clean up
revertChanges();
}
// Run test
runTest().catch(console.error);