503 lines
17 KiB
TypeScript
503 lines
17 KiB
TypeScript
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|||
|
|
import * as net from 'net';
|
|||
|
|
import { createTestSmtpClient } from '../../helpers/smtp.client.ts';
|
|||
|
|
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
|||
|
|
|
|||
|
|
// Helper function to get memory usage
|
|||
|
|
const getMemoryUsage = () => {
|
|||
|
|
const usage = process.memoryUsage();
|
|||
|
|
return {
|
|||
|
|
heapUsed: Math.round(usage.heapUsed / 1024 / 1024 * 100) / 100, // MB
|
|||
|
|
heapTotal: Math.round(usage.heapTotal / 1024 / 1024 * 100) / 100, // MB
|
|||
|
|
external: Math.round(usage.external / 1024 / 1024 * 100) / 100, // MB
|
|||
|
|
rss: Math.round(usage.rss / 1024 / 1024 * 100) / 100 // MB
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Force garbage collection if available
|
|||
|
|
const forceGC = () => {
|
|||
|
|
if (global.gc) {
|
|||
|
|
global.gc();
|
|||
|
|
global.gc(); // Run twice for thoroughness
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
tap.test('CREL-05: Connection Pool Memory Management', async () => {
|
|||
|
|
console.log('\n🧠 Testing SMTP Client Memory Leak Prevention');
|
|||
|
|
console.log('=' .repeat(60));
|
|||
|
|
console.log('\n🏊 Testing connection pool memory management...');
|
|||
|
|
|
|||
|
|
// Create test server
|
|||
|
|
const server = net.createServer(socket => {
|
|||
|
|
socket.write('220 localhost SMTP Test Server\r\n');
|
|||
|
|
|
|||
|
|
socket.on('data', (data) => {
|
|||
|
|
const lines = data.toString().split('\r\n');
|
|||
|
|
|
|||
|
|
lines.forEach(line => {
|
|||
|
|
if (line.startsWith('EHLO') || line.startsWith('HELO')) {
|
|||
|
|
socket.write('250-localhost\r\n');
|
|||
|
|
socket.write('250 SIZE 10485760\r\n');
|
|||
|
|
} else if (line.startsWith('MAIL FROM:')) {
|
|||
|
|
socket.write('250 OK\r\n');
|
|||
|
|
} else if (line.startsWith('RCPT TO:')) {
|
|||
|
|
socket.write('250 OK\r\n');
|
|||
|
|
} else if (line === 'DATA') {
|
|||
|
|
socket.write('354 Send data\r\n');
|
|||
|
|
} else if (line === '.') {
|
|||
|
|
socket.write('250 OK Message accepted\r\n');
|
|||
|
|
} else if (line === 'QUIT') {
|
|||
|
|
socket.write('221 Bye\r\n');
|
|||
|
|
socket.end();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await new Promise<void>((resolve) => {
|
|||
|
|
server.listen(0, '127.0.0.1', () => {
|
|||
|
|
resolve();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const port = (server.address() as net.AddressInfo).port;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const initialMemory = getMemoryUsage();
|
|||
|
|
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap, ${initialMemory.rss}MB RSS`);
|
|||
|
|
|
|||
|
|
console.log(' Phase 1: Creating and using multiple connection pools...');
|
|||
|
|
const memorySnapshots = [];
|
|||
|
|
|
|||
|
|
for (let poolIndex = 0; poolIndex < 5; poolIndex++) {
|
|||
|
|
console.log(` Creating connection pool ${poolIndex + 1}...`);
|
|||
|
|
|
|||
|
|
const smtpClient = createTestSmtpClient({
|
|||
|
|
host: '127.0.0.1',
|
|||
|
|
port: port,
|
|||
|
|
secure: false,
|
|||
|
|
maxConnections: 3,
|
|||
|
|
maxMessages: 20,
|
|||
|
|
connectionTimeout: 1000
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Send emails through this pool
|
|||
|
|
const emails = [];
|
|||
|
|
for (let i = 0; i < 6; i++) {
|
|||
|
|
emails.push(new Email({
|
|||
|
|
from: `sender${poolIndex}@memoryleak.test`,
|
|||
|
|
to: [`recipient${i}@memoryleak.test`],
|
|||
|
|
subject: `Memory Pool Test ${poolIndex + 1}-${i + 1}`,
|
|||
|
|
text: `Testing memory management in pool ${poolIndex + 1}, email ${i + 1}`
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Send emails concurrently
|
|||
|
|
const promises = emails.map((email, index) => {
|
|||
|
|
return smtpClient.sendMail(email).then(result => {
|
|||
|
|
return { success: true, result };
|
|||
|
|
}).catch(error => {
|
|||
|
|
return { success: false, error };
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const results = await Promise.all(promises);
|
|||
|
|
const successful = results.filter(r => r.success).length;
|
|||
|
|
console.log(` Pool ${poolIndex + 1}: ${successful}/${emails.length} emails sent`);
|
|||
|
|
|
|||
|
|
// Close the pool
|
|||
|
|
smtpClient.close();
|
|||
|
|
console.log(` Pool ${poolIndex + 1} closed`);
|
|||
|
|
|
|||
|
|
// Force garbage collection and measure memory
|
|||
|
|
forceGC();
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|||
|
|
|
|||
|
|
const currentMemory = getMemoryUsage();
|
|||
|
|
memorySnapshots.push({
|
|||
|
|
pool: poolIndex + 1,
|
|||
|
|
heap: currentMemory.heapUsed,
|
|||
|
|
rss: currentMemory.rss,
|
|||
|
|
external: currentMemory.external
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(` Memory after pool ${poolIndex + 1}: ${currentMemory.heapUsed}MB heap`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('\n Memory analysis:');
|
|||
|
|
memorySnapshots.forEach((snapshot, index) => {
|
|||
|
|
const memoryIncrease = snapshot.heap - initialMemory.heapUsed;
|
|||
|
|
console.log(` Pool ${snapshot.pool}: +${memoryIncrease.toFixed(2)}MB heap increase`);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Check for memory leaks (memory should not continuously increase)
|
|||
|
|
const firstIncrease = memorySnapshots[0].heap - initialMemory.heapUsed;
|
|||
|
|
const lastIncrease = memorySnapshots[memorySnapshots.length - 1].heap - initialMemory.heapUsed;
|
|||
|
|
const leakGrowth = lastIncrease - firstIncrease;
|
|||
|
|
|
|||
|
|
console.log(` Memory leak assessment:`);
|
|||
|
|
console.log(` First pool increase: +${firstIncrease.toFixed(2)}MB`);
|
|||
|
|
console.log(` Final memory increase: +${lastIncrease.toFixed(2)}MB`);
|
|||
|
|
console.log(` Memory growth across pools: +${leakGrowth.toFixed(2)}MB`);
|
|||
|
|
console.log(` Memory management: ${leakGrowth < 3.0 ? 'Good (< 3MB growth)' : 'Potential leak detected'}`);
|
|||
|
|
|
|||
|
|
expect(leakGrowth).toBeLessThan(5.0); // Allow some memory growth but detect major leaks
|
|||
|
|
|
|||
|
|
} finally {
|
|||
|
|
server.close();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
tap.test('CREL-05: Email Object Memory Lifecycle', async () => {
|
|||
|
|
console.log('\n📧 Testing email object memory lifecycle...');
|
|||
|
|
|
|||
|
|
// Create test server
|
|||
|
|
const server = net.createServer(socket => {
|
|||
|
|
socket.write('220 localhost SMTP Test Server\r\n');
|
|||
|
|
|
|||
|
|
socket.on('data', (data) => {
|
|||
|
|
const lines = data.toString().split('\r\n');
|
|||
|
|
|
|||
|
|
lines.forEach(line => {
|
|||
|
|
if (line.startsWith('EHLO') || line.startsWith('HELO')) {
|
|||
|
|
socket.write('250-localhost\r\n');
|
|||
|
|
socket.write('250 SIZE 10485760\r\n');
|
|||
|
|
} else if (line.startsWith('MAIL FROM:')) {
|
|||
|
|
socket.write('250 OK\r\n');
|
|||
|
|
} else if (line.startsWith('RCPT TO:')) {
|
|||
|
|
socket.write('250 OK\r\n');
|
|||
|
|
} else if (line === 'DATA') {
|
|||
|
|
socket.write('354 Send data\r\n');
|
|||
|
|
} else if (line === '.') {
|
|||
|
|
socket.write('250 OK Message accepted\r\n');
|
|||
|
|
} else if (line === 'QUIT') {
|
|||
|
|
socket.write('221 Bye\r\n');
|
|||
|
|
socket.end();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await new Promise<void>((resolve) => {
|
|||
|
|
server.listen(0, '127.0.0.1', () => {
|
|||
|
|
resolve();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const port = (server.address() as net.AddressInfo).port;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const smtpClient = createTestSmtpClient({
|
|||
|
|
host: '127.0.0.1',
|
|||
|
|
port: port,
|
|||
|
|
secure: false,
|
|||
|
|
maxConnections: 2
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const initialMemory = getMemoryUsage();
|
|||
|
|
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`);
|
|||
|
|
|
|||
|
|
console.log(' Phase 1: Creating large batches of email objects...');
|
|||
|
|
const batchSizes = [50, 100, 150, 100, 50]; // Varying batch sizes
|
|||
|
|
const memorySnapshots = [];
|
|||
|
|
|
|||
|
|
for (let batchIndex = 0; batchIndex < batchSizes.length; batchIndex++) {
|
|||
|
|
const batchSize = batchSizes[batchIndex];
|
|||
|
|
console.log(` Creating batch ${batchIndex + 1} with ${batchSize} emails...`);
|
|||
|
|
|
|||
|
|
const emails = [];
|
|||
|
|
for (let i = 0; i < batchSize; i++) {
|
|||
|
|
emails.push(new Email({
|
|||
|
|
from: 'sender@emailmemory.test',
|
|||
|
|
to: [`recipient${i}@emailmemory.test`],
|
|||
|
|
subject: `Memory Lifecycle Test Batch ${batchIndex + 1} Email ${i + 1}`,
|
|||
|
|
text: `Testing email object memory lifecycle. This is a moderately long email body to test memory usage patterns. Email ${i + 1} in batch ${batchIndex + 1} of ${batchSize} emails.`,
|
|||
|
|
html: `<h1>Email ${i + 1}</h1><p>Testing memory patterns with HTML content. Batch ${batchIndex + 1}.</p>`
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(` Sending batch ${batchIndex + 1}...`);
|
|||
|
|
const promises = emails.map((email, index) => {
|
|||
|
|
return smtpClient.sendMail(email).then(result => {
|
|||
|
|
return { success: true };
|
|||
|
|
}).catch(error => {
|
|||
|
|
return { success: false, error };
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const results = await Promise.all(promises);
|
|||
|
|
const successful = results.filter(r => r.success).length;
|
|||
|
|
console.log(` Batch ${batchIndex + 1}: ${successful}/${batchSize} emails sent`);
|
|||
|
|
|
|||
|
|
// Clear email references
|
|||
|
|
emails.length = 0;
|
|||
|
|
|
|||
|
|
// Force garbage collection
|
|||
|
|
forceGC();
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|||
|
|
|
|||
|
|
const currentMemory = getMemoryUsage();
|
|||
|
|
memorySnapshots.push({
|
|||
|
|
batch: batchIndex + 1,
|
|||
|
|
size: batchSize,
|
|||
|
|
heap: currentMemory.heapUsed,
|
|||
|
|
external: currentMemory.external
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(` Memory after batch ${batchIndex + 1}: ${currentMemory.heapUsed}MB heap`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('\n Email object memory analysis:');
|
|||
|
|
memorySnapshots.forEach((snapshot, index) => {
|
|||
|
|
const memoryIncrease = snapshot.heap - initialMemory.heapUsed;
|
|||
|
|
console.log(` Batch ${snapshot.batch} (${snapshot.size} emails): +${memoryIncrease.toFixed(2)}MB`);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Check if memory scales reasonably with email batch size
|
|||
|
|
const maxMemoryIncrease = Math.max(...memorySnapshots.map(s => s.heap - initialMemory.heapUsed));
|
|||
|
|
const avgBatchSize = batchSizes.reduce((a, b) => a + b, 0) / batchSizes.length;
|
|||
|
|
|
|||
|
|
console.log(` Maximum memory increase: +${maxMemoryIncrease.toFixed(2)}MB`);
|
|||
|
|
console.log(` Average batch size: ${avgBatchSize} emails`);
|
|||
|
|
console.log(` Memory per email: ~${(maxMemoryIncrease / avgBatchSize * 1024).toFixed(1)}KB`);
|
|||
|
|
console.log(` Email object lifecycle: ${maxMemoryIncrease < 10 ? 'Efficient' : 'Needs optimization'}`);
|
|||
|
|
|
|||
|
|
expect(maxMemoryIncrease).toBeLessThan(15); // Allow reasonable memory usage
|
|||
|
|
|
|||
|
|
smtpClient.close();
|
|||
|
|
} finally {
|
|||
|
|
server.close();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
tap.test('CREL-05: Long-Running Client Memory Stability', async () => {
|
|||
|
|
console.log('\n⏱️ Testing long-running client memory stability...');
|
|||
|
|
|
|||
|
|
// Create test server
|
|||
|
|
const server = net.createServer(socket => {
|
|||
|
|
socket.write('220 localhost SMTP Test Server\r\n');
|
|||
|
|
|
|||
|
|
socket.on('data', (data) => {
|
|||
|
|
const lines = data.toString().split('\r\n');
|
|||
|
|
|
|||
|
|
lines.forEach(line => {
|
|||
|
|
if (line.startsWith('EHLO') || line.startsWith('HELO')) {
|
|||
|
|
socket.write('250-localhost\r\n');
|
|||
|
|
socket.write('250 SIZE 10485760\r\n');
|
|||
|
|
} else if (line.startsWith('MAIL FROM:')) {
|
|||
|
|
socket.write('250 OK\r\n');
|
|||
|
|
} else if (line.startsWith('RCPT TO:')) {
|
|||
|
|
socket.write('250 OK\r\n');
|
|||
|
|
} else if (line === 'DATA') {
|
|||
|
|
socket.write('354 Send data\r\n');
|
|||
|
|
} else if (line === '.') {
|
|||
|
|
socket.write('250 OK Message accepted\r\n');
|
|||
|
|
} else if (line === 'QUIT') {
|
|||
|
|
socket.write('221 Bye\r\n');
|
|||
|
|
socket.end();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await new Promise<void>((resolve) => {
|
|||
|
|
server.listen(0, '127.0.0.1', () => {
|
|||
|
|
resolve();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const port = (server.address() as net.AddressInfo).port;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const smtpClient = createTestSmtpClient({
|
|||
|
|
host: '127.0.0.1',
|
|||
|
|
port: port,
|
|||
|
|
secure: false,
|
|||
|
|
maxConnections: 2,
|
|||
|
|
maxMessages: 1000
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const initialMemory = getMemoryUsage();
|
|||
|
|
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`);
|
|||
|
|
|
|||
|
|
console.log(' Starting sustained email sending operation...');
|
|||
|
|
const memoryMeasurements = [];
|
|||
|
|
const totalEmails = 100; // Reduced for test efficiency
|
|||
|
|
const measurementInterval = 20; // Measure every 20 emails
|
|||
|
|
|
|||
|
|
let emailsSent = 0;
|
|||
|
|
let emailsFailed = 0;
|
|||
|
|
|
|||
|
|
for (let i = 0; i < totalEmails; i++) {
|
|||
|
|
const email = new Email({
|
|||
|
|
from: 'sender@longrunning.test',
|
|||
|
|
to: [`recipient${i}@longrunning.test`],
|
|||
|
|
subject: `Long Running Test ${i + 1}`,
|
|||
|
|
text: `Sustained operation test email ${i + 1}`
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await smtpClient.sendMail(email);
|
|||
|
|
emailsSent++;
|
|||
|
|
} catch (error) {
|
|||
|
|
emailsFailed++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Measure memory at intervals
|
|||
|
|
if ((i + 1) % measurementInterval === 0) {
|
|||
|
|
forceGC();
|
|||
|
|
const currentMemory = getMemoryUsage();
|
|||
|
|
memoryMeasurements.push({
|
|||
|
|
emailCount: i + 1,
|
|||
|
|
heap: currentMemory.heapUsed,
|
|||
|
|
rss: currentMemory.rss,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(` ${i + 1}/${totalEmails} emails: ${currentMemory.heapUsed}MB heap`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('\n Long-running memory analysis:');
|
|||
|
|
console.log(` Emails sent: ${emailsSent}, Failed: ${emailsFailed}`);
|
|||
|
|
|
|||
|
|
memoryMeasurements.forEach((measurement, index) => {
|
|||
|
|
const memoryIncrease = measurement.heap - initialMemory.heapUsed;
|
|||
|
|
console.log(` After ${measurement.emailCount} emails: +${memoryIncrease.toFixed(2)}MB heap`);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Analyze memory growth trend
|
|||
|
|
if (memoryMeasurements.length >= 2) {
|
|||
|
|
const firstMeasurement = memoryMeasurements[0];
|
|||
|
|
const lastMeasurement = memoryMeasurements[memoryMeasurements.length - 1];
|
|||
|
|
|
|||
|
|
const memoryGrowth = lastMeasurement.heap - firstMeasurement.heap;
|
|||
|
|
const emailsProcessed = lastMeasurement.emailCount - firstMeasurement.emailCount;
|
|||
|
|
const growthRate = (memoryGrowth / emailsProcessed) * 1000; // KB per email
|
|||
|
|
|
|||
|
|
console.log(` Memory growth over operation: +${memoryGrowth.toFixed(2)}MB`);
|
|||
|
|
console.log(` Growth rate: ~${growthRate.toFixed(2)}KB per email`);
|
|||
|
|
console.log(` Memory stability: ${growthRate < 10 ? 'Excellent' : growthRate < 25 ? 'Good' : 'Concerning'}`);
|
|||
|
|
|
|||
|
|
expect(growthRate).toBeLessThan(50); // Allow reasonable growth but detect major leaks
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
expect(emailsSent).toBeGreaterThanOrEqual(totalEmails - 5); // Most emails should succeed
|
|||
|
|
|
|||
|
|
smtpClient.close();
|
|||
|
|
} finally {
|
|||
|
|
server.close();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
tap.test('CREL-05: Large Content Memory Management', async () => {
|
|||
|
|
console.log('\n🌊 Testing large content memory management...');
|
|||
|
|
|
|||
|
|
// Create test server
|
|||
|
|
const server = net.createServer(socket => {
|
|||
|
|
socket.write('220 localhost SMTP Test Server\r\n');
|
|||
|
|
|
|||
|
|
socket.on('data', (data) => {
|
|||
|
|
const lines = data.toString().split('\r\n');
|
|||
|
|
|
|||
|
|
lines.forEach(line => {
|
|||
|
|
if (line.startsWith('EHLO') || line.startsWith('HELO')) {
|
|||
|
|
socket.write('250-localhost\r\n');
|
|||
|
|
socket.write('250 SIZE 10485760\r\n');
|
|||
|
|
} else if (line.startsWith('MAIL FROM:')) {
|
|||
|
|
socket.write('250 OK\r\n');
|
|||
|
|
} else if (line.startsWith('RCPT TO:')) {
|
|||
|
|
socket.write('250 OK\r\n');
|
|||
|
|
} else if (line === 'DATA') {
|
|||
|
|
socket.write('354 Send data\r\n');
|
|||
|
|
} else if (line === '.') {
|
|||
|
|
socket.write('250 OK Message accepted\r\n');
|
|||
|
|
} else if (line === 'QUIT') {
|
|||
|
|
socket.write('221 Bye\r\n');
|
|||
|
|
socket.end();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await new Promise<void>((resolve) => {
|
|||
|
|
server.listen(0, '127.0.0.1', () => {
|
|||
|
|
resolve();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const port = (server.address() as net.AddressInfo).port;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const smtpClient = createTestSmtpClient({
|
|||
|
|
host: '127.0.0.1',
|
|||
|
|
port: port,
|
|||
|
|
secure: false,
|
|||
|
|
maxConnections: 1
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const initialMemory = getMemoryUsage();
|
|||
|
|
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`);
|
|||
|
|
|
|||
|
|
console.log(' Testing with various content sizes...');
|
|||
|
|
const contentSizes = [
|
|||
|
|
{ size: 1024, name: '1KB' },
|
|||
|
|
{ size: 10240, name: '10KB' },
|
|||
|
|
{ size: 102400, name: '100KB' },
|
|||
|
|
{ size: 256000, name: '250KB' }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (const contentTest of contentSizes) {
|
|||
|
|
console.log(` Testing ${contentTest.name} content size...`);
|
|||
|
|
|
|||
|
|
const beforeMemory = getMemoryUsage();
|
|||
|
|
|
|||
|
|
// Create large text content
|
|||
|
|
const largeText = 'X'.repeat(contentTest.size);
|
|||
|
|
|
|||
|
|
const email = new Email({
|
|||
|
|
from: 'sender@largemem.test',
|
|||
|
|
to: ['recipient@largemem.test'],
|
|||
|
|
subject: `Large Content Test - ${contentTest.name}`,
|
|||
|
|
text: largeText
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await smtpClient.sendMail(email);
|
|||
|
|
console.log(` ✓ ${contentTest.name} email sent successfully`);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.log(` ✗ ${contentTest.name} email failed: ${error.message}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Force cleanup
|
|||
|
|
forceGC();
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|||
|
|
|
|||
|
|
const afterMemory = getMemoryUsage();
|
|||
|
|
const memoryDiff = afterMemory.heapUsed - beforeMemory.heapUsed;
|
|||
|
|
|
|||
|
|
console.log(` Memory impact: ${memoryDiff > 0 ? '+' : ''}${memoryDiff.toFixed(2)}MB`);
|
|||
|
|
console.log(` Efficiency: ${Math.abs(memoryDiff) < (contentTest.size / 1024 / 1024) * 2 ? 'Good' : 'High memory usage'}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const finalMemory = getMemoryUsage();
|
|||
|
|
const totalMemoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed;
|
|||
|
|
|
|||
|
|
console.log(`\n Large content memory summary:`);
|
|||
|
|
console.log(` Total memory increase: +${totalMemoryIncrease.toFixed(2)}MB`);
|
|||
|
|
console.log(` Memory management efficiency: ${totalMemoryIncrease < 5 ? 'Excellent' : 'Needs optimization'}`);
|
|||
|
|
|
|||
|
|
expect(totalMemoryIncrease).toBeLessThan(20); // Allow reasonable memory usage for large content
|
|||
|
|
|
|||
|
|
smtpClient.close();
|
|||
|
|
} finally {
|
|||
|
|
server.close();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
tap.test('CREL-05: Test Summary', async () => {
|
|||
|
|
console.log('\n✅ CREL-05: Memory Leak Prevention Reliability Tests completed');
|
|||
|
|
console.log('🧠 All memory management scenarios tested successfully');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
tap.start();
|