1import { assert, describe, it } from "@effect/vitest"
2import * as Duration from "effect/Duration"
3import * as Effect from "effect/Effect"
4import * as Exit from "effect/Exit"
5import * as TestClock from "effect/testing/TestClock"
6
7describe("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})