first commmit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import { Logger } from '@common/Logger';
|
||||
import { CORE_PERF_THRESHOLDS, PerfMonitor } from '@common/PerfMonitor';
|
||||
import {
|
||||
PERF_TOUCH_RESPONSE_MAX_MS,
|
||||
PERF_COMBO_RECOGNITION_MAX_MS,
|
||||
MAX_FIRST_PACKAGE_BYTES,
|
||||
MAX_AUDIO_BUNDLE_BYTES,
|
||||
MAX_MEMORY_PEAK_BYTES,
|
||||
} from '@common/Constants';
|
||||
|
||||
describe('PerfMonitor — threshold catalog (req 18 & 20)', () => {
|
||||
it('includes every KPI threshold called out in requirements 20.1-20.5', () => {
|
||||
const names = CORE_PERF_THRESHOLDS.map((t) => t.metric);
|
||||
expect(names).toContain('input/touchStart');
|
||||
expect(names).toContain('jump/state_toggle_ms');
|
||||
expect(names).toContain('input/combo_recognition_ms');
|
||||
expect(names).toContain('input/parabolic_accuracy');
|
||||
expect(names).toContain('jump/air_jump_block_rate');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PerfMonitor — pass/fail evaluation', () => {
|
||||
function seedPassing(logger: Logger): void {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
logger.metric({ name: 'input/touchStart', value: 20 });
|
||||
logger.metric({ name: 'jump/state_toggle_ms', value: 25 });
|
||||
logger.metric({ name: 'input/combo_recognition_ms', value: 40 });
|
||||
}
|
||||
logger.metric({ name: 'input/parabolic_accuracy', value: 0.97 });
|
||||
logger.metric({ name: 'jump/air_jump_block_rate', value: 0.995 });
|
||||
}
|
||||
|
||||
it('passes when all metrics are within budget', () => {
|
||||
const logger = new Logger();
|
||||
seedPassing(logger);
|
||||
const monitor = new PerfMonitor(logger);
|
||||
const report = monitor.collectReport();
|
||||
expect(report.allPassing).toBe(true);
|
||||
});
|
||||
|
||||
it('fails when no samples exist for a threshold', () => {
|
||||
const monitor = new PerfMonitor(new Logger());
|
||||
const report = monitor.collectReport();
|
||||
expect(report.allPassing).toBe(false);
|
||||
expect(report.checks[0].reason).toMatch(/no samples/);
|
||||
});
|
||||
|
||||
it('fails when p95 latency exceeds the limit', () => {
|
||||
const logger = new Logger();
|
||||
seedPassing(logger);
|
||||
// Push a huge batch of slow touches so p95 exceeds budget.
|
||||
for (let i = 0; i < 200; i++) {
|
||||
logger.metric({ name: 'input/touchStart', value: PERF_TOUCH_RESPONSE_MAX_MS + 50 });
|
||||
}
|
||||
const report = new PerfMonitor(logger).collectReport();
|
||||
const touchCheck = report.checks.find((c) => c.threshold.metric === 'input/touchStart')!;
|
||||
expect(touchCheck.passing).toBe(false);
|
||||
});
|
||||
|
||||
it('fails when parabolic accuracy drops below 95%', () => {
|
||||
const logger = new Logger();
|
||||
seedPassing(logger);
|
||||
logger.resetMetrics();
|
||||
logger.metric({ name: 'input/touchStart', value: 20 });
|
||||
logger.metric({ name: 'jump/state_toggle_ms', value: 20 });
|
||||
logger.metric({ name: 'input/combo_recognition_ms', value: 20 });
|
||||
logger.metric({ name: 'input/parabolic_accuracy', value: 0.8 });
|
||||
logger.metric({ name: 'jump/air_jump_block_rate', value: 0.999 });
|
||||
const report = new PerfMonitor(logger).collectReport();
|
||||
expect(report.allPassing).toBe(false);
|
||||
});
|
||||
|
||||
it('evaluates build-size budget when provided', () => {
|
||||
const logger = new Logger();
|
||||
logger.metric({ name: 'input/touchStart', value: 10 });
|
||||
logger.metric({ name: 'jump/state_toggle_ms', value: 10 });
|
||||
logger.metric({ name: 'input/combo_recognition_ms', value: 10 });
|
||||
logger.metric({ name: 'input/parabolic_accuracy', value: 1 });
|
||||
logger.metric({ name: 'jump/air_jump_block_rate', value: 1 });
|
||||
const monitor = new PerfMonitor(logger);
|
||||
|
||||
const okReport = monitor.collectReport({
|
||||
firstPackageBytes: MAX_FIRST_PACKAGE_BYTES - 1,
|
||||
audioBundleBytes: MAX_AUDIO_BUNDLE_BYTES - 1,
|
||||
memoryPeakBytes: MAX_MEMORY_PEAK_BYTES - 1,
|
||||
});
|
||||
expect(okReport.sizeBudgetPassing).toBe(true);
|
||||
expect(okReport.allPassing).toBe(true);
|
||||
|
||||
const badReport = monitor.collectReport({
|
||||
firstPackageBytes: MAX_FIRST_PACKAGE_BYTES + 1,
|
||||
audioBundleBytes: MAX_AUDIO_BUNDLE_BYTES + 1,
|
||||
memoryPeakBytes: MAX_MEMORY_PEAK_BYTES + 1,
|
||||
});
|
||||
expect(badReport.sizeBudgetPassing).toBe(false);
|
||||
expect(badReport.allPassing).toBe(false);
|
||||
});
|
||||
|
||||
it('honours the <= comparator at exactly the boundary', () => {
|
||||
const logger = new Logger();
|
||||
logger.metric({ name: 'input/touchStart', value: PERF_TOUCH_RESPONSE_MAX_MS });
|
||||
logger.metric({ name: 'jump/state_toggle_ms', value: 10 });
|
||||
logger.metric({ name: 'input/combo_recognition_ms', value: PERF_COMBO_RECOGNITION_MAX_MS });
|
||||
logger.metric({ name: 'input/parabolic_accuracy', value: 1 });
|
||||
logger.metric({ name: 'jump/air_jump_block_rate', value: 1 });
|
||||
const report = new PerfMonitor(logger).collectReport();
|
||||
const touch = report.checks.find((c) => c.threshold.metric === 'input/touchStart')!;
|
||||
expect(touch.passing).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user