@joystick.js/db-canary
Version: 
JoystickDB - A minimalist database server for the Joystick framework
198 lines (162 loc) ⢠6.92 kB
JavaScript
/**
 * @fileoverview Full debug test runner that mimics the original test runner
 * but with enhanced exception tracking to identify problematic tests.
 */
import { spawn } from 'child_process';
// Track uncaught exceptions with detailed context
const uncaught_exceptions = [];
let current_test_phase = 'startup';
let test_start_time = null;
// Enhanced exception handlers that log but don't exit
process.on('uncaughtException', (error) => {
  const exception_info = {
    type: 'uncaughtException',
    phase: current_test_phase,
    message: error.message,
    stack: error.stack,
    timestamp: new Date().toISOString(),
    elapsed_ms: test_start_time ? Date.now() - test_start_time : 0
  };
  
  uncaught_exceptions.push(exception_info);
  
  console.error(`\nš„ UNCAUGHT EXCEPTION #${uncaught_exceptions.length}:`);
  console.error(`š Phase: ${current_test_phase}`);
  console.error(`ā° Elapsed: ${exception_info.elapsed_ms}ms`);
  console.error(`š„ Error: ${error.message}`);
  console.error(`š Stack (first 5 lines):`);
  const stack_lines = error.stack.split('\n').slice(0, 5);
  stack_lines.forEach(line => console.error(`    ${line}`));
  console.error(`ā° Time: ${exception_info.timestamp}\n`);
});
process.on('unhandledRejection', (reason, promise) => {
  const exception_info = {
    type: 'unhandledRejection',
    phase: current_test_phase,
    reason: reason?.toString() || 'Unknown reason',
    stack: reason?.stack || 'No stack available',
    timestamp: new Date().toISOString(),
    elapsed_ms: test_start_time ? Date.now() - test_start_time : 0
  };
  
  uncaught_exceptions.push(exception_info);
  
  console.error(`\nš„ UNHANDLED REJECTION #${uncaught_exceptions.length}:`);
  console.error(`š Phase: ${current_test_phase}`);
  console.error(`ā° Elapsed: ${exception_info.elapsed_ms}ms`);
  console.error(`š„ Reason: ${reason}`);
  console.error(`š Stack (first 5 lines):`);
  const stack_lines = (reason?.stack || 'No stack available').split('\n').slice(0, 5);
  stack_lines.forEach(line => console.error(`    ${line}`));
  console.error(`ā° Time: ${exception_info.timestamp}\n`);
});
/**
 * Runs the full test suite exactly like the original test runner.
 * @returns {Promise<number>} Exit code
 */
const run_full_test_suite_debug = () => {
  return new Promise((resolve) => {
    current_test_phase = 'full-test-suite';
    test_start_time = Date.now();
    
    console.log(`š Running FULL TEST SUITE with debug tracking`);
    console.log(`š This mimics the exact command: npm test`);
    console.log(`š» Command: ./node_modules/.bin/ava --serial --verbose tests/client/**/*.test.js tests/server/**/*.test.js`);
    console.log(`š§ NODE_OPTIONS: --expose-gc --max-old-space-size=4096`);
    console.log(`ā° Started at: ${new Date().toISOString()}\n`);
    
    const command = './node_modules/.bin/ava';
    const args = ['--serial', '--verbose', 'tests/client/**/*.test.js', 'tests/server/**/*.test.js'];
    
    const child = spawn(command, args, {
      stdio: 'pipe', // Capture output so we can track progress
      env: {
        ...process.env,
        NODE_ENV: 'test',
        NODE_OPTIONS: '--expose-gc --max-old-space-size=4096'
      }
    });
    
    let output_buffer = '';
    let test_count = 0;
    
    child.stdout.on('data', (data) => {
      const text = data.toString();
      output_buffer += text;
      process.stdout.write(text);
      
      // Track test progress
      const test_matches = text.match(/ā/g);
      if (test_matches) {
        test_count += test_matches.length;
        current_test_phase = `test-${test_count}`;
      }
    });
    
    child.stderr.on('data', (data) => {
      const text = data.toString();
      output_buffer += text;
      process.stderr.write(text);
    });
    
    child.on('close', (code) => {
      const elapsed = Date.now() - test_start_time;
      
      console.log(`\nā
 Full test suite completed with exit code: ${code}`);
      console.log(`ā° Total elapsed: ${elapsed}ms`);
      console.log(`š Total tests detected: ${test_count}`);
      
      // Exception analysis
      console.log(`\nš UNCAUGHT EXCEPTION ANALYSIS:`);
      console.log(`Total exceptions detected: ${uncaught_exceptions.length}`);
      
      if (uncaught_exceptions.length > 0) {
        console.log('\nš„ Exception Timeline:');
        
        uncaught_exceptions.forEach((exc, index) => {
          console.log(`\nException #${index + 1}:`);
          console.log(`  Type: ${exc.type}`);
          console.log(`  Phase: ${exc.phase}`);
          console.log(`  Elapsed: ${exc.elapsed_ms}ms`);
          console.log(`  Message: ${exc.message}`);
          console.log(`  Time: ${exc.timestamp}`);
          
          if (exc.stack) {
            console.log(`  Key Stack Lines:`);
            const stack_lines = exc.stack.split('\n')
              .filter(line => line.includes('db/src/') || line.includes('db/tests/'))
              .slice(0, 3);
            stack_lines.forEach(line => console.log(`    ${line.trim()}`));
          }
        });
        
        // Try to correlate with test timing
        console.log('\nš Exception Timing Analysis:');
        uncaught_exceptions.forEach((exc, index) => {
          const test_number_estimate = Math.floor((exc.elapsed_ms / elapsed) * test_count);
          console.log(`  Exception #${index + 1} occurred around test #${test_number_estimate} (${exc.elapsed_ms}ms elapsed)`);
        });
      }
      
      resolve(code);
    });
    
    child.on('error', (error) => {
      console.error(`\nā Full test suite failed: ${error.message}`);
      resolve(1);
    });
  });
};
/**
 * Main execution function.
 */
const main = async () => {
  const args = process.argv.slice(2);
  
  if (args.includes('--help') || args.includes('-h')) {
    console.log(`
š Full Debug Test Runner for JoystickDB
Usage: node full_debug_test_runner.js
This runner executes the complete test suite exactly like 'npm test'
but captures uncaught exceptions and unhandled rejections with detailed
timing and context information to identify problematic tests.
`);
    process.exit(0);
  }
  
  console.log('šÆ Selected strategy: Full Test Suite Debug Analysis');
  
  const exit_code = await run_full_test_suite_debug();
  
  if (exit_code === 0 && uncaught_exceptions.length === 0) {
    console.log(`\nš All tests passed with NO uncaught exceptions!`);
  } else if (exit_code === 0 && uncaught_exceptions.length > 0) {
    console.log(`\nā ļø  All tests passed but ${uncaught_exceptions.length} uncaught exceptions detected`);
  } else {
    console.log(`\nš„ Tests failed with exit code: ${exit_code}`);
  }
  
  process.exit(exit_code);
};
// Run the main function
main().catch(error => {
  console.error(`\nš„ Debug runner error: ${error.message}`);
  console.error(error.stack);
  process.exit(1);
});