Skip to main content
Glama
test-helpers.ts9.21 kB
/** * Test helper utilities for consistent test setup and cleanup */ import { beforeEach, afterEach } from 'vitest'; import { EventEmitter } from 'events'; import logger from '../../../../logger.js'; import { performTestCleanup, registerEventEmitter, registerCleanupFunction, registerSingleton, getMemoryUsage } from './test-cleanup.js'; import { initializeTestServices } from '../setup.js'; import { memoryUtils, testMemoryOptimizer } from './memory-optimizer.js'; /** * Enhanced test wrapper that provides automatic cleanup */ export function withTestCleanup(testName?: string) { const testId = testName || `test-${Date.now()}`; let startMemory: ReturnType<typeof getMemoryUsage>; beforeEach(async () => { // Optimize memory before test memoryUtils.optimizeBeforeTest(); // Record starting memory startMemory = getMemoryUsage(); // Ensure test services are initialized initializeTestServices(); logger.debug({ testId, memory: startMemory.formatted }, 'Test started'); }); afterEach(async () => { try { // Perform cleanup await performTestCleanup(); // Optimize memory after test and check for leaks const leakDetection = memoryUtils.optimizeAfterTest(); // Record ending memory const endMemory = getMemoryUsage(); const memoryDiff = endMemory.heapUsed - startMemory.heapUsed; // Enhanced memory leak detection if (leakDetection?.hasLeaks) { logger.warn({ testId, leakSeverity: leakDetection.leakSeverity, warnings: leakDetection.warnings, recommendations: leakDetection.recommendations }, 'Memory leaks detected'); } if (memoryDiff > 10 * 1024 * 1024) { // > 10MB increase logger.warn({ testId, startMemory: startMemory.formatted.heapUsed, endMemory: endMemory.formatted.heapUsed, diff: `${Math.round(memoryDiff / 1024 / 1024)} MB` }, 'Significant memory increase detected'); } logger.debug({ testId, memory: endMemory.formatted }, 'Test cleanup completed'); } catch (error) { logger.error({ err: error, testId }, 'Test cleanup failed'); } }); } /** * Create a test EventEmitter with automatic cleanup */ export function createTestEventEmitter(name?: string): EventEmitter { const emitter = new EventEmitter(); emitter.setMaxListeners(50); // Higher limit for tests registerEventEmitter(emitter, name); return emitter; } /** * Create a test service with automatic cleanup */ export function createTestService<T>( name: string, factory: () => T, cleanupMethod?: keyof T ): T { const service = factory(); if (cleanupMethod && typeof service[cleanupMethod] === 'function') { registerCleanupFunction(name, () => (service[cleanupMethod] as () => void)()); } return service; } /** * Register a singleton for automatic reset */ export function registerTestSingleton( name: string, getInstanceOrInstance: (() => unknown) | unknown, resetMethod?: string ): void { // Register with both the legacy system and the enhanced singleton manager const instance = typeof getInstanceOrInstance === 'function' ? getInstanceOrInstance() : getInstanceOrInstance; // Legacy registration registerSingleton(name, instance, resetMethod); // Enhanced registration if (typeof getInstanceOrInstance === 'function') { registerSingletonForReset({ name, getInstance: getInstanceOrInstance, resetMethod }); } } /** * Wait for a condition with timeout */ export async function waitFor( condition: () => boolean | Promise<boolean>, timeoutMs: number = 5000, intervalMs: number = 100 ): Promise<void> { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { const result = await condition(); if (result) { return; } await new Promise(resolve => setTimeout(resolve, intervalMs)); } throw new Error(`Condition not met within ${timeoutMs}ms`); } /** * Create a mock function with automatic cleanup */ export function createMockFunction<T extends (...args: unknown[]) => unknown>( name: string, implementation?: T ): T & { cleanup: () => void } { const calls: Array<{ args: Parameters<T>; result: ReturnType<T>; timestamp: number }> = []; const mockFn = ((...args: Parameters<T>): ReturnType<T> => { const result = implementation ? implementation(...args) : undefined; calls.push({ args, result, timestamp: Date.now() }); return result; }) as T & { cleanup: () => void }; mockFn.cleanup = () => { calls.length = 0; }; // Register for cleanup registerCleanupFunction(`mock-${name}`, mockFn.cleanup); return mockFn; } /** * Test timeout wrapper */ export function withTimeout<T>( promise: Promise<T>, timeoutMs: number, errorMessage?: string ): Promise<T> { return Promise.race([ promise, new Promise<never>((_, reject) => { setTimeout(() => { reject(new Error(errorMessage || `Operation timed out after ${timeoutMs}ms`)); }, timeoutMs); }) ]); } /** * Retry a function with exponential backoff */ export async function retryWithBackoff<T>( fn: () => Promise<T>, maxRetries: number = 3, initialDelayMs: number = 100, backoffMultiplier: number = 2 ): Promise<T> { let lastError: Error; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (attempt < maxRetries) { const delay = initialDelayMs * Math.pow(backoffMultiplier, attempt); await new Promise(resolve => setTimeout(resolve, delay)); } } } throw lastError!; } /** * Create a test configuration with overrides */ export function createTestConfig(overrides: unknown = {}): unknown { const baseConfig = { maxConcurrentTasks: 2, defaultTaskTemplate: 'test', dataDirectory: process.env.TEST_OUTPUT_DIR || '/tmp/test', timeouts: { taskExecution: 10000, taskDecomposition: 15000, llmRequest: 10000, fileOperations: 2000, databaseOperations: 2000, networkOperations: 5000 }, retryPolicy: { maxRetries: 1, backoffMultiplier: 1.5, initialDelayMs: 100, maxDelayMs: 1000, enableExponentialBackoff: true } }; return { ...baseConfig, ...overrides }; } /** * Assert memory usage is within acceptable limits */ export function assertMemoryUsage(maxHeapMB: number = 500): void { const usage = getMemoryUsage(); const heapMB = usage.heapUsed / 1024 / 1024; if (heapMB > maxHeapMB) { throw new Error(`Memory usage too high: ${Math.round(heapMB)}MB > ${maxHeapMB}MB`); } } /** * Monitor memory usage during test execution */ export function withMemoryMonitoring(testName?: string) { const testId = testName || `memory-test-${Date.now()}`; beforeEach(() => { memoryUtils.optimizeBeforeTest(); logger.debug({ testId }, 'Memory monitoring started for test'); }); afterEach(() => { const leakDetection = memoryUtils.optimizeAfterTest(); if (leakDetection?.hasLeaks) { logger.warn({ testId, leakDetection }, 'Memory issues detected during test'); } logger.debug({ testId }, 'Memory monitoring completed for test'); }); } /** * Check memory health and throw if unhealthy */ export function ensureMemoryHealth(): void { if (!memoryUtils.checkMemoryHealth()) { const current = memoryUtils.getCurrentMemory(); throw new Error(`Memory health check failed: ${current.formatted.heapUsed} heap usage`); } } /** * Force memory cleanup and optimization */ export function forceMemoryOptimization(): void { memoryUtils.forceCleanup(); // Additional cleanup for test environment if (global.gc) { // Multiple GC cycles for thorough cleanup global.gc(); setTimeout(() => global.gc && global.gc(), 50); setTimeout(() => global.gc && global.gc(), 100); } } /** * Create a memory-optimized test environment */ export function withMemoryOptimization(options: { maxHeapMB?: number; enableMonitoring?: boolean; forceCleanup?: boolean; } = {}) { const { maxHeapMB = 200, enableMonitoring = true, forceCleanup = true } = options; beforeEach(() => { if (forceCleanup) { forceMemoryOptimization(); } if (enableMonitoring) { testMemoryOptimizer.startMonitoring(); } }); afterEach(() => { if (enableMonitoring) { const leakDetection = testMemoryOptimizer.stopMonitoring(); if (leakDetection?.hasLeaks && leakDetection.leakSeverity !== 'low') { logger.warn({ leakDetection }, 'Memory optimization detected issues'); } } if (forceCleanup) { forceMemoryOptimization(); } // Assert memory usage is within limits try { assertMemoryUsage(maxHeapMB); } catch (error) { logger.error({ err: error }, 'Memory usage assertion failed'); // Force cleanup and retry forceMemoryOptimization(); assertMemoryUsage(maxHeapMB * 1.2); // Allow 20% tolerance after cleanup } }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freshtechbro/vibe-coder-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server