glock

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 1, 2023 License: MIT Imports: 3 Imported by: 4

README

Glock

PkgGoDev Build status Latest release

Small go library for mocking parts of the time and context packages.

Time Utilities

The package contains a Clock and Ticker interface that wrap the time.Now, time.After, and time.Sleep functions and the Ticker struct, respectively.

A real clock can be created for general (non-test) use. This implementation simply falls back to the functions provided in the time package.

clock := glock.NewRealClock()
clock.Now()                       // calls time.Now
clock.After(time.Second)          // calls time.After(time.Second)

t := clock.NewTicker(time.Second) // wraps time.NewTicker(time.Second)
t.Chan()                          // returns ticker's C field
t.Stop()                          // stops the ticker

In order to make unit tests that depend on time deterministic (and free of sleep calls), a mock clock can be used in place of the real clock. The mock clock allows you to control the current time with SetCurrent and Advance methods.

clock := glock.NewMockClock()

clock.Now() // returns time of creation
clock.Now() // returns time of creation
clock.SetCurrent(time.Unix(603288000, 0))
clock.Now() // returns Feb 12, 1989
clock.Advance(time.Day)
clock.Now() // returns Feb 13, 1989

The Advance method will also trigger a value on the channels created by the After and Ticker functions, if enough virtual time has elapsed for the events to fire.

clock := glock.NewMockClockAt(time.Unix(603288000, 0))

c1 := clock.After(time.Second)
c2 := clock.After(time.Minute)
clock.GetAfterArgs()            // returns {time.Second, time.Minute}
clock.GetAfterArgs()            // returns {}
clock.Advance(time.Second * 30) // Fires c2
clock.Advance(time.Second * 30) // Fires c1
clock := glock.NewMockClock()

ticker := clock.NewTicker(time.Minute)
defer ticker.Stop()

go func() {
    for range ticker.Chan() {
        // ...
    }
}()

clock.Advance(time.Second * 30)
clock.Advance(time.Second * 30) // Fires ch
clock.Advance(time.Second * 30)
clock.Advance(time.Second * 30) // Fires ch

The Advance method will send a value to any current listener registered to a channel on the clock. Timing these calls in relation with the clock consumer is not always an easy task. A variation of the advance method, BlockingAdvance can be used in its place when you want to first ensure that there is a listener on a channel returned by After.

clock := glock.NewMockClock()

go func() {
    <-clock.After(time.Second * 30)
}()

clock.BlockingAdvance(time.Second * 30) // blocks until the concurrent call to After
clock.BlockingAdvance(time.Second * 30) // blocks indefinitely as there are no listeners

Ticker instances themselves have the same time advancing mechanisms. Using Advance on a ticker (or using Advance on the clock from which a ticker was created) will cause the ticker to fire once and then forward itself to the current time. This mimics the behavior of the Go runtime clock (see the test functions ^TestTickerOffset).

Where the Advance method sends the ticker's time to the consumer in a background goroutine, the BlockingAdvance variant will send the value in the caller's goroutine.

ticker := clock.NewMockTicker(time.Second * 30)
defer ticker.Stop()

go func() {
    <-ticker.Chan()
    <-ticker.Chan()
    <-ticker.Chan()
}()

ticker.BlockingAdvance(time.Second * 15)
ticker.BlockingAdvance(time.Second * 15) // Fires ch
ticker.BlockingAdvance(time.Second * 15)
ticker.BlockingAdvance(time.Second * 15) // Fires ch
ticker.BlockingAdvance(time.Second * 60) // Fires ch _once_

ticker.Advance(time.Second * 30)         // does not block; sent asynchronously
ticker.BlockingAdvance(time.Second * 30) // blocks indefinitely as there are no listeners

Context Utilities

If you'd like to use a context.Context as a way to make a glock Clock available, this package provides WithContext and FromContext utility methods.

To add a Clock to a context you would call WithContext and provide a parent context as well as the Clock you'd like to add.

clock := glock.NewMockClock()

ctx := context.Background()
ctx = glock.WithContext(ctx, clock)

To retrieve the Clock from a context, the FromContext method is available. If a Clock does not already exist within the context FromContext will return a new real clock instance.

// Retrieve a mock clock from the context
clock := glock.NewMockClock()

ctx := context.Background()
ctx = glock.WithContext(ctx, clock)

ctxClock := glock.FromContext(ctx)
// Retrieve a default real clock from the context
ctx := context.Background()
ctx = glock.WithContext(ctx, clock)

ctxClock := glock.FromContext(ctx)

Context Testing Utilities

The package also contains the functions ContextWithDeadline and ContextWithTimeout that mimic the context.WithDeadline and context.WithTimeout functions, but will use a user-provided Clock instance rather than the standard time.After function.

A real clock can be used for non-test scenarios without much additional overhead.

clock := glock.NewRealClock()
ctx, cancel := glock.ContextWithTimeout(context.Background(), clock, time.Second)
defer cancel()

<-ctx.Done() // Waits 1s

