| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import * as Duration from "effect/Duration" |
| 3 | import * as Effect from "effect/Effect" |
| 4 | import * as Exit from "effect/Exit" |
| 5 | import * as TestClock from "effect/testing/TestClock" |
| 6 | |
| 7 | describe("TestClock", () => { |
| 8 | it.effect("sleep - does not require passage of wall time", () => |
| 9 | Effect.gen(function*() { |
| 10 | let elapsed = false |
| 11 | yield* Effect.sync(() => { |
| 12 | elapsed = true |
| 13 | }).pipe(Effect.delay("10 hours"), Effect.forkChild) |
| 14 | yield* TestClock.adjust("11 hours") |
| 15 | assert.isTrue(elapsed) |
| 16 | })) |
| 17 | |
| 18 | it.effect("sleep - delays effects until time is adjusted", () => |
| 19 | Effect.gen(function*() { |
| 20 | let elapsed = false |
| 21 | const fiber = yield* Effect.sync(() => { |
| 22 | elapsed = true |
| 23 | }).pipe(Effect.delay("10 hours"), Effect.forkChild) |
| 24 | yield* TestClock.adjust("9 hours") |
| 25 | assert.isUndefined(fiber.pollUnsafe()) |
| 26 | yield* TestClock.adjust("11 hours") |
| 27 | assert.deepStrictEqual(fiber.pollUnsafe(), Exit.void) |
| 28 | assert.isTrue(elapsed) |
| 29 | })) |
| 30 | |
| 31 | it.effect("sleep - handles multiple sleeps", () => |
| 32 | Effect.gen(function*() { |
| 33 | let message = "" |
| 34 | yield* Effect.sync(() => { |
| 35 | message += "World!" |
| 36 | }).pipe(Effect.delay("3 hours"), Effect.forkScoped) |
| 37 | yield* Effect.sync(() => { |
| 38 | message += "Hello, " |
| 39 | }).pipe(Effect.delay("1 hour"), Effect.forkScoped) |
| 40 | yield* TestClock.adjust("1 hour") |
| 41 | assert.strictEqual(message, "Hello, ") |
| 42 | yield* TestClock.adjust("4 hours") |
| 43 | assert.strictEqual(message, "Hello, World!") |
| 44 | })) |
| 45 | |
| 46 | it.effect("setTime - sleep correctly handles new set time", () => |
| 47 | Effect.gen(function*() { |
| 48 | let elapsed = false |
| 49 | yield* Effect.sync(() => { |
| 50 | elapsed = true |
| 51 | }).pipe(Effect.delay("10 hours"), Effect.forkChild) |
| 52 | assert.isFalse(elapsed) |
| 53 | yield* TestClock.setTime(Duration.toMillis(Duration.hours(11))) |
| 54 | assert.isTrue(elapsed) |
| 55 | })) |
| 56 | |
| 57 | it.effect("setTime - floors nanoseconds for fractional millisecond instants", () => |
| 58 | Effect.gen(function*() { |
| 59 | const testClock = yield* TestClock.make() |
| 60 | yield* testClock.setTime(199023438.0000004) |
| 61 | assert.strictEqual(testClock.currentTimeNanosUnsafe(), 199023438000000n) |
| 62 | })) |
| 63 | |
| 64 | it.effect("setTime - preserves wall-clock nanoseconds for large timestamps", () => |
| 65 | Effect.gen(function*() { |
| 66 | const testClock = yield* TestClock.make() |
| 67 | const timestamp = 1_000_000_000_001 |
| 68 | yield* testClock.setTime(timestamp) |
| 69 | assert.strictEqual(testClock.currentTimeNanosUnsafe(), BigInt(timestamp) * 1_000_000n) |
| 70 | })) |
| 71 | |
| 72 | it.effect("adjust - advances wall and monotonic time", () => |
| 73 | Effect.gen(function*() { |
| 74 | const testClock = yield* TestClock.make() |
| 75 | yield* testClock.adjust("1 second") |
| 76 | assert.strictEqual(testClock.currentTimeMillisUnsafe(), 1_000) |
| 77 | assert.strictEqual(testClock.monotonicTimeNanosUnsafe(), 1_000_000_000n) |
| 78 | assert.strictEqual(yield* testClock.monotonicTimeNanos, 1_000_000_000n) |
| 79 | })) |
| 80 | |
| 81 | it.effect("adjust - preserves precision for nanosecond durations beyond Number.MAX_SAFE_INTEGER", () => |
| 82 | Effect.gen(function*() { |
| 83 | const testClock = yield* TestClock.make() |
| 84 | const nanos = 999_999_999_999_999_000n |
| 85 | yield* testClock.adjust(Duration.nanos(nanos)) |
| 86 | assert.strictEqual(testClock.monotonicTimeNanosUnsafe(), nanos) |
| 87 | assert.strictEqual(testClock.currentTimeNanosUnsafe(), nanos) |
| 88 | })) |
| 89 | |
| 90 | it.effect("adjust - keeps nanosecond access total after infinite durations", () => |
| 91 | Effect.gen(function*() { |
| 92 | for (const duration of [Duration.infinity, Duration.negativeInfinity]) { |
| 93 | const testClock = yield* TestClock.make() |
| 94 | yield* testClock.adjust(duration) |
| 95 | assert.strictEqual(typeof (yield* testClock.currentTimeNanos), "bigint") |
| 96 | } |
| 97 | })) |
| 98 | |
| 99 | it.effect("setTime - advances monotonic time only when moving forward", () => |
| 100 | Effect.gen(function*() { |
| 101 | const testClock = yield* TestClock.make() |
| 102 | yield* testClock.setTime(2_000) |
| 103 | assert.strictEqual(testClock.monotonicTimeNanosUnsafe(), 2_000_000_000n) |
| 104 | yield* testClock.setTime(500) |
| 105 | assert.strictEqual(testClock.currentTimeMillisUnsafe(), 500) |
| 106 | assert.strictEqual(testClock.monotonicTimeNanosUnsafe(), 2_000_000_000n) |
| 107 | yield* testClock.setTime(1_000) |
| 108 | assert.strictEqual(testClock.monotonicTimeNanosUnsafe(), 2_500_000_000n) |
| 109 | })) |
| 110 | |
| 111 | it.effect("setTime - preserves nanosecond precision for far-future timestamps", () => |
| 112 | Effect.gen(function*() { |
| 113 | const testClock = yield* TestClock.make() |
| 114 | const farFuture = 1_000_000_000_000 |
| 115 | yield* testClock.setTime(farFuture) |
| 116 | const before = testClock.monotonicTimeNanosUnsafe() |
| 117 | yield* testClock.setTime(farFuture + 1) |
| 118 | assert.strictEqual(testClock.monotonicTimeNanosUnsafe() - before, 1_000_000n) |
| 119 | })) |
| 120 | |
| 121 | it.effect("adjust - advances monotonic time to intermediate sleep deadlines", () => |
| 122 | Effect.gen(function*() { |
| 123 | const testClock = yield* TestClock.make() |
| 124 | let observed = 0n |
| 125 | yield* Effect.gen(function*() { |
| 126 | yield* testClock.sleep(Duration.seconds(1)) |
| 127 | observed = yield* testClock.monotonicTimeNanos |
| 128 | }).pipe(Effect.forkChild) |
| 129 | yield* testClock.adjust("2 seconds") |
| 130 | assert.strictEqual(observed, 1_000_000_000n) |
| 131 | assert.strictEqual(testClock.monotonicTimeNanosUnsafe(), 2_000_000_000n) |
| 132 | })) |
| 133 | |
| 134 | // `it.effect` and `it.live` provide an ambient Scope, defeating the #2244 regression guard. |
| 135 | it("layer - can adjust when provided without an ambient Scope", () => |
| 136 | Effect.gen(function*() { |
| 137 | yield* TestClock.adjust("1 second") |
| 138 | }).pipe( |
| 139 | Effect.provide(TestClock.layer({})), |
| 140 | Effect.runPromise |
| 141 | )) |
| 142 | }) |