332 lines
10 KiB
TypeScript
332 lines
10 KiB
TypeScript
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(); |