Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions memory-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Memory Test Suite

This directory contains comprehensive memory leak detection and stress testing tools for the sync engine.

## Tests Available

### 1. Basic Memory Test
```bash
npm run test:memory:basic
```
- **Duration**: ~30 seconds
- **Purpose**: Quick validation of resource cleanup
- **Tests**: ResourceManager, Model lifecycle, Sync engine disposal
- **Pass Criteria**: < 5MB memory growth

### 2. Stress Test (Long-running)
```bash
npm run test:memory
```
- **Duration**: 2 minutes (configurable)
- **Purpose**: Detect memory leaks under continuous load
- **Activities**: Continuous model creation, modification, deletion
- **Pass Criteria**: No memory leak detection, stable resource usage

### 3. Memory Monitor
```bash
npm run monitor:memory
```
- **Duration**: Until stopped (Ctrl+C)
- **Purpose**: Monitor memory usage in real-time
- **Output**: Memory statistics every 2 seconds

## What These Tests Validate

### ✅ Resource Management Fixes
- Timer cleanup (setTimeout/setInterval)
- WebSocket connection lifecycle
- Event listener removal
- AbortController cleanup
- Model disposal

### ✅ Memory Leak Prevention
- No unbounded memory growth
- Proper garbage collection
- Resource disposal patterns
- Connection cleanup

### ✅ Stress Test Scenarios
- High-frequency model operations
- Concurrent modifications
- Resource churn (create/dispose cycles)
- Sync engine operations under load

## Expected Results

### Healthy System:
```
📊 Memory Growth: +0.17 MB
✅ PASS: Memory growth within acceptable limits
🎉 Basic Memory Test PASSED - No memory leaks detected!
```

### Memory Leak Detected:
```
⚠️ MEMORY LEAK DETECTED - Heap grew by more than 20MB
❌ FAIL: Memory growth exceeds 5MB threshold
💥 Basic Memory Test FAILED - Memory leaks detected!
```

## Understanding the Output

### Memory Metrics:
- **Heap Used**: Active memory usage
- **Heap Total**: Total heap allocated
- **External**: Memory used by C++ objects
- **RSS**: Resident Set Size (total process memory)

### Growth Thresholds:
- **Basic Test**: < 5MB acceptable
- **Stress Test**: No continuous growth pattern
- **Long-term**: Stable over time

### Key Indicators:
1. **Memory Growth**: Should be minimal and stable
2. **Resource Counts**: Should not grow unbounded
3. **Error Rate**: Should be < 1% of operations
4. **Cleanup Success**: All resources properly disposed

## Troubleshooting

### High Memory Growth:
1. Check for uncleaned timers
2. Look for undisposed event listeners
3. Verify model disposal calls
4. Check WebSocket cleanup

### Test Failures:
1. Verify all dependencies installed (`npm install`)
2. Check Node.js version (v16+ recommended)
3. Ensure sufficient system memory
4. Check for other memory-intensive processes

### Performance Issues:
1. Reduce test duration or intensity
2. Increase sleep intervals in stress test
3. Lower batch sizes for operations
4. Check system resource availability

## Integration with CI/CD

These tests can be integrated into continuous integration:

```yaml
# Example GitHub Actions
- name: Run Memory Tests
run: |
npm run test:memory:basic
npm run test:memory
```

Memory test results help ensure:
- No regressions in resource management
- Stable memory usage patterns
- Production readiness validation
- Long-running application safety
201 changes: 201 additions & 0 deletions memory-test/basic-memory-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#!/usr/bin/env tsx

/**
* Basic memory test to verify resource cleanup works correctly
* This is a shorter, more focused test for quick validation
*/

import { ResourceManager } from '../src/utils/resource-manager';
import { IndexedDBStore } from '../src/storage/indexed-db-store';
import { IndexedBaseModel } from '../src/models/indexed-base-model';
import { HashSyncEngine } from '../src/sync/hash-sync-engine';
import { ModelRegistry } from '../src/model-registry';

// Enable fake IndexedDB
import 'fake-indexeddb/auto';

// Mock WebSocket
(global as any).WebSocket = class {
static OPEN = 1;
readyState = 1;
constructor() {}
send() {}
close() {}
};

// Mock browser APIs
if (!global.navigator) (global as any).navigator = { onLine: true };
if (!global.window) (global as any).window = { addEventListener: () => {}, removeEventListener: () => {} };

