Files
proffesor-for-testing__agen…/tests/agents/BaseAgent.race-condition.test.ts
Profa 26908cb677 chore(release): v1.8.1 - Safety & Test Quality Improvements
## Fixed

- **P0: Simulation Mode Runtime Guards** (TestExecutorAgent)
  - Requires AQE_ALLOW_SIMULATION=true environment variable
  - Prevents silent simulation in production environments
  - Throws explicit error with clear instructions

- **P2: Explicit Error Handling** (RealAgentDBAdapter)
  - Replaced silent fallback (return 0) with explicit validation
  - Fails loudly with actionable error messages
  - Detects database corruption and schema mismatches

- **P1: Test Isolation with UUID** (Integration Tests)
  - Replaced Date.now() with randomUUID() for database paths
  - Eliminates race conditions in parallel test execution
  - Uses OS temp directory for proper cleanup

## Changed

- Updated CHANGELOG.md with v1.8.1 release notes
- Updated README.md version badge and What's New section
- Updated package.json version: 1.8.0 → 1.8.1

## Documentation

- Created docs/releases/v1.8.1-RELEASE-NOTES.md (comprehensive guide)
- Created docs/releases/v1.8.1-SUMMARY.md (quick reference)
- Created docs/releases/v1.8.1-VERIFICATION.md (test verification)
- Created docs/reviews/BRUTAL-HONESTY-ISSUE-52-CODE-REVIEW.md (code review)

## Issues

- Closes #52 (Technical Debt Remediation) - 6/8 fixes completed
- Addresses #55 P0, P1, P2 priorities
- Creates #57 for remaining work (performance optimization, adapter verification)

## Impact

- Build Status: ✅ Passing (0 errors)
- Runtime Safety: ✅ Improved (guards added)
- Error Handling: ✅ Improved (explicit errors)
- Test Isolation: ✅ Improved (UUID-based)
- Breaking Changes: ❌ None (fully backward-compatible)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 11:44:44 +00:00

389 lines
11 KiB
TypeScript

