Initial Commit
This commit is contained in:
279
docs/old-network-planning/test-failures-analysis.md
Normal file
279
docs/old-network-planning/test-failures-analysis.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# Analysis of Test Failures in Network Library
|
||||
|
||||
## Failing Tests Overview
|
||||
|
||||
When running `bun test libs/network/src`, these tests fail:
|
||||
|
||||
```sql
|
||||
✗ MemoryTransport > Simulated network conditions > should simulate connection delay
|
||||
✗ MemoryTransport > Simulated network conditions > should simulate latency
|
||||
✗ VotingManager > session management > should get vote history
|
||||
✗ NetworkNodePhase4 > initialization > should register core network reliability calculator
|
||||
✗ NetworkNodePhase4 > host selection > should select the best host
|
||||
✗ MessageBatch > Age tracking > should track batch age
|
||||
✗ TransportTester > testMessageDelivery > should return true when message is delivered
|
||||
✗ TransportTester > testReliability > should report perfect reliability when all messages delivered
|
||||
✗ TransportTester > testReliability > should report partial reliability when some messages lost
|
||||
```
|
||||
|
||||
However, when running each test file individually, all tests pass. This indicates an issue with test isolation.
|
||||
|
||||
## Root Cause Analysis for Each Failing Test
|
||||
|
||||
### 1. MemoryTransport - Connection Delay & Latency Tests
|
||||
|
||||
**File:** `libs/network/src/transport/memory-transport.spec.ts`
|
||||
|
||||
**Issues:**
|
||||
|
||||
- Both tests use real `setTimeout` for time-based assertions
|
||||
- The `MemoryTransport` class uses a static `_instances` Map that isn't properly reset between tests
|
||||
- Tests make timing assertions that can be affected by other running tests
|
||||
|
||||
**Relevant code:**
|
||||
|
||||
```typescript
|
||||
it('should simulate connection delay', async () => {
|
||||
const simulationOptions: MemoryTransportSimulationOptions = {
|
||||
connectionDelay: 50
|
||||
};
|
||||
|
||||
const transport = new MemoryTransport('delayed-transport', {
|
||||
simulationOptions,
|
||||
eventEmitter: new NetworkEventEmitter()
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
await transport.connect();
|
||||
const endTime = Date.now();
|
||||
|
||||
expect(endTime - startTime).toBeGreaterThanOrEqual(50);
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Update `MemoryTransport` to accept a `TimerController` instance
|
||||
2. Add a static `resetRegistry` method to clear all instances
|
||||
3. Refactor tests to use `TimerController` for time simulation
|
||||
|
||||
### 2. VotingManager - Vote History Test
|
||||
|
||||
**File:** `libs/network/src/voting/voting-manager.spec.ts`
|
||||
|
||||
**Issues:**
|
||||
|
||||
- The test creates and completes multiple sessions
|
||||
- Some sessions may be added to history out of order due to timing issues
|
||||
- Depends on proper cleanup between tests
|
||||
|
||||
**Relevant code:**
|
||||
|
||||
```typescript
|
||||
it('should get vote history', () => {
|
||||
// Create a vote session
|
||||
const sessionId = votingManager.createVoteSession(VoteType.HOST_SELECTION, 'session');
|
||||
|
||||
// Complete the session to move it to history
|
||||
const session = votingManager.getSession(sessionId);
|
||||
if (session) {
|
||||
// Add some votes
|
||||
(session as any)._testAddVote({
|
||||
voteId: 'vote1',
|
||||
voterId: 'node1',
|
||||
value: { selectedHost: 'node1' },
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Force completion
|
||||
(session as any)._testSetStatus(VoteSessionStatus.COMPLETED);
|
||||
}
|
||||
|
||||
// Get vote history
|
||||
const history = votingManager.getVoteHistory();
|
||||
|
||||
// Verify history
|
||||
expect(history.length).toBe(1);
|
||||
expect(history[0].id).toBe(sessionId);
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Ensure `VotingManager` properly cleans up between tests
|
||||
2. Use a consistent timestamp source that can be controlled
|
||||
3. Improve assertion to check if the expected session is in the history, not just the count
|
||||
|
||||
### 3. NetworkNodePhase4 - Initialization and Host Selection
|
||||
|
||||
**File:** `libs/network/src/node/network-node-phase4.spec.ts`
|
||||
|
||||
**Issues:**
|
||||
|
||||
- The test creates multiple extensions in the same test file
|
||||
- Each extension registers calculators with shared resources
|
||||
- The host selection test depends on mocked responses that could be affected by other tests
|
||||
|
||||
**Relevant code:**
|
||||
|
||||
```typescript
|
||||
it('should register core network reliability calculator', () => {
|
||||
// Spy on metrics manager
|
||||
const registerCalculatorSpy = jest.spyOn(nodePhase4.metricsManager, 'registerCalculator');
|
||||
|
||||
// Create a new extension to verify registration
|
||||
const newExtension = new NetworkNodePhase4(mockNode, eventEmitter);
|
||||
|
||||
// Verify calculator was registered
|
||||
expect(registerCalculatorSpy).toHaveBeenCalled();
|
||||
|
||||
// Clean up
|
||||
newExtension.dispose();
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Ensure proper cleanup between tests with more thorough `dispose()` methods
|
||||
2. Isolate dependencies so that new instances don't interfere with each other
|
||||
3. Replace event emitter instances in each test
|
||||
|
||||
### 4. MessageBatch - Age Tracking
|
||||
|
||||
**File:** `libs/network/src/router/message-batch.spec.ts`
|
||||
|
||||
**Issues:**
|
||||
|
||||
- Uses real `setTimeout` which can be affected by other tests
|
||||
- Depends on actual timing for assertions
|
||||
|
||||
**Relevant code:**
|
||||
|
||||
```typescript
|
||||
it('should track batch age', async () => {
|
||||
// Create a batch and wait a bit
|
||||
const initialAge = batch.age;
|
||||
|
||||
// Wait at least 10ms
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
// Age should have increased
|
||||
expect(batch.age).toBeGreaterThan(initialAge);
|
||||
expect(batch.age).toBeGreaterThanOrEqual(10);
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Update `MessageBatch` to optionally accept a `TimerController`
|
||||
2. Refactor test to use controlled time advancement
|
||||
|
||||
### 5. TransportTester - Message Delivery and Reliability Tests
|
||||
|
||||
**File:** `libs/network/src/transport/pdk/transport-testing.spec.ts`
|
||||
|
||||
**Issues:**
|
||||
|
||||
- Tests rely on asynchronous callbacks and real timing
|
||||
- MockTransport implementation uses real `setTimeout`
|
||||
- Race conditions can cause tests to fail when run concurrently
|
||||
|
||||
**Relevant code:**
|
||||
|
||||
```typescript
|
||||
it('should return true when message is delivered', async () => {
|
||||
// Setup receiver to simulate receiving the message
|
||||
const testMessage = { test: true };
|
||||
|
||||
// We need to simulate the receipt of the message when the sender sends it
|
||||
sender.on(TransportEvents.MESSAGE_SENT, (msg) => {
|
||||
receiver.simulateMessageReceived(msg, sender.id);
|
||||
});
|
||||
|
||||
const result = await TransportTester.testMessageDelivery(sender, receiver, testMessage);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Update `MockTransport` to use `TimerController`
|
||||
2. Modify `TransportTester` to optionally accept a `TimerController`
|
||||
3. Use controlled time advancement rather than actual timeouts
|
||||
|
||||
## Common Patterns and Solution Strategy
|
||||
|
||||
### Core Issues
|
||||
|
||||
1. **Shared Static State**: Several classes use static properties or singletons
|
||||
2. **Real Timing Dependency**: Tests rely on actual setTimeout/setInterval
|
||||
3. **Timestamp Variations**: Inconsistent approaches to time tracking
|
||||
|
||||
### Solution Strategy
|
||||
|
||||
1. **Refactor TimerController**:
|
||||
- Remove the singleton pattern
|
||||
- Create factory for isolated instances
|
||||
- Ensure proper cleanup methods
|
||||
|
||||
2. **Update Key Classes**:
|
||||
|
||||
```typescript
|
||||
interface TimeOptions {
|
||||
timerController?: TimerController;
|
||||
useRealTime?: boolean; // Default to false in tests
|
||||
}
|
||||
```
|
||||
|
||||
3. **Consistent Cleanup Pattern**:
|
||||
|
||||
```typescript
|
||||
beforeEach(() => {
|
||||
// Create isolated timer instances for each test
|
||||
const timer = TimerController.createInstance();
|
||||
|
||||
// Reset any static state
|
||||
MemoryTransport.resetRegistry();
|
||||
|
||||
// Create test instances with isolated timers
|
||||
testInstance = new ClassUnderTest({
|
||||
timerController: timer
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up instances
|
||||
testInstance.dispose();
|
||||
|
||||
// Reset any static state again
|
||||
MemoryTransport.resetRegistry();
|
||||
});
|
||||
```
|
||||
|
||||
4. **Time-based Assertions**:
|
||||
|
||||
```typescript
|
||||
// Before
|
||||
const startTime = Date.now();
|
||||
await someAsyncOperation();
|
||||
const elapsed = Date.now() - startTime;
|
||||
expect(elapsed).toBeGreaterThanOrEqual(expectedTime);
|
||||
|
||||
// After
|
||||
const timer = TimerController.createInstance();
|
||||
const startTime = timer.getCurrentTime();
|
||||
|
||||
// Start the operation without await
|
||||
const operation = someAsyncOperation();
|
||||
|
||||
// Advance time
|
||||
timer.advanceTime(expectedTime + 10);
|
||||
|
||||
// Now wait for the operation to complete
|
||||
await operation;
|
||||
|
||||
// Check the operation had the expected effect
|
||||
// But don't assert based on actual elapsed time
|
||||
```
|
||||
|
||||
By implementing these changes systematically across the test suite, we can ensure proper test isolation and eliminate the timing-related test failures.
|
||||
Reference in New Issue
Block a user