mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-09-16 02:22:17 +00:00
test(performance): add a local pokédex benchmark and run it in CI
Measures the pokédex load against a local Supabase with a generated fixture, and compares runs so a regression shows up as a number rather than a hunch. Wired into test:ci and given its own job so the artifacts survive a failure.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { mkdtemp, mkdir, cp, rm, readFile, writeFile } from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const directory = await mkdtemp(path.join(os.tmpdir(), 'livingdex-grid-tests-'));
|
||||
const port = process.env.PERF_PORT ?? '4185';
|
||||
const env = {
|
||||
...process.env,
|
||||
NODE_ADAPTER: 'true',
|
||||
PUBLIC_USE_LOCAL_POKEMON_SPRITE_FOLDER: 'true',
|
||||
POKEDEX_PERFORMANCE: 'true',
|
||||
PERF_FIXTURE_DIR: directory,
|
||||
PERF_BASE_URL: `http://127.0.0.1:${port}`,
|
||||
PORT: port,
|
||||
HOST: '127.0.0.1'
|
||||
};
|
||||
function run(command, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const process = spawn(command, args, { env, stdio: 'inherit' });
|
||||
process.once('error', reject);
|
||||
process.once('exit', (code) =>
|
||||
code === 0 ? resolve() : reject(new Error(`${command} failed (${code})`))
|
||||
);
|
||||
});
|
||||
}
|
||||
let server;
|
||||
let fixture = false;
|
||||
try {
|
||||
await run('npm', ['run', 'build-inject-manifest-node']);
|
||||
fixture = true;
|
||||
await run('node', ['scripts/performance/fixture.mjs']);
|
||||
server = spawn('node', ['build'], { env, stdio: 'inherit' });
|
||||
await new Promise((resolve, reject) => {
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
if ((await fetch(env.PERF_BASE_URL)).ok) {
|
||||
clearInterval(timer);
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
}
|
||||
} catch {}
|
||||
}, 200);
|
||||
const timeout = setTimeout(() => {
|
||||
clearInterval(timer);
|
||||
reject(new Error('Local production server did not start'));
|
||||
}, 15000);
|
||||
server.once('error', (error) => {
|
||||
clearInterval(timer);
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
});
|
||||
server.once('exit', (code) => {
|
||||
clearInterval(timer);
|
||||
clearTimeout(timeout);
|
||||
reject(new Error(`Local server exited (${code})`));
|
||||
});
|
||||
});
|
||||
if (process.env.PERF_BENCHMARK === 'true') {
|
||||
const data = JSON.parse(await readFile(path.join(directory, 'fixture.json'), 'utf8'));
|
||||
await writeFile(
|
||||
path.join(directory, 'benchmark-config.json'),
|
||||
JSON.stringify({
|
||||
samples: 30,
|
||||
revision: process.env.PERF_REVISION ?? 'local-working-tree',
|
||||
databaseLabel: 'local-seeded-supabase',
|
||||
clientLocation: 'local-loopback',
|
||||
environment: 'local-node',
|
||||
hosts: [
|
||||
{
|
||||
label: 'node',
|
||||
url: env.PERF_BASE_URL,
|
||||
storageState: path.join(directory, 'storage-state.json'),
|
||||
fixtures: data.dexes.map((dex) => ({
|
||||
id: dex.id,
|
||||
name: dex.name,
|
||||
label: dex.gameScope ? 'scoped-forms' : 'national'
|
||||
}))
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
await run('node', [
|
||||
'scripts/performance/benchmark.mjs',
|
||||
path.join(directory, 'benchmark-config.json'),
|
||||
path.join(directory, 'benchmark.json')
|
||||
]);
|
||||
await mkdir('test-results/performance', { recursive: true });
|
||||
await cp(path.join(directory, 'benchmark.json'), 'test-results/performance/benchmark.json');
|
||||
}
|
||||
await run('node', ['scripts/performance/verify.mjs']);
|
||||
await run('node', ['scripts/performance/verify-behavior.mjs']);
|
||||
await mkdir('test-results/performance', { recursive: true });
|
||||
for (const file of ['verification.json', 'behavior.json', 'national.png', 'scoped.png'])
|
||||
await cp(path.join(directory, file), path.join('test-results/performance', file));
|
||||
} finally {
|
||||
server?.kill('SIGTERM');
|
||||
if (fixture)
|
||||
await run('node', ['scripts/performance/fixture.mjs', '--cleanup']).catch((error) => {
|
||||
throw new Error(`Fixture cleanup failed; retained recovery files in ${directory}`, {
|
||||
cause: error
|
||||
});
|
||||
});
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
Reference in New Issue
Block a user