@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
609 lines (522 loc) • 18 kB
text/typescript
/**
* @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:
--text-color:
}
.container.light {
--bg-color:
--text-color:
}
}
}
`;
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');
});
});
});