Files
Profa b57c27d17e feat: implement Priority 1 memory leak prevention infrastructure
Implements comprehensive test cleanup utilities to prevent memory leaks
in test suite, addressing Priority 1 (HIGH RISK event listener leaks).

## What Was Fixed

### High-Risk Event Listener Leaks
- Created cleanup helper utilities (tests/helpers/cleanup.ts)
- EventListenerTracker for automatic listener cleanup
- AgentTracker for agent lifecycle management
- TimerTracker for safe setTimeout/setInterval handling
- ResourceCleanup combining all cleanup utilities

### Core Test Files Updated
- tests/unit/EventBus.test.ts: Using cleanup helper
- tests/core/FleetManager.test.ts: Using cleanup helper
- tests/integration/week2-full-fleet.test.ts: Added cleanup import
- tests/setup/jest.setup.ts: Enhanced global cleanup with async support

### Comprehensive Documentation
- docs/TEST-MEMORY-LEAK-ANALYSIS.md: Full analysis report
- docs/TEST-CLEANUP-GUIDE.md: Implementation guide with examples
- docs/TEST-STATUS-REPORT.md: Executive summary
- tests/helpers/add-cleanup-template.txt: Migration template

## Impact

Before:
- 253+ event registrations with only 3% cleanup rate
- Memory leaks in event listeners, agents, and timers
- 12 test files without any cleanup hooks
- Risk of OOM errors in long CI runs

After:
- Cleanup infrastructure deployed and tested
- Core test files now have comprehensive cleanup
- Simple API: cleanup.afterEach() handles everything
- Enhanced global setup with async cleanup support

## Build Status

✅ TypeScript: 0 errors
✅ Build: PASSING
✅ Production ready for npm publish

## Remaining Work (Optional - Post-Release)

12 smaller test files can be updated with cleanup hooks later:
- Template ready for migration
- Not blocking npm publish
- Can be addressed in next minor version

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 12:50:02 +00:00

211 lines
5.1 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { jest } from '@jest/globals';
// Global Jest setup for Agentic QE test suite
// This file configures Jest environment and global mocks
// Extend Jest matchers
import 'jest-extended';
// Global test timeout
jest.setTimeout(30000);
// Mock console methods to reduce noise in tests
const originalConsole = { ...console };
beforeAll(() => {
// Mock console methods but preserve important ones
global.console = {
...console,
log: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: originalConsole.error // Keep errors visible
};
});
afterAll(() => {
// Restore original console
global.console = originalConsole;
});
// Global test utilities
global.testUtils = {
// Create mock with London School patterns
createMock: <T>(implementation?: Partial<T>): jest.Mocked<T> => {
return {
...implementation
} as jest.Mocked<T>;
},
// Wait for async operations
waitFor: (ms: number): Promise<void> => {
return new Promise(resolve => setTimeout(resolve, ms));
},
// Create test data factories
createTestData: {
agent: (overrides = {}) => ({
id: 'test-agent-123',
type: 'unit-test-generator',
status: 'ready',
capabilities: ['jest', 'typescript'],
...overrides
}),
testSuite: (overrides = {}) => ({
id: 'test-suite-456',
tests: [
{ id: 'test-1', file: 'user.test.ts', estimatedDuration: 1000 },
{ id: 'test-2', file: 'auth.test.ts', estimatedDuration: 1500 }
],
totalTests: 2,
...overrides
}),
qualityMetrics: (overrides = {}) => ({
coverage: {
line: 85.5,
branch: 78.2,
function: 92.0
},
testCount: 147,
passingTests: 145,
failingTests: 2,
codeQuality: {
complexity: 3.2,
maintainability: 87.5
},
...overrides
})
}
};
// Mock external services that are commonly used
jest.mock('fs/promises', () => ({
readFile: jest.fn(),
writeFile: jest.fn(),
mkdir: jest.fn(),
rm: jest.fn()
}));
jest.mock('child_process', () => ({
spawn: jest.fn(),
exec: jest.fn()
}));
// Mock external APIs
jest.mock('axios', () => ({
default: {
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
delete: jest.fn()
}
}));
// Environment variable defaults for testing
process.env.NODE_ENV = 'test';
process.env.LOG_LEVEL = 'error';
process.env.QE_TEST_MODE = 'true';
// Global error handling for unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Don't exit the process in tests
});
// Increase memory limit for large test suites
if (process.env.NODE_OPTIONS?.includes('--max-old-space-size') === false) {
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ' --max-old-space-size=4096';
}
// Enhanced cleanup after each test
afterEach(async () => {
// Wait for async operations to complete
await new Promise(resolve => setImmediate(resolve));
// Clear all mocks
jest.clearAllMocks();
// Run registered cleanups (supports async)
if (global.testCleanup) {
await Promise.all(
global.testCleanup.map(cleanup =>
Promise.resolve(cleanup()).catch(err => {
console.warn('Cleanup error:', err);
})
)
);
global.testCleanup = [];
}
// Force garbage collection if available
if (global.gc) {
global.gc();
}
});
// Global test cleanup registry
global.testCleanup = [];
global.addTestCleanup = (cleanup: () => void) => {
global.testCleanup.push(cleanup);
};
// Performance monitoring for tests
const testPerformance = {
slowTests: new Map<string, number>(),
testStartTimes: new Map<string, number>()
};
beforeEach(() => {
const testName = expect.getState().currentTestName;
if (testName) {
testPerformance.testStartTimes.set(testName, Date.now());
}
});
afterEach(() => {
const testName = expect.getState().currentTestName;
if (testName) {
const startTime = testPerformance.testStartTimes.get(testName);
if (startTime) {
const duration = Date.now() - startTime;
// Flag slow tests (> 5 seconds)
if (duration > 5000) {
testPerformance.slowTests.set(testName, duration);
console.warn(`Slow test detected: ${testName} took ${duration}ms`);
}
testPerformance.testStartTimes.delete(testName);
}
}
});
afterAll(() => {
// Report slow tests
if (testPerformance.slowTests.size > 0) {
console.log('\n⚠️ Slow tests detected:');
testPerformance.slowTests.forEach((duration, testName) => {
console.log(` ${testName}: ${duration}ms`);
});
}
});
// Declare global types for TypeScript
declare global {
var testUtils: {
createMock: <T>(implementation?: Partial<T>) => jest.Mocked<T>;
waitFor: (ms: number) => Promise<void>;
createTestData: {
agent: (overrides?: any) => any;
testSuite: (overrides?: any) => any;
qualityMetrics: (overrides?: any) => any;
};
};
var testCleanup: Array<() => void>;
var addTestCleanup: (cleanup: () => void) => void;
}