155 lines
4.8 KiB
Markdown
155 lines
4.8 KiB
Markdown
# Test Isolation Fix Plan
|
|
|
|
## Problem Analysis
|
|
|
|
After analyzing the failing tests, we've identified several issues that cause tests to fail when run together but pass individually:
|
|
|
|
1. **Singleton TimerController**: The `TimerController` class uses a singleton pattern, which means all tests share the same timer instance. This causes interference between tests.
|
|
|
|
2. **Static Registry in MemoryTransport**: The `MemoryTransport` class uses a static registry (`_instances`) to track all transport instances, which isn't properly cleaned up between tests.
|
|
|
|
3. **Time-sensitive Tests**: Several tests rely on actual timing with `setTimeout`, making them flaky.
|
|
|
|
4. **Date.now() Usage**: Tests using `Date.now()` can be affected by other tests that mock time-related functions.
|
|
|
|
## Proposed Solutions
|
|
|
|
### 1. Update TimerController to Support Multiple Instances
|
|
|
|
The current `TimerController` is implemented as a singleton, which prevents proper test isolation. We need to modify it to:
|
|
|
|
- Allow creation of independent instances for each test
|
|
- Add a method to create a factory that provides isolated instances
|
|
- Ensure `reset()` is called in `beforeEach` and `afterEach` blocks
|
|
|
|
### 2. Fix MemoryTransport Static Registry
|
|
|
|
- Add a `resetRegistry()` static method to clear all instances
|
|
- Ensure this is called in `beforeEach` and `afterEach` hooks in tests
|
|
- Make sure disconnection properly removes instances from the registry
|
|
|
|
### 3. Refactor Time-Sensitive Tests
|
|
|
|
For each of the failing tests:
|
|
|
|
#### MemoryTransport Tests
|
|
|
|
- Update `should simulate connection delay` to use TimerController instead of real delays
|
|
- Update `should simulate latency` to use TimerController
|
|
|
|
#### MessageBatch Tests
|
|
|
|
- Update `should track batch age` to use TimerController instead of real setTimeout
|
|
|
|
#### TransportTester Tests
|
|
|
|
- Update message delivery tests to use TimerController
|
|
- Update reliability tests to address race conditions
|
|
|
|
#### NetworkNodePhase4 Tests
|
|
|
|
- Ensure proper cleanup and isolation of network components
|
|
|
|
### 4. Create Test Utility Classes
|
|
|
|
Create test utility classes/functions that:
|
|
|
|
- Provide isolated TimerController instances
|
|
- Help mock Date.now() consistently
|
|
- Ensure all static registries are reset between tests
|
|
|
|
## Implementation Details
|
|
|
|
### TimerController Changes
|
|
|
|
```typescript
|
|
// Modifications needed to TimerController
|
|
export class TimerController {
|
|
private _currentTime = 0;
|
|
private _timers: Map<number, ScheduledTimer> = new Map();
|
|
private _nextTimerId = 1;
|
|
|
|
// Remove static singleton instance
|
|
// private static _instance: TimerController;
|
|
|
|
// Replace getInstance with createInstance
|
|
public static createInstance(): TimerController {
|
|
return new TimerController();
|
|
}
|
|
|
|
// Other methods remain the same...
|
|
}
|
|
|
|
// Helper for tests
|
|
export function createIsolatedTimerController(): TimerController {
|
|
return TimerController.createInstance();
|
|
}
|
|
```
|
|
|
|
### MemoryTransport Test Setup
|
|
|
|
```typescript
|
|
// In memory-transport.spec.ts
|
|
beforeEach(() => {
|
|
// Reset static registry
|
|
MemoryTransport.resetRegistry();
|
|
|
|
// Create fresh instances with isolated timer controllers
|
|
const timer1 = createIsolatedTimerController();
|
|
const timer2 = createIsolatedTimerController();
|
|
|
|
// Create transports with these timers
|
|
transport1 = new MemoryTransport('transport-1', {
|
|
eventEmitter: eventEmitter1,
|
|
timerController: timer1
|
|
});
|
|
|
|
transport2 = new MemoryTransport('transport-2', {
|
|
eventEmitter: eventEmitter2,
|
|
timerController: timer2
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clean up
|
|
MemoryTransport.resetRegistry();
|
|
});
|
|
```
|
|
|
|
### TransportTester Tests
|
|
|
|
The TransportTester tests require special attention to ensure proper timing control. The issue is that these tests verify asynchronous behaviors like message delivery and reliability.
|
|
|
|
```typescript
|
|
// In transport-testing.spec.ts
|
|
beforeEach(() => {
|
|
// Reset static registries and create isolated timer controllers
|
|
MemoryTransport.resetRegistry();
|
|
const senderTimer = createIsolatedTimerController();
|
|
const receiverTimer = createIsolatedTimerController();
|
|
|
|
// Configure transports with isolated timers
|
|
sender = new MockTransport('sender', undefined, senderTimer);
|
|
receiver = new MockTransport('receiver', undefined, receiverTimer);
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clean up
|
|
MemoryTransport.resetRegistry();
|
|
// Additional cleanup as needed
|
|
});
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
After implementing these changes:
|
|
|
|
1. Run individual test suites to verify they still pass
|
|
2. Run the full test suite to verify the fixes resolved the isolation issues
|
|
3. Add documentation about the importance of test isolation
|
|
4. Consider adding a linting rule to prevent direct usage of setTimeout/setInterval in tests
|
|
|
|
## Implementation Recommendation
|
|
|
|
To implement these changes, we should switch to Code mode and systematically update each test file, ensuring that each component of the system is properly isolated from others during testing.
|