smartclock

package module
v0.0.0-...-e423391 Latest Latest
Warning

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

Go to latest
Published: Jan 8, 2022 License: MIT Imports: 4 Imported by: 0

README

SmartClock

It is usually hard to test code that relies on real clocks because it hard to control time and how much time is spent in functions or whether two events happening at the same time appears in the same order at every run.

To solve that problem, smartclock allows to drive time from the tests in a deterministic way so that there is no more randomness due to time in your tests. This greatly improves the experience of writting tests because everything is deterministic, meaning reproducible and easily debuggable.

Get Started

Import smartclock into your project

go get github.com/clems4ever/go-smartclock

Wherever you need a clock, you should use the smartclock.Clock interface. That way, the framework is able to smartly injects the smart mocked clock and allow you to drive it from your tests.

Then you can inject a real clock in production code like so

SetupTimerIn(&smartclock.RealClock{}, 20 * time.Minute, doSomething)

And in your tests, just use a mock. Obviously, the test will execute instaneously and have the expected behavior when it comes to the timers being triggered.

clockMock := smartclock.Mock(t, time.Date(2023, 4, 15, 11, 45, 0, 0, time.UTC))

SetupTimerIn(clockMock, 10 * time.Minute, doSomething)

// Move the clock forward 30 minutes, meaning that it will call doSomething after 10 minutes
// and keep moving forward until it reaches the target date.
// If clock.Now() is called in doSomething, it will return 2023-04-15 11:55:00.
clockMock.MoveForward(30 * time.Minutes)

// However, at this stage, i.e., after the clock has been moved forward,
// clockMock.Now() returns 2023-04-15 12:15:00 

For a concrete example, check the examples/ directory.

To leverage the maximum capacity of SmartClock, we advise to avoid the use of the After() function altogether and prefer using AfterFunc() instead because it is way harder to control the occurence of an event with channels than with deterministic function calls which is what smartclock relies on through the use of a priority queue.

How it works

The framework provides a clock interface smartclock.Clock that can be implemented by the real time.Time clock in production code and by a mocked clock that can be manually driven in test code.

This mock clock is essentially a clock holding a queue of timers supposed to trigger in the future. When moving the clock forward, the clock checks whether some timers are supposed to trigger along the way and it triggers them in order as if they were called at the expected time. This means that if you call clock.Now() from within the time handler, it will return the time when the timer is supposed to trigger. If multiple timers are supposed to trigger until the target time, then they are sequentially triggered at their respective time. If multiple timers are a supposed to trigger at the same time, they are triggered sequentially in the order they were started.

License

This library is licensed under the MIT license.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ExampleBaseTime = time.Date(2023, 5, 23, 10, 45, 30, 0, time.UTC)
View Source
var Year = 365 * 24 * time.Hour

Functions

This section is empty.

Types

type Clock

type Clock interface {
	Now() time.Time
	AfterFunc(duration time.Duration, fn func()) Timer
	After(d time.Duration) <-chan time.Time
}

type MockClock

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

func Mock

func Mock(t *testing.T, baseTime time.Time) *MockClock

func (*MockClock) After

func (mc *MockClock) After(d time.Duration) <-chan time.Time

func (*MockClock) AfterFunc

func (mc *MockClock) AfterFunc(duration time.Duration, fn func()) Timer

func (*MockClock) MoveForward

func (mc *MockClock) MoveForward(d time.Duration)

MoveForward moves the clock forward by a duration D.

func (*MockClock) MoveTo

func (mc *MockClock) MoveTo(targetTime time.Time)

func (*MockClock) Now

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

Now returns the current date.

type MockTimer

type MockTimer struct {
	// this ID is used to execute timers with same date in the order they were initially inserted in.
	ID   int
	Date time.Time
	Func func()
	// contains filtered or unexported fields
}

func (*MockTimer) Reset

func (mt *MockTimer) Reset(d time.Duration) bool

func (*MockTimer) Stop

func (mt *MockTimer) Stop() bool

type MockTimerUpdate

type MockTimerUpdate struct {
	Type MockTimerUpdateType
	Date time.Time
}

type MockTimerUpdateType

type MockTimerUpdateType int
const (
	ResetMockTimerUpdateType MockTimerUpdateType = iota
	StopMockTimerUpdateType  MockTimerUpdateType = iota
)

type PriorityQueue

type PriorityQueue []*MockTimer

A PriorityQueue implements heap.Interface and holds Items.

func (PriorityQueue) Len

func (pq PriorityQueue) Len() int

func (PriorityQueue) Less

func (pq PriorityQueue) Less(i, j int) bool

func (*PriorityQueue) Pop

func (pq *PriorityQueue) Pop() any

func (*PriorityQueue) Push

func (pq *PriorityQueue) Push(x any)

func (PriorityQueue) Swap

func (pq PriorityQueue) Swap(i, j int)

type RealClock

type RealClock struct{}

func (*RealClock) After

func (rc *RealClock) After(d time.Duration) <-chan time.Time

func (*RealClock) AfterFunc

func (c *RealClock) AfterFunc(duration time.Duration, fn func()) Timer

func (*RealClock) Now

func (rc *RealClock) Now() time.Time

type Timer

type Timer interface {
	Stop() bool
	Reset(d time.Duration) bool
}

type TimerWrapper

type TimerWrapper struct {
	*time.Timer
}

Jump to

Keyboard shortcuts

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