| /** | |
| * Controllable `Clock` service for tests. | |
| * | |
| * Instead of waiting for real time to pass, effects that use `Effect.sleep`, | |
| * timeouts, schedules, retries, and other time-based operators can be driven by | |
| * advancing the test clock. This makes time-based tests deterministic and fast. | |
| * The module also includes helpers for moving test time, temporarily using the | |
| * live clock, and warning when a test appears to be waiting on time without | |
| * advancing it. | |
| * | |
| * @since 2.0.0 | |
| */ | |
| import * as Arr from "../Array.js"; | |
| import * as Clock from "../Clock.js"; | |
| import * as Data from "../Data.js"; | |
| import * as Duration from "../Duration.js"; | |
| import * as Effect from "../Effect.js"; | |
| import * as Fiber from "../Fiber.js"; | |
| import { flow } from "../Function.js"; | |
| import * as Latch from "../Latch.js"; | |
| import * as Layer from "../Layer.js"; | |
| import * as Order from "../Order.js"; | |
| import * as Semaphore from "../Semaphore.js"; | |
| /** | |
| * The warning message that will be displayed if a test is using time but is | |
| * not advancing the `TestClock`. | |
| */ | |
| const warningMessage = "A test is using time, but is not advancing the test " + "clock, which may result in the test hanging. Use TestClock.adjust to " + "manually advance the time."; | |
| const defaultOptions = { | |
| warningDelay: "1 second" | |
| }; | |
| const SleepOrder = /*#__PURE__*/Order.flip(/*#__PURE__*/Order.Struct({ | |
| timestamp: Order.Number, | |
| sequence: Order.Number | |
| })); | |
| /** | |
| * Creates a `TestClock` with optional configuration. | |
| * | |
| * **Example** (Creating a test clock) | |
| * | |
| * ```ts | |
| * import { Effect } from "effect" | |
| * import { TestClock } from "effect/testing" | |
| * | |
| * const program = Effect.gen(function*() { | |
| * // Create a TestClock with default settings | |
| * const testClock = yield* TestClock.make() | |
| * | |
| * // Create a TestClock with custom warning delay | |
| * const customTestClock = yield* TestClock.make({ | |
| * warningDelay: "10 seconds" | |
| * }) | |
| * | |
| * // Use the TestClock to control time in tests | |
| * yield* testClock.adjust("1 hour") | |
| * const currentTime = testClock.currentTimeMillisUnsafe() | |
| * console.log(currentTime) // Time advanced by 1 hour | |
| * }) | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const make = /*#__PURE__*/Effect.fnUntraced(function* (options) { | |
| const config = Object.assign({}, defaultOptions, options); | |
| let sequence = 0; | |
| const sleeps = []; | |
| const liveClock = yield* Clock.clockWith(Effect.succeed); | |
| const warningSemaphore = yield* Semaphore.make(1); | |
| let currentTimestamp = new Date(0).getTime(); | |
| let warningState = WarningState.Start(); | |
| function currentTimeMillisUnsafe() { | |
| return currentTimestamp; | |
| } | |
| function currentTimeNanosUnsafe() { | |
| return BigInt(Math.floor(currentTimestamp * 1000000)); | |
| } | |
| const currentTimeMillis = Effect.sync(currentTimeMillisUnsafe); | |
| const currentTimeNanos = Effect.sync(currentTimeNanosUnsafe); | |
| function withLive(effect) { | |
| return Effect.provideService(effect, Clock.Clock, liveClock); | |
| } | |
| /** | |
| * Forks a fiber that will display a warning message if a test is using time | |
| * but is not advancing the `TestClock`. | |
| */ | |
| const warningStart = warningSemaphore.withPermits(1)(Effect.suspend(() => { | |
| if (warningState._tag === "Start") { | |
| return Effect.logWarning(warningMessage).pipe(Effect.delay(config.warningDelay), withLive, Effect.forkChild, Effect.interruptible, Effect.flatMap(fiber => Effect.sync(() => { | |
| warningState = WarningState.Pending({ | |
| fiber | |
| }); | |
| }))); | |
| } | |
| return Effect.void; | |
| })); | |
| /** | |
| * Cancels the warning message that is displayed if a test is using time but | |
| * is not advancing the `TestClock`. | |
| */ | |
| const warningDone = warningSemaphore.withPermits(1)(Effect.suspend(() => { | |
| switch (warningState._tag) { | |
| case "Pending": | |
| { | |
| return Fiber.interrupt(warningState.fiber).pipe(Effect.andThen(Effect.sync(() => { | |
| warningState = WarningState.Done(); | |
| }))); | |
| } | |
| case "Start": | |
| case "Done": | |
| { | |
| warningState = WarningState.Done(); | |
| return Effect.void; | |
| } | |
| } | |
| })); | |
| const sleep = Effect.fnUntraced(function* (duration) { | |
| const millis = Duration.toMillis(duration); | |
| const end = currentTimestamp + millis; | |
| if (end <= currentTimestamp) return; | |
| const latch = Latch.makeUnsafe(); | |
| sleeps.push({ | |
| sequence: sequence++, | |
| timestamp: end, | |
| latch | |
| }); | |
| sleeps.sort(SleepOrder); | |
| yield* warningStart; | |
| yield* latch.await; | |
| }); | |
| const runSemaphore = yield* Semaphore.make(1); | |
| const run = Effect.fnUntraced(function* (step) { | |
| yield* Fiber.await(yield* Effect.forkChild(Effect.yieldNow)); | |
| const endTimestamp = step(currentTimestamp); | |
| while (Arr.isArrayNonEmpty(sleeps)) { | |
| if (Arr.lastNonEmpty(sleeps).timestamp > endTimestamp) break; | |
| const entry = sleeps.pop(); | |
| currentTimestamp = entry.timestamp; | |
| entry.latch.openUnsafe(); | |
| yield* Effect.yieldNow; | |
| } | |
| currentTimestamp = endTimestamp; | |
| }, runSemaphore.withPermits(1)); | |
| function adjust(duration) { | |
| const millis = Duration.toMillis(Duration.fromInputUnsafe(duration)); | |
| return warningDone.pipe(Effect.andThen(run(timestamp => timestamp + millis))); | |
| } | |
| function setTime(timestamp) { | |
| return warningDone.pipe(Effect.andThen(run(() => timestamp))); | |
| } | |
| yield* Effect.addFinalizer(() => warningDone); | |
| return { | |
| currentTimeMillisUnsafe, | |
| currentTimeNanosUnsafe, | |
| currentTimeMillis, | |
| currentTimeNanos, | |
| adjust, | |
| setTime, | |
| sleep, | |
| withLive | |
| }; | |
| }); | |
| /** | |
| * Creates a `Layer` which constructs a `TestClock`. | |
| * | |
| * **Example** (Providing a test clock layer) | |
| * | |
| * ```ts | |
| * import { Effect } from "effect" | |
| * import { TestClock } from "effect/testing" | |
| * | |
| * // Create a TestClock layer | |
| * const testClockLayer = TestClock.layer() | |
| * | |
| * // Create a TestClock layer with custom options | |
| * const customTestClockLayer = TestClock.layer({ | |
| * warningDelay: "5 seconds" | |
| * }) | |
| * | |
| * const program = Effect.gen(function*() { | |
| * // Use the layer in your program | |
| * yield* TestClock.adjust("1 hour") | |
| * }).pipe(Effect.provide(testClockLayer)) | |
| * ``` | |
| * | |
| * @category layers | |
| * @since 4.0.0 | |
| */ | |
| export const layer = /*#__PURE__*/flow(make, /*#__PURE__*/Layer.effect(Clock.Clock)); | |
| /** | |
| * Retrieves the `TestClock` service for this test and uses it to run the | |
| * specified workflow. | |
| * | |
| * **Example** (Accessing the test clock) | |
| * | |
| * ```ts | |
| * import { Effect } from "effect" | |
| * import { TestClock } from "effect/testing" | |
| * | |
| * const program = Effect.gen(function*() { | |
| * // Use testClockWith to access the TestClock instance | |
| * const currentTime = yield* TestClock.testClockWith((testClock) => | |
| * Effect.succeed(testClock.currentTimeMillisUnsafe()) | |
| * ) | |
| * | |
| * // Adjust time using the TestClock instance | |
| * yield* TestClock.testClockWith((testClock) => testClock.adjust("2 hours")) | |
| * | |
| * console.log(currentTime) // Initial time | |
| * }) | |
| * ``` | |
| * | |
| * @category testing | |
| * @since 2.0.0 | |
| */ | |
| export const testClockWith = f => Effect.withFiber(fiber => f(fiber.getRef(Clock.Clock))); | |
| /** | |
| * Accesses a `TestClock` instance in the context and increments the time | |
| * by the specified duration, running any actions scheduled for on or before | |
| * the new time in order. | |
| * | |
| * **Example** (Advancing the test clock) | |
| * | |
| * ```ts | |
| * import { Effect } from "effect" | |
| * import { TestClock } from "effect/testing" | |
| * | |
| * const program = Effect.gen(function*() { | |
| * let executed = false | |
| * | |
| * // Fork an effect that sleeps for 30 minutes | |
| * const fiber = yield* Effect.gen(function*() { | |
| * yield* Effect.sleep("30 minutes") | |
| * executed = true | |
| * }).pipe(Effect.forkChild) | |
| * | |
| * // Advance the clock by 30 minutes | |
| * yield* TestClock.adjust("30 minutes") | |
| * | |
| * // The effect should now be executed | |
| * console.log(executed) // true | |
| * }) | |
| * ``` | |
| * | |
| * @category testing | |
| * @since 2.0.0 | |
| */ | |
| export const adjust = duration => testClockWith(testClock => testClock.adjust(duration)); | |
| /** | |
| * Sets the current clock time to the specified `timestamp`. Any effects that | |
| * were scheduled to occur on or before the new time will be run in order. | |
| * | |
| * **Example** (Setting the test clock time) | |
| * | |
| * ```ts | |
| * import { Duration, Effect } from "effect" | |
| * import { TestClock } from "effect/testing" | |
| * | |
| * const program = Effect.gen(function*() { | |
| * let executed = false | |
| * | |
| * // Fork an effect that sleeps for 2 hours | |
| * const fiber = yield* Effect.gen(function*() { | |
| * yield* Effect.sleep("2 hours") | |
| * executed = true | |
| * }).pipe(Effect.forkChild) | |
| * | |
| * // Set the clock to a specific timestamp (2 hours from epoch) | |
| * const targetTime = Duration.toMillis(Duration.hours(2)) | |
| * yield* TestClock.setTime(targetTime) | |
| * | |
| * // The effect should now be executed | |
| * console.log(executed) // true | |
| * }) | |
| * ``` | |
| * | |
| * @category testing | |
| * @since 2.0.0 | |
| */ | |
| export const setTime = timestamp => testClockWith(testClock => testClock.setTime(timestamp)); | |
| /** | |
| * Executes the specified effect with the live `Clock` instead of the | |
| * `TestClock`. | |
| * | |
| * **Example** (Running with the live clock) | |
| * | |
| * ```ts | |
| * import { Clock, Effect } from "effect" | |
| * import { TestClock } from "effect/testing" | |
| * | |
| * const program = Effect.gen(function*() { | |
| * // Get the current test time (starts at epoch) | |
| * const testTime = yield* Clock.currentTimeMillis | |
| * console.log(testTime) // 0 | |
| * | |
| * // Get the actual system time using withLive | |
| * const realTime = yield* TestClock.withLive(Clock.currentTimeMillis) | |
| * console.log(realTime) // Actual system timestamp | |
| * | |
| * // Advance test time | |
| * yield* TestClock.adjust("1 hour") | |
| * | |
| * // Test time is now 1 hour ahead | |
| * const newTestTime = yield* Clock.currentTimeMillis | |
| * console.log(newTestTime) // 3600000 (1 hour in milliseconds) | |
| * }) | |
| * ``` | |
| * | |
| * @category testing | |
| * @since 4.0.0 | |
| */ | |
| export const withLive = effect => testClockWith(testClock => testClock.withLive(effect)); | |
| const WarningState = /*#__PURE__*/Data.taggedEnum(); | |
| //# sourceMappingURL=TestClock.js.map |
Xet Storage Details
- Size:
- 10 kB
- Xet hash:
- 6834d7349f081e8bf63d2d070eace5d9170897af046ff8e81f402c61b3efe68b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.