/**
* BaseAgent Race Condition Tests
* Tests thread-safe initialization with concurrent calls
* Issue: #52 - Race condition in BaseAgent.initialize()
*/
import { BaseAgent, BaseAgentConfig } from '../../src/agents/BaseAgent';
import { AgentStatus, AgentCapability, QETask } from '../../src/types';
import { EventEmitter } from 'events';
import { SwarmMemoryManager } from '../../src/core/memory/SwarmMemoryManager';
/**
* Test implementation of BaseAgent
*/
class TestAgent extends BaseAgent {
public initializeCallCount = 0;
public initializeComponentsCallCount = 0;
protected async initializeComponents(): Promise<void> {
this.initializeComponentsCallCount++;
// Simulate async work that takes time
await new Promise(resolve => setTimeout(resolve, 100));
}
protected async performTask(_task: QETask): Promise<any> {
return { success: true };
}
protected async loadKnowledge(): Promise<void> {
// No-op for testing
}
protected async cleanup(): Promise<void> {
// No-op for testing
}
}
describe('BaseAgent Race Condition Tests', () => {
let eventBus: EventEmitter;
let memoryStore: SwarmMemoryManager;
beforeEach(async () => {
eventBus = new EventEmitter();
memoryStore = new SwarmMemoryManager(':memory:');
await memoryStore.initialize();
});
afterEach(async () => {
await memoryStore.close();
});
const createTestAgent = (): TestAgent => {
const capabilities: AgentCapability[] = [
{
name: 'test-capability',
description: 'Test capability',
version: '1.0.0'
}
];
const config: BaseAgentConfig = {
type: 'test-generator',
capabilities,
context: {
environment: 'test',
project: {
name: 'race-condition-test',
version: '1.0.0'
}
},
memoryStore,
eventBus,
enableLearning: false
};
return new TestAgent(config);
};
describe('Concurrent Initialization', () => {
it('should handle concurrent initialize() calls without double-initialization', async () => {
const agent = createTestAgent();
// Call initialize() 5 times concurrently
const initPromises = Array.from({ length: 5 }, () => agent.initialize());
// All promises should resolve successfully
await Promise.all(initPromises);
// Verify initialization only happened once
expect(agent.initializeComponentsCallCount).toBe(1);
// Verify agent is in correct state
const status = agent.getStatus();
expect(status.status).toBe(AgentStatus.IDLE);
await agent.terminate();
});
it('should handle rapid sequential initialize() calls', async () => {
const agent = createTestAgent();
// Call initialize() multiple times in sequence without awaiting
const promise1 = agent.initialize();
const promise2 = agent.initialize();
const promise3 = agent.initialize();
await Promise.all([promise1, promise2, promise3]);
// Verify initialization only happened once
expect(agent.initializeComponentsCallCount).toBe(1);
const status = agent.getStatus();
expect(status.status).toBe(AgentStatus.IDLE);
await agent.terminate();
});
it('should allow re-initialization after termination', async () => {
const agent = createTestAgent();
// First initialization
await agent.initialize();
expect(agent.initializeComponentsCallCount).toBe(1);
// Terminate
await agent.terminate();
expect(agent.getStatus().status).toBe(AgentStatus.TERMINATED);
// Second initialization should be allowed after termination
await agent.initialize();
expect(agent.initializeComponentsCallCount).toBe(2);
await agent.terminate();
});
it('should wait for in-progress initialization', async () => {
const agent = createTestAgent();
// Start first initialization
const firstInit = agent.initialize();
// Start second initialization while first is in progress
const secondInit = agent.initialize();
await Promise.all([firstInit, secondInit]);
// Both should complete successfully
expect(agent.initializeComponentsCallCount).toBe(1);
expect(agent.getStatus().status).toBe(AgentStatus.IDLE);
await agent.terminate();
});
it('should handle concurrent initialize() from multiple sources', async () => {
const agent = createTestAgent();
// Simulate initialization from different "threads"
const initFromThread1 = agent.initialize();
const initFromThread2 = agent.initialize();
const initFromThread3 = agent.initialize();
// Wait for all to complete
await Promise.all([initFromThread1, initFromThread2, initFromThread3]);
// Verify single initialization
expect(agent.initializeComponentsCallCount).toBe(1);
const status = agent.getStatus();
expect(status.status).toBe(AgentStatus.IDLE);
await agent.terminate();
});
});
describe('Initialization Idempotency', () => {
it('should be safe to call initialize() multiple times after completion', async () => {
const agent = createTestAgent();
// First initialization
await agent.initialize();
const firstCallCount = agent.initializeComponentsCallCount;
expect(firstCallCount).toBe(1);
// Second call after completion
await agent.initialize();
expect(agent.initializeComponentsCallCount).toBe(firstCallCount); // No additional initialization
// Third call
await agent.initialize();
expect(agent.initializeComponentsCallCount).toBe(firstCallCount);
await agent.terminate();
});
it('should maintain correct status during concurrent initialization', async () => {
const agent = createTestAgent();
const statusChecks: AgentStatus[] = [];
// Start initialization and check status concurrently
const initPromise = agent.initialize();
// Check status during initialization
const checkStatus = async () => {
for (let i = 0; i < 10; i++) {
statusChecks.push(agent.getStatus().status);
await new Promise(resolve => setTimeout(resolve, 10));
}
};
await Promise.all([initPromise, checkStatus()]);
// Status should eventually be IDLE
expect(agent.getStatus().status).toBe(AgentStatus.IDLE);
// All status checks should be valid states
const validStates = [
AgentStatus.INITIALIZING,
AgentStatus.IDLE,
AgentStatus.ACTIVE
];
for (const status of statusChecks) {
expect(validStates).toContain(status);
}
await agent.terminate();
});
});
describe('Error Handling in Concurrent Initialization', () => {
it('should handle initialization errors consistently across concurrent calls', async () => {
class FailingAgent extends TestAgent {
protected async initializeComponents(): Promise<void> {
this.initializeComponentsCallCount++;
throw new Error('Initialization failed');
}
}
const agent = new FailingAgent({
type: 'test-generator',
capabilities: [],
context: {
environment: 'test',
project: { name: 'test', version: '1.0.0' }
},
memoryStore,
eventBus,
enableLearning: false
});
// All concurrent calls should fail with the same error
const initPromises = Array.from({ length: 3 }, () =>
agent.initialize().catch(e => e)
);
const results = await Promise.all(initPromises);
// All should have failed
for (const result of results) {
expect(result).toBeInstanceOf(Error);
}
// Only one initialization attempt should have occurred
expect(agent.initializeComponentsCallCount).toBe(1);
// Agent should be in ERROR state
expect(agent.getStatus().status).toBe(AgentStatus.ERROR);
});
});
describe('Memory and Resource Safety', () => {
it('should not leak memory with multiple concurrent initialize() calls', async () => {
const agent = createTestAgent();
// Run multiple rounds of concurrent initialization
for (let round = 0; round < 3; round++) {
const promises = Array.from({ length: 10 }, () => agent.initialize());
await Promise.all(promises);
}
// Verify clean state
expect(agent.initializeComponentsCallCount).toBe(1);
expect(agent.getStatus().status).toBe(AgentStatus.IDLE);
await agent.terminate();
});
it('should properly clean up mutex after initialization', async () => {
const agent = createTestAgent();
await agent.initialize();
// Access private property for testing (TypeScript hack)
const mutex = (agent as any).initializationMutex;
expect(mutex).toBeUndefined();
await agent.terminate();
});
});
describe('Event-Driven Coordination with Concurrent Initialization', () => {
it('should emit correct events during concurrent initialization', async () => {
const agent = createTestAgent();
const events: string[] = [];
eventBus.on('agent.initialized', () => events.push('initialized'));
eventBus.on('agent.error', () => events.push('error'));
// Concurrent initialization
await Promise.all([
agent.initialize(),
agent.initialize(),
agent.initialize()
]);
// Should have exactly one initialized event
expect(events.filter(e => e === 'initialized')).toHaveLength(1);
expect(events.filter(e => e === 'error')).toHaveLength(0);
await agent.terminate();
});
it('should support waitForReady() during concurrent initialization', async () => {
const agent = createTestAgent();
// Start initialization (don't await yet)
const initPromise = agent.initialize();
// Give initialization a moment to start
await new Promise(resolve => setTimeout(resolve, 50));
// Wait for ready from another "thread" (with longer timeout for CI)
const waitPromise = agent.waitForReady(10000);
await Promise.all([initPromise, waitPromise]);
expect(agent.getStatus().status).toBe(AgentStatus.IDLE);
await agent.terminate();
});
});
describe('Integration with AgentDB', () => {
it('should prevent double-initialization of AgentDB', async () => {
// Create fresh memory store for this test
const agentDBMemoryStore = new SwarmMemoryManager(':memory:');
await agentDBMemoryStore.initialize();
const agentDBConfig = {
dbPath: ':memory:',
enableQUICSync: false,
enableLearning: true,
enableReasoning: true
};
const config: BaseAgentConfig = {
type: 'test-generator',
capabilities: [],
context: {
environment: 'test',
project: { name: 'test', version: '1.0.0' }
},
memoryStore: agentDBMemoryStore,
eventBus,
enableLearning: true,
agentDBConfig
};
const agent = new TestAgent(config);
// Concurrent initialization with AgentDB
await Promise.all([
agent.initialize(),
agent.initialize(),
agent.initialize()
]);
// Verify AgentDB is initialized
expect(agent.hasAgentDB()).toBe(true);
// Verify single initialization
expect(agent.initializeComponentsCallCount).toBe(1);
await agent.terminate();
await agentDBMemoryStore.close();
});
});
});