/** * @file test.perf-01.detection-speed.ts * @description Performance tests for format detection speed */ import { expect, tap } from '@git.zone/tstest/tapbundle'; import { FormatDetector } from '../../../ts/formats/utils/format.detector.js'; import { InvoiceFormat } from '../../../ts/interfaces/common.js'; // Simple performance tracking class SimplePerformanceTracker { private measurements: Map = new Map(); private name: string; constructor(name: string) { this.name = name; } addMeasurement(key: string, time: number): void { if (!this.measurements.has(key)) { this.measurements.set(key, []); } this.measurements.get(key)!.push(time); } getStats(key: string) { const times = this.measurements.get(key) || []; if (times.length === 0) return null; const sorted = [...times].sort((a, b) => a - b); return { avg: times.reduce((a, b) => a + b, 0) / times.length, min: sorted[0], max: sorted[sorted.length - 1], p95: sorted[Math.floor(sorted.length * 0.95)] }; } printSummary(): void { console.log(`\n${this.name} - Performance Summary:`); for (const [key, times] of this.measurements) { const stats = this.getStats(key); if (stats) { console.log(` ${key}: avg=${stats.avg.toFixed(2)}ms, min=${stats.min.toFixed(2)}ms, max=${stats.max.toFixed(2)}ms, p95=${stats.p95.toFixed(2)}ms`); } } } } const performanceTracker = new SimplePerformanceTracker('PERF-01: Format Detection Speed'); tap.test('PERF-01: Single file detection benchmarks', async () => { const testCases = [ { name: 'UBL Invoice', content: ` 123 2025-01-25 `, expectedFormat: InvoiceFormat.UBL }, { name: 'CII Invoice', content: ` 123 `, expectedFormat: InvoiceFormat.CII }, { name: 'Factur-X', content: ` urn:factur-x.eu:1p0:minimum `, expectedFormat: InvoiceFormat.FACTURX } ]; const iterations = 100; for (const testCase of testCases) { const times: number[] = []; for (let i = 0; i < iterations; i++) { const startTime = performance.now(); const format = FormatDetector.detectFormat(testCase.content); const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); performanceTracker.addMeasurement(`detect-${testCase.name}`, duration); if (i === 0) { expect(format).toEqual(testCase.expectedFormat); } } // Calculate statistics times.sort((a, b) => a - b); const avg = times.reduce((a, b) => a + b, 0) / times.length; const p95 = times[Math.floor(times.length * 0.95)]; console.log(`${testCase.name}: avg=${avg.toFixed(3)}ms, p95=${p95.toFixed(3)}ms`); // Performance assertions expect(avg).toBeLessThan(5); // Average should be less than 5ms expect(p95).toBeLessThan(10); // 95th percentile should be less than 10ms } }); tap.test('PERF-01: Quick detection performance', async () => { // Test the quick string-based detection performance const largeInvoice = ` LARGE-TEST-001 2025-01-25 ${Array(1000).fill('1').join('')} `; const iterations = 50; const times: number[] = []; for (let i = 0; i < iterations; i++) { const startTime = performance.now(); const format = FormatDetector.detectFormat(largeInvoice); const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); performanceTracker.addMeasurement('large-invoice-detection', duration); } const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`Large invoice detection: avg=${avg.toFixed(3)}ms`); // Even large invoices should be detected quickly due to quick string check expect(avg).toBeLessThan(10); }); tap.test('PERF-01: Edge cases detection performance', async () => { const edgeCases = [ { name: 'Empty string', content: '', expectedFormat: InvoiceFormat.UNKNOWN }, { name: 'Invalid XML', content: 'test', expectedFormat: InvoiceFormat.UNKNOWN } ]; for (const testCase of edgeCases) { const times: number[] = []; for (let i = 0; i < 100; i++) { const startTime = performance.now(); const format = FormatDetector.detectFormat(testCase.content); const endTime = performance.now(); times.push(endTime - startTime); if (i === 0) { expect(format).toEqual(testCase.expectedFormat); } } const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`${testCase.name}: avg=${avg.toFixed(3)}ms`); // Edge cases should be detected very quickly expect(avg).toBeLessThan(1); } }); tap.test('PERF-01: Concurrent detection performance', async () => { const xmlContent = ` CONCURRENT-TEST `; const concurrentCount = 10; const iterations = 5; for (let iter = 0; iter < iterations; iter++) { const startTime = performance.now(); // Run multiple detections concurrently const promises = Array(concurrentCount).fill(null).map(() => Promise.resolve(FormatDetector.detectFormat(xmlContent)) ); const results = await Promise.all(promises); const endTime = performance.now(); const duration = endTime - startTime; performanceTracker.addMeasurement('concurrent-detection', duration); // All should detect the same format expect(results.every(r => r === InvoiceFormat.UBL)).toEqual(true); console.log(`Concurrent detection (${concurrentCount} parallel): ${duration.toFixed(3)}ms`); } const stats = performanceTracker.getStats('concurrent-detection'); if (stats) { // Concurrent detection should still be fast expect(stats.avg).toBeLessThan(50); } }); tap.test('PERF-01: Memory usage during detection', async () => { const initialMemory = process.memoryUsage(); // Create a reasonably large test set const testXmls = Array(1000).fill(null).map((_, i) => ` MEM-TEST-${i} 2025-01-25 `); // Detect all formats const startTime = performance.now(); const formats = testXmls.map(xml => FormatDetector.detectFormat(xml)); const endTime = performance.now(); const afterMemory = process.memoryUsage(); const memoryIncrease = (afterMemory.heapUsed - initialMemory.heapUsed) / 1024 / 1024; console.log(`Detected ${formats.length} formats in ${(endTime - startTime).toFixed(2)}ms`); console.log(`Memory increase: ${memoryIncrease.toFixed(2)} MB`); // Memory increase should be reasonable expect(memoryIncrease).toBeLessThan(50); // Less than 50MB for 1000 detections // All should be detected as UBL expect(formats.every(f => f === InvoiceFormat.UBL)).toEqual(true); }); tap.test('PERF-01: Performance Summary', async () => { performanceTracker.printSummary(); console.log('\nFormat detection performance tests completed successfully'); }); tap.start();