In order to make unit tests that depend on context timeouts deterministic, a mock clock can be used in place of the real clock. The mock clock can be advanced in the same was a described in the previous section.

clock := glock.NewMockClock()
ctx, cancel := glock.ContextWithTimeout(context.Background(), clock, time.Second)
defer cancel()

go func() {
    <-time.After(time.Millisecond * 250)
    clock.BlockingAdvance(time.Second)
}()

<-ctx.Done() // Waits around 250ms

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ContextWithDeadline

func ContextWithDeadline(ctx context.Context, clock Clock, deadline time.Time) (context.Context, context.CancelFunc)

ContextWithDeadline mimmics context.WithDeadline, but uses the given clock instance instead of the using standard time.After function directly.

func ContextWithTimeout

func ContextWithTimeout(ctx context.Context, clock Clock, timeout time.Duration) (context.Context, context.CancelFunc)

ContextWithTimeout mimmics context.WithTimeout, but uses the given clock instance instead of the using standard time.After function directly.

func WithContext

func WithContext(ctx context.Context, clock Clock) context.Context

WithContext returns a context derived from the provided context with the provided Clock as a value.

Types

type Advanceable

type Advanceable interface {
	Advance(duration time.Duration)
	BlockingAdvance(duration time.Duration)
	SetCurrent(now time.Time)
}

type Clock

type Clock interface {
	// Now returns the current time.
	Now() time.Time

	// After returns a channel which receives the current time after
	// the given duration elapses.
	After(duration time.Duration) <-chan time.Time

	// Sleep blocks until the given duration elapses.
	Sleep(duration time.Duration)

	// Since returns the time elapsed since t.
	Since(t time.Time) time.Duration

	// Until returns the duration until t.
	Until(t time.Time) time.Duration

	// NewTicker will construct a ticker which will continually fire,
	// pausing for the given duration in between invocations.
	NewTicker(duration time.Duration) Ticker

	// NewTimer will construct a timer which will fire once after the
	// given duration.
	NewTimer(duration time.Duration) Timer

	// AfterFunc waits for the duration to elapse and then calls f in
	// its own goroutine. It returns a Timer that can be used to cancel the call
	// using its Stop method.
	AfterFunc(duration time.Duration, f func()) Timer
}

Clock is a wrapper around common functions in the time package. This interface is designed to allow easy mocking of time functions.

func FromContext

func FromContext(ctx context.Context) Clock

FromContext retrieves the Clock value from the provided context. If a Clock is not set on the context a new real clock will be returned.

func NewRealClock

func NewRealClock() Clock

NewRealClock returns a Clock whose implementation falls back to the methods available in the time package.

type MockClock

type MockClock struct {
	// contains filtered or unexported fields
}

MockClock is an implementation of Clock that can be moved forward in time in increments for testing code that relies on timeouts or other time-sensitive constructs.

func NewMockClock

func NewMockClock() *MockClock

NewMockClock creates a new MockClock with the internal time set to time.Now().

func NewMockClockAt

func NewMockClockAt(now time.Time) *MockClock

NewMockClockAt creates a new MockClick with the internal time set to the given time.

func (MockClock) Advance

func (a MockClock) Advance(duration time.Duration)

Advance will advance the clock's internal time by the given duration.

func (*MockClock) After

func (c *MockClock) After(duration time.Duration) <-chan time.Time

After returns a channel that will be sent the clock's internal time once the clock's internal time is at or past the supplied duration.

func (*MockClock) AfterFunc added in v1.1.0

func (c *MockClock) AfterFunc(duration time.Duration, f func()) Timer

AfterFunc creates a new Timer tied to the internal MockClock time that functions similar to time.AfterFunc().

func (*MockClock) BlockedOnAfter

func (c *MockClock) BlockedOnAfter() int

BlockedOnAfter returns the number of calls to After that are blocked waiting for a call to Advance to trigger them.

func (*MockClock) BlockingAdvance

func (c *MockClock) BlockingAdvance(duration time.Duration)

BlockingAdvance will call Advance but only after there is another goroutine with a reference to a new channel returned by the After method.

func (*MockClock) GetAfterArgs

func (c *MockClock) GetAfterArgs() []time.Duration

GetAfterArgs returns the duration of each call to After in the same order as they were called. The list is cleared each time GetAfterArgs is called.

func (*MockClock) GetTickerArgs

func (c *MockClock) GetTickerArgs() []time.Duration

GetTickerArgs returns the duration of each call to create a new ticker in the same order as they were called. The list is cleared each time GetTickerArgs is called.

func (*MockClock) NewTicker

func (c *MockClock) NewTicker(duration time.Duration) Ticker

NewTicker creates a new Ticker tied to the internal MockClock time that ticks at intervals similar to time.NewTicker(). It will also skip or drop ticks for slow readers similar to time.NewTicker() as well.

func (*MockClock) NewTimer added in v1.1.0

func (c *MockClock) NewTimer(duration time.Duration) Timer

