import { test } from "node:test"; import assert from "node:assert/strict"; import { createScheduler, compareTier, type ScheduledTask } from "./scheduler"; // Run with: pnpm --filter yt-dlp-transcript-common exec tsx --test jobs/scheduler.test.ts // // The scheduler is the single source of truth for queue ordering, generalizing // registry.enqueue's foreground-before-background insertion to priority tiers. // Each test uses a fresh createScheduler() so there is no shared global state. let seq = 0; function task( queueKey: string, tier: ScheduledTask["tier"], concurrency = 1, ): { task: ScheduledTask; started: () => boolean; cancelled: () => boolean } { let started = false; let cancelled = false; const t: ScheduledTask = { id: `t${seq++}`, queueKey, tier, concurrency, start: () => { started = true; }, onCancel: () => { cancelled = true; }, }; return { task: t, started: () => started, cancelled: () => cancelled }; } test("compareTier orders urgent < foreground < background", () => { assert.ok(compareTier("urgent", "foreground") < 0); assert.ok(compareTier("foreground", "background") < 0); assert.ok(compareTier("background", "urgent") > 0); assert.equal(compareTier("foreground", "foreground"), 0); }); test("first task on a queue runs immediately", () => { const s = createScheduler(); const a = task("k", "foreground"); const r = s.submit(a.task); assert.deepEqual(r, { willRunNow: true, position: 0 }); assert.ok(a.started()); assert.deepEqual(s.queues(), [{ name: "k", running: [a.task.id], queued: [] }]); }); test("tiers order the queued section: urgent before foreground before background", () => { const s = createScheduler(); const head = task("k", "foreground"); // runs const bg = task("k", "background"); const fg = task("k", "foreground"); const urg = task("k", "urgent"); s.submit(head.task); s.submit(bg.task); s.submit(fg.task); s.submit(urg.task); const view = s.queues()[0]; assert.deepEqual(view.running, [head.task.id]); assert.deepEqual(view.queued, [urg.task.id, fg.task.id, bg.task.id]); // Only the running head actually started. assert.ok(head.started()); assert.ok(!fg.started() && !bg.started() && !urg.started()); }); test("FIFO within a tier", () => { const s = createScheduler(); const head = task("k", "foreground"); const a = task("k", "foreground"); const b = task("k", "foreground"); s.submit(head.task); s.submit(a.task); s.submit(b.task); assert.deepEqual(s.queues()[0].queued, [a.task.id, b.task.id]); }); test("concurrency > 1 runs that many at once, queues the rest", () => { const s = createScheduler(); const ts = Array.from({ length: 5 }, () => task("k", "foreground", 3)); ts.forEach((t) => s.submit(t.task)); const view = s.queues()[0]; assert.equal(view.running.length, 3); assert.equal(view.queued.length, 2); assert.ok(ts.slice(0, 3).every((t) => t.started())); assert.ok(ts.slice(3).every((t) => !t.started())); }); test("complete promotes the next queued task and fires its start", () => { const s = createScheduler(); const head = task("k", "foreground"); const next = task("k", "foreground"); s.submit(head.task); s.submit(next.task); assert.ok(!next.started()); s.complete(head.task.id); assert.ok(next.started()); assert.deepEqual(s.queues(), [{ name: "k", running: [next.task.id], queued: [] }]); }); test("completing the last task removes the queue", () => { const s = createScheduler(); const a = task("k", "foreground"); s.submit(a.task); s.complete(a.task.id); assert.deepEqual(s.queues(), []); assert.equal(s.positionInQueue(a.task.id), -1); }); test("cancel removes a queued task and fires onCancel; running/unknown return false", () => { const s = createScheduler(); const head = task("k", "foreground"); const q = task("k", "foreground"); s.submit(head.task); s.submit(q.task); assert.equal(s.cancel(q.task.id), true); assert.ok(q.cancelled()); assert.deepEqual(s.queues()[0].queued, []); // Running head can't be cancelled via the scheduler (caller hard-aborts). assert.equal(s.cancel(head.task.id), false); assert.equal(s.cancel("nope"), false); }); test("promote moves a queued task to the front of the queued section", () => { const s = createScheduler(); const head = task("k", "foreground"); const a = task("k", "background"); const b = task("k", "background"); s.submit(head.task); s.submit(a.task); s.submit(b.task); assert.deepEqual(s.queues()[0].queued, [a.task.id, b.task.id]); assert.equal(s.promote(b.task.id), true); assert.deepEqual(s.queues()[0].queued, [b.task.id, a.task.id]); // Running task can't be promoted. assert.equal(s.promote(head.task.id), false); }); test("reorder swaps adjacent queued tasks but never moves the running head", () => { const s = createScheduler(); const head = task("k", "foreground"); const a = task("k", "foreground"); const b = task("k", "foreground"); s.submit(head.task); s.submit(a.task); s.submit(b.task); // Move b up past a. assert.equal(s.reorder(b.task.id, -1), true); assert.deepEqual(s.queues()[0].queued, [b.task.id, a.task.id]); // b is now at the front of the queued section: can't move up into running. assert.equal(s.reorder(b.task.id, -1), false); // a is last: can't move down past the end. assert.equal(s.reorder(a.task.id, 1), false); // Running head can't be reordered. assert.equal(s.reorder(head.task.id, 1), false); }); test('queueKey "" runs immediately in parallel and is not tracked', () => { const s = createScheduler(); const a = task("", "foreground"); const b = task("", "foreground"); assert.deepEqual(s.submit(a.task), { willRunNow: true, position: 0 }); assert.deepEqual(s.submit(b.task), { willRunNow: true, position: 0 }); assert.ok(a.started() && b.started()); assert.deepEqual(s.queues(), []); assert.equal(s.positionInQueue(a.task.id), -1); s.complete(a.task.id); // no-op, no throw }); test("positionInQueue reports 0 for running head, then queued offsets", () => { const s = createScheduler(); const head = task("k", "foreground"); const a = task("k", "foreground"); const b = task("k", "foreground"); s.submit(head.task); s.submit(a.task); s.submit(b.task); assert.equal(s.positionInQueue(head.task.id), 0); assert.equal(s.positionInQueue(a.task.id), 1); assert.equal(s.positionInQueue(b.task.id), 2); });