feat(control-station): add atomic recorded-session playback
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { after, before, test } from "node:test";
|
||||
|
||||
import { createServer } from "vite";
|
||||
|
||||
let server;
|
||||
let admission;
|
||||
let rerunPresentationStatus;
|
||||
let recordedMediaPresentationState;
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
appType: "custom",
|
||||
logLevel: "silent",
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
admission = await server.ssrLoadModule(
|
||||
"/src/core/observation/recordedSessionAdmission.ts",
|
||||
);
|
||||
({ rerunPresentationStatus } = await server.ssrLoadModule(
|
||||
"/src/components/RerunViewport.tsx",
|
||||
));
|
||||
({ recordedMediaPresentationState } = await server.ssrLoadModule(
|
||||
"/src/components/RecordedFmp4Player.tsx",
|
||||
));
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server?.close();
|
||||
});
|
||||
|
||||
function camera(phase, byteLength = 1_024) {
|
||||
return { phase, byteLength, message: null };
|
||||
}
|
||||
|
||||
test("recorded session admits RRD and every declared camera as one atomic generation", () => {
|
||||
const ids = ["camera.left", "camera.right"];
|
||||
const oneCamera = {
|
||||
"camera.left": camera("ready"),
|
||||
"camera.right": camera("pending"),
|
||||
};
|
||||
const partialGate = admission.recordedSessionAdmissionPhase("ready", ids, oneCamera);
|
||||
assert.equal(partialGate, "loading");
|
||||
assert.equal(rerunPresentationStatus("ready", partialGate, true), "loading");
|
||||
assert.equal(
|
||||
recordedMediaPresentationState("ready", "generation", "generation", false, partialGate),
|
||||
"loading",
|
||||
);
|
||||
|
||||
const completeGate = admission.recordedSessionAdmissionPhase("ready", ids, {
|
||||
...oneCamera,
|
||||
"camera.right": camera("ready"),
|
||||
});
|
||||
assert.equal(completeGate, "ready");
|
||||
assert.equal(rerunPresentationStatus("ready", completeGate, true), "ready");
|
||||
assert.equal(
|
||||
recordedMediaPresentationState("ready", "generation", "generation", false, completeGate),
|
||||
"ready",
|
||||
);
|
||||
});
|
||||
|
||||
test("any RRD or camera failure closes the complete recorded session", () => {
|
||||
const ids = ["camera.left", "camera.right"];
|
||||
const cameras = {
|
||||
"camera.left": camera("ready"),
|
||||
"camera.right": camera("error"),
|
||||
};
|
||||
assert.equal(admission.recordedSessionAdmissionPhase("ready", ids, cameras), "error");
|
||||
assert.equal(admission.recordedSessionAdmissionPhase("error", ids, {
|
||||
...cameras,
|
||||
"camera.right": camera("ready"),
|
||||
}), "error");
|
||||
assert.equal(rerunPresentationStatus("ready", "error", true), "error");
|
||||
assert.equal(
|
||||
recordedMediaPresentationState("ready", "generation", "generation", false, "error"),
|
||||
"error",
|
||||
);
|
||||
});
|
||||
|
||||
test("camera admission enforces independent 16/128/512 MiB limits before fetching", () => {
|
||||
const {
|
||||
MAX_RECORDED_CAMERA_SOURCES,
|
||||
MAX_RECORDED_MEDIA_SOURCE_BYTES,
|
||||
MAX_RECORDED_SESSION_CAMERA_BYTES,
|
||||
recordedCameraDescriptorPreflight,
|
||||
} = admission;
|
||||
assert.equal(MAX_RECORDED_CAMERA_SOURCES, 16);
|
||||
assert.equal(MAX_RECORDED_MEDIA_SOURCE_BYTES, 128 * 1024 * 1024);
|
||||
assert.equal(MAX_RECORDED_SESSION_CAMERA_BYTES, 512 * 1024 * 1024);
|
||||
assert.equal(recordedCameraDescriptorPreflight(
|
||||
Array.from({ length: 4 }, (_, index) => ({
|
||||
id: `camera.${index}`,
|
||||
byteLength: MAX_RECORDED_MEDIA_SOURCE_BYTES,
|
||||
})),
|
||||
), "ready");
|
||||
assert.equal(recordedCameraDescriptorPreflight(
|
||||
Array.from({ length: 17 }, (_, index) => ({ id: `camera.${index}`, byteLength: 1 })),
|
||||
), "error");
|
||||
assert.equal(recordedCameraDescriptorPreflight([
|
||||
{ id: "camera.large", byteLength: MAX_RECORDED_MEDIA_SOURCE_BYTES + 1 },
|
||||
]), "error");
|
||||
assert.equal(recordedCameraDescriptorPreflight(
|
||||
Array.from({ length: 5 }, (_, index) => ({
|
||||
id: `camera.${index}`,
|
||||
byteLength: 110 * 1024 * 1024,
|
||||
})),
|
||||
), "error");
|
||||
assert.equal(recordedCameraDescriptorPreflight([
|
||||
{ id: "camera.duplicate", byteLength: 1 },
|
||||
{ id: "camera.duplicate", byteLength: 1 },
|
||||
]), "error");
|
||||
});
|
||||
|
||||
test("camera preparation remains single-flight and retains the loading permit", () => {
|
||||
const ids = ["camera.first", "camera.preferred"];
|
||||
const cameras = {
|
||||
"camera.first": camera("loading"),
|
||||
"camera.preferred": camera("pending"),
|
||||
};
|
||||
assert.deepEqual(
|
||||
admission.nextRecordedCameraPreparationIds(
|
||||
ids,
|
||||
cameras,
|
||||
new Set(["camera.preferred"]),
|
||||
),
|
||||
["camera.first"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
admission.nextRecordedCameraPreparationIds(ids, {
|
||||
...cameras,
|
||||
"camera.first": camera("ready"),
|
||||
}),
|
||||
["camera.preferred"],
|
||||
);
|
||||
});
|
||||
|
||||
test("new render workers close readiness and stale callbacks cannot reopen it", () => {
|
||||
const ready = { ...camera("ready"), workerGeneration: 1 };
|
||||
const remounted = admission.mergeRecordedCameraAdmission(ready, {
|
||||
...camera("loading"),
|
||||
workerGeneration: 2,
|
||||
});
|
||||
assert.equal(remounted.phase, "loading");
|
||||
assert.equal(remounted.workerGeneration, 2);
|
||||
assert.equal(admission.mergeRecordedCameraAdmission(remounted, {
|
||||
...camera("ready"),
|
||||
workerGeneration: 1,
|
||||
}), remounted);
|
||||
const admitted = admission.mergeRecordedCameraAdmission(remounted, {
|
||||
...camera("ready"),
|
||||
workerGeneration: 2,
|
||||
});
|
||||
assert.equal(admitted.phase, "ready");
|
||||
const failed = admission.mergeRecordedCameraAdmission(admitted, {
|
||||
...camera("error"),
|
||||
workerGeneration: 2,
|
||||
});
|
||||
assert.equal(failed.phase, "error");
|
||||
assert.equal(admission.mergeRecordedCameraAdmission(failed, {
|
||||
...camera("loading"),
|
||||
workerGeneration: 3,
|
||||
}), failed);
|
||||
});
|
||||
|
||||
test("recorded player is not mounted for a camera without a scheduler permit", async () => {
|
||||
const source = await readFile(
|
||||
new URL("../src/components/ObservationSources.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const placeholder = source.indexOf("if (!prepareRecorded)");
|
||||
const player = source.indexOf("<RecordedFmp4Player", placeholder);
|
||||
assert.ok(placeholder >= 0 && player > placeholder);
|
||||
assert.match(source.slice(placeholder, player), /return\s*\(/);
|
||||
});
|
||||
Reference in New Issue
Block a user