Files
iptvnator/apps/electron-backend-e2e/src/performance/m3u-import-benchmark-support.ts
T
4gray a2fafcfc08 test(performance): add end-to-end Xtream benchmark harness (#1300)
* docs(performance): plan Xtream benchmark

* feat(xtream-mock-server): add deterministic 100k fixture

* style(xtream-mock-server): apply repository formatting

* fix(xtream-mock-server): harden performance fixture data

* feat(xtream-mock-server): add performance control plane

* docs(performance): correct Xtream capture plan

* fix(xtream-mock-server): harden performance controls

* fix(xtream-mock-server): harden control lifecycle

* feat(performance): add Xtream preload markers

* feat(performance): trace Xtream main phases

* feat(performance): mark Xtream store publications

* feat(performance): trace Xtream database phases

* feat(performance): trace Xtream delete cancellation

* feat(performance): capture Xtream phase attribution

* feat(performance): mark Sources Xtream refresh

* test(performance): define Xtream benchmark evidence contracts

* test(performance): add Xtream benchmark runner

* test(performance): surface failure evidence writes

* test(performance): align database read clock

* test(performance): preserve capture failure contracts
2026-07-28 08:08:07 +02:00

304 lines
9.5 KiB
TypeScript

import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { access, lstat, mkdir, readFile, writeFile } from 'node:fs/promises';
import { createServer as createTcpServer } from 'node:net';
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
import { expect, type Page } from '@playwright/test';
import {
type LaunchedElectronApp,
workspaceRoot,
} from '../electron-test-fixtures';
import { assertPerformanceArtifactCapacity } from './performance-artifact-preflight';
import type { RendererWindowIdentity } from './renderer-window-rss-session';
const OUTPUT_VARIANT_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
export const M3U_IMPORT_RENDERER_CDP_PORT = 9222;
export interface M3uImportGitSourceState {
readonly commit: string;
readonly diffSha256: string;
readonly dirty: boolean;
}
export interface M3uImportOutputLayout {
readonly outputRoot: string;
readonly variantDirectory: string;
}
export interface M3uImportBenchmarkConfiguration extends M3uImportOutputLayout {
readonly electronVersion: string;
readonly measuredRuns: number;
readonly rendererCdpPort: number;
readonly smoke: boolean;
readonly sourceState: M3uImportGitSourceState;
readonly variant: string;
}
export async function resolveM3uImportBenchmarkConfiguration(): Promise<M3uImportBenchmarkConfiguration> {
const requestedRoot = process.env['IPTVNATOR_PERF_OUTPUT_DIR'];
if (!requestedRoot) {
throw new Error('IPTVNATOR_PERF_OUTPUT_DIR is required');
}
const variant = process.env['IPTVNATOR_PERF_VARIANT'] ?? 'baseline';
const layout = resolveM3uImportOutputLayout(
workspaceRoot,
requestedRoot,
variant
);
const smoke = process.env['IPTVNATOR_PERF_SMOKE'] === '1';
const rendererCdpPort = resolveM3uImportRendererCdpPort(
smoke,
process.env['IPTVNATOR_PERF_CDP_PORT']
);
const sourceState = readGitSourceState(workspaceRoot);
assertM3uImportSourceState(smoke, sourceState);
const electronPackage = JSON.parse(
await readFile(
join(workspaceRoot, 'node_modules', 'electron', 'package.json'),
'utf8'
)
) as { version?: unknown };
if (typeof electronPackage.version !== 'string') {
throw new Error('Unable to resolve the Electron runtime version');
}
await assertM3uImportOutputPathHasNoSymlinks(
workspaceRoot,
layout.outputRoot
);
await assertMissing(layout.variantDirectory);
await assertPerformanceArtifactCapacity(layout.variantDirectory);
await mkdir(layout.variantDirectory, {
mode: 0o700,
recursive: true,
});
return Object.freeze({
...layout,
electronVersion: electronPackage.version,
measuredRuns: smoke ? 1 : 5,
rendererCdpPort,
smoke,
sourceState,
variant,
});
}
export function resolveM3uImportOutputLayout(
repositoryRoot: string,
requestedRoot: string,
variant: string
): M3uImportOutputLayout {
const performanceRoot = resolve(repositoryRoot, 'dist', 'performance');
const outputRoot = resolve(requestedRoot);
if (
!isAbsolute(requestedRoot) ||
!isStrictDescendant(performanceRoot, outputRoot)
) {
throw new Error(
'Performance output must be an absolute path below dist/performance'
);
}
if (!OUTPUT_VARIANT_PATTERN.test(variant)) {
throw new Error('IPTVNATOR_PERF_VARIANT is invalid');
}
const variantDirectory = resolve(outputRoot, variant);
if (!isStrictDescendant(outputRoot, variantDirectory)) {
throw new Error('IPTVNATOR_PERF_VARIANT is invalid');
}
return Object.freeze({ outputRoot, variantDirectory });
}
export async function assertM3uImportOutputPathHasNoSymlinks(
repositoryRoot: string,
outputRoot: string
): Promise<void> {
const root = resolve(repositoryRoot);
const output = resolve(outputRoot);
if (!isStrictDescendant(root, output)) {
throw new Error('Performance output path escapes the repository');
}
const components = relative(root, output).split(sep);
let current = root;
for (const component of ['', ...components]) {
current = component === '' ? current : join(current, component);
try {
const stats = await lstat(current);
if (stats.isSymbolicLink()) {
throw new Error(
`Performance output path contains a symbolic link: ${current}`
);
}
} catch (error) {
if (isMissingPath(error)) {
return;
}
throw error;
}
}
}
export function assertM3uImportSourceState(
smoke: boolean,
state: M3uImportGitSourceState
): void {
if (!smoke && state.dirty) {
throw new Error(
'Formal performance runs require a clean Git worktree; commit or stash changes first'
);
}
}
export function resolveM3uImportRendererCdpPort(
smoke: boolean,
requestedPort: string | undefined
): number {
const port =
requestedPort === undefined
? M3U_IMPORT_RENDERER_CDP_PORT
: Number(requestedPort);
if (
(requestedPort !== undefined && !/^[1-9]\d*$/.test(requestedPort)) ||
!Number.isSafeInteger(port) ||
port < 1024 ||
port > 65_535
) {
throw new Error('IPTVNATOR_PERF_CDP_PORT is invalid');
}
if (!smoke && port !== M3U_IMPORT_RENDERER_CDP_PORT) {
throw new Error(
`Formal performance runs require CDP port ${M3U_IMPORT_RENDERER_CDP_PORT}`
);
}
return port;
}
export async function resolveRendererWindowIdentity(
app: LaunchedElectronApp
): Promise<RendererWindowIdentity> {
const handle = await app.electronApp.browserWindow(app.mainWindow);
try {
return await handle.evaluate((browserWindow) => {
const exactWindow = browserWindow as unknown as {
readonly id: number;
readonly webContents: { readonly id: number };
};
return {
browserWindowId: exactWindow.id,
webContentsId: exactWindow.webContents.id,
};
});
} finally {
await handle.dispose();
}
}
export async function assertTcpPortAvailable(port: number): Promise<void> {
const server = createTcpServer();
await new Promise<void>((resolvePromise, rejectPromise) => {
server.once('error', rejectPromise);
server.listen(
{ exclusive: true, host: '127.0.0.1', port },
resolvePromise
);
});
await new Promise<void>((resolvePromise, rejectPromise) => {
server.close((error) =>
error ? rejectPromise(error) : resolvePromise()
);
});
}
export async function assertRendererCdpTarget(
page: Page,
port: number
): Promise<void> {
await expect
.poll(
async () => {
try {
const response = await fetch(
`http://127.0.0.1:${port}/json/list`
);
const targets = response.ok
? ((await response.json()) as unknown)
: null;
return (
Array.isArray(targets) &&
targets.some(
(target) =>
typeof target === 'object' &&
target !== null &&
(target as Record<string, unknown>)['type'] ===
'page' &&
(target as Record<string, unknown>)['url'] ===
page.url()
)
);
} catch {
return false;
}
},
{ timeout: 10_000 }
)
.toBe(true);
}
export async function writeM3uImportJson(
path: string,
value: unknown
): Promise<void> {
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}
function readGitSourceState(repositoryRoot: string): M3uImportGitSourceState {
const git = (args: readonly string[]): string =>
execFileSync('git', [...args], {
cwd: repositoryRoot,
encoding: 'utf8',
});
const commit = git(['rev-parse', 'HEAD']).trim();
const status = git(['status', '--porcelain=v1', '--untracked-files=all']);
const diffSha256 = createHash('sha256')
.update(status)
.update('\0')
.update(git(['diff', '--binary', 'HEAD']))
.update('\0')
.update(git(['diff', '--binary', '--cached', 'HEAD']))
.digest('hex');
return Object.freeze({
commit,
diffSha256,
dirty: status.trim().length > 0,
});
}
function isStrictDescendant(parent: string, candidate: string): boolean {
const pathFromParent = relative(resolve(parent), resolve(candidate));
return (
pathFromParent.length > 0 &&
pathFromParent !== '..' &&
!pathFromParent.startsWith(`..${sep}`) &&
!isAbsolute(pathFromParent)
);
}
async function assertMissing(path: string): Promise<void> {
try {
await access(path);
} catch {
return;
}
throw new Error(`Performance output already exists: ${path}`);
}
function isMissingPath(error: unknown): boolean {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
error.code === 'ENOENT'
);
}