同步KDL工程源码到云仓库
This commit is contained in:
206
kdl-wasm/web/tests/kdl/rpc.test.ts
Normal file
206
kdl-wasm/web/tests/kdl/rpc.test.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { KdlWorkerClient, type KdlWorkerLike } from "../../src/kdl/kdlClient.js";
|
||||
import type { NativeKdlModule } from "../../src/kdl/nativeModule.js";
|
||||
import { KdlStructuredError, type KdlRpcRequest, type KdlRpcResponse } from "../../src/kdl/rpc.js";
|
||||
import { createKdlWorkerRuntime } from "../../src/kdl/runtime.js";
|
||||
import { dispatchKdlRpcRequest } from "../../src/kdl/workerRpc.js";
|
||||
|
||||
class FakeWorker implements KdlWorkerLike {
|
||||
readonly sent: Array<KdlRpcRequest<unknown[]>> = [];
|
||||
terminated = false;
|
||||
private messageListeners = new Set<(event: MessageEvent<KdlRpcResponse>) => void>();
|
||||
private errorListeners = new Set<(event: ErrorEvent) => void>();
|
||||
|
||||
postMessage(message: KdlRpcRequest<unknown[]>): void {
|
||||
this.sent.push(message);
|
||||
}
|
||||
|
||||
terminate(): void {
|
||||
this.terminated = true;
|
||||
}
|
||||
|
||||
addEventListener(type: "message", listener: (event: MessageEvent<KdlRpcResponse>) => void): void;
|
||||
addEventListener(type: "error", listener: (event: ErrorEvent) => void): void;
|
||||
addEventListener(type: "message" | "error", listener: unknown): void {
|
||||
if (type === "message") {
|
||||
this.messageListeners.add(listener as (event: MessageEvent<KdlRpcResponse>) => void);
|
||||
return;
|
||||
}
|
||||
this.errorListeners.add(listener as (event: ErrorEvent) => void);
|
||||
}
|
||||
|
||||
removeEventListener(type: "message", listener: (event: MessageEvent<KdlRpcResponse>) => void): void;
|
||||
removeEventListener(type: "error", listener: (event: ErrorEvent) => void): void;
|
||||
removeEventListener(type: "message" | "error", listener: unknown): void {
|
||||
if (type === "message") {
|
||||
this.messageListeners.delete(listener as (event: MessageEvent<KdlRpcResponse>) => void);
|
||||
return;
|
||||
}
|
||||
this.errorListeners.delete(listener as (event: ErrorEvent) => void);
|
||||
}
|
||||
|
||||
emitResponse(response: KdlRpcResponse): void {
|
||||
const event = { data: response } as MessageEvent<KdlRpcResponse>;
|
||||
for (const listener of this.messageListeners) {
|
||||
listener(event);
|
||||
}
|
||||
}
|
||||
|
||||
emitError(message: string): void {
|
||||
const event = { message, error: new Error(message) } as ErrorEvent;
|
||||
for (const listener of this.errorListeners) {
|
||||
listener(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("KDL Worker RPC", () => {
|
||||
it("loads the native WASM module during init when a loader is configured", async () => {
|
||||
const calls: unknown[][] = [];
|
||||
const native: NativeKdlModule = {
|
||||
ccall: (...args) => {
|
||||
calls.push(args);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
const runtime = createKdlWorkerRuntime(async () => native);
|
||||
|
||||
const response = await dispatchKdlRpcRequest(runtime, {
|
||||
id: 1,
|
||||
method: "init",
|
||||
payload: [{ wasmBuild: "native-test" }]
|
||||
});
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.result).toMatchObject({ wasmBuild: "native-test" });
|
||||
expect(calls).toEqual([["kdl_init", "number", ["string"], ['{"wasmBuild":"native-test"}']]]);
|
||||
});
|
||||
|
||||
it("normalizes native WASM initialization failures", async () => {
|
||||
const runtime = createKdlWorkerRuntime(async () => {
|
||||
throw new Error("cannot load kdl.js");
|
||||
});
|
||||
|
||||
const response = await dispatchKdlRpcRequest(runtime, {
|
||||
id: 11,
|
||||
method: "init",
|
||||
payload: [{}]
|
||||
});
|
||||
|
||||
expect(response.ok).toBe(false);
|
||||
expect(response.error).toMatchObject({
|
||||
code: "KDL_WASM_INIT_FAILED",
|
||||
diagnostics: [
|
||||
{
|
||||
severity: "error",
|
||||
code: "KDL_WASM_INIT_FAILED"
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it("dispatches init and dispose through structured responses", async () => {
|
||||
const runtime = createKdlWorkerRuntime();
|
||||
const initResponse = await dispatchKdlRpcRequest(runtime, {
|
||||
id: 1,
|
||||
method: "init",
|
||||
payload: [{ wasmBuild: "test", useThreads: true }]
|
||||
});
|
||||
|
||||
expect(initResponse.ok).toBe(true);
|
||||
expect(initResponse.result).toMatchObject({
|
||||
version: "0.1.0",
|
||||
wasmBuild: "test",
|
||||
supportsThreads: true
|
||||
});
|
||||
|
||||
const disposeResponse = await dispatchKdlRpcRequest(runtime, {
|
||||
id: 2,
|
||||
method: "dispose",
|
||||
payload: []
|
||||
});
|
||||
|
||||
expect(disposeResponse).toMatchObject({ id: 2, ok: true });
|
||||
});
|
||||
|
||||
it("returns a structured error when an implemented method is called before init", async () => {
|
||||
const response = await dispatchKdlRpcRequest(createKdlWorkerRuntime(), {
|
||||
id: 7,
|
||||
method: "fk",
|
||||
payload: [1, new Float64Array([0])]
|
||||
});
|
||||
|
||||
expect(response.ok).toBe(false);
|
||||
expect(response.error).toMatchObject({
|
||||
code: "KDL_NOT_INITIALIZED",
|
||||
diagnostics: [
|
||||
{
|
||||
severity: "error",
|
||||
code: "KDL_NOT_INITIALIZED"
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it("uses unique request ids and resolves responses by id", async () => {
|
||||
const workers: FakeWorker[] = [];
|
||||
const client = new KdlWorkerClient(() => {
|
||||
const worker = new FakeWorker();
|
||||
workers.push(worker);
|
||||
return worker;
|
||||
});
|
||||
|
||||
const first = client.call("init", { wasmBuild: "a" });
|
||||
const second = client.call("dispose");
|
||||
|
||||
expect(workers).toHaveLength(1);
|
||||
expect(workers[0]?.sent.map((request) => request.id)).toEqual([1, 2]);
|
||||
|
||||
workers[0]?.emitResponse({ id: 2, ok: true });
|
||||
workers[0]?.emitResponse({
|
||||
id: 1,
|
||||
ok: true,
|
||||
result: {
|
||||
version: "0.1.0",
|
||||
wasmBuild: "a",
|
||||
supportsThreads: false,
|
||||
supportsWasmFs: false
|
||||
}
|
||||
});
|
||||
|
||||
await expect(second).resolves.toBeUndefined();
|
||||
await expect(first).resolves.toMatchObject({ wasmBuild: "a" });
|
||||
});
|
||||
|
||||
it("rejects pending requests on worker failure and can create a fresh worker", async () => {
|
||||
const workers: FakeWorker[] = [];
|
||||
const client = new KdlWorkerClient(() => {
|
||||
const worker = new FakeWorker();
|
||||
workers.push(worker);
|
||||
return worker;
|
||||
});
|
||||
|
||||
const pending = client.init();
|
||||
workers[0]?.emitError("boom");
|
||||
|
||||
await expect(pending).rejects.toMatchObject({
|
||||
code: "KDL_WORKER_CRASHED"
|
||||
});
|
||||
expect(workers[0]?.terminated).toBe(true);
|
||||
|
||||
const restarted = client.init({ wasmBuild: "restart" });
|
||||
expect(workers).toHaveLength(2);
|
||||
workers[1]?.emitResponse({
|
||||
id: 2,
|
||||
ok: true,
|
||||
result: {
|
||||
version: "0.1.0",
|
||||
wasmBuild: "restart",
|
||||
supportsThreads: false,
|
||||
supportsWasmFs: false
|
||||
}
|
||||
});
|
||||
|
||||
await expect(restarted).resolves.toMatchObject({ wasmBuild: "restart" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user