import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartpreview from '../ts/index.js'; // Create test data with different sizes const createTestPdf = (complexity: 'simple' | 'complex'): Buffer => { if (complexity === 'simple') { const pdfContent = `%PDF-1.4 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >> endobj 4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj 5 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello World) Tj ET endstream endobj xref 0 6 0000000000 65535 f 0000000010 00000 n 0000000079 00000 n 0000000136 00000 n 0000000273 00000 n 0000000362 00000 n trailer << /Size 6 /Root 1 0 R >> startxref 456 %%EOF`; return Buffer.from(pdfContent, 'utf8'); } else { // Complex PDF with more content const complexPdfContent = `%PDF-1.4 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R 7 0 R] /Count 2 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >> endobj 4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj 5 0 obj << /Length 200 >> stream BT /F1 12 Tf 72 720 Td (Complex PDF Document - Page 1) Tj 0 -20 Td (This is a more complex PDF with multiple lines) Tj 0 -20 Td (Line 3 with some content) Tj 0 -20 Td (Line 4 with more text for testing) Tj 0 -20 Td (Final line on page 1) Tj ET endstream endobj 7 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 8 0 R >> endobj 8 0 obj << /Length 150 >> stream BT /F1 12 Tf 72 720 Td (Complex PDF Document - Page 2) Tj 0 -20 Td (Second page content) Tj 0 -20 Td (More text on page 2) Tj 0 -20 Td (End of document) Tj ET endstream endobj xref 0 9 0000000000 65535 f 0000000010 00000 n 0000000079 00000 n 0000000136 00000 n 0000000340 00000 n 0000000429 00000 n 0000000000 65535 f 0000000680 00000 n 0000000884 00000 n trailer << /Size 9 /Root 1 0 R >> startxref 1085 %%EOF`; return Buffer.from(complexPdfContent, 'utf8'); } }; interface IPerformanceMetrics { initTime: number; conversionTimes: number[]; averageConversionTime: number; minConversionTime: number; maxConversionTime: number; standardDeviation: number; throughput: number; // conversions per second memoryUsed?: number; } // Comprehensive performance benchmark tap.test('Performance Benchmark Suite', async () => { console.log('\nšŸš€ Starting SmartPreview Performance Benchmark\n'); const results: { [key: string]: IPerformanceMetrics } = {}; // Test different configurations const testConfigs = [ { name: 'Low Quality Small', quality: 30, width: 200, height: 150 }, { name: 'Medium Quality Medium', quality: 60, width: 400, height: 300 }, { name: 'High Quality Large', quality: 90, width: 800, height: 600 }, { name: 'Ultra Quality XLarge', quality: 100, width: 1200, height: 900 }, ]; for (const config of testConfigs) { console.log(`šŸ“Š Testing configuration: ${config.name}`); const preview = new smartpreview.SmartPreview(); const testPdf = createTestPdf('simple'); const iterations = 5; try { // Measure initialization time const initStartTime = performance.now(); await preview.init(); const initTime = performance.now() - initStartTime; // Measure memory before conversions const memoryBefore = process.memoryUsage(); // Run multiple conversions to get average const conversionTimes: number[] = []; for (let i = 0; i < iterations; i++) { const startTime = performance.now(); await preview.generatePreview(testPdf, { quality: config.quality, width: config.width, height: config.height }); const conversionTime = performance.now() - startTime; conversionTimes.push(conversionTime); } // Measure memory after conversions const memoryAfter = process.memoryUsage(); const memoryUsed = (memoryAfter.heapUsed - memoryBefore.heapUsed) / 1024 / 1024; // MB // Calculate metrics const averageConversionTime = conversionTimes.reduce((a, b) => a + b, 0) / conversionTimes.length; const minConversionTime = Math.min(...conversionTimes); const maxConversionTime = Math.max(...conversionTimes); const variance = conversionTimes.reduce((acc, time) => acc + Math.pow(time - averageConversionTime, 2), 0) / conversionTimes.length; const standardDeviation = Math.sqrt(variance); const throughput = 1000 / averageConversionTime; // conversions per second results[config.name] = { initTime, conversionTimes, averageConversionTime, minConversionTime, maxConversionTime, standardDeviation, throughput, memoryUsed }; // Print results for this config console.log(` ⚔ Init Time: ${initTime.toFixed(2)}ms`); console.log(` ā±ļø Avg Conversion: ${averageConversionTime.toFixed(2)}ms`); console.log(` šŸ“ˆ Throughput: ${throughput.toFixed(2)} conversions/sec`); console.log(` šŸ’¾ Memory Used: ${memoryUsed.toFixed(2)}MB`); console.log(` šŸ“Š Std Dev: ${standardDeviation.toFixed(2)}ms\n`); await preview.cleanup(); } catch (error) { console.log(` āŒ Test skipped: ${error.message}\n`); // Expected if dependencies are not available expect(error).toBeInstanceOf(smartpreview.PreviewError); } } // Performance assertions for (const [configName, metrics] of Object.entries(results)) { // Initialization should be reasonable expect(metrics.initTime).toBeLessThan(10000); // < 10 seconds // Conversion times should be consistent (allow some variance for very fast operations) expect(metrics.standardDeviation).toBeLessThan(metrics.averageConversionTime * 5); // Std dev shouldn't be more than 5x average // Should achieve minimum throughput expect(metrics.throughput).toBeGreaterThan(0.1); // At least 0.1 conversions per second console.log(`āœ… ${configName} performance benchmarks passed`); } }); // Memory usage analysis tap.test('Memory Usage Analysis', async () => { console.log('\n🧠 Memory Usage Analysis\n'); const preview = new smartpreview.SmartPreview(); const testPdf = createTestPdf('complex'); try { await preview.init(); const initialMemory = process.memoryUsage(); console.log(`šŸ“Š Initial Memory Usage:`); console.log(` Heap Used: ${(initialMemory.heapUsed / 1024 / 1024).toFixed(2)}MB`); console.log(` Heap Total: ${(initialMemory.heapTotal / 1024 / 1024).toFixed(2)}MB`); console.log(` RSS: ${(initialMemory.rss / 1024 / 1024).toFixed(2)}MB\n`); // Perform multiple conversions to check for memory leaks const iterations = 10; const memorySnapshots: any[] = []; for (let i = 0; i < iterations; i++) { await preview.generatePreview(testPdf, { quality: 80, width: 600, height: 400 }); const memory = process.memoryUsage(); memorySnapshots.push({ iteration: i + 1, heapUsed: memory.heapUsed / 1024 / 1024, heapTotal: memory.heapTotal / 1024 / 1024, rss: memory.rss / 1024 / 1024 }); // Force garbage collection if available if (global.gc) { global.gc(); } } // Analyze memory growth const firstHeap = memorySnapshots[0].heapUsed; const lastHeap = memorySnapshots[memorySnapshots.length - 1].heapUsed; const memoryGrowth = lastHeap - firstHeap; console.log(`šŸ“ˆ Memory Growth Analysis:`); console.log(` First Iteration: ${firstHeap.toFixed(2)}MB`); console.log(` Last Iteration: ${lastHeap.toFixed(2)}MB`); console.log(` Total Growth: ${memoryGrowth.toFixed(2)}MB`); console.log(` Growth per Conversion: ${(memoryGrowth / iterations).toFixed(2)}MB\n`); // Memory growth should be minimal (indicating no major memory leaks) expect(memoryGrowth).toBeLessThan(50); // Less than 50MB growth for 10 conversions await preview.cleanup(); } catch (error) { console.log(`āŒ Memory test skipped: ${error.message}`); expect(error).toBeInstanceOf(smartpreview.PreviewError); } }); // Stress test tap.test('Stress Test - Rapid Conversions', async () => { console.log('\nšŸ”„ Stress Test - Rapid Conversions\n'); const preview = new smartpreview.SmartPreview(); const testPdf = createTestPdf('simple'); const rapidIterations = 20; try { await preview.init(); const startTime = performance.now(); const promises: Promise[] = []; // Start multiple conversions simultaneously for (let i = 0; i < rapidIterations; i++) { const promise = preview.generatePreview(testPdf, { quality: 70, width: 300, height: 200 }); promises.push(promise); } // Wait for all conversions to complete const results = await Promise.allSettled(promises); const totalTime = performance.now() - startTime; const successful = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; console.log(`⚔ Stress Test Results:`); console.log(` Total Conversions: ${rapidIterations}`); console.log(` Successful: ${successful}`); console.log(` Failed: ${failed}`); console.log(` Total Time: ${totalTime.toFixed(2)}ms`); console.log(` Average Time per Conversion: ${(totalTime / successful).toFixed(2)}ms`); console.log(` Concurrent Throughput: ${(successful * 1000 / totalTime).toFixed(2)} conversions/sec\n`); // Most conversions should succeed expect(successful).toBeGreaterThan(rapidIterations * 0.8); // At least 80% success rate expect(failed).toBeLessThan(rapidIterations * 0.2); // Less than 20% failure rate await preview.cleanup(); } catch (error) { console.log(`āŒ Stress test skipped: ${error.message}`); expect(error).toBeInstanceOf(smartpreview.PreviewError); } }); export default tap.start();