global-test-manager.cjsā¢9.89 kB
#!/usr/bin/env node
/**
* Global Test Manager - Singleton system that can be called from any tool
* Ensures only one test runs globally and benefits all tool calls
*/
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const GLOBAL_STATUS_FILE = path.join(__dirname, 'global-test-status.json');
const LOCK_FILE = path.join(__dirname, 'global-test.lock');
class GlobalTestManager {
// Check if any test is running globally
static isTestRunning() {
try {
// Check lock file first (fast check)
if (!fs.existsSync(LOCK_FILE)) {
return { isRunning: false };
}
// Check status file for details
if (!fs.existsSync(GLOBAL_STATUS_FILE)) {
return { isRunning: false, hasLock: true };
}
const status = JSON.parse(fs.readFileSync(GLOBAL_STATUS_FILE, 'utf8'));
// Verify process is still alive
if (status.isRunning && status.pid) {
const ageMinutes = (Date.now() - new Date(status.lastUpdate)) / (1000 * 60);
// If status is older than 10 minutes, assume dead
if (ageMinutes > 10) {
return { isRunning: false, reason: 'Status too old' };
}
return {
isRunning: true,
pid: status.pid,
startedAt: status.startedAt,
message: status.message,
progress: status.progress,
testType: status.testType,
benefits: status.benefits || []
};
}
return { isRunning: false };
} catch (error) {
console.error('ā Failed to check global test status:', error.message);
return { isRunning: false, reason: error.message };
}
}
// Start global test (anywhere in system)
static async startGlobalTest(testType = 'complete', callerTool = 'unknown') {
const currentStatus = this.isTestRunning();
if (currentStatus.isRunning) {
// Add benefit for this caller
this.addBenefit(callerTool, currentStatus);
return {
success: true,
alreadyRunning: true,
message: `Test already running (PID: ${currentStatus.pid}) - ${callerTool} benefiting from active test`,
status: currentStatus,
callerTool: callerTool,
testType: testType
};
}
try {
// Create lock file
fs.writeFileSync(LOCK_FILE, JSON.stringify({
locked: true,
timestamp: new Date().toISOString(),
initiator: callerTool
}));
let testScript;
if (testType === 'dummy') {
testScript = 'dummy-test-runner.cjs';
} else if (testType === 'simple') {
testScript = 'simple-test-runner.cjs';
} else {
// Default to complete system test
testScript = '../../../test-complete-system.cjs';
}
const testPath = path.join(__dirname, testScript);
console.log(`š Starting global test: ${testScript} (requested by: ${callerTool})`);
// Start test as detached background process
const testProcess = spawn('node', [testPath], {
detached: true,
stdio: ['ignore', 'pipe', 'pipe'],
env: {
...process.env,
NODE_ENV: 'production',
GLOBAL_TEST_CALLER: callerTool,
GLOBAL_TEST_TYPE: testType
}
});
testProcess.unref();
// Give it time to initialize
await new Promise(resolve => setTimeout(resolve, 2000));
// Create global status
const newStatus = {
isRunning: true,
pid: testProcess.pid,
startedAt: new Date().toISOString(),
lastUpdate: new Date().toISOString(),
message: `${testType} test started by ${callerTool}`,
progress: 0,
testType: testType,
initiator: callerTool,
benefits: [
{
tool: callerTool,
timestamp: new Date().toISOString(),
action: 'initiated',
benefit: 'Started global test'
}
]
};
fs.writeFileSync(GLOBAL_STATUS_FILE, JSON.stringify(newStatus, null, 2));
return {
success: true,
alreadyRunning: false,
message: `Global ${testType} test started successfully (PID: ${testProcess.pid})`,
status: newStatus,
callerTool: callerTool,
testType: testType
};
} catch (error) {
// Clean up lock on error
try { fs.unlinkSync(LOCK_FILE); } catch (e) {}
console.error('ā Failed to start global test:', error.message);
return {
success: false,
message: `Failed to start global test: ${error.message}`,
callerTool: callerTool
};
}
}
// Add benefit record for caller
static addBenefit(callerTool, currentStatus) {
try {
if (!fs.existsSync(GLOBAL_STATUS_FILE)) return;
const status = JSON.parse(fs.readFileSync(GLOBAL_STATUS_FILE, 'utf8'));
if (!status.benefits) status.benefits = [];
status.benefits.push({
tool: callerTool,
timestamp: new Date().toISOString(),
action: 'benefited',
benefit: `Tool ${callerTool} used existing test (PID: ${currentStatus.pid})`
});
status.lastUpdate = new Date().toISOString();
fs.writeFileSync(GLOBAL_STATUS_FILE, JSON.stringify(status, null, 2));
console.log(`š” ${callerTool} is benefiting from active test (PID: ${currentStatus.pid})`);
} catch (error) {
console.error('ā Failed to add benefit:', error.message);
}
}
// Stop global test
static async stopGlobalTest() {
const currentStatus = this.isTestRunning();
if (!currentStatus.isRunning) {
return {
success: true,
alreadyStopped: true,
message: 'No global test is currently running'
};
}
try {
const pid = currentStatus.pid;
console.log(`š Stopping global test (PID: ${pid})`);
// Graceful stop
process.kill(pid, 'SIGTERM');
await new Promise(resolve => setTimeout(resolve, 3000));
// Force stop if needed
const statusAfter = this.isTestRunning();
if (statusAfter.isRunning) {
console.log(`ā” Force killing global test (PID: ${pid})`);
process.kill(pid, 'SIGKILL');
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Clean up files
try {
fs.unlinkSync(GLOBAL_STATUS_FILE);
fs.unlinkSync(LOCK_FILE);
} catch (error) {
console.warn('ā ļø Could not clean up files:', error.message);
}
return {
success: true,
message: `Global test stopped successfully (was PID: ${pid})`
};
} catch (error) {
console.error('ā Failed to stop global test:', error.message);
return {
success: false,
message: `Failed to stop global test: ${error.message}`
};
}
}
// Get global status
static getGlobalStatus() {
const status = this.isTestRunning();
return {
isRunning: status.isRunning,
pid: status.pid || null,
startedAt: status.startedAt || null,
lastUpdate: status.lastUpdate || null,
message: status.message || null,
progress: status.progress || null,
testType: status.testType || null,
initiator: status.initiator || null,
benefits: status.benefits || [],
lockFile: fs.existsSync(LOCK_FILE),
statusFile: fs.existsSync(GLOBAL_STATUS_FILE)
};
}
// Tool interface - can be called from anywhere
static async ensureTestForTool(toolName, testType = 'complete') {
console.log(`š§ Tool "${toolName}" requesting test access...`);
const result = await this.startGlobalTest(testType, toolName);
if (result.success) {
console.log(`ā
Tool "${toolName}" has test access:`);
console.log(` Test Type: ${result.testType}`);
console.log(` Status: ${result.alreadyRunning ? 'Benefiting from existing' : 'Started new'}`);
console.log(` PID: ${result.status.pid}`);
// Log benefits for tracking
if (result.status.benefits) {
console.log(` Total Benefits: ${result.status.benefits.length}`);
}
}
return result;
}
}
// Export for use by any tool
module.exports = { GlobalTestManager };
// CLI interface for direct use
if (require.main === module) {
const args = process.argv.slice(2);
const command = args[0];
const toolName = args[1] || 'cli';
const testType = args[2] || 'complete';
switch (command) {
case 'start':
GlobalTestManager.ensureTestForTool(toolName, testType).then(result => {
console.log(JSON.stringify(result, null, 2));
});
break;
case 'stop':
GlobalTestManager.stopGlobalTest().then(result => {
console.log(JSON.stringify(result, null, 2));
});
break;
case 'status':
const status = GlobalTestManager.getGlobalStatus();
console.log(JSON.stringify(status, null, 2));
break;
default:
console.log(`
Global Test Manager - Universal Singleton Test System
Usage: node global-test-manager.cjs <command> [toolName] [testType]
Commands:
start [toolName] [testType] Start test (toolName benefits from existing if running)
stop Stop any running test
status Show global test status
Test Types:
complete Full system test (test-complete-system.cjs)
simple Simple database test (simple-test-runner.cjs)
dummy Dummy test for validation (dummy-test-runner.cjs)
Examples:
node global-test-manager.cjs start my-tool complete
node global-test-manager.cjs status
node global-test-manager.cjs stop
`);
}
}