Files
wehub-resource-sync e904b667c6
Build/Publish Develop Docs / deploy (push) Failing after 1s
PaddleOCR Code Style Check / check-code-style (push) Failing after 1s
PaddleOCR PR Tests GPU / detect-changes (push) Failing after 1s
PaddleOCR PR Tests / detect-changes (push) Failing after 1s
PaddleOCR PR Tests GPU / test-pr-gpu (push) Has been cancelled
PaddleOCR PR Tests / test-pr (push) Has been cancelled
PaddleOCR PR Tests GPU / test-pr-gpu-impl (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.13) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.8) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.9) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:59:26 +08:00

234 lines
6.8 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
const sourceToWorkerPayload = vi.fn();
vi.mock("../src/platform/browser", () => ({
sourceToWorkerPayload
}));
afterEach(() => {
vi.clearAllMocks();
});
function basePipelineConfig() {
return {
pipelineName: "OCR",
raw: {},
warnings: [] as string[],
unsupportedFeatures: [] as string[],
modelSelection: {
textDetectionModelName: "PP-OCRv5_mobile_det",
textRecognitionModelName: "PP-OCRv5_mobile_rec"
},
assets: {
det: { id: "det" },
rec: { id: "rec" }
},
runtimeDefaults: {} as Record<string, unknown>,
pipelineBatchSize: 1,
textDetectionBatchSize: 1,
textRecognitionBatchSize: 1
};
}
function createWorkerBackedOptions(overrides: Record<string, unknown> = {}) {
const { pipelineConfig: pipelineOverrides, ...rest } = overrides;
return {
pipelineConfig: {
...basePipelineConfig(),
...(pipelineOverrides as object)
},
ortOptions: {},
...rest
};
}
describe("worker-backed OCR adapter", () => {
it("initializes once and forces wasm proxy off in worker mode", async () => {
const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed");
const transportClient = {
request: vi.fn().mockResolvedValue({
summary: { backend: "wasm" },
modelConfig: { det: { name: "det" }, rec: { name: "rec" } }
}),
dispose: vi.fn()
};
const ocr = new WorkerBackedPaddleOCR(
createWorkerBackedOptions({
pipelineConfig: { runtimeDefaults: { text_det_limit_side_len: 64 } },
ortOptions: { proxy: true }
}),
transportClient
);
const first = await ocr.initialize();
const second = await ocr.initialize();
expect(first).toEqual({ backend: "wasm" });
expect(second).toEqual({ backend: "wasm" });
expect(transportClient.request).toHaveBeenCalledTimes(1);
expect(transportClient.request).toHaveBeenCalledWith("init", {
options: expect.objectContaining({
ortOptions: {
proxy: true,
disableWasmProxy: true
}
})
});
expect(ocr.getInitializationSummary()).toEqual({ backend: "wasm" });
expect(ocr.getModelConfig()).toEqual({ det: { name: "det" }, rec: { name: "rec" } });
});
it("predicts through the worker transport using transferable payloads", async () => {
const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed");
const transferables = [{ id: "bitmap" }];
const mockOcrResult = {
image: { width: 1, height: 1 },
items: [{ poly: [[0, 0]], text: "hello", score: 1 }],
metrics: {
detMs: 1,
recMs: 1,
totalMs: 2,
detectedBoxes: 1,
recognizedCount: 1
},
runtime: {
requestedBackend: "auto",
detProvider: "wasm",
recProvider: "wasm",
webgpuAvailable: false
}
};
const transportClient = {
request: vi
.fn()
.mockResolvedValueOnce({
summary: { backend: "wasm" },
modelConfig: { det: {}, rec: {} }
})
.mockResolvedValueOnce([mockOcrResult]),
dispose: vi.fn()
};
sourceToWorkerPayload.mockResolvedValue({
payload: {
kind: "imageBitmap",
imageBitmap: transferables[0]
},
transferables
});
const ocr = new WorkerBackedPaddleOCR(createWorkerBackedOptions(), transportClient);
const result = await ocr.predict({ kind: "source" }, { text_rec_score_thresh: 0.5 });
expect(sourceToWorkerPayload).toHaveBeenCalledWith({ kind: "source" });
expect(transportClient.request).toHaveBeenNthCalledWith(
2,
"predict",
{
sources: [
{
kind: "imageBitmap",
imageBitmap: transferables[0]
}
],
params: { text_rec_score_thresh: 0.5 }
},
transferables
);
expect(result).toEqual([mockOcrResult]);
});
it("predicts multiple sources when input is an array", async () => {
const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed");
const mockOcrResult = {
image: { width: 10, height: 10 },
items: [],
metrics: {
detMs: 0,
recMs: 0,
totalMs: 0,
detectedBoxes: 0,
recognizedCount: 0
},
runtime: {
requestedBackend: "auto",
detProvider: "wasm",
recProvider: "wasm",
webgpuAvailable: false
}
};
const transportClient = {
request: vi
.fn()
.mockResolvedValueOnce({
summary: { backend: "wasm" },
modelConfig: { det: {}, rec: {} }
})
.mockResolvedValueOnce([
mockOcrResult,
{ ...mockOcrResult, image: { width: 20, height: 20 } }
]),
dispose: vi.fn()
};
sourceToWorkerPayload.mockResolvedValue({
payload: { kind: "imageBitmap", imageBitmap: {} },
transferables: []
});
const ocr = new WorkerBackedPaddleOCR(createWorkerBackedOptions(), transportClient);
await ocr.predict([{ a: 1 }, { b: 2 }], {});
expect(sourceToWorkerPayload).toHaveBeenCalledTimes(2);
expect(transportClient.request).toHaveBeenNthCalledWith(
2,
"predict",
{
sources: [
{ kind: "imageBitmap", imageBitmap: {} },
{ kind: "imageBitmap", imageBitmap: {} }
],
params: {}
},
[]
);
});
it("disposes the transport after initialization failures and allows retrying", async () => {
const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed");
const transportClient = {
request: vi
.fn()
.mockRejectedValueOnce(new Error("init failed"))
.mockResolvedValueOnce({
summary: { backend: "wasm" },
modelConfig: { det: {}, rec: {} }
}),
dispose: vi.fn()
};
const ocr = new WorkerBackedPaddleOCR(createWorkerBackedOptions(), transportClient);
await expect(ocr.initialize()).rejects.toThrow("init failed");
expect(transportClient.dispose).toHaveBeenCalledTimes(1);
await expect(ocr.initialize()).resolves.toEqual({ backend: "wasm" });
});
it("swallows dispose request failures and rejects use after disposal", async () => {
const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed");
const transportClient = {
request: vi.fn().mockRejectedValue(new Error("worker already gone")),
dispose: vi.fn()
};
const ocr = new WorkerBackedPaddleOCR(createWorkerBackedOptions(), transportClient);
await expect(ocr.dispose()).resolves.toBeUndefined();
expect(transportClient.request).toHaveBeenCalledWith("dispose", {});
expect(transportClient.dispose).toHaveBeenCalledTimes(1);
await expect(ocr.predict({}, {})).rejects.toThrow(/worker instance has been disposed/i);
});
});