einvoice/test/suite/einvoice_conversion/test.conv-12.performance.ts

585 lines
22 KiB
TypeScript
Raw Normal View History

2025-05-25 19:45:37 +00:00
/**
* @file test.conv-12.performance.ts
* @description Performance benchmarks for format conversion operations
*/
2025-05-26 13:33:21 +00:00
import { expect, tap } from '@git.zone/tstest/tapbundle';
2025-05-25 19:45:37 +00:00
import * as plugins from '../../plugins.js';
import { EInvoice } from '../../../ts/index.js';
2025-05-26 13:33:21 +00:00
tap.test('CONV-12: Performance - should measure single XML load/export performance', async () => {
const einvoice = new EInvoice();
const benchmarks = [];
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
// Define test scenarios
const scenarios = [
{ format: 'ubl', name: 'UBL Load/Export' },
{ format: 'cii', name: 'CII Load/Export' }
];
// Create test invoices for each format
const testInvoices = {
ubl: `<?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>PERF-UBL-001</cbc:ID>
<cbc:IssueDate>2024-01-30</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>UBL Performance Test Seller</cbc:Name>
</cac:PartyName>
2025-05-28 11:31:31 +00:00
<cac:PostalAddress>
<cbc:StreetName>Test Street 1</cbc:StreetName>
<cbc:CityName>Test City</cbc:CityName>
<cbc:PostalZone>12345</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
2025-05-26 13:33:21 +00:00
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>UBL Performance Test Buyer</cbc:Name>
</cac:PartyName>
2025-05-28 11:31:31 +00:00
<cac:PostalAddress>
<cbc:StreetName>Buyer Street 10</cbc:StreetName>
<cbc:CityName>Buyer City</cbc:CityName>
<cbc:PostalZone>54321</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
2025-05-26 13:33:21 +00:00
</cac:Party>
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Product</cbc:Name>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
<cac:LegalMonetaryTotal>
<cbc:PayableAmount currencyID="EUR">110.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
</Invoice>`,
cii: `<?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:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocument>
<ram:ID>PERF-CII-001</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20240130</udt:DateTimeString>
</ram:IssueDateTime>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
2025-05-28 11:31:31 +00:00
<ram:IncludedSupplyChainTradeLineItem>
<ram:AssociatedDocumentLineDocument>
<ram:LineID>1</ram:LineID>
</ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct>
<ram:Name>Product</ram:Name>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity unitCode="EA">2</ram:BilledQuantity>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount>200.00</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>
2025-05-26 13:33:21 +00:00
<ram:ApplicableHeaderTradeAgreement>
<ram:SellerTradeParty>
<ram:Name>CII Performance Test Seller</ram:Name>
2025-05-28 11:31:31 +00:00
<ram:PostalTradeAddress>
<ram:LineOne>Test Street 1</ram:LineOne>
<ram:CityName>Test City</ram:CityName>
<ram:PostcodeCode>12345</ram:PostcodeCode>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
2025-05-26 13:33:21 +00:00
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
<ram:Name>CII Performance Test Buyer</ram:Name>
2025-05-28 11:31:31 +00:00
<ram:PostalTradeAddress>
<ram:LineOne>Buyer Street 10</ram:LineOne>
<ram:CityName>Buyer City</ram:CityName>
<ram:PostcodeCode>54321</ram:PostcodeCode>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
2025-05-26 13:33:21 +00:00
</ram:BuyerTradeParty>
</ram:ApplicableHeaderTradeAgreement>
2025-05-28 11:31:31 +00:00
<ram:ApplicableHeaderTradeDelivery/>
2025-05-26 13:33:21 +00:00
<ram:ApplicableHeaderTradeSettlement>
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
2025-05-28 11:31:31 +00:00
<ram:TaxBasisTotalAmount>200.00</ram:TaxBasisTotalAmount>
<ram:TaxTotalAmount currencyID="EUR">38.00</ram:TaxTotalAmount>
2025-05-26 13:33:21 +00:00
<ram:GrandTotalAmount>238.00</ram:GrandTotalAmount>
2025-05-28 11:31:31 +00:00
<ram:DuePayableAmount>238.00</ram:DuePayableAmount>
2025-05-26 13:33:21 +00:00
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>`
};
// Run benchmarks
for (const scenario of scenarios) {
const iterations = 10;
const times = [];
for (let i = 0; i < iterations; i++) {
const startTime = process.hrtime.bigint();
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
try {
// Load XML
await einvoice.loadXml(testInvoices[scenario.format]);
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
// Export back to XML
await einvoice.toXmlString(scenario.format as any);
2025-05-25 19:45:37 +00:00
const endTime = process.hrtime.bigint();
2025-05-26 13:33:21 +00:00
const duration = Number(endTime - startTime) / 1_000_000; // Convert to milliseconds
times.push(duration);
} catch (error) {
console.log(`Error in ${scenario.name}:`, error);
2025-05-25 19:45:37 +00:00
}
}
2025-05-26 13:33:21 +00:00
if (times.length > 0) {
times.sort((a, b) => a - b);
benchmarks.push({
scenario: scenario.name,
min: times[0],
max: times[times.length - 1],
avg: times.reduce((a, b) => a + b, 0) / times.length,
median: times[Math.floor(times.length / 2)],
p95: times[Math.floor(times.length * 0.95)] || times[times.length - 1]
});
}
}
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
console.log('\nSingle Operation Benchmarks (10 iterations each):');
benchmarks.forEach(bench => {
console.log(` ${bench.scenario}:`);
console.log(` - Min: ${bench.min.toFixed(2)}ms, Max: ${bench.max.toFixed(2)}ms`);
console.log(` - Average: ${bench.avg.toFixed(2)}ms, Median: ${bench.median.toFixed(2)}ms, P95: ${bench.p95.toFixed(2)}ms`);
});
expect(benchmarks.length).toBeGreaterThan(0);
benchmarks.forEach(bench => {
expect(bench.avg).toBeLessThan(100); // Should process in less than 100ms on average
});
});
tap.test('CONV-12: Performance - should handle complex invoice with many items', async () => {
const einvoice = new EInvoice();
// Create complex invoice with many items
const itemCount = 100;
const complexInvoice = `<?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>PERF-COMPLEX-001</cbc:ID>
<cbc:IssueDate>2024-01-30</cbc:IssueDate>
<cbc:DueDate>2024-02-29</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cbc:Note>This is a complex invoice with ${itemCount} line items for performance testing purposes.</cbc:Note>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Complex International Trading Company Ltd.</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Global Business Center, Tower A, Floor 25</cbc:StreetName>
<cbc:CityName>London</cbc:CityName>
<cbc:PostalZone>EC2M 7PY</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>GB</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>GB123456789</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Multinational Buyer Corporation GmbH</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Industriestraße 100-200</cbc:StreetName>
<cbc:CityName>Frankfurt</cbc:CityName>
<cbc:PostalZone>60311</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingCustomerParty>
${Array.from({ length: itemCount }, (_, i) => `
<cac:InvoiceLine>
<cbc:ID>${i + 1}</cbc:ID>
<cbc:InvoicedQuantity unitCode="EA">${Math.floor(Math.random() * 100) + 1}</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">${(Math.random() * 1000).toFixed(2)}</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Product Line Item ${i + 1} - Detailed description with technical specifications</cbc:Name>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">${(Math.random() * 100).toFixed(2)}</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>`).join('')}
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="EUR">50000.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="EUR">50000.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">59500.00</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="EUR">59500.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
</Invoice>`;
const results = [];
const operations = ['load', 'export'];
for (const operation of operations) {
const startTime = process.hrtime.bigint();
let success = false;
try {
if (operation === 'load') {
await einvoice.loadXml(complexInvoice);
success = einvoice.id === 'PERF-COMPLEX-001';
} else {
const exported = await einvoice.toXmlString('ubl');
success = exported.includes('PERF-COMPLEX-001');
2025-05-25 19:45:37 +00:00
}
2025-05-26 13:33:21 +00:00
} catch (e) {
console.log(`Error in ${operation}:`, e);
2025-05-25 19:45:37 +00:00
}
2025-05-26 13:33:21 +00:00
const endTime = process.hrtime.bigint();
const duration = Number(endTime - startTime) / 1_000_000;
results.push({
operation,
duration,
success,
itemsPerSecond: success ? (itemCount / (duration / 1000)).toFixed(2) : 'N/A'
});
}
console.log('\nComplex Invoice Performance (100 items):');
results.forEach(result => {
console.log(` ${result.operation}: ${result.duration.toFixed(2)}ms (${result.itemsPerSecond} items/sec) - ${result.success ? 'SUCCESS' : 'FAILED'}`);
});
expect(results.filter(r => r.success).length).toBeGreaterThan(0);
});
tap.test('CONV-12: Performance - should analyze memory usage during operations', async () => {
const memorySnapshots = [];
// Force garbage collection if available
if (global.gc) global.gc();
const baselineMemory = process.memoryUsage();
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
// Create invoices of increasing size
const sizes = [1, 10, 50, 100];
for (const size of sizes) {
const einvoice = new EInvoice();
const invoice = `<?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>MEM-TEST-${size}</cbc:ID>
<cbc:IssueDate>2024-01-30</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Memory Test Seller</cbc:Name>
</cac:PartyName>
2025-05-28 11:31:31 +00:00
<cac:PostalAddress>
<cbc:StreetName>Memory Street 1</cbc:StreetName>
<cbc:CityName>Memory City</cbc:CityName>
<cbc:PostalZone>10000</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
2025-05-26 13:33:21 +00:00
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Memory Test Buyer</cbc:Name>
</cac:PartyName>
2025-05-28 11:31:31 +00:00
<cac:PostalAddress>
<cbc:StreetName>Buyer Street 5</cbc:StreetName>
<cbc:CityName>Buyer City</cbc:CityName>
<cbc:PostalZone>20000</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
2025-05-26 13:33:21 +00:00
</cac:Party>
</cac:AccountingCustomerParty>
${Array.from({ length: size }, (_, i) => `
<cac:InvoiceLine>
<cbc:ID>${i + 1}</cbc:ID>
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Item ${i + 1} with a reasonably long description to simulate real-world data</cbc:Name>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>`).join('')}
<cac:LegalMonetaryTotal>
<cbc:PayableAmount currencyID="EUR">${size * 110}.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
</Invoice>`;
// Measure memory before and after operations
const beforeOperation = process.memoryUsage();
try {
await einvoice.loadXml(invoice);
await einvoice.toXmlString('ubl');
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
const afterOperation = process.memoryUsage();
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
memorySnapshots.push({
items: size,
heapUsedBefore: Math.round((beforeOperation.heapUsed - baselineMemory.heapUsed) / 1024 / 1024 * 100) / 100,
heapUsedAfter: Math.round((afterOperation.heapUsed - baselineMemory.heapUsed) / 1024 / 1024 * 100) / 100,
heapIncrease: Math.round((afterOperation.heapUsed - beforeOperation.heapUsed) / 1024 / 1024 * 100) / 100,
external: Math.round((afterOperation.external - baselineMemory.external) / 1024 / 1024 * 100) / 100
});
} catch (error) {
// Skip if operation fails
}
}
// Force garbage collection and measure final state
if (global.gc) global.gc();
const finalMemory = process.memoryUsage();
const totalMemoryIncrease = Math.round((finalMemory.heapUsed - baselineMemory.heapUsed) / 1024 / 1024 * 100) / 100;
const memoryPerItem = memorySnapshots.length > 0 ?
(memorySnapshots[memorySnapshots.length - 1].heapIncrease / sizes[sizes.length - 1]).toFixed(3) : 'N/A';
console.log('\nMemory Usage Analysis:');
memorySnapshots.forEach(snap => {
console.log(` ${snap.items} items: ${snap.heapIncrease}MB heap increase`);
});
console.log(` Total memory increase: ${totalMemoryIncrease}MB`);
console.log(` Average memory per item: ${memoryPerItem}MB`);
expect(memorySnapshots.length).toBeGreaterThan(0);
// Memory increase should be reasonable
expect(totalMemoryIncrease).toBeLessThan(50);
});
tap.test('CONV-12: Performance - should handle concurrent operations', async () => {
const concurrencyLevels = [1, 5, 10];
const results = [];
// Create test invoice
const testInvoice = `<?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>CONC-TEST-001</cbc:ID>
<cbc:IssueDate>2024-01-30</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Concurrent Seller</cbc:Name>
</cac:PartyName>
2025-05-28 11:31:31 +00:00
<cac:PostalAddress>
<cbc:StreetName>Seller Street 1</cbc:StreetName>
<cbc:CityName>Seller City</cbc:CityName>
<cbc:PostalZone>11111</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
2025-05-26 13:33:21 +00:00
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Concurrent Buyer</cbc:Name>
</cac:PartyName>
2025-05-28 11:31:31 +00:00
<cac:PostalAddress>
<cbc:StreetName>Buyer Street 1</cbc:StreetName>
<cbc:CityName>Buyer City</cbc:CityName>
<cbc:PostalZone>22222</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
2025-05-26 13:33:21 +00:00
</cac:Party>
</cac:AccountingCustomerParty>
2025-05-28 11:31:31 +00:00
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">10</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Test Product</cbc:Name>
</cac:Item>
</cac:InvoiceLine>
2025-05-26 13:33:21 +00:00
<cac:LegalMonetaryTotal>
<cbc:PayableAmount currencyID="EUR">1100.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
</Invoice>`;
for (const concurrency of concurrencyLevels) {
const startTime = Date.now();
// Create concurrent load/export tasks
const tasks = Array.from({ length: concurrency }, async () => {
try {
const einvoice = new EInvoice();
await einvoice.loadXml(testInvoice);
await einvoice.toXmlString('ubl');
return true;
} catch {
return false;
2025-05-25 19:45:37 +00:00
}
2025-05-26 13:33:21 +00:00
});
const taskResults = await Promise.all(tasks);
const endTime = Date.now();
const successful = taskResults.filter(r => r).length;
const duration = endTime - startTime;
const throughput = (successful / (duration / 1000)).toFixed(2);
results.push({
concurrency,
duration,
successful,
failed: concurrency - successful,
throughput: `${throughput} operations/sec`
});
}
console.log('\nConcurrent Operations Performance:');
results.forEach(result => {
console.log(` ${result.concurrency} concurrent: ${result.duration}ms total, ${result.throughput}`);
});
expect(results.every(r => r.successful > 0)).toEqual(true);
});
tap.test('CONV-12: Performance - should analyze corpus file processing performance', async () => {
const corpusDir = plugins.path.join(process.cwd(), 'test/assets/corpus');
const performanceData = {
totalFiles: 0,
successfulLoads: 0,
processingTimes: [] as number[],
sizeCategories: {
small: { count: 0, avgTime: 0, totalTime: 0 }, // < 10KB
medium: { count: 0, avgTime: 0, totalTime: 0 }, // 10KB - 100KB
large: { count: 0, avgTime: 0, totalTime: 0 } // > 100KB
2025-05-25 19:45:37 +00:00
}
2025-05-26 13:33:21 +00:00
};
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
// Sample a few known corpus files
const testFiles = [
'XML-Rechnung/UBL/EN16931_Einfach.ubl.xml',
'XML-Rechnung/CII/EN16931_Einfach.cii.xml',
'XML-Rechnung/UBL/EN16931_Rabatte.ubl.xml',
'XML-Rechnung/CII/EN16931_Rabatte.cii.xml',
'PEPPOL/Valid/billing-3.0-invoice-full-sample.xml'
];
for (const file of testFiles) {
const fullPath = plugins.path.join(corpusDir, file);
try {
const content = await plugins.fs.readFile(fullPath, 'utf-8');
const fileSize = Buffer.byteLength(content, 'utf-8');
performanceData.totalFiles++;
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
// Categorize by size
const sizeCategory = fileSize < 10240 ? 'small' :
fileSize < 102400 ? 'medium' : 'large';
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
// Measure load time
const startTime = process.hrtime.bigint();
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
try {
const einvoice = new EInvoice();
await einvoice.loadXml(content);
const endTime = process.hrtime.bigint();
const duration = Number(endTime - startTime) / 1_000_000;
if (einvoice.id) {
performanceData.successfulLoads++;
performanceData.processingTimes.push(duration);
// Update size category stats
performanceData.sizeCategories[sizeCategory].count++;
performanceData.sizeCategories[sizeCategory].totalTime += duration;
2025-05-25 19:45:37 +00:00
}
2025-05-26 13:33:21 +00:00
} catch (error) {
// Skip files that can't be loaded
2025-05-25 19:45:37 +00:00
}
2025-05-26 13:33:21 +00:00
} catch (error) {
// File doesn't exist
2025-05-25 19:45:37 +00:00
}
2025-05-26 13:33:21 +00:00
}
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
// Calculate averages
for (const category of Object.keys(performanceData.sizeCategories)) {
const cat = performanceData.sizeCategories[category];
if (cat.count > 0) {
cat.avgTime = cat.totalTime / cat.count;
}
}
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
const avgProcessingTime = performanceData.processingTimes.length > 0 ?
performanceData.processingTimes.reduce((a, b) => a + b, 0) / performanceData.processingTimes.length : 0;
2025-05-25 19:45:37 +00:00
2025-05-26 13:33:21 +00:00
console.log('\nCorpus File Processing Performance:');
console.log(` Files tested: ${performanceData.totalFiles}`);
console.log(` Successfully loaded: ${performanceData.successfulLoads}`);
console.log(` Average processing time: ${avgProcessingTime.toFixed(2)}ms`);
console.log(' By size:');
Object.entries(performanceData.sizeCategories).forEach(([size, data]) => {
if (data.count > 0) {
console.log(` - ${size}: ${data.count} files, avg ${data.avgTime.toFixed(2)}ms`);
}
2025-05-25 19:45:37 +00:00
});
2025-05-26 13:33:21 +00:00
expect(performanceData.successfulLoads).toBeGreaterThan(0);
// Average processing time should be reasonable
expect(avgProcessingTime).toBeLessThan(500);
2025-05-25 19:45:37 +00:00
});
tap.start();