264 lines
8.7 KiB
TypeScript
264 lines
8.7 KiB
TypeScript
/**
|
|
* @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<string, number[]> = 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: `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<ID>123</ID>
|
|
<IssueDate>2025-01-25</IssueDate>
|
|
</Invoice>`,
|
|
expectedFormat: InvoiceFormat.UBL
|
|
},
|
|
{
|
|
name: 'CII Invoice',
|
|
content: `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
|
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100">
|
|
<rsm:ExchangedDocument>
|
|
<ram:ID>123</ram:ID>
|
|
</rsm:ExchangedDocument>
|
|
</rsm:CrossIndustryInvoice>`,
|
|
expectedFormat: InvoiceFormat.CII
|
|
},
|
|
{
|
|
name: 'Factur-X',
|
|
content: `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
|
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
|
xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100">
|
|
<rsm:ExchangedDocumentContext>
|
|
<ram:GuidelineSpecifiedDocumentContextParameter>
|
|
<ram:ID>urn:factur-x.eu:1p0:minimum</ram:ID>
|
|
</ram:GuidelineSpecifiedDocumentContextParameter>
|
|
</rsm:ExchangedDocumentContext>
|
|
</rsm:CrossIndustryInvoice>`,
|
|
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 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
|
<cbc:ID>LARGE-TEST-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
${Array(1000).fill('<cac:InvoiceLine><cbc:ID>1</cbc:ID></cac:InvoiceLine>').join('')}
|
|
</Invoice>`;
|
|
|
|
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: '<not-closed',
|
|
expectedFormat: InvoiceFormat.UNKNOWN
|
|
},
|
|
{
|
|
name: 'Non-invoice XML',
|
|
content: '<?xml version="1.0"?><root><data>test</data></root>',
|
|
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 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<ID>CONCURRENT-TEST</ID>
|
|
</Invoice>`;
|
|
|
|
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) => `<?xml version="1.0"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<ID>MEM-TEST-${i}</ID>
|
|
<IssueDate>2025-01-25</IssueDate>
|
|
</Invoice>`);
|
|
|
|
// 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(); |