| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { assertFalse, assertTrue, strictEqual } from "@effect/vitest/utils" |
| 3 | import { Array, Deferred, Effect, Exit, Fiber, FiberSet, pipe, Ref, Scope } from "effect" |
| 4 | import { TestClock } from "effect/testing" |
| 5 | |
| 6 | describe("FiberSet", () => { |
| 7 | it.effect("run defers startup", () => |
| 8 | Effect.gen(function*() { |
| 9 | const container = yield* FiberSet.make() |
| 10 | let started = false |
| 11 | const fiber = yield* FiberSet.run( |
| 12 | container, |
| 13 | Effect.sync(() => { |
| 14 | started = true |
| 15 | }), |
| 16 | { startImmediately: false } |
| 17 | ) |
| 18 | assert.isFalse(started) |
| 19 | yield* Fiber.join(fiber) |
| 20 | assert.isTrue(started) |
| 21 | })) |
| 22 | |
| 23 | it.effect("identifies FiberSet in JSON", () => |
| 24 | Effect.gen(function*() { |
| 25 | const set = yield* FiberSet.make() |
| 26 | strictEqual((set.toJSON() as { readonly _id: string })._id, "FiberSet") |
| 27 | })) |
| 28 | |
| 29 | it.effect("interrupts running fibers when the scope closes", () => |
| 30 | Effect.gen(function*() { |
| 31 | const ref = yield* Ref.make(0) |
| 32 | yield* Effect.scoped( |
| 33 | Effect.gen(function*() { |
| 34 | const set = yield* FiberSet.make() |
| 35 | yield* Effect.onInterrupt( |
| 36 | Effect.never, |
| 37 | () => Ref.update(ref, (n) => n + 1) |
| 38 | ).pipe( |
| 39 | FiberSet.run(set), |
| 40 | Effect.repeat({ times: 9 }) |
| 41 | ) |
| 42 | |
| 43 | yield* Effect.yieldNow |
| 44 | }) |
| 45 | ) |
| 46 | |
| 47 | strictEqual(yield* Ref.get(ref), 10) |
| 48 | })) |
| 49 | |
| 50 | it.effect("runtime", () => |
| 51 | Effect.gen(function*() { |
| 52 | const ref = yield* Ref.make(0) |
| 53 | yield* pipe( |
| 54 | Effect.gen(function*() { |
| 55 | const set = yield* FiberSet.make() |
| 56 | const run = yield* FiberSet.runtime(set)<never>() |
| 57 | Array.range(1, 10).forEach(() => |
| 58 | run( |
| 59 | Effect.onInterrupt( |
| 60 | Effect.never, |
| 61 | () => Ref.update(ref, (n) => n + 1) |
| 62 | ) |
| 63 | ) |
| 64 | ) |
| 65 | yield* Effect.yieldNow |
| 66 | }), |
| 67 | Effect.scoped |
| 68 | ) |
| 69 | |
| 70 | strictEqual(yield* Ref.get(ref), 10) |
| 71 | })) |
| 72 | |
| 73 | it.effect("runs fibers concurrently and awaitEmpty waits for completion", () => |
| 74 | Effect.gen(function*() { |
| 75 | const set = yield* FiberSet.make() |
| 76 | FiberSet.addUnsafe(set, Effect.runFork(Effect.void)) |
| 77 | FiberSet.addUnsafe(set, Effect.runFork(Effect.void)) |
| 78 | FiberSet.addUnsafe(set, Effect.runFork(Effect.fail("fail"))) |
| 79 | const result = yield* pipe(FiberSet.join(set), Effect.flip) |
| 80 | strictEqual(result, "fail") |
| 81 | })) |
| 82 | |
| 83 | it.effect("size", () => |
| 84 | Effect.gen(function*() { |
| 85 | const scope = yield* Scope.make() |
| 86 | const set = yield* pipe(FiberSet.make(), Scope.provide(scope)) |
| 87 | FiberSet.addUnsafe(set, Effect.runFork(Effect.never)) |
| 88 | FiberSet.addUnsafe(set, Effect.runFork(Effect.never)) |
| 89 | strictEqual(yield* FiberSet.size(set), 2) |
| 90 | yield* Scope.close(scope, Exit.void) |
| 91 | strictEqual(yield* FiberSet.size(set), 0) |
| 92 | })) |
| 93 | |
| 94 | it.effect("propagateInterruption false ignores external interruption", () => |
| 95 | Effect.gen(function*() { |
| 96 | const set = yield* FiberSet.make() |
| 97 | const fiber = yield* FiberSet.run(set, Effect.never, { |
| 98 | propagateInterruption: false |
| 99 | }) |
| 100 | yield* Effect.yieldNow |
| 101 | yield* Fiber.interrupt(fiber) |
| 102 | assertFalse(yield* Deferred.isDone(set.deferred)) |
| 103 | })) |
| 104 | |
| 105 | it.effect("propagateInterruption true fails join on external interruption", () => |
| 106 | Effect.gen(function*() { |
| 107 | const set = yield* FiberSet.make() |
| 108 | const fiber = yield* FiberSet.run(set, Effect.never, { |
| 109 | propagateInterruption: true |
| 110 | }) |
| 111 | yield* Effect.yieldNow |
| 112 | yield* Fiber.interrupt(fiber) |
| 113 | assertTrue(Exit.hasInterrupts( |
| 114 | yield* FiberSet.join(set).pipe( |
| 115 | Effect.exit |
| 116 | ) |
| 117 | )) |
| 118 | })) |
| 119 | |
| 120 | it.effect("awaitEmpty", () => |
| 121 | Effect.gen(function*() { |
| 122 | const set = yield* FiberSet.make() |
| 123 | yield* FiberSet.run(set, Effect.sleep(1000)) |
| 124 | yield* FiberSet.run(set, Effect.sleep(1000)) |
| 125 | yield* FiberSet.run(set, Effect.sleep(1000)) |
| 126 | yield* FiberSet.run(set, Effect.sleep(1000)) |
| 127 | |
| 128 | const fiber = yield* Effect.forkChild(FiberSet.awaitEmpty(set)) |
| 129 | yield* TestClock.adjust(500) |
| 130 | assert.isUndefined(fiber.pollUnsafe()) |
| 131 | yield* TestClock.adjust(500) |
| 132 | assert.isDefined(fiber.pollUnsafe()) |
| 133 | })) |
| 134 | |
| 135 | it.effect("makeRuntimePromise", () => |
| 136 | Effect.gen(function*() { |
| 137 | const run = yield* FiberSet.makeRuntimePromise() |
| 138 | const result = yield* Effect.promise(() => run(Effect.succeed("done"))) |
| 139 | strictEqual(result, "done") |
| 140 | })) |
| 141 | }) |