1import { assert, describe, it } from "@effect/vitest"
2import { Deferred, Effect, Exit, Fiber } from "effect"
3import * as Worker from "effect/workers/Worker"
4import { isWorkerError, WorkerError, WorkerReceiveError } from "effect/workers/WorkerError"
5
6interface TestPort {
7 readonly postMessage: (message: any, transfers?: any) => void
8}
9
10const testPort: TestPort = { postMessage: () => {} }
11
12const makePlatform = (
13 onListen: (options: {
14 readonly emit: (data: any) => void
15 readonly deferred: Deferred.Deferred<never, WorkerError>
16 }) => void
17) =>
18 Worker.makePlatform<TestPort>()({
19 setup: ({ worker }) => Effect.succeed(worker),
20 listen: ({ deferred, emit }) => Effect.sync(() => onListen({ deferred, emit }))
21 })
22
23const spawnerLayer = Worker.layerSpawner(() => testPort)
24
25const exitError = new WorkerError({
26 reason: new WorkerReceiveError({ message: "The worker has exited with code: 1" })
27})
28
29describe("Worker", () => {
30 it.effect("run fails when the worker exits before the ready message", () =>
31 Effect.gen(function*() {
32 const platform = makePlatform(({ deferred }) => {
33 Deferred.doneUnsafe(deferred, exitError)
34 })
35 const worker = yield* platform.spawn(0)
36 const error = yield* Effect.flip(worker.run(() => Effect.void))
37 assert.isTrue(isWorkerError(error))
38 }).pipe(Effect.provide(spawnerLayer)))
39
40 it.effect("run fails when the worker exits while waiting for ready", () =>
41 Effect.gen(function*() {
42 let deferred: Deferred.Deferred<never, WorkerError> | undefined
43 const platform = makePlatform((options) => {
44 deferred = options.deferred
45 })
46 const worker = yield* platform.spawn(0)
47 const fiber = yield* Effect.forkChild(
48 worker.run(() => Effect.void),
49 { startImmediately: true }
50 )
51 assert.isUndefined(fiber.pollUnsafe())
52
53 Deferred.doneUnsafe(deferred!, exitError)
54 const exit = yield* Fiber.await(fiber)
55 assert.isTrue(Exit.isFailure(exit))
56 }).pipe(Effect.provide(spawnerLayer)))
57
58 it.effect("run is interruptible while waiting for ready", () =>
59 Effect.gen(function*() {
60 const platform = makePlatform(() => {})
61 const worker = yield* platform.spawn(0)
62 const fiber = yield* Effect.forkChild(
63 worker.run(() => Effect.void),
64 { startImmediately: true }
65 )
66 assert.isUndefined(fiber.pollUnsafe())
67
68 yield* Fiber.interrupt(fiber)
69 const exit = yield* Fiber.await(fiber)
70 assert.isTrue(Exit.hasInterrupts(exit))
71 }).pipe(Effect.provide(spawnerLayer)))
72
73 it.effect("run still fails on worker exit after ready", () =>
74 Effect.gen(function*() {
75 let deferred: Deferred.Deferred<never, WorkerError> | undefined
76 const platform = makePlatform((options) => {
77 deferred = options.deferred
78 options.emit([0])
79 })
80 const worker = yield* platform.spawn(0)
81 const fiber = yield* Effect.forkChild(
82 worker.run(() => Effect.void),
83 { startImmediately: true }
84 )
85 assert.isUndefined(fiber.pollUnsafe())
86
87 Deferred.doneUnsafe(deferred!, exitError)
88 const exit = yield* Fiber.await(fiber)
89 assert.isTrue(Exit.isFailure(exit))
90 }).pipe(Effect.provide(spawnerLayer)))
91})