Documentation
¶
Overview ¶
Package timecop is a pure-Go (CGO-free) reimplementation of the deterministic core of Ruby's timecop gem — a controllable clock that lets code "freeze", "travel" through, and "scale" the passage of time, with block-scoped nesting and a return-to-baseline mechanism. It reproduces the gem's TimeStackItem mock-time formulas and its stack semantics exactly, without any Ruby runtime.
It is the timecop engine for [go-embedded-ruby](https://github.com/go-embedded-ruby/ruby), but a standalone, reusable module: a host (rbgo) binds Ruby's Timecop module methods (Timecop.freeze/travel/scale/return/return_to_baseline plus their block forms) to a Clock and routes Time.now / Date.today / DateTime.now through Clock.Current.
The real-clock seam ¶
timecop mocks time relative to the real system clock: a travelled or scaled clock keeps advancing as wall-clock time passes. To stay deterministic and testable, the "real now" is an injectable seam — the Clock.Now field. When it is nil the clock reads time.Now; a test (or a host) sets it to a controlled function so no reading of the wall clock ever leaks in. This mirrors the gem's Time.now_without_mock_time.
Mock-time formulas ¶
A Clock holds a stack of mock-time frames; Clock.Current reports the time according to the top frame (or the real clock when the stack is empty). Each frame captures the real "now" at the moment it was pushed (timeWas) and a target time, and reports:
- freeze: the fixed target — time does not move.
- travel: realNow + (target - timeWas) — offset that keeps advancing at 1×.
- scale: target + (realNow - timeWas) * factor — advances at factor×.
Flow ¶
c := timecop.New()
c.Freeze(t) // Time.now == t until Return
c.Travel(t) // jump to t, then keep ticking
c.Scale(4, t) // from t, time runs 4× real
c.WithFrozen(t, func() { // block scope: pushed, then popped
// ... time frozen at t here ...
}) // popped even if fn panics
c.Return() // unwind everything, back to real time
Baseline ¶
Clock.SetBaseline records a baseline time (pushing a travel frame at it); Clock.ReturnToBaseline unwinds every nested frame back down to that baseline, mirroring Timecop.return_to_baseline.
Index ¶
- func Baseline() (time.Time, bool)
- func Freeze(t time.Time) time.Time
- func Frozen() bool
- func Mocked() bool
- func Now() time.Time
- func Return()
- func ReturnToBaseline() time.Time
- func Scale(factor float64, t time.Time) time.Time
- func Scaled() bool
- func SetBaseline(t time.Time)
- func SetNow(now func() time.Time)
- func Travel(t time.Time) time.Time
- func Travelled() bool
- func WithFrozen(t time.Time, fn func())
- func WithReturn(fn func())
- func WithScale(factor float64, t time.Time, fn func())
- func WithTravel(t time.Time, fn func())
- type Clock
- func (c *Clock) Baseline() (time.Time, bool)
- func (c *Clock) Current() time.Time
- func (c *Clock) Depth() int
- func (c *Clock) Freeze(t time.Time) time.Time
- func (c *Clock) Frozen() bool
- func (c *Clock) Mocked() bool
- func (c *Clock) Return()
- func (c *Clock) ReturnToBaseline() time.Time
- func (c *Clock) Scale(factor float64, t time.Time) time.Time
- func (c *Clock) Scaled() bool
- func (c *Clock) ScalingFactor() (float64, bool)
- func (c *Clock) SetBaseline(t time.Time)
- func (c *Clock) Travel(t time.Time) time.Time
- func (c *Clock) Travelled() bool
- func (c *Clock) WithFrozen(t time.Time, fn func())
- func (c *Clock) WithReturn(fn func())
- func (c *Clock) WithScale(factor float64, t time.Time, fn func())
- func (c *Clock) WithTravel(t time.Time, fn func())
- type Mode
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Frozen ¶
func Frozen() bool
Frozen reports whether the default clock is frozen. Mirrors Timecop.frozen?.
func Now ¶
Now reports the default clock's current mock time — the analogue of Ruby's mocked Time.now.
func ReturnToBaseline ¶
ReturnToBaseline unwinds the default clock to its baseline. Mirrors Timecop.return_to_baseline.
func Scaled ¶
func Scaled() bool
Scaled reports whether the default clock is scaled. Mirrors Timecop.scaled?.
func SetBaseline ¶
SetBaseline sets the default clock's baseline. Mirrors Timecop's `baseline=`.
func SetNow ¶
SetNow installs now as the real-clock seam of the default clock. Passing nil restores the real system clock (time.Now). Tests use this to make the default clock deterministic.
func Travelled ¶
func Travelled() bool
Travelled reports whether the default clock is travelled. Mirrors Timecop.travelled?.
func WithFrozen ¶
WithFrozen runs fn with the default clock frozen at t. Mirrors Timecop.freeze(t) { }.
func WithReturn ¶
func WithReturn(fn func())
WithReturn runs fn with the default clock reverted to real time, then restores it. Mirrors Timecop.return { }.
func WithScale ¶
WithScale runs fn with the default clock scaled by factor from t. Mirrors Timecop.scale(factor, t) { }.
func WithTravel ¶
WithTravel runs fn with the default clock travelled to t. Mirrors Timecop.travel(t) { }.
Types ¶
type Clock ¶
type Clock struct {
// Now is the real-clock seam. When nil the clock reads [time.Now]; set it
// to a deterministic function in tests or to a host-provided clock. It is
// the analogue of timecop's Time.now_without_mock_time.
Now func() time.Time
// contains filtered or unexported fields
}
Clock is a controllable clock: the pure-Go engine behind Ruby's Timecop module. It maintains a stack of mock-time frames (freeze/travel/scale) plus an optional baseline, and reports the current mock time through Clock.Current.
A Clock is not safe for concurrent use; guard it externally if shared. This matches the gem, whose per-thread stacks the host manages.
func Default ¶
func Default() *Clock
Default returns the process-wide default Clock backing the package-level functions.
func NewWith ¶
NewWith returns a Clock whose real-clock seam is now. A nil now falls back to time.Now at read time.
func (*Clock) Current ¶
Current reports the current mock time: the top stack frame's time, or the real now when the stack is empty. It is the analogue of Ruby's mocked Time.now.
func (*Clock) Freeze ¶
Freeze pushes a freeze frame pinning time at t and returns the resulting current mock time (which equals t). Mirrors Timecop.freeze(t) without a block.
func (*Clock) Return ¶
func (c *Clock) Return()
Return unwinds all mock-time frames and clears any baseline, reverting to the real clock. Mirrors Timecop.return / Timecop.unfreeze without a block.
func (*Clock) ReturnToBaseline ¶
ReturnToBaseline unwinds every nested frame back down to the baseline frame (keeping only the bottom-most frame), or reverts entirely to the real clock when no baseline is set. It returns the resulting current mock time. Mirrors Timecop.return_to_baseline.
func (*Clock) Scale ¶
Scale pushes a scale frame: time jumps to t and then advances at factor× the real rate. Returns the resulting current mock time (≈ t). Mirrors Timecop.scale(factor, t) without a block.
func (*Clock) ScalingFactor ¶
ScalingFactor returns the top frame's scaling factor and true when the top frame is a scale, or 0 and false otherwise. Mirrors Timecop.scaling_factor.
func (*Clock) SetBaseline ¶
SetBaseline records t as the baseline and pushes a travel frame at it, so the clock travels to the baseline. Clock.ReturnToBaseline later unwinds back to this frame. Mirrors Timecop's `baseline=`.
func (*Clock) Travel ¶
Travel pushes a travel frame jumping time to t; time then keeps advancing at the real rate. Returns the resulting current mock time (≈ t). Mirrors Timecop.travel(t) without a block.
func (*Clock) Travelled ¶
Travelled reports whether the top frame is a travel. Mirrors Timecop.travelled?.
func (*Clock) WithFrozen ¶
WithFrozen runs fn with time frozen at t, then pops the frame (even on panic). Mirrors Timecop.freeze(t) { ... }.
func (*Clock) WithReturn ¶
func (c *Clock) WithReturn(fn func())
WithReturn reverts to the real clock for the duration of fn, then restores the previous stack and baseline — even if fn panics. Mirrors Timecop.return { ... }.
func (*Clock) WithScale ¶
WithScale runs fn with time scaled by factor from t, then pops the frame (even on panic). Mirrors Timecop.scale(factor, t) { ... }.
func (*Clock) WithTravel ¶
WithTravel runs fn with time travelled to t, then pops the frame (even on panic). Mirrors Timecop.travel(t) { ... }.
type Mode ¶
type Mode int
Mode is the kind of mock-time frame on a Clock's stack — the Go analogue of timecop's TimeStackItem mock_type (:freeze, :travel, :scale).
const ( // ModeFreeze pins time at a fixed instant; it does not advance. ModeFreeze Mode = iota // ModeTravel jumps time to an instant, after which it keeps advancing at // real (1×) rate. ModeTravel // ModeScale jumps time to an instant, after which it advances at a // configurable factor of the real rate. ModeScale )
