UNPKG

claude-flow

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

550 lines (457 loc) 18.5 kB
const { TestHarness } = require('../test-harness'); const assert = require('assert'); describe('Batch Error Handling Tests', () => { let harness; beforeEach(() => { harness = new TestHarness(); harness.createMockProject('standard'); }); afterEach(() => { harness.reset(); }); describe('Partial Failure Handling', () => { it('should continue processing after individual failures', async () => { const operations = [ { id: 1, shouldFail: false }, { id: 2, shouldFail: true, error: 'Network timeout' }, { id: 3, shouldFail: false }, { id: 4, shouldFail: true, error: 'Permission denied' }, { id: 5, shouldFail: false } ]; const results = await harness.executeBatch(operations, async (op) => { if (op.shouldFail) { throw new Error(op.error); } return { id: op.id, processed: true }; }); assert.strictEqual(results.successful.length, 3); assert.strictEqual(results.failed.length, 2); assert.strictEqual(results.totalProcessed, 5); assert.strictEqual(results.successRate, 0.6); // Verify successful operations completed const successIds = results.successful.map(r => r.id); assert.deepStrictEqual(successIds, [1, 3, 5]); // Verify error details captured assert.strictEqual(results.failed[0].error.message, 'Network timeout'); assert.strictEqual(results.failed[1].error.message, 'Permission denied'); }); it('should handle mixed synchronous and asynchronous errors', async () => { const tasks = [ async () => 'success1', () => { throw new Error('Sync error'); }, async () => { throw new Error('Async error'); }, async () => 'success2', () => { throw new TypeError('Type mismatch'); } ]; const results = await harness.executeBatch(tasks, async (task) => await task()); assert.strictEqual(results.successful.length, 2); assert.strictEqual(results.failed.length, 3); // Check error types preserved const errorTypes = results.failed.map(f => f.error.constructor.name); assert(errorTypes.includes('Error')); assert(errorTypes.includes('TypeError')); }); it('should handle cascading failures gracefully', async () => { // Simulate a scenario where one failure affects subsequent operations let sharedResource = { available: true }; const operations = Array.from({ length: 10 }, (_, i) => async () => { if (i === 3) { // Operation 3 corrupts the shared resource sharedResource.available = false; throw new Error('Resource corruption'); } if (!sharedResource.available) { throw new Error('Resource unavailable due to previous failure'); } return { operation: i, status: 'completed' }; }); const results = await harness.executeBatch(operations, async (op) => await op()); // First 3 operations should succeed const successfulOps = results.successful.map(r => r.operation); assert.deepStrictEqual(successfulOps, [0, 1, 2]); // Remaining operations should fail assert.strictEqual(results.failed.length, 7); // Check cascade error messages const cascadeErrors = results.failed.filter(f => f.error.message.includes('Resource unavailable') ); assert(cascadeErrors.length >= 6); }); }); describe('Error Recovery Strategies', () => { it('should implement retry logic for transient errors', async () => { const operations = Array.from({ length: 5 }, (_, i) => { let attempts = 0; return { id: i, operation: async () => { attempts++; // Fail first 2 attempts for some operations if (i % 2 === 0 && attempts < 3) { throw new Error(`Transient error attempt ${attempts}`); } return { id: i, attempts, success: true }; } }; }); // Implement retry wrapper const retryWrapper = async (operation, maxRetries = 3) => { let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt < maxRetries - 1) { await harness.simulateDelay(50 * (attempt + 1)); // Exponential backoff } } } throw lastError; }; const results = await harness.executeBatch(operations, async (op) => await retryWrapper(op.operation) ); assert.strictEqual(results.successful.length, 5); assert.strictEqual(results.failed.length, 0); // Verify retry attempts const evenResults = results.successful.filter(r => r.id % 2 === 0); assert(evenResults.every(r => r.attempts === 3)); }); it('should handle timeout errors appropriately', async () => { const timeoutMs = 100; const operations = [ { id: 1, duration: 50 }, // Should complete { id: 2, duration: 150 }, // Should timeout { id: 3, duration: 80 }, // Should complete { id: 4, duration: 200 }, // Should timeout { id: 5, duration: 30 } // Should complete ]; const withTimeout = async (promise, ms) => { let timeoutId; const timeout = new Promise((_, reject) => { timeoutId = setTimeout(() => reject(new Error('Operation timeout')), ms); }); try { return await Promise.race([promise, timeout]); } finally { clearTimeout(timeoutId); } }; const results = await harness.executeBatch(operations, async (op) => { const operation = harness.simulateDelay(op.duration).then(() => ({ id: op.id, completed: true })); return await withTimeout(operation, timeoutMs); }); assert.strictEqual(results.successful.length, 3); assert.strictEqual(results.failed.length, 2); // Verify timeout errors const timeoutErrors = results.failed.filter(f => f.error.message === 'Operation timeout' ); assert.strictEqual(timeoutErrors.length, 2); }); it('should implement circuit breaker pattern', async () => { // Circuit breaker configuration const circuitBreaker = { failureThreshold: 3, resetTimeout: 500, state: 'closed', failures: 0, lastFailureTime: null }; const operations = Array.from({ length: 10 }, (_, i) => ({ id: i, // Operations 2, 3, 4 will fail shouldFail: i >= 2 && i <= 4 })); const executeWithCircuitBreaker = async (operation) => { // Check if circuit is open if (circuitBreaker.state === 'open') { const now = Date.now(); if (now - circuitBreaker.lastFailureTime < circuitBreaker.resetTimeout) { throw new Error('Circuit breaker is open'); } else { // Try to reset circuitBreaker.state = 'half-open'; } } try { if (operation.shouldFail) { throw new Error('Operation failed'); } // Success - reset circuit if needed if (circuitBreaker.state === 'half-open') { circuitBreaker.state = 'closed'; circuitBreaker.failures = 0; } return { id: operation.id, status: 'success' }; } catch (error) { circuitBreaker.failures++; circuitBreaker.lastFailureTime = Date.now(); if (circuitBreaker.failures >= circuitBreaker.failureThreshold) { circuitBreaker.state = 'open'; } throw error; } }; const results = await harness.executeBatch(operations, executeWithCircuitBreaker); // First 2 operations should succeed assert(results.successful.some(r => r.id === 0)); assert(results.successful.some(r => r.id === 1)); // Circuit should open after 3 failures const circuitBreakerErrors = results.failed.filter(f => f.error.message === 'Circuit breaker is open' ); assert(circuitBreakerErrors.length > 0); }); }); describe('Error Aggregation and Reporting', () => { it('should aggregate errors by type and severity', async () => { const operations = [ { id: 1, error: new Error('Network error'), severity: 'high' }, { id: 2, error: new TypeError('Type mismatch'), severity: 'medium' }, { id: 3, error: new Error('Network error'), severity: 'high' }, { id: 4, error: new RangeError('Index out of bounds'), severity: 'low' }, { id: 5, error: new TypeError('Cannot read property'), severity: 'medium' }, { id: 6, error: null }, // Success { id: 7, error: new Error('Network error'), severity: 'high' } ]; const results = await harness.executeBatch(operations, async (op) => { if (op.error) { op.error.severity = op.severity; throw op.error; } return { id: op.id, success: true }; }); // Aggregate errors const errorAggregation = results.failed.reduce((acc, failure) => { const errorType = failure.error.constructor.name; const severity = failure.error.severity || 'unknown'; if (!acc[errorType]) { acc[errorType] = { count: 0, severities: {} }; } acc[errorType].count++; acc[errorType].severities[severity] = (acc[errorType].severities[severity] || 0) + 1; return acc; }, {}); console.log('\n=== Error Aggregation Report ==='); console.log('Error Type | Count | High | Medium | Low'); console.log('-------------|-------|------|--------|-----'); Object.entries(errorAggregation).forEach(([type, data]) => { console.log(`${type.padEnd(12)} | ${data.count.toString().padEnd(5)} | ${(data.severities.high || 0).toString().padEnd(4)} | ${(data.severities.medium || 0).toString().padEnd(6)} | ${(data.severities.low || 0)}`); }); assert.strictEqual(errorAggregation.Error.count, 3); assert.strictEqual(errorAggregation.TypeError.count, 2); assert.strictEqual(errorAggregation.RangeError.count, 1); }); it('should provide detailed error context', async () => { const files = [ 'src/valid.js', 'src/missing.js', 'src/corrupt.js', 'src/locked.js' ]; // Set up test files harness.mockFS.set('src/valid.js', 'console.log("valid");'); harness.mockFS.set('src/corrupt.js', ''); // Empty file to simulate corruption const fileOperations = files.map(file => ({ file, operation: async () => { if (file.includes('missing')) { const error = new Error('File not found'); error.code = 'ENOENT'; error.path = file; error.syscall = 'open'; throw error; } if (file.includes('locked')) { const error = new Error('Permission denied'); error.code = 'EACCES'; error.path = file; error.syscall = 'open'; throw error; } const content = await harness.mockReadFile(file); if (file.includes('corrupt') && content.length === 0) { const error = new Error('File appears to be corrupted'); error.code = 'CORRUPT'; error.path = file; error.details = { size: 0, expected: '>0' }; throw error; } return { file, content, size: content.length }; } })); const results = await harness.executeBatch(fileOperations, async (op) => await op.operation() ); // Generate detailed error report const errorReport = results.failed.map(failure => ({ file: fileOperations[failure.index].file, error: { message: failure.error.message, code: failure.error.code, path: failure.error.path, syscall: failure.error.syscall, details: failure.error.details } })); console.log('\n=== Detailed Error Report ==='); errorReport.forEach(report => { console.log(`\nFile: ${report.file}`); console.log(`Error: ${report.error.message}`); console.log(`Code: ${report.error.code || 'N/A'}`); if (report.error.syscall) console.log(`Syscall: ${report.error.syscall}`); if (report.error.details) console.log(`Details: ${JSON.stringify(report.error.details)}`); }); assert.strictEqual(results.failed.length, 3); assert(errorReport.some(r => r.error.code === 'ENOENT')); assert(errorReport.some(r => r.error.code === 'EACCES')); assert(errorReport.some(r => r.error.code === 'CORRUPT')); }); }); describe('Error Boundary Testing', () => { it('should handle catastrophic failures without crashing', async () => { const operations = [ async () => 'success1', async () => { // Simulate stack overflow const recursiveCall = () => recursiveCall(); try { recursiveCall(); } catch (e) { throw new Error('Stack overflow detected'); } }, async () => 'success2', async () => { // Simulate out of memory try { const huge = new Array(1e9).fill('x'); } catch (e) { throw new Error('Memory allocation failed'); } }, async () => 'success3' ]; let systemStable = true; try { const results = await harness.executeBatch(operations, async (op) => { try { return await op(); } catch (error) { // Log catastrophic errors if (error.message.includes('Stack overflow') || error.message.includes('Memory allocation')) { console.log(`Catastrophic error caught: ${error.message}`); } throw error; } }); assert(results.successful.length >= 3); assert(results.failed.length >= 2); } catch (systemError) { systemStable = false; } assert(systemStable, 'System should remain stable despite catastrophic failures'); }); it('should isolate errors between parallel operations', async () => { const sharedState = { counter: 0 }; const operations = Array.from({ length: 10 }, (_, i) => async () => { // Each operation tries to modify shared state const localCounter = sharedState.counter; if (i === 5) { // Operation 5 tries to corrupt shared state throw new Error('Attempting to corrupt shared state'); } // Increment counter sharedState.counter = localCounter + 1; return { operation: i, counterBefore: localCounter, counterAfter: sharedState.counter }; }); harness.concurrencyLimit = 5; const results = await harness.executeBatch(operations, async (op) => await op()); // Despite the error, other operations should complete assert(results.successful.length >= 9); assert.strictEqual(results.failed.length, 1); // Shared state should reflect successful operations assert(sharedState.counter >= 9); }); }); describe('Error Recovery Validation', () => { it('should validate error recovery mechanisms', async () => { const recoveryStrategies = { retry: async (operation, error) => { if (error.code === 'TEMPORARY') { await harness.simulateDelay(100); return await operation(); } throw error; }, fallback: async (operation, error) => { if (error.code === 'SERVICE_UNAVAILABLE') { return { source: 'fallback', data: 'cached_value' }; } throw error; }, skip: async (operation, error) => { if (error.code === 'OPTIONAL') { return { skipped: true, reason: error.message }; } throw error; } }; const operations = [ { id: 1, errorCode: 'TEMPORARY', attempts: 0 }, { id: 2, errorCode: 'SERVICE_UNAVAILABLE' }, { id: 3, errorCode: 'OPTIONAL' }, { id: 4, errorCode: 'FATAL' }, { id: 5, errorCode: null } // No error ]; const executeWithRecovery = async (op) => { const operation = async () => { if (op.errorCode === 'TEMPORARY' && op.attempts === 0) { op.attempts++; const error = new Error('Temporary failure'); error.code = op.errorCode; throw error; } if (op.errorCode && op.errorCode !== 'TEMPORARY') { const error = new Error(`${op.errorCode} error`); error.code = op.errorCode; throw error; } return { id: op.id, success: true }; }; try { return await operation(); } catch (error) { // Try recovery strategies in order for (const [strategy, handler] of Object.entries(recoveryStrategies)) { try { return await handler(operation, error); } catch (recoveryError) { // Continue to next strategy } } throw error; } }; const results = await harness.executeBatch(operations, executeWithRecovery); // Verify recovery strategies worked assert(results.successful.length >= 4); // All except FATAL const recovered = results.successful; assert(recovered.some(r => r.success && r.id === 1)); // Retry worked assert(recovered.some(r => r.source === 'fallback')); // Fallback worked assert(recovered.some(r => r.skipped === true)); // Skip worked // FATAL error should not recover assert(results.failed.some(f => f.error.code === 'FATAL')); }); }); });