diff --git a/scripts/fixtures/stub-worker.mjs b/scripts/fixtures/stub-worker.mjs index ce777b6..e138201 100644 --- a/scripts/fixtures/stub-worker.mjs +++ b/scripts/fixtures/stub-worker.mjs @@ -1,7 +1,7 @@ // Stands in for kimodo-resident-worker.py in the daemon's tests. It binds port 0, prints the ready line the // daemon waits for, and answers /health and /generate with what the daemon gave it. The --model value picks a // misbehaviour: crash-before-ready, hang-before-ready, sick, slow-health, stubborn, fail-generate, -// refuse-generate, drop-generate, slow-generate, writer (appends to .writes every 50 ms, never answers, ignores SIGTERM), slow-to-stop (no answer; prints a line and exits 1.5 s after SIGTERM), chatty (prints a line the moment it is healthy), encoder (a grandchild, then no answer), graceful (exits 0.5 s after SIGTERM), die-after-ready, bvh-outside (lists a BVH outside the job's directory), bvh-link (lists a symlink as its BVH), bvh-fifo (lists a FIFO as its BVH), plant-link (a normal answer with a file symlink and a directory symlink planted beside the BVH), no-loops (a normal answer that leaves out the loops a loop request asked for), bad-loops (loops whose window is not a frame range), short-loops (one window fewer than samples). A loop request is answered with loops, sample k as {first 3 + k, last 223 + k}. A request with bvh writes .bvh (motion/motion_NN.bvh for several samples) and lists it. Any other value is just the model the worker serves. +// refuse-generate, drop-generate, slow-generate, writer (appends to .writes every 50 ms, never answers, ignores SIGTERM), slow-to-stop (no answer; prints a line and exits 1.5 s after SIGTERM), chatty (prints a line the moment it is healthy), encoder (a grandchild, then no answer), graceful (exits 0.5 s after SIGTERM), die-after-ready, bvh-outside (lists a BVH outside the job's directory), bvh-link (lists a symlink as its BVH), bvh-fifo (lists a FIFO as its BVH), plant-link (a normal answer with a file symlink and a directory symlink planted beside the BVH), no-loops (a normal answer that leaves out the loops a loop request asked for), bad-loops, negative-loops, empty-loops, fractional-last-loops (loops whose window is not a frame range: a fractional first, a negative first, first equal to last, a fractional last), short-loops (one window fewer than samples). A loop request is answered with loops, sample k as {first 3 + k, last 223 + k}. A request with bvh writes .bvh (motion/motion_NN.bvh for several samples) and lists it. Any other value is just the model the worker serves. import http from "node:http"; import os from "node:os"; import path from "node:path"; @@ -14,6 +14,12 @@ const flag = (name) => { return index < 0 ? undefined : argv[index + 1]; }; const model = flag("--model"); +const BAD_WINDOWS = { + "bad-loops": { first: 5.5, last: 9 }, + "negative-loops": { first: -1, last: 9 }, + "empty-loops": { first: 7, last: 7 }, + "fractional-last-loops": { first: 5, last: 9.5 }, +}; const spawnLog = path.join(process.env.VILI_EMBEDDINGS_DIR, "stub-spawns.log"); let requests = 0; let healthCalls = 0; @@ -118,9 +124,8 @@ if (model === "hang-before-ready") { metaSeed: payload.meta?.seed, bvh: payload.bvh, ...(payload.loop && model !== "no-loops" ? { - loops: Array.from({ length: payload.numSamples - (model === "short-loops" ? 1 : 0) }, (_, k) => (model === "bad-loops" - ? { first: 5.5, last: 9, poseCost: 0, velocityCost: 0 } - : { first: 3 + k, last: 223 + k, poseCost: 0.03, velocityCost: 0.2 })), + loops: Array.from({ length: payload.numSamples - (model === "short-loops" ? 1 : 0) }, (_, k) => (BAD_WINDOWS[model] + ?? { first: 3 + k, last: 223 + k, poseCost: 0.03, velocityCost: 0.2 })), } : {}), bvhStandardTpose: payload.bvhStandardTpose, model: model || "stub-model", diff --git a/scripts/test-vili-daemon.mjs b/scripts/test-vili-daemon.mjs index dc3ffaf..6f56fe8 100644 --- a/scripts/test-vili-daemon.mjs +++ b/scripts/test-vili-daemon.mjs @@ -1480,30 +1480,6 @@ test("loop asks for the seamless window, and the window is the FBX's frame range } }); -test("constraints given twice are refused before anything is written or spawned", async () => { - const daemon = await startDaemon(); - try { - const inline = [{ type: "root2d" }]; - for (const bad of [ - { constraints: inline, constraints_path: "/canary/a.json" }, - { constraints: inline, constraintsPath: "/canary/a.json" }, - { constraints: "/canary/a.json", constraints_path: "/canary/b.json" }, - { constraints_path: "/canary/a.json", constraintsPath: "/canary/b.json" }, - ]) { - const answer = await generate(daemon, exportRequest(bad)); - assert.equal(answer.status, 400, JSON.stringify(bad)); - assert.deepEqual(answer.body, { ok: false, error: "Invalid constraints request." }); - assert.ok(!answer.raw.includes("canary"), "the refusal repeated the input"); - } - assert.equal(existsSync(path.join(daemon.stateRoot, "artifacts")), false, "no artifact directory was made"); - assert.deepEqual(jobFiles(daemon), []); - assert.deepEqual(daemon.spawns(), [], "no worker was started"); - assert.equal((await generate(daemon, exportRequest({ constraints_path: "/some/where.json" }))).status, 200, "one input is fine"); - } finally { - await daemon.stop(); - } -}); - test("past 100 samples each clip is exported over the window of its own sample", async () => { const daemon = await startDaemon(); try { @@ -1568,7 +1544,7 @@ test("loop is a boolean and needs fbx, refused before anything is written or spa }); test("a loop request whose answer has no usable window for a sample fails without exporting that sample", async () => { - for (const [model, exported] of [["no-loops", 0], ["bad-loops", 0], ["short-loops", 1]]) { + for (const [model, exported] of [["no-loops", 0], ["bad-loops", 0], ["negative-loops", 0], ["empty-loops", 0], ["fractional-last-loops", 0], ["short-loops", 1]]) { const daemon = await startDaemon(); try { const answer = await generate(daemon, exportRequest({ model, loop: true, numSamples: 2 })); diff --git a/scripts/test_bvh_to_fbx.py b/scripts/test_bvh_to_fbx.py index 844efec..5ce081d 100644 --- a/scripts/test_bvh_to_fbx.py +++ b/scripts/test_bvh_to_fbx.py @@ -242,14 +242,16 @@ class BvhToFbx(unittest.TestCase): def test_frames_outside_the_take_or_malformed_exit_without_a_file(self): bvh = self.walk("short", 31) - for index, (frames, exit_code) in enumerate(( - (("0", "10"), 4), (("5", "32"), 4), (("10", "10"), 4), (("20", "10"), 4), - (("5",), 3), (("a", "9"), 3), (("-1", "9"), 3), + for index, (arguments, exit_code) in enumerate(( + (("--frames", "0", "10"), 4), (("--frames", "5", "32"), 4), (("--frames", "10", "10"), 4), + (("--frames", "20", "10"), 4), + (("--frames", "5"), 3), (("--frames", "a", "9"), 3), (("--frames", "-1", "9"), 3), + (("--frames", "4", "20", "9"), 3), (("--range", "4", "20"), 3), (("4", "20"), 3), )): fbx = self.dir / f"refused{index}.fbx" - run = blender("--python", str(SCRIPT), "--", str(bvh), str(fbx), "refused", "--frames", *frames) - self.assertEqual(run.returncode, exit_code, frames) - self.assertFalse(fbx.exists(), frames) + run = blender("--python", str(SCRIPT), "--", str(bvh), str(fbx), "refused", *arguments) + self.assertEqual(run.returncode, exit_code, arguments) + self.assertFalse(fbx.exists(), arguments) if __name__ == "__main__":