Files
hyperframes/packages/studio/tests/e2e/generateStudioLoadFixture.test.ts
Miguel Angel Simon Sierra b82105ac0e test(studio): back the load fixture with real reused media
The load fixture gave every clip its own source, so it could not reproduce the
request amplification a real project produces: opening a 273-clip project issues
337 media requests for 79 files on disk, one track.mp3 seven-plus times. A gate
built on the old shape would measure nothing.

Add mediaSourceCount/mediaClipCount, independently controllable, with clips
round-robining over the sources so every source is reused at least seven times.
Sources are real, browser-decodable media generated deterministically: 32x32
solid PNGs, one-second 8 kHz silent WAVs, and a checked-in 1,684-byte H.264 clip
each source varies with an ignored trailing free box, so no two sources are
byte-identical and no content-addressed cache can collapse them.

Media defaults to off, so existing zero-media generation is unchanged.
2026-08-03 21:49:10 -07:00

210 lines
8.0 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { existsSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { generateStudioLoadFixture, writeStudioLoadFixture } from "./generateStudioLoadFixture.mjs";
type FixtureFiles = Record<string, string | Buffer>;
const temporaryProjects: string[] = [];
const CLI_LINT_PATH = fileURLToPath(
new URL("../../../cli/src/utils/lintProject.ts", import.meta.url),
);
/**
* 16 distinct sources behind 112 clip instances — the divergence that produces
* request amplification. Every source is referenced 7 times, mirroring the
* measured project where one `track.mp3` was fetched seven-plus times.
*/
const MEDIA_OPTIONS = {
clipCount: 300,
trackCount: 50,
mediaSourceCount: 16,
mediaClipCount: 112,
};
/**
* The fixture exists to avoid measuring against a 748 MB real project, so its
* own weight is capped. Today's media configuration lands around 117 KiB.
*/
const FIXTURE_WEIGHT_CEILING_BYTES = 512 * 1024;
function createTemporaryProject(): string {
const projectDir = mkdtempSync(join(tmpdir(), "studio-load-fixture-"));
temporaryProjects.push(projectDir);
return projectDir;
}
function clipTags(files: FixtureFiles): string[] {
return Object.values(files).flatMap((contents) =>
typeof contents === "string"
? (contents.match(/<[^>]+\bclass="[^"]*\bclip\b[^"]*"[^>]*>/g) ?? [])
: [],
);
}
function mediaReferences(files: FixtureFiles): string[] {
const markup = String(files["index.html"]);
return [...markup.matchAll(/<(?:audio|video|img)\b[^>]*\bsrc="([^"]+)"/g)].map((match) =>
String(match[1]),
);
}
function expectProjectToPassLint(projectDir: string): void {
const program = `
import { lintProject } from ${JSON.stringify(pathToFileURL(CLI_LINT_PATH).href)};
const result = await lintProject(${JSON.stringify(projectDir)});
const errors = result.results.flatMap(({ file, result }) =>
result.findings.filter(({ severity }) => severity === "error").map((finding) => ({ file, ...finding })),
);
console.log(JSON.stringify({ errors, totalErrors: result.totalErrors }));
if (result.totalErrors > 0) process.exitCode = 1;
`;
const result = spawnSync("bun", ["-e", program], { encoding: "utf8" });
expect(
{ error: result.error?.message, status: result.status },
result.stderr || result.stdout,
).toEqual({ error: undefined, status: 0 });
}
afterEach(() => {
for (const projectDir of temporaryProjects.splice(0)) {
rmSync(projectDir, { force: true, recursive: true });
}
});
describe("generateStudioLoadFixture", () => {
it("generates exactly 1,000 clips across every requested track", () => {
const files = generateStudioLoadFixture({ clipCount: 1_000, trackCount: 37 });
const tags = clipTags(files);
const tracks = new Set(
tags.flatMap((tag) => {
const match = tag.match(/\bdata-track-index="(\d+)"/);
return match ? [Number(match[1])] : [];
}),
);
expect(tags).toHaveLength(1_000);
expect([...tracks].sort((a, b) => a - b)).toEqual(
Array.from({ length: 37 }, (_, index) => index),
);
});
it("produces byte-identical files for the same inputs", () => {
expect(generateStudioLoadFixture({ clipCount: 1_000, trackCount: 50 })).toStrictEqual(
generateStudioLoadFixture({ clipCount: 1_000, trackCount: 50 }),
);
});
it("keeps the authored DOM id ratio below 30 percent", () => {
const tags = clipTags(generateStudioLoadFixture({ clipCount: 1_000, trackCount: 50 }));
const authoredIdCount = tags.filter((tag) => /\sid="[^"]+"/.test(tag)).length;
expect(authoredIdCount / tags.length).toBeLessThan(0.3);
});
it("passes the HyperFrames CLI structural lint", () => {
const projectDir = createTemporaryProject();
writeStudioLoadFixture(projectDir, { clipCount: 1_000, trackCount: 50 });
expectProjectToPassLint(projectDir);
}, 15_000);
it("references an existing sub-composition from the root", () => {
const projectDir = createTemporaryProject();
const files = generateStudioLoadFixture({ clipCount: 1_000, trackCount: 50 });
writeStudioLoadFixture(projectDir, { clipCount: 1_000, trackCount: 50 });
const reference = files["index.html"].match(/data-composition-src="([^"]+)"/)?.[1];
expect(reference).toBeDefined();
expect(reference && existsSync(join(projectDir, reference))).toBe(true);
});
it("generates a structurally valid zero-clip project", () => {
const files = generateStudioLoadFixture({ clipCount: 0, trackCount: 1 });
const projectDir = createTemporaryProject();
writeStudioLoadFixture(projectDir, { clipCount: 0, trackCount: 1 });
expect(clipTags(files)).toHaveLength(0);
expectProjectToPassLint(projectDir);
});
it("emits no media files when no media is requested", () => {
const paths = Object.keys(generateStudioLoadFixture({ clipCount: 1_000, trackCount: 50 }));
expect(paths.filter((path) => path.startsWith("assets/"))).toEqual([]);
expect(
mediaReferences(generateStudioLoadFixture({ clipCount: 1_000, trackCount: 50 })),
).toEqual([]);
});
it("diverges distinct media sources from media clip instances", () => {
const files = generateStudioLoadFixture(MEDIA_OPTIONS);
const references = mediaReferences(files);
const referenceCounts = new Map<string, number>();
for (const source of references) {
referenceCounts.set(source, (referenceCounts.get(source) ?? 0) + 1);
}
expect(references).toHaveLength(MEDIA_OPTIONS.mediaClipCount);
expect(referenceCounts.size).toBe(MEDIA_OPTIONS.mediaSourceCount);
expect(Math.max(...referenceCounts.values())).toBeGreaterThanOrEqual(7);
// Every referenced source is a real file the fixture emits, and all three
// media kinds appear — images are the second-largest request contributor.
expect([...referenceCounts.keys()].filter((source) => files[source] === undefined)).toEqual([]);
for (const extension of [".wav", ".mp4", ".png"]) {
expect(references.some((source) => source.endsWith(extension))).toBe(true);
}
});
it("controls source count and clip count independently", () => {
const moreClips = mediaReferences(
generateStudioLoadFixture({ ...MEDIA_OPTIONS, mediaClipCount: 160 }),
);
const fewerSources = mediaReferences(
generateStudioLoadFixture({ ...MEDIA_OPTIONS, mediaSourceCount: 8 }),
);
expect(moreClips).toHaveLength(160);
expect(new Set(moreClips).size).toBe(MEDIA_OPTIONS.mediaSourceCount);
expect(fewerSources).toHaveLength(MEDIA_OPTIONS.mediaClipCount);
expect(new Set(fewerSources).size).toBe(8);
});
it("rejects a media configuration that would not amplify", () => {
expect(() =>
generateStudioLoadFixture({ ...MEDIA_OPTIONS, mediaSourceCount: 16, mediaClipCount: 20 }),
).toThrow(RangeError);
expect(() => generateStudioLoadFixture({ ...MEDIA_OPTIONS, mediaClipCount: 1_000 })).toThrow(
RangeError,
);
});
it("produces byte-identical media for the same inputs", () => {
const first = generateStudioLoadFixture(MEDIA_OPTIONS);
const second = generateStudioLoadFixture(MEDIA_OPTIONS);
expect(Object.keys(second)).toEqual(Object.keys(first));
for (const [path, contents] of Object.entries(first)) {
expect(Buffer.from(second[path] as string | Buffer), path).toEqual(Buffer.from(contents));
}
});
it("keeps the media fixture under the stated weight ceiling", () => {
const totalBytes = Object.values(generateStudioLoadFixture(MEDIA_OPTIONS)).reduce(
(total, contents) => total + Buffer.byteLength(contents),
0,
);
expect(totalBytes).toBeLessThan(FIXTURE_WEIGHT_CEILING_BYTES);
});
it("passes the HyperFrames CLI structural lint with media", () => {
const projectDir = createTemporaryProject();
writeStudioLoadFixture(projectDir, MEDIA_OPTIONS);
expectProjectToPassLint(projectDir);
}, 15_000);
});