import { describe, expect, it } from "vitest"; import type { RegexExecutionRequest, RegexExecutionResult, } from "../model/match"; import type { RegexFlavourId } from "../model/flavour"; import { EngineSupervisor } from "./EngineSupervisor"; import { ENGINE_WORKER_REGISTRY, EngineWorkerRegistry, } from "./engine-registry"; import type { WorkerLike } from "./WorkerSupervisor"; import { WORKER_PROTOCOL_VERSION, type EngineWorkerOperation, type EngineWorkerResult, type WorkerRequest, type WorkerResponse, } from "./worker-protocol"; function request(flavour: RegexFlavourId): RegexExecutionRequest { return { flavour, flavourVersion: "fixture", pattern: "a", flags: [], options: {}, subject: "a", captureMetadata: [], scanAll: false, maximumMatches: 10, maximumCaptureRows: 10, }; } function result(flavour: RegexFlavourId): RegexExecutionResult { return { accepted: true, engine: { flavour, adapterVersion: "fixture", engineName: `${flavour} fixture`, engineVersion: "fixture", offsetUnit: "utf16", capabilities: { compilation: true, matching: true, replacement: false, namedCaptures: false, captureHistory: false, actualTrace: false, benchmark: false, }, }, flags: { userFlags: "", effectiveFlags: "", internallyAddedIndicesFlag: false, internallyAddedGlobalFlag: false, }, matches: [], diagnostics: [], elapsedMs: 0, truncated: false, }; } class ResponsiveWorker implements WorkerLike { onmessage: ((event: MessageEvent) => void) | null = null; onerror: ((event: ErrorEvent) => void) | null = null; onmessageerror: ((event: MessageEvent) => void) | null = null; readonly operations: EngineWorkerOperation[] = []; terminateCalls = 0; postMessage(message: unknown): void { const request = message as WorkerRequest; this.operations.push(request.payload); const payload: EngineWorkerResult = request.payload.kind === "execute" ? { kind: "execute", result: result(request.payload.request.flavour), } : { kind: "replace", result: { execution: result(request.payload.request.flavour), output: "", outputBytes: 0, outputTruncated: false, truncated: false, }, }; const response: WorkerResponse = { protocolVersion: WORKER_PROTOCOL_VERSION, requestId: request.requestId, generation: request.generation, ok: true, payload, }; this.onmessage?.({ data: response } as MessageEvent); } terminate(): void { this.terminateCalls += 1; } } describe("engine worker selection", () => { it("registers the shipped ECMAScript and PCRE2 execution workers", () => { expect( ENGINE_WORKER_REGISTRY.registrations.map( (registration) => registration.flavour, ), ).toEqual(["ecmascript", "pcre2"]); expect(ENGINE_WORKER_REGISTRY.require("pcre2").label).toBe("PCRE2 engine"); }); it("selects and reuses a dedicated supervisor per registered flavour", async () => { const workers = new Map(); const registration = (flavour: RegexFlavourId) => ({ flavour, label: `${flavour} fixture`, createWorker: () => { const worker = new ResponsiveWorker(); workers.set(flavour, [...(workers.get(flavour) ?? []), worker]); return worker; }, }); const supervisor = new EngineSupervisor( new EngineWorkerRegistry([ registration("ecmascript"), registration("pcre2"), ]), ); await expect( supervisor.execute(request("ecmascript"), 100), ).resolves.toEqual( expect.objectContaining({ engine: expect.objectContaining({ flavour: "ecmascript" }), }), ); await expect(supervisor.execute(request("pcre2"), 100)).resolves.toEqual( expect.objectContaining({ engine: expect.objectContaining({ flavour: "pcre2" }), }), ); await supervisor.execute(request("ecmascript"), 100); expect(workers.get("ecmascript")).toHaveLength(1); expect(workers.get("pcre2")).toHaveLength(1); expect(workers.get("ecmascript")?.[0]?.operations).toHaveLength(2); supervisor.dispose(); expect(workers.get("ecmascript")?.[0]?.terminateCalls).toBe(1); expect(workers.get("pcre2")?.[0]?.terminateCalls).toBe(1); }); it("fails closed before constructing a worker for an unavailable flavour", async () => { const supervisor = new EngineSupervisor(new EngineWorkerRegistry([])); await expect(supervisor.execute(request("pcre2"), 100)).rejects.toThrow( /No execution worker is registered/u, ); supervisor.dispose(); }); });