mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-04 21:17:30 +00:00
Ctrl+C during a render left its whole work dir of captured frames (and a staging dir) in the renders folder. Puppeteer's default SIGINT handler kills Chrome and exits the process before the render's own cancel path can remove them. The engine keeps that default unless the host owns Ctrl+C; the CLI's render cancellation scope claims it while a render runs, so the render aborts and cleans up after itself. A render killed outright (SIGKILL, crash) cannot clean up, so each work dir and staging dir now records its owner process (pid, hostname, boot, and on Linux the pid namespace), and the next render into the same folder removes the ones whose owner has exited. Cancelling a render while its browsers start no longer leaves anything running that kept a batch render alive after Ctrl+C: the BeginFrame warm-up loop, the probe browser and file server, or a capture browser whose launch finished after the cancel (now closed before the worker settles, since Puppeteer's exit hook skips every other live browser).
91 lines
3.8 KiB
JavaScript
91 lines
3.8 KiB
JavaScript
// fallow-ignore-file complexity
|
|
import { readdirSync, readFileSync } from "node:fs";
|
|
import { dirname, relative, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export const PRODUCER_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
|
// These tests need host capabilities such as Chrome, ffmpeg, worker threads,
|
|
// or local sockets. Keep the list explicit so a filename-only rename does not
|
|
// make Git/fallow re-audit thousands of unchanged test lines as new code.
|
|
const INTEGRATION_TEST_FILES = new Set([
|
|
"tests/distributed/_smoke/webm-concat-copy.test.ts",
|
|
"tests/playback-rate-av-parity/playback-rate-av-parity.test.ts",
|
|
"src/regression-harness-psnr.test.ts",
|
|
"src/services/coreRuntimeBrowser.test.ts",
|
|
"src/services/deterministicFonts-systemCapture.test.ts",
|
|
"src/services/vfxDeterminism.test.ts",
|
|
"src/services/distributed/assemble.test.ts",
|
|
"src/services/distributed/chunkBoundary.test.ts",
|
|
"src/services/distributed/crossWorkerIdempotency.test.ts",
|
|
"src/services/distributed/plan.test.ts",
|
|
"src/services/distributed/planSizeCap.test.ts",
|
|
"src/services/distributed/renderChunk.test.ts",
|
|
"src/services/fileServer.test.ts",
|
|
"src/services/fileServer.file-race.test.ts",
|
|
"src/services/healthWorker.test.ts",
|
|
"src/services/hyperframeLint.file-race.test.ts",
|
|
"src/services/assetMediaType.test.ts",
|
|
"src/services/htmlCompiler.mediaType.test.ts",
|
|
"src/services/htmlCompiler.naturalDuration.test.ts",
|
|
"src/services/render/hlsRender.integration.test.ts",
|
|
"src/services/render/renderCancel.integration.test.ts",
|
|
"src/services/render/stages/compileStage.mediaType.test.ts",
|
|
"src/services/render/stages/gifEncodeArgs.test.ts",
|
|
"src/utils/audioRegression.test.ts",
|
|
"src/utils/streamDurationParity.test.ts",
|
|
]);
|
|
|
|
function collectTestFiles(directory, files = []) {
|
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
const entryPath = resolve(directory, entry.name);
|
|
if (entry.isDirectory()) collectTestFiles(entryPath, files);
|
|
else if (entry.isFile() && entry.name.endsWith(".test.ts")) files.push(entryPath);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
export function classifyTestSource(filePath, source, integrationFiles = INTEGRATION_TEST_FILES) {
|
|
const importsBun = /\bfrom\s+["']bun:test["']/.test(source);
|
|
const importsVitest = /\bfrom\s+["']vitest["']/.test(source);
|
|
if (importsBun === importsVitest) {
|
|
const detail = importsBun ? "imports both bun:test and vitest" : "imports neither runner";
|
|
throw new Error(`${filePath}: ${detail}`);
|
|
}
|
|
return {
|
|
file: filePath,
|
|
runner: importsBun ? "bun" : "vitest",
|
|
lane: integrationFiles.has(filePath) ? "integration" : "unit",
|
|
};
|
|
}
|
|
|
|
export function discoverProducerTests(producerRoot = PRODUCER_ROOT) {
|
|
const roots = ["src", "tests"].map((directory) => resolve(producerRoot, directory));
|
|
const files = roots
|
|
.flatMap((directory) => collectTestFiles(directory))
|
|
.map((absolutePath) => ({
|
|
absolutePath,
|
|
filePath: relative(producerRoot, absolutePath).replaceAll("\\", "/"),
|
|
}));
|
|
const discoveredFiles = new Set(files.map((test) => test.filePath));
|
|
const staleEntries = [...INTEGRATION_TEST_FILES].filter((file) => !discoveredFiles.has(file));
|
|
if (staleEntries.length > 0) {
|
|
throw new Error(`Integration test manifest contains missing files: ${staleEntries.join(", ")}`);
|
|
}
|
|
return files
|
|
.map(({ absolutePath, filePath }) => {
|
|
return classifyTestSource(filePath, readFileSync(absolutePath, "utf8"));
|
|
})
|
|
.sort((left, right) => left.file.localeCompare(right.file));
|
|
}
|
|
|
|
export function summarizeTests(tests) {
|
|
const summary = {
|
|
total: tests.length,
|
|
unit: { bun: 0, vitest: 0 },
|
|
integration: { bun: 0, vitest: 0 },
|
|
};
|
|
for (const test of tests) summary[test.lane][test.runner] += 1;
|
|
return summary;
|
|
}
|