NewTimer creates a new Timer tied to the internal MockClock time that functions similar to time.NewTimer().

func (*MockClock) Now

func (c *MockClock) Now() time.Time

Now returns the clock's internal time.

func (MockClock) SetCurrent

func (a MockClock) SetCurrent(now time.Time)

SetCurrent sets the clock's internal time to the given time.

func (*MockClock) Since

func (c *MockClock) Since(t time.Time) time.Duration

Since returns the time elapsed since t.

func (*MockClock) Sleep

func (c *MockClock) Sleep(duration time.Duration)

Sleep will block until the clock's internal time is at or past the given duration.

func (*MockClock) Until

func (c *MockClock) Until(t time.Time) time.Duration

Until returns the duration until t.

type MockTicker

type MockTicker struct {
	// contains filtered or unexported fields
}

MockTicker is an implementation of Ticker that can be moved forward in time in increments for testing code that relies on timeouts or other time-sensitive constructs.

func NewMockTicker

func NewMockTicker(duration time.Duration) *MockTicker

NewMockTicker creates a new MockTicker with the internal time set to time.Now().

func NewMockTickerAt

func NewMockTickerAt(now time.Time, duration time.Duration) *MockTicker

NewMockTickerAt creates a new MockTicker with the internal time set to the given time.

func (MockTicker) Advance

func (a MockTicker) Advance(duration time.Duration)

Advance will advance the clock's internal time by the given duration.

func (*MockTicker) BlockingAdvance

func (t *MockTicker) BlockingAdvance(duration time.Duration)

BlockingAdvance will bump the ticker's internal time by the given duration. If If the new internal time passes the next tick threshold, a signal will be sent. This method will not return until the signal is read by a consumer of the ticker.

func (*MockTicker) Chan

func (t *MockTicker) Chan() <-chan time.Time

Chan returns a channel which will receive the tickers's internal time at the interval given when creating the ticker.

func (MockTicker) SetCurrent

func (a MockTicker) SetCurrent(now time.Time)

SetCurrent sets the clock's internal time to the given time.

func (*MockTicker) Stop

func (t *MockTicker) Stop()

Stop will stop the ticker from ticking.

type MockTimer added in v1.1.0

type MockTimer struct {
	// contains filtered or unexported fields
}

MockTimer is an implementation of Timer that can be moved forward in time in increments for testing code that relies on timeouts or other time-sensitive constructs.

func NewMockTimer added in v1.1.0

func NewMockTimer(duration time.Duration) *MockTimer

NewMockTimer creates a new MockTimer with the internal time set to time.Now().

func NewMockTimerAt added in v1.1.0

func NewMockTimerAt(now time.Time, duration time.Duration) *MockTimer

NewMockTimerAt creates a new MockTimer with the internal time set to the given time.

func (MockTimer) Advance added in v1.1.0

func (a MockTimer) Advance(duration time.Duration)

Advance will advance the clock's internal time by the given duration.

func (*MockTimer) BlockingAdvance added in v1.1.0

func (t *MockTimer) BlockingAdvance(duration time.Duration)

BlockingAdvance will bump the timer's internal time by the given duration. If the new internal time passes the timer's trigger threshold, a signal will be sent. This method will not return until the signal is read by a consumer of the Timer's channel.

func (*MockTimer) Chan added in v1.1.0

func (t *MockTimer) Chan() <-chan time.Time

Chan returns a channel which will receive the Timer's internal time at the point where the Timer is triggered.

func (*MockTimer) Reset added in v1.1.0

func (t *MockTimer) Reset(duration time.Duration) bool

Reset will reset the deadline of the Timer to trigger after the new duration based on the Timer's internal current time. If the Timer was running when Reset was called it will return true.

func (MockTimer) SetCurrent added in v1.1.0

func (a MockTimer) SetCurrent(now time.Time)

SetCurrent sets the clock's internal time to the given time.

func (*MockTimer) Stop added in v1.1.0

func (t *MockTimer) Stop() bool

Stop will stop the Timer from running.

type Ticker

type Ticker interface {
	// Chan returns the underlying ticker channel.
	Chan() <-chan time.Time

	// Stop stops the ticker.
	Stop()
}

Ticker is a wrapper around a time.Ticker, which allows interface access to the underlying channel (instead of bare access like the time.Ticker struct allows).

func NewRealTicker

func NewRealTicker(duration time.Duration) Ticker

type Timer added in v1.1.0

type Timer interface {
	// Chan returns the underlying timer channel.
	Chan() <-chan time.Time

	// Reset will reset the duration of the timer to the new duration. If
	// the Timer was running when Reset was called it will return true. For
	// more information about when Reset can be called, see the time.Timer
	// documentation.
	Reset(d time.Duration) bool
	// Stop stops the timer. If the timer was running when Stop was
	// called it will return true.
	Stop() bool
}

Timer is a wrapper around a time.Timer, which allows interface access to the underlying Timer.

func NewRealTimer added in v1.1.0

func NewRealTimer(duration time.Duration) Timer

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL