mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
feat: add resource management types and utilities for memory optimization
- Add Disposable, BufferConfig, MemoryMetrics, CleanupRegistration types - Create src/config/buffer-limits.ts with consolidated buffer size constants - Create src/config/map-limits.ts with Map size limits to prevent unbounded growth - Implement BufferAccumulator utility with configurable trim and onTrim callback - Implement LRUMap with automatic eviction and O(1) operations - Implement CleanupManager for unified resource cleanup with isStopped guard - Add comprehensive tests for all new utilities This lays the foundation for memory leak prevention and performance improvements. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Tests for BufferAccumulator utility.
|
||||
*
|
||||
* Port: N/A (unit tests, no server)
|
||||
*/
|
||||
|
||||
import { BufferAccumulator } from '../../src/utils/buffer-accumulator.js';
|
||||
|
||||
describe('BufferAccumulator', () => {
|
||||
describe('basic operations', () => {
|
||||
it('should start empty', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
expect(buffer.value).toBe('');
|
||||
expect(buffer.length).toBe(0);
|
||||
expect(buffer.isEmpty).toBe(true);
|
||||
});
|
||||
|
||||
it('should append data', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello');
|
||||
buffer.append(' ');
|
||||
buffer.append('world');
|
||||
|
||||
expect(buffer.value).toBe('hello world');
|
||||
expect(buffer.length).toBe(11);
|
||||
expect(buffer.isEmpty).toBe(false);
|
||||
});
|
||||
|
||||
it('should ignore empty appends', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello');
|
||||
buffer.append('');
|
||||
buffer.append(null as unknown as string);
|
||||
buffer.append(undefined as unknown as string);
|
||||
|
||||
expect(buffer.value).toBe('hello');
|
||||
expect(buffer.length).toBe(5);
|
||||
});
|
||||
|
||||
it('should clear the buffer', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello world');
|
||||
buffer.clear();
|
||||
|
||||
expect(buffer.value).toBe('');
|
||||
expect(buffer.length).toBe(0);
|
||||
expect(buffer.isEmpty).toBe(true);
|
||||
});
|
||||
|
||||
it('should set buffer to specific value', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello');
|
||||
buffer.set('goodbye');
|
||||
|
||||
expect(buffer.value).toBe('goodbye');
|
||||
expect(buffer.length).toBe(7);
|
||||
});
|
||||
|
||||
it('should set to empty value', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello');
|
||||
buffer.set('');
|
||||
|
||||
expect(buffer.value).toBe('');
|
||||
expect(buffer.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('automatic trimming', () => {
|
||||
it('should trim when maxSize is exceeded', () => {
|
||||
const buffer = new BufferAccumulator(100, 50);
|
||||
|
||||
// Fill buffer beyond max
|
||||
buffer.append('a'.repeat(60));
|
||||
buffer.append('b'.repeat(60)); // Total 120, exceeds 100
|
||||
|
||||
// Should have trimmed to 50
|
||||
expect(buffer.length).toBe(50);
|
||||
// Should keep most recent data (all 'b's)
|
||||
expect(buffer.value).toBe('b'.repeat(50));
|
||||
});
|
||||
|
||||
it('should call onTrim callback when trimming', () => {
|
||||
let trimmedBytes = 0;
|
||||
const buffer = new BufferAccumulator({
|
||||
maxSize: 100,
|
||||
trimSize: 50,
|
||||
onTrim: (bytes) => { trimmedBytes = bytes; },
|
||||
});
|
||||
|
||||
// Fill buffer beyond max
|
||||
buffer.append('a'.repeat(60));
|
||||
buffer.append('b'.repeat(60)); // Total 120, trims to 50
|
||||
|
||||
expect(trimmedBytes).toBe(70); // 120 - 50 = 70
|
||||
});
|
||||
|
||||
it('should not call onTrim when no trimming needed', () => {
|
||||
let trimCalled = false;
|
||||
const buffer = new BufferAccumulator({
|
||||
maxSize: 100,
|
||||
trimSize: 50,
|
||||
onTrim: () => { trimCalled = true; },
|
||||
});
|
||||
|
||||
buffer.append('a'.repeat(50)); // Under max
|
||||
|
||||
expect(trimCalled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tail and search operations', () => {
|
||||
it('should get tail of buffer', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello world');
|
||||
|
||||
expect(buffer.tail(5)).toBe('world');
|
||||
expect(buffer.tail(6)).toBe(' world');
|
||||
expect(buffer.tail(100)).toBe('hello world'); // Returns all if n > length
|
||||
});
|
||||
|
||||
it('should check endsWith', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello world');
|
||||
|
||||
expect(buffer.endsWith('world')).toBe(true);
|
||||
expect(buffer.endsWith('hello')).toBe(false);
|
||||
expect(buffer.endsWith('')).toBe(true);
|
||||
expect(buffer.endsWith('a very long string')).toBe(false);
|
||||
});
|
||||
|
||||
it('should search with contains (string)', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello world, how are you?');
|
||||
|
||||
expect(buffer.contains('world')).toBe(true);
|
||||
expect(buffer.contains('foo')).toBe(false);
|
||||
expect(buffer.contains('you', 10)).toBe(true);
|
||||
expect(buffer.contains('hello', 10)).toBe(false);
|
||||
});
|
||||
|
||||
it('should search with contains (regex)', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello world 123');
|
||||
|
||||
expect(buffer.contains(/\d+/)).toBe(true);
|
||||
expect(buffer.contains(/foo/)).toBe(false);
|
||||
expect(buffer.contains(/world/)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor overloads', () => {
|
||||
it('should accept simple number parameters', () => {
|
||||
const buffer = new BufferAccumulator(100, 50);
|
||||
buffer.append('a'.repeat(120));
|
||||
expect(buffer.length).toBe(50);
|
||||
});
|
||||
|
||||
it('should accept BufferConfig object', () => {
|
||||
const buffer = new BufferAccumulator({
|
||||
maxSize: 100,
|
||||
trimSize: 50,
|
||||
});
|
||||
buffer.append('a'.repeat(120));
|
||||
expect(buffer.length).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('chunk consolidation', () => {
|
||||
it('should consolidate chunks on value access', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('a');
|
||||
buffer.append('b');
|
||||
buffer.append('c');
|
||||
|
||||
// First access consolidates
|
||||
const value1 = buffer.value;
|
||||
expect(value1).toBe('abc');
|
||||
|
||||
// Second access should return same value efficiently
|
||||
const value2 = buffer.value;
|
||||
expect(value2).toBe('abc');
|
||||
});
|
||||
|
||||
it('should handle single chunk efficiently', () => {
|
||||
const buffer = new BufferAccumulator(1000, 800);
|
||||
buffer.append('hello world');
|
||||
|
||||
// Single chunk doesn't need joining
|
||||
expect(buffer.value).toBe('hello world');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user