1import { assert, describe, it } from "@effect/vitest"
2import { Cause, Deferred, Effect, Exit, Fiber, Schema } from "effect"
3import * as Worker from "effect/workers/Worker"
4import {
5 isWorkerError,
6 WorkerError,
7 WorkerErrorReason,
8 WorkerReceiveError,
9 WorkerSendError,
10 WorkerSpawnError,
11 WorkerUnknownError
12} from "effect/workers/WorkerError"
13
14describe("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})