| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Cause, Deferred, Effect, Exit, Fiber, Schema } from "effect" |
| 3 | import * as Worker from "effect/workers/Worker" |
| 4 | import { |
| 5 | isWorkerError, |
| 6 | WorkerError, |
| 7 | WorkerErrorReason, |
| 8 | WorkerReceiveError, |
| 9 | WorkerSendError, |
| 10 | WorkerSpawnError, |
| 11 | WorkerUnknownError |
| 12 | } from "effect/workers/WorkerError" |
| 13 | |
| 14 | describe("WorkerError", () => { |
| 15 | describe("reason handling", () => { |
| 16 | it("delegates message from reason", () => { |
| 17 | const error = new WorkerError({ |
| 18 | reason: new WorkerSendError({ |
| 19 | message: "Failed to send message to worker" |
| 20 | }) |
| 21 | }) |
| 22 | |
| 23 | assert.strictEqual(error.message, "Failed to send message to worker") |
| 24 | assert.strictEqual(error.reason._tag, "WorkerSendError") |
| 25 | }) |
| 26 | |
| 27 | it("WorkerError delegates message and cause from reason", () => { |
| 28 | const cause = new Error("boom") |
| 29 | const reason = new WorkerSpawnError({ |
| 30 | message: "Failed to spawn worker", |
| 31 | cause |
| 32 | }) |
| 33 | |
| 34 | assert.strictEqual(reason.message, "Failed to spawn worker") |
| 35 | assert.strictEqual(reason.cause, cause) |
| 36 | }) |
| 37 | }) |
| 38 | |
| 39 | describe("isWorkerError", () => { |
| 40 | it("matches wrapper only", () => { |
| 41 | const wrapper = new WorkerError({ |
| 42 | reason: new WorkerReceiveError({ |
| 43 | message: "Failed to receive message" |
| 44 | }) |
| 45 | }) |
| 46 | |
| 47 | assert.isTrue(isWorkerError(wrapper)) |
| 48 | assert.isFalse(isWorkerError(new WorkerReceiveError({ message: "Failed to receive message" }))) |
| 49 | assert.isFalse(isWorkerError(new Error("regular error"))) |
| 50 | assert.isFalse(isWorkerError({ _tag: "WorkerError" })) |
| 51 | }) |
| 52 | }) |
| 53 | |
| 54 | describe("schema roundtrip", () => { |
| 55 | it.effect("WorkerSpawnError roundtrip", () => |
| 56 | Effect.gen(function*() { |
| 57 | const reason = new WorkerSpawnError({ |
| 58 | message: "Failed to spawn worker" |
| 59 | }) |
| 60 | const encoded = yield* Schema.encodeEffect(WorkerSpawnError)(reason) |
| 61 | const decoded = yield* Schema.decodeEffect(WorkerSpawnError)(encoded) |
| 62 | assert.strictEqual(decoded._tag, "WorkerSpawnError") |
| 63 | assert.strictEqual(decoded.message, "Failed to spawn worker") |
| 64 | })) |
| 65 | |
| 66 | it.effect("WorkerErrorReason union roundtrip", () => |
| 67 | Effect.gen(function*() { |
| 68 | const reason: WorkerErrorReason = new WorkerUnknownError({ |
| 69 | message: "Worker crashed unexpectedly" |
| 70 | }) |
| 71 | const encoded = yield* Schema.encodeEffect(WorkerErrorReason)(reason) |
| 72 | const decoded = yield* Schema.decodeEffect(WorkerErrorReason)(encoded) |
| 73 | assert.strictEqual(decoded._tag, "WorkerUnknownError") |
| 74 | assert.strictEqual(decoded.message, "Worker crashed unexpectedly") |
| 75 | })) |
| 76 | |
| 77 | it.effect("WorkerError roundtrip", () => |
| 78 | Effect.gen(function*() { |
| 79 | const error = new WorkerError({ |
| 80 | reason: new WorkerSendError({ |
| 81 | message: "Failed to send message" |
| 82 | }) |
| 83 | }) |
| 84 | const encoded = yield* Schema.encodeEffect(WorkerError)(error) |
| 85 | const decoded = yield* Schema.decodeEffect(WorkerError)(encoded) |
| 86 | assert.strictEqual(decoded._tag, "WorkerError") |
| 87 | assert.strictEqual(decoded.reason._tag, "WorkerSendError") |
| 88 | assert.strictEqual(decoded.message, "Failed to send message") |
| 89 | })) |
| 90 | }) |
| 91 | |
| 92 | describe("buffered sends", () => { |
| 93 | it.effect("reports postMessage failures as WorkerSendError", () => |
| 94 | Effect.gen(function*() { |
| 95 | const listening = yield* Deferred.make<void>() |
| 96 | let emit!: (message: Worker.PlatformMessage) => void |
| 97 | const platform = Worker.makePlatform<object>()({ |
| 98 | setup: () => |
| 99 | Effect.succeed({ |
| 100 | postMessage() { |
| 101 | throw new Error("post failed") |
| 102 | } |
| 103 | }), |
| 104 | listen: (options) => |
| 105 | Effect.sync(() => { |
| 106 | emit = options.emit |
| 107 | }).pipe(Effect.andThen(Deferred.succeed(listening, void 0))) |
| 108 | }) |
| 109 | const worker = yield* platform.spawn(0).pipe( |
| 110 | Effect.provideService(Worker.Spawner, () => ({})) |
| 111 | ) |
| 112 | |
| 113 | yield* worker.send("buffered") |
| 114 | const run = yield* Effect.forkChild(worker.run(() => Effect.void)) |
| 115 | yield* Deferred.await(listening) |
| 116 | yield* Effect.sync(() => emit([0])) |
| 117 | const exit = yield* Fiber.await(run) |
| 118 | |
| 119 | assert(Exit.isFailure(exit)) |
| 120 | if (Exit.isFailure(exit)) { |
| 121 | assert.isFalse(Cause.hasDies(exit.cause)) |
| 122 | const error = Cause.squash(exit.cause) |
| 123 | assert.isTrue(isWorkerError(error)) |
| 124 | if (isWorkerError(error)) { |
| 125 | assert.strictEqual(error.reason._tag, "WorkerSendError") |
| 126 | } |
| 127 | } |
| 128 | })) |
| 129 | }) |
| 130 | }) |