A Crystal shard (library) that hijacks Crystal's event loop to control time in specs. Requires Crystal >= 1.19.1 and the -Dexecution_context compile flag.
After editing any .cr file, format it with:
crystal tool format <file>The -Dexecution_context flag is required for all compilation and testing.
crystal spec -Dexecution_context # run all tests
crystal spec spec/time_control_spec.cr -Dexecution_context # run a single spec file
ameba # lint
shards install # install dependenciesTimeControl.control { |controller| ... } enables fake time for the duration of the block. The block receives a Controller object used to call controller.advance(duration).
When enabled:
- All
Crystal::EventLoopsubclasses that definesleepare monkey-patched via a compile-time macro to intercept non-zero sleeps: sleeping fibers are registered in a virtual timer queue insideContextinstead of the real event loop, then suspended viaFiber.suspend. Fiber#timeoutandFiber#cancel_timeoutare monkey-patched to interceptselect ... when timeout(...).Crystal::System::Time.clock_gettimeis monkey-patched to return virtual monotonic time;Crystal::System::Time.compute_utc_seconds_and_nanosecondsis patched to return virtual UTC time.- A
Fiber::ExecutionContext::Isolatedruns a dedicated timer thread. Whenadvance(N)is called, the timer thread processes all virtual timers withwake_at <= virtual_now + Nin order, enqueuing sleeping fibers back into their original execution contexts. - After each batch of woken fibers, the timer thread waits 1ms (real sleep — the timer loop thread is tracked on
Contextand excluded from interception viaTimeControl.when_controlling) to allow chained sleeps to register before rechecking. - If the control block exits with timers still pending,
PendingTimersErroris raised.
TimeControl.control— the main entry pointController#advance(duration)— advances virtual time by a fixed amountController#advance— advances virtual time to the next pending timerTimeControl::Error,TimeControl::PendingTimersError— error classes
Everything else is marked # :nodoc: or private. Do not add doc comments to internal methods, patch methods, or instance variables.