mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2e8642030b
🎯 Test Suite Stabilization - 94.2% Pass Rate Achieved! This release represents a major quality milestone with systematic test stabilization that increased the unit test pass rate from 71.1% (619/870) to 94.2% (903/959), exceeding the 90% goal set in GitHub issue #33. Major Changes: - Fixed all TypeScript compilation errors blocking release - Created batched integration test script to prevent OOM crashes - Documented integration test memory leak root cause (issue #35) - Enhanced mock infrastructure for better test reliability - Updated release verification policies in CLAUDE.md TypeScript Fixes: - Fixed TestGeneratorAgent AgentCapability structure (4 errors) - Rewrote Database mock with proper type casting (36 methods) - Rewrote LearningEngine mock with proper type casting - Excluded __mocks__ from TypeScript compilation Test Infrastructure: - Created scripts/test-integration-batched.sh for safe execution - Batches 46 integration tests (5 files at a time) - Prevents workspace crashes in constrained environments - Updated package.json test:integration to use batched script Memory Leak Investigation: - Root cause: MemoryManager setInterval leak (138 timers) - Missing cleanup in 31 of 46 integration tests - Database connection pool exhaustion (23 instances) - Documented in GitHub issue #35 for v1.4.4 fix Release Verification: - aqe init: 19 agents, 34 skills, 8 commands ✅ - Databases: memory.db (221KB), patterns.db (155KB) ✅ - MCP interface: 54 tools functional ✅ - Fleet startup: 5 agents running successfully ✅ Files Changed: - Modified: 40 files (tests, mocks, core agents) - New: 2 files (batched script, LearningEngine mock) - Version: 1.4.2 → 1.4.3 Closes #33 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
444 lines
14 KiB
TypeScript
444 lines
14 KiB
TypeScript
// Mock Logger to prevent undefined errors
|
|
jest.mock('@utils/Logger', () => ({
|
|
Logger: {
|
|
getInstance: jest.fn(() => ({
|
|
info: jest.fn(),
|
|
error: jest.fn(),
|
|
warn: jest.fn(),
|
|
debug: jest.fn(),
|
|
log: jest.fn()
|
|
}))
|
|
}
|
|
}));
|
|
|
|
import {
|
|
FlakyDetectionSwarmCoordinator,
|
|
SwarmMemoryStore
|
|
} from '../../../src/learning/SwarmIntegration';
|
|
import { TestResult, FlakyTest } from '../../../src/learning/types';
|
|
|
|
describe('FlakyDetectionSwarmCoordinator - Comprehensive Tests', () => {
|
|
let coordinator: FlakyDetectionSwarmCoordinator;
|
|
let mockMemoryStore: jest.Mocked<SwarmMemoryStore>;
|
|
|
|
beforeEach(() => {
|
|
mockMemoryStore = {
|
|
store: jest.fn().mockResolvedValue(undefined),
|
|
retrieve: jest.fn().mockResolvedValue(null),
|
|
delete: jest.fn().mockResolvedValue(undefined),
|
|
search: jest.fn().mockResolvedValue([])
|
|
};
|
|
|
|
coordinator = new FlakyDetectionSwarmCoordinator(
|
|
mockMemoryStore,
|
|
{
|
|
minRuns: 5,
|
|
passRateThreshold: 0.8,
|
|
confidenceThreshold: 0.7,
|
|
useMLModel: true
|
|
},
|
|
'phase2'
|
|
);
|
|
});
|
|
|
|
describe('Memory Integration', () => {
|
|
it('should store individual test analyses', async () => {
|
|
const history: TestResult[] = [
|
|
{ name: 'test1', passed: true, duration: 100, timestamp: Date.now() - 5000 },
|
|
{ name: 'test1', passed: false, duration: 150, timestamp: Date.now() - 4000 },
|
|
{ name: 'test1', passed: true, duration: 120, timestamp: Date.now() - 3000 },
|
|
{ name: 'test1', passed: false, duration: 130, timestamp: Date.now() - 2000 },
|
|
{ name: 'test1', passed: true, duration: 110, timestamp: Date.now() - 1000 }
|
|
];
|
|
|
|
await coordinator.detectAndStore(history);
|
|
|
|
// Verify both aggregate and individual test storage
|
|
expect(mockMemoryStore.store).toHaveBeenCalledWith(
|
|
expect.stringContaining('flaky-tests'),
|
|
expect.any(Object),
|
|
expect.any(Object)
|
|
);
|
|
});
|
|
|
|
it('should handle memory store errors gracefully', async () => {
|
|
mockMemoryStore.store.mockRejectedValue(new Error('Storage failed'));
|
|
|
|
const history: TestResult[] = [
|
|
{ name: 'test1', passed: true, duration: 100, timestamp: Date.now() }
|
|
];
|
|
|
|
await expect(coordinator.detectAndStore(history)).rejects.toThrow('Storage failed');
|
|
});
|
|
|
|
it('should handle memory retrieval errors', async () => {
|
|
mockMemoryStore.retrieve.mockRejectedValue(new Error('Retrieval failed'));
|
|
|
|
await expect(coordinator.retrieveResults()).rejects.toThrow('Retrieval failed');
|
|
});
|
|
});
|
|
|
|
describe('Event System Integration', () => {
|
|
it('should subscribe to events', async () => {
|
|
const callback = jest.fn();
|
|
await coordinator.subscribeToEvents(callback);
|
|
|
|
expect(mockMemoryStore.store).toHaveBeenCalledWith(
|
|
expect.stringContaining('event-subscriptions'),
|
|
expect.objectContaining({
|
|
subscriber: 'flaky-detection-coordinator',
|
|
events: expect.arrayContaining([
|
|
'test:flaky-detected',
|
|
'test:pattern-identified',
|
|
'model:trained'
|
|
])
|
|
}),
|
|
expect.objectContaining({ partition: 'coordination', ttl: 3600 })
|
|
);
|
|
});
|
|
|
|
it('should emit flaky test events', async () => {
|
|
const flakyTest: FlakyTest = {
|
|
name: 'test1',
|
|
passRate: 0.6,
|
|
variance: 800,
|
|
confidence: 0.75,
|
|
totalRuns: 10,
|
|
failurePattern: 'intermittent',
|
|
severity: 'high',
|
|
firstDetected: Date.now(),
|
|
lastSeen: Date.now(),
|
|
recommendation: {
|
|
priority: 'high',
|
|
category: 'timing',
|
|
recommendation: 'Add explicit waits',
|
|
estimatedEffort: 'medium'
|
|
}
|
|
};
|
|
|
|
await coordinator.emitFlakyTestEvent(flakyTest);
|
|
|
|
expect(mockMemoryStore.store).toHaveBeenCalledWith(
|
|
expect.stringContaining('events/flaky-detected/test1'),
|
|
expect.objectContaining({
|
|
type: 'test:flaky-detected',
|
|
test: flakyTest,
|
|
severity: 'high',
|
|
pattern: 'intermittent'
|
|
}),
|
|
expect.objectContaining({ partition: 'coordination', ttl: 86400 })
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Training and Learning', () => {
|
|
it('should train model from swarm memory', async () => {
|
|
const now = Date.now();
|
|
const mockTrainingData = {
|
|
'test1': {
|
|
results: [
|
|
{ name: 'test1', passed: true, duration: 100, timestamp: now - 5000 },
|
|
{ name: 'test1', passed: false, duration: 150, timestamp: now - 4000 },
|
|
{ name: 'test1', passed: true, duration: 120, timestamp: now - 3000 },
|
|
{ name: 'test1', passed: false, duration: 130, timestamp: now - 2000 },
|
|
{ name: 'test1', passed: true, duration: 110, timestamp: now - 1000 }
|
|
],
|
|
isFlaky: true
|
|
},
|
|
'test2': {
|
|
results: [
|
|
{ name: 'test2', passed: true, duration: 100, timestamp: now - 5000 },
|
|
{ name: 'test2', passed: true, duration: 105, timestamp: now - 4000 },
|
|
{ name: 'test2', passed: true, duration: 102, timestamp: now - 3000 },
|
|
{ name: 'test2', passed: true, duration: 98, timestamp: now - 2000 },
|
|
{ name: 'test2', passed: true, duration: 103, timestamp: now - 1000 }
|
|
],
|
|
isFlaky: false
|
|
}
|
|
};
|
|
|
|
mockMemoryStore.retrieve.mockResolvedValue(mockTrainingData);
|
|
|
|
await coordinator.trainFromSwarmMemory();
|
|
|
|
expect(mockMemoryStore.retrieve).toHaveBeenCalledWith(
|
|
expect.stringContaining('training-data'),
|
|
expect.objectContaining({ partition: 'coordination' })
|
|
);
|
|
|
|
expect(mockMemoryStore.store).toHaveBeenCalledWith(
|
|
expect.stringContaining('model-training'),
|
|
expect.objectContaining({
|
|
status: 'completed',
|
|
trainingSamples: 2
|
|
}),
|
|
expect.any(Object)
|
|
);
|
|
});
|
|
|
|
it('should handle training with no data', async () => {
|
|
mockMemoryStore.retrieve.mockResolvedValue(null);
|
|
|
|
await coordinator.trainFromSwarmMemory();
|
|
|
|
// Should not throw error, just skip training
|
|
expect(mockMemoryStore.store).not.toHaveBeenCalledWith(
|
|
expect.stringContaining('model-training'),
|
|
expect.anything(),
|
|
expect.anything()
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Checkpoint Management', () => {
|
|
it('should create checkpoints with session ID', async () => {
|
|
const mockResults = {
|
|
tests: [
|
|
{
|
|
name: 'test1',
|
|
passRate: 0.7,
|
|
variance: 600,
|
|
confidence: 0.8,
|
|
totalRuns: 10,
|
|
failurePattern: 'timing' as const,
|
|
severity: 'medium' as const,
|
|
firstDetected: Date.now(),
|
|
lastSeen: Date.now(),
|
|
recommendation: {
|
|
priority: 'medium' as const,
|
|
category: 'timing' as const,
|
|
recommendation: 'Review timing',
|
|
estimatedEffort: 'low' as const
|
|
}
|
|
}
|
|
],
|
|
statistics: { total: 1 },
|
|
timestamp: Date.now()
|
|
};
|
|
|
|
mockMemoryStore.retrieve.mockResolvedValue(mockResults);
|
|
|
|
await coordinator.createCheckpoint('session-abc123');
|
|
|
|
expect(mockMemoryStore.store).toHaveBeenCalledWith(
|
|
expect.stringContaining('checkpoints/session-abc123'),
|
|
expect.objectContaining({
|
|
sessionId: 'session-abc123',
|
|
results: expect.any(Array),
|
|
statistics: expect.any(Object)
|
|
}),
|
|
expect.objectContaining({ partition: 'coordination', ttl: 604800 })
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Statistics Aggregation', () => {
|
|
it('should aggregate statistics across multiple tests', async () => {
|
|
const mockResults = {
|
|
tests: [
|
|
{
|
|
name: 'test1',
|
|
passRate: 0.6,
|
|
variance: 500,
|
|
confidence: 0.8,
|
|
totalRuns: 10,
|
|
failurePattern: 'intermittent' as const,
|
|
severity: 'high' as const,
|
|
firstDetected: Date.now() - 2000,
|
|
lastSeen: Date.now(),
|
|
recommendation: {
|
|
priority: 'high' as const,
|
|
category: 'timing' as const,
|
|
recommendation: 'Fix timing',
|
|
estimatedEffort: 'medium' as const
|
|
}
|
|
},
|
|
{
|
|
name: 'test2',
|
|
passRate: 0.5,
|
|
variance: 700,
|
|
confidence: 0.75,
|
|
totalRuns: 8,
|
|
failurePattern: 'environmental' as const,
|
|
severity: 'critical' as const,
|
|
firstDetected: Date.now() - 1000,
|
|
lastSeen: Date.now(),
|
|
recommendation: {
|
|
priority: 'critical' as const,
|
|
category: 'environmental' as const,
|
|
recommendation: 'Fix environment',
|
|
estimatedEffort: 'high' as const
|
|
}
|
|
}
|
|
],
|
|
statistics: {
|
|
total: 2,
|
|
bySeverity: { critical: 1, high: 1, medium: 0, low: 0 },
|
|
byPattern: { intermittent: 1, environmental: 1, timing: 0, resource: 0 }
|
|
},
|
|
timestamp: Date.now()
|
|
};
|
|
|
|
mockMemoryStore.retrieve.mockResolvedValue(mockResults);
|
|
|
|
const stats = await coordinator.getAggregateStatistics();
|
|
|
|
expect(stats.totalFlaky).toBe(2);
|
|
expect(stats.bySeverity.critical).toBe(1);
|
|
expect(stats.bySeverity.high).toBe(1);
|
|
expect(stats.byPattern.intermittent).toBe(1);
|
|
expect(stats.byPattern.environmental).toBe(1);
|
|
expect(stats.recentDetections).toBe(2);
|
|
});
|
|
|
|
it('should calculate recent detections correctly', async () => {
|
|
const now = Date.now();
|
|
const oneDayAgo = now - 86400000;
|
|
const twoDaysAgo = now - 172800000;
|
|
|
|
const mockResults = {
|
|
tests: [
|
|
{
|
|
name: 'test1',
|
|
passRate: 0.6,
|
|
variance: 500,
|
|
confidence: 0.8,
|
|
totalRuns: 10,
|
|
failurePattern: 'intermittent' as const,
|
|
severity: 'high' as const,
|
|
firstDetected: now - 1000, // Recent
|
|
lastSeen: now,
|
|
recommendation: {
|
|
priority: 'high' as const,
|
|
category: 'timing' as const,
|
|
recommendation: 'Fix timing',
|
|
estimatedEffort: 'medium' as const
|
|
}
|
|
},
|
|
{
|
|
name: 'test2',
|
|
passRate: 0.5,
|
|
variance: 700,
|
|
confidence: 0.75,
|
|
totalRuns: 8,
|
|
failurePattern: 'environmental' as const,
|
|
severity: 'medium' as const,
|
|
firstDetected: twoDaysAgo, // Old
|
|
lastSeen: now,
|
|
recommendation: {
|
|
priority: 'medium' as const,
|
|
category: 'environmental' as const,
|
|
recommendation: 'Fix environment',
|
|
estimatedEffort: 'low' as const
|
|
}
|
|
}
|
|
],
|
|
statistics: {
|
|
total: 2,
|
|
bySeverity: { critical: 0, high: 1, medium: 1, low: 0 },
|
|
byPattern: { intermittent: 1, environmental: 1, timing: 0, resource: 0 }
|
|
},
|
|
timestamp: now
|
|
};
|
|
|
|
mockMemoryStore.retrieve.mockResolvedValue(mockResults);
|
|
|
|
const stats = await coordinator.getAggregateStatistics();
|
|
|
|
expect(stats.recentDetections).toBe(1); // Only test1 is recent
|
|
});
|
|
});
|
|
|
|
describe('Search and Retrieval', () => {
|
|
it('should retrieve specific test analysis', async () => {
|
|
const mockTest: FlakyTest = {
|
|
name: 'test1',
|
|
passRate: 0.6,
|
|
variance: 500,
|
|
confidence: 0.8,
|
|
totalRuns: 10,
|
|
failurePattern: 'intermittent',
|
|
severity: 'high',
|
|
firstDetected: Date.now(),
|
|
lastSeen: Date.now(),
|
|
recommendation: {
|
|
priority: 'high',
|
|
category: 'timing',
|
|
recommendation: 'Fix timing',
|
|
estimatedEffort: 'medium'
|
|
}
|
|
};
|
|
|
|
mockMemoryStore.retrieve.mockResolvedValue(mockTest);
|
|
|
|
const result = await coordinator.retrieveTestAnalysis('test1');
|
|
|
|
expect(result).toEqual(mockTest);
|
|
expect(mockMemoryStore.retrieve).toHaveBeenCalledWith(
|
|
expect.stringContaining('test-analysis/test1'),
|
|
expect.objectContaining({ partition: 'coordination' })
|
|
);
|
|
});
|
|
|
|
it('should return null for non-existent test analysis', async () => {
|
|
mockMemoryStore.retrieve.mockResolvedValue(null);
|
|
|
|
const result = await coordinator.retrieveTestAnalysis('nonexistent');
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should search tests with wildcards', async () => {
|
|
const mockSearchResults = [
|
|
{
|
|
key: 'phase2/test-analysis/test1',
|
|
value: {
|
|
name: 'test1',
|
|
passRate: 0.6,
|
|
variance: 500,
|
|
confidence: 0.8,
|
|
totalRuns: 10,
|
|
failurePattern: 'intermittent' as const,
|
|
severity: 'high' as const,
|
|
firstDetected: Date.now(),
|
|
lastSeen: Date.now(),
|
|
recommendation: {
|
|
priority: 'high' as const,
|
|
category: 'timing' as const,
|
|
recommendation: 'Fix',
|
|
estimatedEffort: 'medium' as const
|
|
}
|
|
}
|
|
},
|
|
{
|
|
key: 'phase2/test-analysis/test2',
|
|
value: {
|
|
name: 'test2',
|
|
passRate: 0.5,
|
|
variance: 700,
|
|
confidence: 0.75,
|
|
totalRuns: 8,
|
|
failurePattern: 'environmental' as const,
|
|
severity: 'medium' as const,
|
|
firstDetected: Date.now(),
|
|
lastSeen: Date.now(),
|
|
recommendation: {
|
|
priority: 'medium' as const,
|
|
category: 'environmental' as const,
|
|
recommendation: 'Fix env',
|
|
estimatedEffort: 'low' as const
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
mockMemoryStore.search.mockResolvedValue(mockSearchResults);
|
|
|
|
const results = await coordinator.searchFlakyTests('test*');
|
|
|
|
expect(results).toHaveLength(2);
|
|
expect(results[0].name).toBe('test1');
|
|
expect(results[1].name).toBe('test2');
|
|
});
|
|
});
|
|
});
|