UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

609 lines (522 loc) 18 kB
/** * @fileoverview Performance Benchmarker Tests */ import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { BenchmarkConfig, PerformanceMetrics } from './performance-benchmarker.js'; import { PerformanceBenchmarker } from './performance-benchmarker.js'; // Mock performance API const mockPerformance = { now: vi.fn(() => Date.now()) }; // Mock global performance global.performance = mockPerformance as any; describe('PerformanceBenchmarker', () => { let benchmarker: PerformanceBenchmarker; beforeEach(() => { benchmarker = new PerformanceBenchmarker(); vi.clearAllMocks(); }); describe('constructor', () => { it('should initialize with default configuration', () => { expect(benchmarker).toBeInstanceOf(PerformanceBenchmarker); }); it('should accept custom configuration', () => { const customConfig: Partial<BenchmarkConfig> = { iterations: 5, includeRuntime: false, measureMemory: false }; const customBenchmarker = new PerformanceBenchmarker(customConfig); expect(customBenchmarker).toBeInstanceOf(PerformanceBenchmarker); }); }); describe('benchmarkComponent', () => { it('should benchmark a simple component', async () => { const source = ` component SimpleComponent { client { let count = 0; function increment() { count++; } } markup { <div> <h1>Hello World</h1> <button onclick="increment">Click me</button> </div> } } `; const result = await benchmarker.benchmarkComponent(source, 'SimpleComponent'); expect(result).toBeDefined(); expect(result.componentName).toBe('SimpleComponent'); expect(result.averageMetrics).toBeDefined(); expect(result.measurements).toHaveLength(10); // Default iterations expect(result.validation).toBeDefined(); expect(result.suggestions).toBeDefined(); }); it('should handle complex components', async () => { const source = ` component ComplexComponent { client { let users = []; let loading = false; let error = null; function fetchUsers() { loading = true; // Simulate API call setTimeout(() => { users = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]; loading = false; }, 1000); } function addUser(name) { users.push({ id: users.length + 1, name }); } function removeUser(id) { users = users.filter(user => user.id !== id); } } markup { <div class="container"> <h1>User Management</h1> {loading ? <p>Loading...</p> : null} {error ? <p class="error">{error}</p> : null} <button onclick="fetchUsers">Load Users</button> <ul> {users.map(user => <li key={user.id}> {user.name} <button onclick={() => removeUser(user.id)}>Delete</button> </li> )} </ul> </div> } } `; const result = await benchmarker.benchmarkComponent(source, 'ComplexComponent'); expect(result.averageMetrics.compilationTime).toBeGreaterThan(0); expect(result.averageMetrics.bundleSize).toBeGreaterThan(0); expect(result.averageMetrics.astNodeCount).toBeGreaterThan(0); expect(result.averageMetrics.tokenCount).toBeGreaterThan(0); }); it('should handle components with server blocks', async () => { const source = ` component ServerComponent { server { public async function getData() { return { message: "Hello from server" }; } function internalHelper() { return "internal"; } } client { let data = null; async function loadData() { data = await server.getData(); } } markup { <div> <h1>Server Component</h1> {data ? <p>{data.message}</p> : <p>No data</p>} <button onclick="loadData">Load Data</button> </div> } } `; const result = await benchmarker.benchmarkComponent(source, 'ServerComponent'); expect(result.averageMetrics.compilationTime).toBeGreaterThan(0); expect(result.averageMetrics.bundleSize).toBeGreaterThan(0); }); it('should handle components with CSS', async () => { const source = ` component StyledComponent { client { let theme = 'light'; function toggleTheme() { theme = theme === 'light' ? 'dark' : 'light'; } } markup { <div class="container"> <h1>Styled Component</h1> <button onclick="toggleTheme">Toggle Theme</button> </div> } style { .container { padding: 20px; background: var(--bg-color); color: var(--text-color); } .container.dark { --bg-color: #333; --text-color: #fff; } .container.light { --bg-color: #fff; --text-color: #333; } } } `; const result = await benchmarker.benchmarkComponent(source, 'StyledComponent'); expect(result.averageMetrics.compilationTime).toBeGreaterThan(0); expect(result.averageMetrics.bundleSize).toBeGreaterThan(0); }); it('should handle baseline comparison', async () => { const source = ` component TestComponent { client { let count = 0; function increment() { count++; } } markup { <div> <h1>Test</h1> <button onclick="increment">Click</button> </div> } } `; // Set baseline const baselineMetrics: PerformanceMetrics = { compilationTime: 50, lexingTime: 10, parsingTime: 15, codeGenerationTime: 20, optimizationTime: 5, bundleSize: 5000, gzippedSize: 1500, brotliSize: 1200, astNodeCount: 50, tokenCount: 200, memoryUsage: 1000000 }; benchmarker.setBaseline('baseline', baselineMetrics); const result = await benchmarker.benchmarkComponent(source, 'TestComponent', 'baseline'); expect(result.comparison).toBeDefined(); expect(result.comparison!.baseline).toEqual(baselineMetrics); expect(result.comparison!.improvement).toBeDefined(); }); it('should handle performance validation', async () => { const customBenchmarker = new PerformanceBenchmarker({ thresholds: { maxCompilationTime: 10, // Very strict threshold maxBundleSize: 1000, // Very strict threshold maxHydrationTime: 50, maxTimeToFirstPaint: 1000, maxTimeToInteractive: 1500 } }); const source = ` component LargeComponent { client { let data = []; function generateData() { for (let i = 0; i < 1000; i++) { data.push({ id: i, value: Math.random() }); } } } markup { <div> <h1>Large Component</h1> <button onclick="generateData">Generate Data</button> {data.map(item => <div key={item.id}>{item.value}</div>)} </div> } } `; const result = await customBenchmarker.benchmarkComponent(source, 'LargeComponent'); expect(result.validation.passed).toBe(false); expect(result.validation.failures.length).toBeGreaterThan(0); expect(result.validation.score).toBeLessThan(100); }); it('should generate optimization suggestions', async () => { const source = ` component OptimizableComponent { client { let unusedVar = "unused"; let usedVar = "used"; function unusedFunction() { return "unused"; } function usedFunction() { return usedVar; } } markup { <div> <h1>{usedFunction()}</h1> </div> } } `; const result = await benchmarker.benchmarkComponent(source, 'OptimizableComponent'); expect(result.suggestions.length).toBeGreaterThan(0); expect(result.suggestions.some(s => s.includes('tree shaking'))).toBe(true); }); it('should handle errors gracefully', async () => { const invalidSource = ` component InvalidComponent { client { let count = 0; function increment() { count++; } } markup { <div> <h1>Invalid Component <!-- Missing closing tag --> </div> } } `; await expect(benchmarker.benchmarkComponent(invalidSource, 'InvalidComponent')).rejects.toThrow(); const errors = benchmarker.getErrors(); expect(errors.length).toBeGreaterThan(0); }); it('should handle empty components', async () => { const emptySource = ` component EmptyComponent { markup { <div></div> } } `; const result = await benchmarker.benchmarkComponent(emptySource, 'EmptyComponent'); expect(result.averageMetrics.compilationTime).toBeGreaterThan(0); expect(result.averageMetrics.bundleSize).toBeGreaterThan(0); expect(result.averageMetrics.astNodeCount).toBeGreaterThan(0); }); it('should handle components with many reactive variables', async () => { const source = ` component ReactiveComponent { client { let var1 = 1; let var2 = 2; let var3 = 3; let var4 = 4; let var5 = 5; let var6 = 6; let var7 = 7; let var8 = 8; let var9 = 9; let var10 = 10; function updateAll() { var1++; var2++; var3++; var4++; var5++; var6++; var7++; var8++; var9++; var10++; } } markup { <div> <h1>Reactive Component</h1> <p>Var1: {var1}</p> <p>Var2: {var2}</p> <p>Var3: {var3}</p> <p>Var4: {var4}</p> <p>Var5: {var5}</p> <p>Var6: {var6}</p> <p>Var7: {var7}</p> <p>Var8: {var8}</p> <p>Var9: {var9}</p> <p>Var10: {var10}</p> <button onclick="updateAll">Update All</button> </div> } } `; const result = await benchmarker.benchmarkComponent(source, 'ReactiveComponent'); expect(result.averageMetrics.compilationTime).toBeGreaterThan(0); expect(result.averageMetrics.bundleSize).toBeGreaterThan(0); expect(result.averageMetrics.astNodeCount).toBeGreaterThan(10); }); it('should handle components with complex expressions', async () => { const source = ` component ComplexExpressionsComponent { client { let items = [1, 2, 3, 4, 5]; let filter = ''; function filteredItems() { return items.filter(item => item.toString().includes(filter) ).map(item => ({ value: item, doubled: item * 2, squared: item * item })); } function addItem() { items.push(items.length + 1); } } markup { <div> <h1>Complex Expressions</h1> <input bind:value="filter" placeholder="Filter items" /> <button onclick="addItem">Add Item</button> <ul> {filteredItems().map(item => <li key={item.value}> {item.value} (doubled: {item.doubled}, squared: {item.squared}) </li> )} </ul> </div> } } `; const result = await benchmarker.benchmarkComponent(source, 'ComplexExpressionsComponent'); expect(result.averageMetrics.compilationTime).toBeGreaterThan(0); expect(result.averageMetrics.bundleSize).toBeGreaterThan(0); expect(result.averageMetrics.tokenCount).toBeGreaterThan(100); }); }); describe('configuration options', () => { it('should respect iterations configuration', async () => { const customBenchmarker = new PerformanceBenchmarker({ iterations: 3 }); const source = ` component TestComponent { client { let count = 0; function increment() { count++; } } markup { <div> <h1>Test</h1> <button onclick="increment">Click</button> </div> } } `; const result = await customBenchmarker.benchmarkComponent(source, 'TestComponent'); expect(result.measurements).toHaveLength(3); }); it('should respect includeRuntime configuration', async () => { const benchmarkerWithoutRuntime = new PerformanceBenchmarker({ includeRuntime: false }); const source = ` component TestComponent { client { let count = 0; function increment() { count++; } } markup { <div> <h1>Test</h1> <button onclick="increment">Click</button> </div> } } `; const result = await benchmarkerWithoutRuntime.benchmarkComponent(source, 'TestComponent'); expect(result.averageMetrics.runtimeMetrics).toBeUndefined(); }); it('should respect measureMemory configuration', async () => { const benchmarkerWithoutMemory = new PerformanceBenchmarker({ measureMemory: false }); const source = ` component TestComponent { client { let count = 0; function increment() { count++; } } markup { <div> <h1>Test</h1> <button onclick="increment">Click</button> </div> } } `; const result = await benchmarkerWithoutMemory.benchmarkComponent(source, 'TestComponent'); expect(result.averageMetrics.memoryUsage).toBe(0); }); }); describe('baseline management', () => { it('should set and get baseline metrics', () => { const metrics: PerformanceMetrics = { compilationTime: 100, lexingTime: 20, parsingTime: 30, codeGenerationTime: 40, optimizationTime: 10, bundleSize: 10000, gzippedSize: 3000, brotliSize: 2500, astNodeCount: 100, tokenCount: 500, memoryUsage: 2000000 }; benchmarker.setBaseline('test-baseline', metrics); const retrieved = benchmarker.getBaseline('test-baseline'); expect(retrieved).toEqual(metrics); }); it('should return undefined for non-existent baseline', () => { const retrieved = benchmarker.getBaseline('non-existent'); expect(retrieved).toBeUndefined(); }); }); describe('error handling', () => { it('should handle null source', async () => { await expect(benchmarker.benchmarkComponent(null as any, 'TestComponent')).rejects.toThrow(); }); it('should handle undefined source', async () => { await expect(benchmarker.benchmarkComponent(undefined as any, 'TestComponent')).rejects.toThrow(); }); it('should handle empty source', async () => { await expect(benchmarker.benchmarkComponent('', 'TestComponent')).rejects.toThrow(); }); it('should handle malformed source', async () => { const malformedSource = ` component MalformedComponent { client { let count = 0; function increment() { count++; } } markup { <div> <h1>Malformed <!-- Missing closing tags and syntax errors --> </div> } } `; await expect(benchmarker.benchmarkComponent(malformedSource, 'MalformedComponent')).rejects.toThrow(); }); }); describe('report generation', () => { it('should generate comprehensive performance report', async () => { const source = ` component ReportComponent { client { let data = []; function loadData() { data = [1, 2, 3, 4, 5]; } } markup { <div> <h1>Report Component</h1> <button onclick="loadData">Load Data</button> {data.map(item => <div key={item}>{item}</div>)} </div> } } `; const result = await benchmarker.benchmarkComponent(source, 'ReportComponent'); const report = benchmarker.generateReport(result); expect(report).toContain('# Performance Report: ReportComponent'); expect(report).toContain('## Compilation Metrics'); expect(report).toContain('## Bundle Metrics'); expect(report).toContain('## Performance Validation'); expect(report).toContain('## Optimization Suggestions'); }); }); });