Files
2026-07-13 12:22:50 +08:00

225 lines
7.2 KiB
TypeScript

import {compareReports, MemoryReport} from '../../e2e/scripts/memory-compare';
function makeReport(memoryMap: Record<string, number> = {}): MemoryReport {
const defaults: Record<string, number> = {
app_launch: 100,
models_screen: 110,
chat_screen: 115,
model_loaded: 300,
chat_active: 350,
post_chat_idle: 320,
model_unloaded: 105,
};
const merged = {...defaults, ...memoryMap};
return {
version: '1.0',
commit: 'abc123',
device: 'TestDevice',
os_version: '18.0',
platform: 'ios',
timestamp: new Date().toISOString(),
model: 'test-model',
checkpoints: Object.entries(merged).map(([label, mb]) => ({
label,
timestamp: new Date().toISOString(),
native: {
phys_footprint: mb * 1024 * 1024,
available_memory: 3e9,
},
})),
peak_memory_mb: Math.max(...Object.values(merged)),
};
}
describe('compareReports', () => {
it('passes when current matches baseline', () => {
const result = compareReports(makeReport(), makeReport());
expect(result.pass).toBe(true);
expect(result.regressions).toHaveLength(0);
expect(result.deltas).toHaveLength(7);
});
it('passes when delta is high % but low absolute (below mb threshold)', () => {
const baseline = makeReport({app_launch: 100});
const current = makeReport({app_launch: 120}); // +20% but only +20 MB
const result = compareReports(baseline, current);
expect(result.pass).toBe(true);
expect(result.regressions).toHaveLength(0);
});
it('passes when delta is high absolute but low % (below pct threshold)', () => {
const baseline = makeReport({model_loaded: 3000});
const current = makeReport({model_loaded: 3150}); // +150 MB but only +5%
const result = compareReports(baseline, current);
expect(result.pass).toBe(true);
expect(result.regressions).toHaveLength(0);
});
it('fails when delta exceeds BOTH thresholds', () => {
const baseline = makeReport({model_loaded: 1000});
const current = makeReport({model_loaded: 1500}); // +500 MB and +50%
const result = compareReports(baseline, current);
expect(result.pass).toBe(false);
expect(result.regressions).toHaveLength(1);
expect(result.regressions[0].checkpoint).toBe('model_loaded');
expect(result.regressions[0].delta_mb).toBeCloseTo(500, 0);
expect(result.regressions[0].delta_pct).toBeCloseTo(50, 0);
});
it('respects custom thresholds', () => {
const baseline = makeReport({chat_active: 200});
const current = makeReport({chat_active: 260}); // +60 MB, +30%
// Default thresholds (10%, 200 MB) — passes (60 MB < 200 MB)
expect(compareReports(baseline, current).pass).toBe(true);
// Custom thresholds (5%, 50 MB) — fails (both exceeded)
const result = compareReports(baseline, current, {pct: 5, mb: 50});
expect(result.pass).toBe(false);
expect(result.regressions).toHaveLength(1);
});
it('reports deltas for all checkpoints even when passing', () => {
const baseline = makeReport({app_launch: 100, model_loaded: 300});
const current = makeReport({app_launch: 105, model_loaded: 310});
const result = compareReports(baseline, current);
expect(result.pass).toBe(true);
const appDelta = result.deltas.find(d => d.checkpoint === 'app_launch');
expect(appDelta).toBeDefined();
expect(appDelta!.delta_mb).toBeCloseTo(5, 0);
expect(appDelta!.delta_pct).toBeCloseTo(5, 0);
});
it('handles Android pss_total metric', () => {
const androidReport = (memMb: number): MemoryReport => ({
...makeReport(),
platform: 'android',
checkpoints: [
{
label: 'app_launch',
timestamp: new Date().toISOString(),
native: {pss_total: memMb * 1024 * 1024, available_memory: 3e9},
},
],
});
const result = compareReports(androidReport(200), androidReport(200));
expect(result.pass).toBe(true);
expect(result.deltas[0].metric).toBe('pss_total');
});
it('skips checkpoints missing from baseline', () => {
const baseline = makeReport();
const current: MemoryReport = {
...makeReport(),
checkpoints: [
...makeReport().checkpoints,
{
label: 'extra_checkpoint',
timestamp: new Date().toISOString(),
native: {phys_footprint: 999 * 1024 * 1024, available_memory: 1e9},
},
],
};
const result = compareReports(baseline, current);
// extra_checkpoint has no baseline — should not appear in deltas
expect(
result.deltas.find(d => d.checkpoint === 'extra_checkpoint'),
).toBeUndefined();
});
it('includes peak comparison in result', () => {
const baseline = makeReport({chat_active: 500});
const current = makeReport({chat_active: 520});
const result = compareReports(baseline, current);
expect(result.peak_baseline_mb).toBe(500);
expect(result.peak_current_mb).toBe(520);
});
it('detects memory decrease (negative delta) without flagging', () => {
const baseline = makeReport({model_loaded: 1000});
const current = makeReport({model_loaded: 800}); // -200 MB, improvement
const result = compareReports(baseline, current);
expect(result.pass).toBe(true);
const delta = result.deltas.find(d => d.checkpoint === 'model_loaded');
expect(delta!.delta_mb).toBeCloseTo(-200, 0);
expect(delta!.delta_pct).toBeCloseTo(-20, 0);
});
it('includes metal_allocated in iOS total when present', () => {
const MB = 1024 * 1024;
const makeIosReport = (phys: number, metal: number): MemoryReport => ({
...makeReport(),
checkpoints: [
{
label: 'model_loaded',
timestamp: new Date().toISOString(),
native: {
phys_footprint: phys * MB,
metal_allocated: metal * MB,
available_memory: 3e9,
},
},
],
peak_memory_mb: phys + metal,
});
const baseline = makeIosReport(400, 1700); // total 2100
const current = makeIosReport(410, 1900); // total 2310, +210 MB, +10%
const result = compareReports(baseline, current);
const delta = result.deltas[0];
expect(delta.metric).toBe('phys+metal');
expect(delta.baseline_mb).toBeCloseTo(2100, 0);
expect(delta.current_mb).toBeCloseTo(2310, 0);
expect(delta.delta_mb).toBeCloseTo(210, 0);
expect(delta.delta_pct).toBeCloseTo(10, 0);
});
it('flags regression when metal causes total to exceed thresholds', () => {
const MB = 1024 * 1024;
const makeIosReport = (phys: number, metal: number): MemoryReport => ({
...makeReport(),
checkpoints: [
{
label: 'model_loaded',
timestamp: new Date().toISOString(),
native: {
phys_footprint: phys * MB,
metal_allocated: metal * MB,
available_memory: 3e9,
},
},
],
peak_memory_mb: phys + metal,
});
const baseline = makeIosReport(400, 1700); // total 2100
const current = makeIosReport(400, 2100); // total 2500, +400 MB, +19%
const result = compareReports(baseline, current);
expect(result.pass).toBe(false);
expect(result.regressions).toHaveLength(1);
expect(result.regressions[0].delta_mb).toBeCloseTo(400, 0);
});
});