Documentation
¶
Overview ¶
Package clock provides a flexible interface for time-related operations, allowing for consistent time handling in both production and test environments.
The key benefit of this package is that it enables deterministic testing of time-dependent code by providing mock implementations that can be controlled in tests, while using the system clock in production.
The package offers: - A standard interface for getting the current time - A production implementation that uses the system clock - A test implementation that allows precise control over time
Example:
// In production code:
func ProcessWithExpiry(clock clock.Clock, data []Item) []Item {
now := clock.Now()
result := make([]Item, 0)
for _, item := range data {
if item.ExpiresAt.After(now) {
result = append(result, item)
}
}
return result
}
// In production:
processor := &Processor{clock: clock.New()}
// In tests:
func TestProcessWithExpiry(t *testing.T) {
clock := clock.NewTestClock()
// Set a fixed time for deterministic testing
fixedTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)
clock.Set(fixedTime)
// Create test data that expires at different times
data := []Item{
{ID: "1", ExpiresAt: fixedTime.Add(-time.Hour)}, // expired
{ID: "2", ExpiresAt: fixedTime.Add(time.Hour)}, // not expired
}
result := ProcessWithExpiry(clock, data)
// Should only contain the non-expired item
assert.Len(t, result, 1)
assert.Equal(t, "2", result[0].ID)
// Advance time past the expiration of the second item
clock.Tick(2 * time.Hour)
// Now all items should be expired
result = ProcessWithExpiry(clock, data)
assert.Empty(t, result)
}
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CachedClock ¶
type CachedClock struct {
// contains filtered or unexported fields
}
CachedClock implements the Clock interface using a cached atomic value to avoid the overhead of system calls from time.Now().
func NewCachedClock ¶
func NewCachedClock(resolution time.Duration) *CachedClock
NewCachedClock creates a new CachedClock that uses the system time cached every [resolution].
Example:
clock := clock.NewCachedClock(time.Millisecond) currentTime := clock.Now()
func (*CachedClock) Close ¶
func (c *CachedClock) Close()
Close stops the background goroutine that updates the cached time. After calling Close, the clock will continue to return the last cached time but will no longer update. This method should be called to clean up resources when the CachedClock is no longer needed.
func (*CachedClock) NewTicker ¶
func (c *CachedClock) NewTicker(d time.Duration) Ticker
NewTicker delegates to time.NewTicker. Cached time is only an optimization for Now reads; ticker scheduling still runs against the system clock so periodic work fires on real wall-time intervals.
func (*CachedClock) Now ¶
func (c *CachedClock) Now() time.Time
Now returns the current system time. This implementation returns the cached time value.
type Clock ¶
type Clock interface {
// Now returns the current time.
// In production implementations, this returns the system time.
// In test implementations, this returns a controlled time that
// can be manipulated for testing purposes.
Now() time.Time
// NewTicker returns a Ticker that fires every d. Production
// implementations delegate to time.NewTicker. Test implementations
// fire when simulated time advances past the ticker period, so a
// background goroutine driven by NewTicker observes the same notion
// of time as the rest of the test instead of running on real time.
NewTicker(d time.Duration) Ticker
}
Clock is an interface for getting the current time and creating tickers. By abstracting time operations behind this interface, code can be written that works with both the system clock in production and controlled time in tests, enabling deterministic testing of time-dependent logic.
This approach helps avoid flaky tests caused by timing dependencies and allows for simulating time-based scenarios without waiting for real time to pass.
type RealClock ¶
type RealClock struct {
}
RealClock implements the Clock interface using the system clock. This is the implementation that should be used in production code.
func New ¶
func New() *RealClock
New creates a new RealClock that uses the system time. This is the standard clock implementation for production use.
Example:
clock := clock.New() currentTime := clock.Now()
func (*RealClock) NewTicker ¶
NewTicker delegates to time.NewTicker. The returned ticker is a thin adapter around time.Ticker and shares its drop-on-slow-consumer semantics.
type TestClock ¶
type TestClock struct {
// contains filtered or unexported fields
}
TestClock implements the Clock interface with a controlled time value. It allows tests to manually set and advance time to create deterministic test scenarios for time-dependent code.
func NewTestClock ¶
NewTestClock creates a new TestClock instance. If a specific time is provided, the clock will be initialized to that time. Otherwise, it will be initialized to the current system time.
Example:
// Create a test clock with the current time clock := clock.NewTestClock() // Create a test clock with a specific time fixedTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) clock := clock.NewTestClock(fixedTime)
func (*TestClock) NewTicker ¶
NewTicker registers a Ticker driven by simulated time. Each Tick or forward Set call delivers every tick whose scheduled fire time fell within the advance, in order, blocking on the consumer for each delivery. A zero or negative period panics, matching the contract of time.NewTicker.
func (*TestClock) Now ¶
Now returns the current time as maintained by the TestClock. Unlike RealClock, this time is controlled programmatically rather than being tied to the system clock.
func (*TestClock) Set ¶
Set changes the clock to the given time and returns the new time. Forward jumps fire pending Tickers in the same way as TestClock.Tick; backward jumps only adjust the clock value.
Example:
clock := clock.NewTestClock() // Set to a specific date and time newYear := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) clock.Set(newYear)
func (*TestClock) Tick ¶
Tick advances the clock by the given duration and returns the new time. Forward ticks fire any pending Ticker registrations whose period elapsed during the advance, in order. Each fire blocks until the consumer receives the tick from the channel, so by the time Tick returns every due tick has been delivered. The consumer's post-receive work runs asynchronously, so tests asserting on its effects should use a polling helper or a done channel rather than treating Tick's return as a barrier.
Negative or zero durations only update the clock value and do not fire tickers.
Example:
clock := clock.NewTestClock() // Fast-forward 1 hour newTime := clock.Tick(time.Hour) // Fast-forward 30 days newTime = clock.Tick(30 * 24 * time.Hour)
type Ticker ¶
type Ticker interface {
// C returns the channel on which the ticks are delivered. Real
// tickers buffer one tick and drop further ticks while the channel
// is full; test tickers send synchronously, so a slow consumer
// holds back simulated time advance until it catches up.
C() <-chan time.Time
// Stop turns off the ticker. After Stop, no further ticks are
// delivered, but the channel is not closed, matching time.Ticker.
Stop()
}
Ticker delivers ticks on a channel at a regular cadence. It mirrors the surface of time.Ticker needed by callers that work against the Clock interface, so production code can stay agnostic of whether the clock is real or simulated.