EdgeAIG's picture
download
raw
10 kB
/**
* 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.