import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { createSmtpClient, createPooledSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js'; import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.js'; import { Email } from '../../../ts/mail/core/classes.email.js'; let testServer: ITestServer; // Helper function to get memory usage const getMemoryUsage = () => { if (process.memoryUsage) { const usage = process.memoryUsage(); return { heapUsed: usage.heapUsed, heapTotal: usage.heapTotal, external: usage.external, rss: usage.rss }; } return null; }; // Helper function to format bytes const formatBytes = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; tap.test('setup - start SMTP server for memory tests', async () => { testServer = await startTestServer({ port: 0, enableStarttls: false, authRequired: false }); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CPERF-03: Memory usage during connection lifecycle', async (tools) => { tools.timeout(30000); const memoryBefore = getMemoryUsage(); console.log('Initial memory usage:', { heapUsed: formatBytes(memoryBefore.heapUsed), heapTotal: formatBytes(memoryBefore.heapTotal), rss: formatBytes(memoryBefore.rss) }); // Create and close multiple connections const connectionCount = 10; for (let i = 0; i < connectionCount; i++) { const client = await createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, debug: false }); // Send a test email const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Memory test ${i + 1}`, text: 'Testing memory usage' }); await client.sendMail(email); await client.close(); // Small delay between connections await new Promise(resolve => setTimeout(resolve, 100)); } // Force garbage collection if available if (global.gc) { global.gc(); await new Promise(resolve => setTimeout(resolve, 100)); } const memoryAfter = getMemoryUsage(); const memoryIncrease = memoryAfter.heapUsed - memoryBefore.heapUsed; console.log(`Memory after ${connectionCount} connections:`, { heapUsed: formatBytes(memoryAfter.heapUsed), heapTotal: formatBytes(memoryAfter.heapTotal), rss: formatBytes(memoryAfter.rss) }); console.log(`Memory increase: ${formatBytes(memoryIncrease)}`); console.log(`Average per connection: ${formatBytes(memoryIncrease / connectionCount)}`); // Memory increase should be reasonable expect(memoryIncrease / connectionCount).toBeLessThan(1024 * 1024); // Less than 1MB per connection }); tap.test('CPERF-03: Memory usage with large messages', async (tools) => { tools.timeout(30000); const client = await createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, debug: false }); const memoryBefore = getMemoryUsage(); console.log('Memory before large messages:', { heapUsed: formatBytes(memoryBefore.heapUsed) }); // Send messages of increasing size const sizes = [1024, 10240, 102400]; // 1KB, 10KB, 100KB for (const size of sizes) { const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Large message test (${formatBytes(size)})`, text: 'x'.repeat(size) }); await client.sendMail(email); const memoryAfter = getMemoryUsage(); console.log(`Memory after ${formatBytes(size)} message:`, { heapUsed: formatBytes(memoryAfter.heapUsed), increase: formatBytes(memoryAfter.heapUsed - memoryBefore.heapUsed) }); // Small delay await new Promise(resolve => setTimeout(resolve, 200)); } await client.close(); const memoryFinal = getMemoryUsage(); const totalIncrease = memoryFinal.heapUsed - memoryBefore.heapUsed; console.log(`Total memory increase: ${formatBytes(totalIncrease)}`); // Memory should not grow excessively expect(totalIncrease).toBeLessThan(10 * 1024 * 1024); // Less than 10MB total }); tap.test('CPERF-03: Memory usage with connection pooling', async (tools) => { tools.timeout(30000); const memoryBefore = getMemoryUsage(); console.log('Memory before pooling test:', { heapUsed: formatBytes(memoryBefore.heapUsed) }); const pooledClient = await createPooledSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, maxConnections: 3, debug: false }); // Send multiple emails through the pool const emailCount = 15; const emails = Array(emailCount).fill(null).map((_, i) => new Email({ from: 'sender@example.com', to: [`recipient${i}@example.com`], subject: `Pooled memory test ${i + 1}`, text: 'Testing memory with connection pooling' }) ); // Send in batches for (let i = 0; i < emails.length; i += 3) { const batch = emails.slice(i, i + 3); await Promise.all(batch.map(email => pooledClient.sendMail(email).catch(err => console.log('Send error:', err.message)) )); // Check memory after each batch const memoryNow = getMemoryUsage(); console.log(`Memory after batch ${Math.floor(i/3) + 1}:`, { heapUsed: formatBytes(memoryNow.heapUsed), increase: formatBytes(memoryNow.heapUsed - memoryBefore.heapUsed) }); await new Promise(resolve => setTimeout(resolve, 100)); } await pooledClient.close(); const memoryFinal = getMemoryUsage(); const totalIncrease = memoryFinal.heapUsed - memoryBefore.heapUsed; console.log(`Total memory increase with pooling: ${formatBytes(totalIncrease)}`); console.log(`Average per email: ${formatBytes(totalIncrease / emailCount)}`); // Pooling should be memory efficient expect(totalIncrease / emailCount).toBeLessThan(500 * 1024); // Less than 500KB per email }); tap.test('CPERF-03: Memory cleanup after errors', async (tools) => { tools.timeout(30000); const memoryBefore = getMemoryUsage(); console.log('Memory before error test:', { heapUsed: formatBytes(memoryBefore.heapUsed) }); // Try to send emails that might fail const errorCount = 5; for (let i = 0; i < errorCount; i++) { try { const client = await createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 1000, // Short timeout debug: false }); // Create a large email that might cause issues const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Error test ${i + 1}`, text: 'x'.repeat(100000), // 100KB attachments: [{ filename: 'test.txt', content: Buffer.alloc(50000).toString('base64'), // 50KB attachment encoding: 'base64' }] }); await client.sendMail(email); await client.close(); } catch (error) { console.log(`Error ${i + 1} handled: ${error.message}`); } await new Promise(resolve => setTimeout(resolve, 100)); } // Force garbage collection if available if (global.gc) { global.gc(); await new Promise(resolve => setTimeout(resolve, 100)); } const memoryAfter = getMemoryUsage(); const memoryIncrease = memoryAfter.heapUsed - memoryBefore.heapUsed; console.log(`Memory after ${errorCount} error scenarios:`, { heapUsed: formatBytes(memoryAfter.heapUsed), increase: formatBytes(memoryIncrease) }); // Memory should be properly cleaned up after errors expect(memoryIncrease).toBeLessThan(5 * 1024 * 1024); // Less than 5MB increase }); tap.test('CPERF-03: Long-running memory stability', async (tools) => { tools.timeout(60000); const client = await createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, debug: false }); const memorySnapshots = []; const duration = 10000; // 10 seconds const interval = 2000; // Check every 2 seconds const startTime = Date.now(); console.log('Testing memory stability over time...'); let emailsSent = 0; while (Date.now() - startTime < duration) { // Send an email const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Stability test ${++emailsSent}`, text: `Testing memory stability at ${new Date().toISOString()}` }); try { await client.sendMail(email); } catch (error) { console.log('Send error:', error.message); } // Take memory snapshot const memory = getMemoryUsage(); const elapsed = Date.now() - startTime; memorySnapshots.push({ time: elapsed, heapUsed: memory.heapUsed }); console.log(`[${elapsed}ms] Heap: ${formatBytes(memory.heapUsed)}, Emails sent: ${emailsSent}`); await new Promise(resolve => setTimeout(resolve, interval)); } await client.close(); // Analyze memory growth const firstSnapshot = memorySnapshots[0]; const lastSnapshot = memorySnapshots[memorySnapshots.length - 1]; const memoryGrowth = lastSnapshot.heapUsed - firstSnapshot.heapUsed; const growthRate = memoryGrowth / (lastSnapshot.time / 1000); // bytes per second console.log(`\nMemory stability results:`); console.log(` Duration: ${lastSnapshot.time}ms`); console.log(` Emails sent: ${emailsSent}`); console.log(` Memory growth: ${formatBytes(memoryGrowth)}`); console.log(` Growth rate: ${formatBytes(growthRate)}/second`); // Memory growth should be minimal over time expect(growthRate).toBeLessThan(150 * 1024); // Less than 150KB/second growth }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); }); export default tap.start();