mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
test(producer): preserve single-chunk-path coverage + add auto-size integration
Address PR review feedback on #939: - Pin chunkSize=240 on the golden planDir layout test so the 1-chunk path through plan() stays exercised after the auto-sizer change. Assert chunkCount === 1 explicitly (previously just >= 1). - Add an integration test that runs plan() with chunkSize=undefined and asserts the auto-sizer produces multi-chunk output end-to-end (chunkCount=3, encoder.gopSize=10, encoder.chunkSize=10) for the same 30-frame fixture. - Document the GOP/file-size trade-off on the chunkSize docstring so adopters who optimize for output bytes know to pin chunkSize. - Update the resolveChunkPlan docstring formula to reference the operative variable (resolvedChunkSize) instead of the now-ambiguous chunkSize. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -164,9 +164,12 @@ describe("plan() — golden planDir + planHash determinism", () => {
|
||||
async () => {
|
||||
const planDir = join(runRoot, "plan-layout");
|
||||
mkdirSync(planDir, { recursive: true });
|
||||
// Pin chunkSize=240 so this fixture exercises the single-chunk path
|
||||
// (totalFrames=30 → ceil(30/240)=1 chunk). The auto-sized variant
|
||||
// (chunkSize=undefined) is exercised by the dedicated test below.
|
||||
const result = await plan(
|
||||
projectDir,
|
||||
{ fps: 30, width: 320, height: 240, format: "mp4" },
|
||||
{ fps: 30, width: 320, height: 240, format: "mp4", chunkSize: 240 },
|
||||
planDir,
|
||||
);
|
||||
|
||||
@@ -185,7 +188,7 @@ describe("plan() — golden planDir + planHash determinism", () => {
|
||||
// ── PlanResult contract ─────────────────────────────────────────────
|
||||
expect(result.planDir).toBe(planDir);
|
||||
expect(result.planHash).toMatch(/^[0-9a-f]{64}$/);
|
||||
expect(result.chunkCount).toBeGreaterThanOrEqual(1);
|
||||
expect(result.chunkCount).toBe(1);
|
||||
expect(result.totalFrames).toBe(30); // 1s @ 30fps
|
||||
expect(result.width).toBe(320);
|
||||
expect(result.height).toBe(240);
|
||||
@@ -218,6 +221,37 @@ describe("plan() — golden planDir + planHash determinism", () => {
|
||||
TIMEOUT_MS,
|
||||
);
|
||||
|
||||
it(
|
||||
"auto-sizes chunkSize end-to-end when caller omits it",
|
||||
async () => {
|
||||
// Integration check that the auto-sizer wired through plan() actually
|
||||
// produces multi-chunk output for the same fixture that single-chunks
|
||||
// when chunkSize is pinned. With totalFrames=30 and the default
|
||||
// maxParallelChunks=16, the auto-sizer picks
|
||||
// max(MIN_CHUNK_SIZE=10, ceil(30/16)=2) = 10 → ceil(30/10) = 3 chunks.
|
||||
const planDir = join(runRoot, "plan-autosized");
|
||||
mkdirSync(planDir, { recursive: true });
|
||||
const result = await plan(
|
||||
projectDir,
|
||||
{ fps: 30, width: 320, height: 240, format: "mp4" },
|
||||
planDir,
|
||||
);
|
||||
expect(result.chunkCount).toBe(3);
|
||||
const chunks = JSON.parse(
|
||||
readFileSync(join(planDir, "meta", "chunks.json"), "utf-8"),
|
||||
) as Array<{ index: number; startFrame: number; endFrame: number }>;
|
||||
expect(chunks).toHaveLength(3);
|
||||
// Encoder gopSize must follow the auto-sized chunk so chunk-boundary
|
||||
// IDR keyframes still land at frame 0 of each chunk.
|
||||
const encoder = JSON.parse(
|
||||
readFileSync(join(planDir, "meta", "encoder.json"), "utf-8"),
|
||||
) as Record<string, unknown>;
|
||||
expect(encoder.gopSize).toBe(10);
|
||||
expect(encoder.chunkSize).toBe(10);
|
||||
},
|
||||
TIMEOUT_MS,
|
||||
);
|
||||
|
||||
it(
|
||||
"produces a byte-identical planHash on a second invocation",
|
||||
async () => {
|
||||
|
||||
@@ -109,6 +109,13 @@ export interface DistributedRenderConfig {
|
||||
* ceil(totalFrames / maxParallelChunks))`. The auto-size floor
|
||||
* (`MIN_CHUNK_SIZE = 10`) keeps per-chunk fixed overhead from
|
||||
* swamping the parallelism gain on tiny renders.
|
||||
*
|
||||
* `effectiveChunkSize` also drives `LockedRenderConfig.gopSize` — every
|
||||
* chunk's first frame is an IDR keyframe, so smaller chunks mean a
|
||||
* tighter GOP and larger encoded files. Callers who optimize for
|
||||
* output bytes (rather than wall-clock parallelism) should pass an
|
||||
* explicit `chunkSize` matching their target GOP — e.g. `240` for the
|
||||
* old 8-second-GOP behavior.
|
||||
*/
|
||||
chunkSize?: number;
|
||||
/** Default `16`. Caps long renders to fewer-but-longer chunks for operational fairness. */
|
||||
@@ -350,22 +357,22 @@ export function measurePlanDirBytes(planDir: string): number {
|
||||
|
||||
/**
|
||||
* Compute `(chunkCount, effectiveChunkSize)` from total frames and the
|
||||
* caller's chunking knobs:
|
||||
* caller's chunking knobs. The operative chunk size is
|
||||
* `resolvedChunkSize` — equal to `configChunkSize` when the caller
|
||||
* passes one, otherwise auto-sized from `maxParallelChunks`:
|
||||
*
|
||||
* chunkCount = min(maxParallelChunks, ceil(totalFrames / chunkSize))
|
||||
* effectiveChunkSize = max(configChunkSize, ceil(totalFrames / maxParallelChunks))
|
||||
* resolvedChunkSize = configChunkSize ?? max(MIN_CHUNK_SIZE, ceil(totalFrames / maxParallelChunks))
|
||||
* chunkCount = min(maxParallelChunks, ceil(totalFrames / resolvedChunkSize))
|
||||
* effectiveChunkSize = max(resolvedChunkSize, ceil(totalFrames / chunkCount))
|
||||
*
|
||||
* Long renders auto-rescale to fewer-but-longer chunks rather than
|
||||
* fragmenting infinitely. Returned `chunkCount >= 1` (`totalFrames === 0`
|
||||
* is rejected upstream); `effectiveChunkSize >= configChunkSize`.
|
||||
* is rejected upstream); `effectiveChunkSize >= resolvedChunkSize`.
|
||||
*
|
||||
* When `configChunkSize` is `undefined`, the input is auto-sized from
|
||||
* `maxParallelChunks`: `max(MIN_CHUNK_SIZE, ceil(totalFrames /
|
||||
* maxParallelChunks))`. This honors the caller's fan-out intent — passing
|
||||
* `maxParallelChunks=16` without `chunkSize` now produces 16 chunks
|
||||
* (subject to the `MIN_CHUNK_SIZE` floor on tiny renders) instead of
|
||||
* silently clamping to a 240-frame default. Explicit numbers, including
|
||||
* `240`, take precedence over the auto-sizer.
|
||||
* The auto-sizer (triggered when `configChunkSize` is `undefined`) honors
|
||||
* the caller's fan-out intent: passing `maxParallelChunks=16` without
|
||||
* `chunkSize` produces 16 chunks (subject to the `MIN_CHUNK_SIZE` floor
|
||||
* on tiny renders). Explicit numbers, including `240`, take precedence.
|
||||
*/
|
||||
export function resolveChunkPlan(
|
||||
totalFrames: number,
|
||||
|
||||
Reference in New Issue
Block a user