class TestModel extends IndexedBaseModel {
name = '';

setName(name: string) {
this.name = name;
this.markDirty();
}

toJSON() {
return { id: this.id, name: this.name, _version: this._version, updatedAt: new Date().toISOString() };
}
}

async function runBasicMemoryTest() {
console.log('🧪 Running Basic Memory Test...\n');

let initialMemory: any = null;
let peakMemory: any = null;
let finalMemory: any = null;

// Record initial memory
if (typeof process !== 'undefined' && process.memoryUsage) {
initialMemory = process.memoryUsage();
console.log(`📊 Initial Memory: ${(initialMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
}

// Test 1: ResourceManager
console.log('\n🔧 Test 1: ResourceManager Resource Cleanup');
await testResourceManager();

// Test 2: Model Creation and Disposal
console.log('\n🏗️ Test 2: Model Creation and Disposal');
await testModelLifecycle();

// Test 3: Sync Engine Lifecycle
console.log('\n🔄 Test 3: Sync Engine Lifecycle');
await testSyncEngineLifecycle();

// Force garbage collection if available
if (global.gc) {
console.log('\n🗑️ Forcing garbage collection...');
global.gc();
}

// Record final memory
if (typeof process !== 'undefined' && process.memoryUsage) {
finalMemory = process.memoryUsage();
console.log(`📊 Final Memory: ${(finalMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);

const growth = finalMemory.heapUsed - initialMemory.heapUsed;
const growthMB = growth / 1024 / 1024;

console.log(`📈 Memory Growth: ${growthMB > 0 ? '+' : ''}${growthMB.toFixed(2)} MB`);

if (growthMB > 5) {
console.error('❌ FAIL: Memory growth exceeds 5MB threshold');
return false;
} else {
console.log('✅ PASS: Memory growth within acceptable limits');
return true;
}
}

return true;
}

async function testResourceManager() {
const managers: ResourceManager[] = [];

// Create many resource managers with timers
for (let i = 0; i < 100; i++) {
const manager = new ResourceManager();

// Add various resources
manager.setTimeout(() => {}, 10000); // Long timeout
manager.setInterval(() => {}, 1000); // Interval
manager.createAbortController(); // AbortController

managers.push(manager);
}

console.log(` Created ${managers.length} ResourceManagers with timers and controllers`);

// Dispose all managers
for (const manager of managers) {
await manager.dispose();
}

console.log(' ✅ All ResourceManagers disposed');
}

async function testModelLifecycle() {
// Set up storage
const store = new IndexedDBStore({ dbName: 'basic_memory_test' });
await store.initialize([{
name: 'TestModel',
loadStrategy: 'full' as const,
schemaVersion: 1,
properties: new Map([['name', { type: 'property', indexed: false }]])
}]);

IndexedBaseModel.setStore(store);
ModelRegistry.registerModel('TestModel', TestModel as any);
ModelRegistry.registerProperty('TestModel', 'name', { type: 'property', indexed: false });

const models: TestModel[] = [];

// Create many models
for (let i = 0; i < 200; i++) {
const model = new TestModel(`model_${i}`);
model.setName(`Test Model ${i}`);
models.push(model);
}

console.log(` Created ${models.length} models`);

// Dispose all models
for (const model of models) {
await model.dispose();
}

console.log(' ✅ All models disposed');

// Cleanup store
await store.deleteDatabase();
}

async function testSyncEngineLifecycle() {
const engines: HashSyncEngine[] = [];

for (let i = 0; i < 10; i++) {
const store = new IndexedDBStore({ dbName: `sync_test_${i}` });
await store.initialize([]);

const engine = new HashSyncEngine(store, {
serverUrl: `ws://localhost:808${i}`,
clientId: `test_${i}`,
syncIntervalMs: 1000,
heartbeatIntervalMs: 5000
});

await engine.initialize();
engines.push(engine);
}

console.log(` Created ${engines.length} sync engines`);

// Dispose all engines
for (const engine of engines) {
await engine.dispose();
}

console.log(' ✅ All sync engines disposed');
}

// Run the test
if (require.main === module) {
runBasicMemoryTest()
.then(success => {
if (success) {
console.log('\n🎉 Basic Memory Test PASSED - No memory leaks detected!');
process.exit(0);
} else {
console.log('\n💥 Basic Memory Test FAILED - Memory leaks detected!');
process.exit(1);
}
})
.catch(error => {
console.error('\n💥 Basic Memory Test ERROR:', error);
process.exit(1);
});
}
Loading