mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-24 14:11:25 +00:00
364 lines
8.6 KiB
Go
364 lines
8.6 KiB
Go
package clock
|
|
|
|
import (
|
|
"runtime"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Clock represents an interface to the functions in the standard library time
|
|
// package. Two implementations are available in the clock package. The first
|
|
// is a real-time clock which simply wraps the time package's functions. The
|
|
// second is a mock clock which will only make forward progress when
|
|
// programmatically adjusted.
|
|
type Clock interface {
|
|
After(d time.Duration) <-chan time.Time
|
|
AfterFunc(d time.Duration, f func()) *Timer
|
|
Now() time.Time
|
|
Sleep(d time.Duration)
|
|
Tick(d time.Duration) <-chan time.Time
|
|
Ticker(d time.Duration) *Ticker
|
|
Timer(d time.Duration) *Timer
|
|
}
|
|
|
|
// New returns an instance of a real-time clock.
|
|
func New() Clock {
|
|
return &clock{}
|
|
}
|
|
|
|
// clock implements a real-time clock by simply wrapping the time package functions.
|
|
type clock struct{}
|
|
|
|
func (c *clock) After(d time.Duration) <-chan time.Time { return time.After(d) }
|
|
|
|
func (c *clock) AfterFunc(d time.Duration, f func()) *Timer {
|
|
return &Timer{timer: time.AfterFunc(d, f)}
|
|
}
|
|
|
|
func (c *clock) Now() time.Time { return time.Now() }
|
|
|
|
func (c *clock) Sleep(d time.Duration) { time.Sleep(d) }
|
|
|
|
func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) }
|
|
|
|
func (c *clock) Ticker(d time.Duration) *Ticker {
|
|
t := time.NewTicker(d)
|
|
return &Ticker{C: t.C, ticker: t}
|
|
}
|
|
|
|
func (c *clock) Timer(d time.Duration) *Timer {
|
|
t := time.NewTimer(d)
|
|
return &Timer{C: t.C, timer: t}
|
|
}
|
|
|
|
// Mock represents a mock clock that only moves forward programmically.
|
|
// It can be preferable to a real-time clock when testing time-based functionality.
|
|
type Mock struct {
|
|
mu sync.Mutex
|
|
now time.Time // current time
|
|
timers clockTimers // tickers & timers
|
|
|
|
calls Calls
|
|
waiting []waiting
|
|
callsMutex sync.Mutex
|
|
}
|
|
|
|
// NewMock returns an instance of a mock clock.
|
|
// The current time of the mock clock on initialization is the Unix epoch.
|
|
func NewMock() *Mock {
|
|
return &Mock{now: time.Unix(0, 0)}
|
|
}
|
|
|
|
// Add moves the current time of the mock clock forward by the duration.
|
|
// This should only be called from a single goroutine at a time.
|
|
func (m *Mock) Add(d time.Duration) {
|
|
// Calculate the final current time.
|
|
t := m.now.Add(d)
|
|
|
|
// Continue to execute timers until there are no more before the new time.
|
|
for {
|
|
if !m.runNextTimer(t) {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Ensure that we end with the new time.
|
|
m.mu.Lock()
|
|
m.now = t
|
|
m.mu.Unlock()
|
|
|
|
// Give a small buffer to make sure the other goroutines get handled.
|
|
gosched()
|
|
}
|
|
|
|
// runNextTimer executes the next timer in chronological order and moves the
|
|
// current time to the timer's next tick time. The next time is not executed if
|
|
// it's next time if after the max time. Returns true if a timer is executed.
|
|
func (m *Mock) runNextTimer(max time.Time) bool {
|
|
m.mu.Lock()
|
|
|
|
// Sort timers by time.
|
|
sort.Sort(m.timers)
|
|
|
|
// If we have no more timers then exit.
|
|
if len(m.timers) == 0 {
|
|
m.mu.Unlock()
|
|
return false
|
|
}
|
|
|
|
// Retrieve next timer. Exit if next tick is after new time.
|
|
t := m.timers[0]
|
|
if t.Next().After(max) {
|
|
m.mu.Unlock()
|
|
return false
|
|
}
|
|
|
|
// Move "now" forward and unlock clock.
|
|
m.now = t.Next()
|
|
m.mu.Unlock()
|
|
|
|
// Execute timer.
|
|
t.Tick(m.now)
|
|
return true
|
|
}
|
|
|
|
// After waits for the duration to elapse and then sends the current time on the returned channel.
|
|
func (m *Mock) After(d time.Duration) <-chan time.Time {
|
|
defer m.inc(&m.calls.After)
|
|
return m.Timer(d).C
|
|
}
|
|
|
|
// AfterFunc waits for the duration to elapse and then executes a function.
|
|
// A Timer is returned that can be stopped.
|
|
func (m *Mock) AfterFunc(d time.Duration, f func()) *Timer {
|
|
defer m.inc(&m.calls.AfterFunc)
|
|
t := m.Timer(d)
|
|
t.C = nil
|
|
t.fn = f
|
|
return t
|
|
}
|
|
|
|
// Now returns the current wall time on the mock clock.
|
|
func (m *Mock) Now() time.Time {
|
|
defer m.inc(&m.calls.Now)
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.now
|
|
}
|
|
|
|
// Sleep pauses the goroutine for the given duration on the mock clock.
|
|
// The clock must be moved forward in a separate goroutine.
|
|
func (m *Mock) Sleep(d time.Duration) {
|
|
defer m.inc(&m.calls.Sleep)
|
|
<-m.After(d)
|
|
}
|
|
|
|
// Tick is a convenience function for Ticker().
|
|
// It will return a ticker channel that cannot be stopped.
|
|
func (m *Mock) Tick(d time.Duration) <-chan time.Time {
|
|
defer m.inc(&m.calls.Tick)
|
|
return m.Ticker(d).C
|
|
}
|
|
|
|
// Ticker creates a new instance of Ticker.
|
|
func (m *Mock) Ticker(d time.Duration) *Ticker {
|
|
defer m.inc(&m.calls.Ticker)
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
ch := make(chan time.Time)
|
|
t := &Ticker{
|
|
C: ch,
|
|
c: ch,
|
|
mock: m,
|
|
d: d,
|
|
next: m.now.Add(d),
|
|
}
|
|
m.timers = append(m.timers, (*internalTicker)(t))
|
|
return t
|
|
}
|
|
|
|
// Timer creates a new instance of Timer.
|
|
func (m *Mock) Timer(d time.Duration) *Timer {
|
|
defer m.inc(&m.calls.Timer)
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
ch := make(chan time.Time)
|
|
t := &Timer{
|
|
C: ch,
|
|
c: ch,
|
|
mock: m,
|
|
next: m.now.Add(d),
|
|
}
|
|
m.timers = append(m.timers, (*internalTimer)(t))
|
|
return t
|
|
}
|
|
|
|
func (m *Mock) removeClockTimer(t clockTimer) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
for i, timer := range m.timers {
|
|
if timer == t {
|
|
copy(m.timers[i:], m.timers[i+1:])
|
|
m.timers[len(m.timers)-1] = nil
|
|
m.timers = m.timers[:len(m.timers)-1]
|
|
break
|
|
}
|
|
}
|
|
sort.Sort(m.timers)
|
|
}
|
|
|
|
func (m *Mock) inc(addr *uint32) {
|
|
m.callsMutex.Lock()
|
|
defer m.callsMutex.Unlock()
|
|
*addr++
|
|
var newWaiting []waiting
|
|
for _, w := range m.waiting {
|
|
if m.calls.atLeast(w.expected) {
|
|
close(w.done)
|
|
continue
|
|
}
|
|
newWaiting = append(newWaiting, w)
|
|
}
|
|
m.waiting = newWaiting
|
|
}
|
|
|
|
// Wait waits for at least the relevant calls before returning. The expected
|
|
// Calls are always over the lifetime of the Mock. Values in the Calls struct
|
|
// are used as the minimum number of calls, this allows you to wait for only
|
|
// the calls you care about.
|
|
func (m *Mock) Wait(s Calls) {
|
|
m.callsMutex.Lock()
|
|
if m.calls.atLeast(s) {
|
|
m.callsMutex.Unlock()
|
|
return
|
|
}
|
|
done := make(chan struct{})
|
|
m.waiting = append(m.waiting, waiting{expected: s, done: done})
|
|
m.callsMutex.Unlock()
|
|
<-done
|
|
}
|
|
|
|
// clockTimer represents an object with an associated start time.
|
|
type clockTimer interface {
|
|
Next() time.Time
|
|
Tick(time.Time)
|
|
}
|
|
|
|
// clockTimers represents a list of sortable timers.
|
|
type clockTimers []clockTimer
|
|
|
|
func (a clockTimers) Len() int { return len(a) }
|
|
func (a clockTimers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next()) }
|
|
|
|
// Timer represents a single event.
|
|
// The current time will be sent on C, unless the timer was created by AfterFunc.
|
|
type Timer struct {
|
|
C <-chan time.Time
|
|
c chan time.Time
|
|
timer *time.Timer // realtime impl, if set
|
|
next time.Time // next tick time
|
|
mock *Mock // mock clock, if set
|
|
fn func() // AfterFunc function, if set
|
|
}
|
|
|
|
// Stop turns off the ticker.
|
|
func (t *Timer) Stop() {
|
|
if t.timer != nil {
|
|
t.timer.Stop()
|
|
} else {
|
|
t.mock.removeClockTimer((*internalTimer)(t))
|
|
}
|
|
}
|
|
|
|
type internalTimer Timer
|
|
|
|
func (t *internalTimer) Next() time.Time { return t.next }
|
|
func (t *internalTimer) Tick(now time.Time) {
|
|
if t.fn != nil {
|
|
t.fn()
|
|
} else {
|
|
t.c <- now
|
|
}
|
|
t.mock.removeClockTimer((*internalTimer)(t))
|
|
gosched()
|
|
}
|
|
|
|
// Ticker holds a channel that receives "ticks" at regular intervals.
|
|
type Ticker struct {
|
|
C <-chan time.Time
|
|
c chan time.Time
|
|
ticker *time.Ticker // realtime impl, if set
|
|
next time.Time // next tick time
|
|
mock *Mock // mock clock, if set
|
|
d time.Duration // time between ticks
|
|
}
|
|
|
|
// Stop turns off the ticker.
|
|
func (t *Ticker) Stop() {
|
|
if t.ticker != nil {
|
|
t.ticker.Stop()
|
|
} else {
|
|
t.mock.removeClockTimer((*internalTicker)(t))
|
|
}
|
|
}
|
|
|
|
type internalTicker Ticker
|
|
|
|
func (t *internalTicker) Next() time.Time { return t.next }
|
|
func (t *internalTicker) Tick(now time.Time) {
|
|
select {
|
|
case t.c <- now:
|
|
case <-time.After(1 * time.Millisecond):
|
|
}
|
|
t.next = now.Add(t.d)
|
|
gosched()
|
|
}
|
|
|
|
// Sleep momentarily so that other goroutines can process.
|
|
func gosched() { runtime.Gosched() }
|
|
|
|
// Calls keeps track of the count of calls for each of the methods on the Clock
|
|
// interface.
|
|
type Calls struct {
|
|
After uint32
|
|
AfterFunc uint32
|
|
Now uint32
|
|
Sleep uint32
|
|
Tick uint32
|
|
Ticker uint32
|
|
Timer uint32
|
|
}
|
|
|
|
// atLeast returns true if at least the number of calls in o have been made.
|
|
func (c Calls) atLeast(o Calls) bool {
|
|
if c.After < o.After {
|
|
return false
|
|
}
|
|
if c.AfterFunc < o.AfterFunc {
|
|
return false
|
|
}
|
|
if c.Now < o.Now {
|
|
return false
|
|
}
|
|
if c.Sleep < o.Sleep {
|
|
return false
|
|
}
|
|
if c.Tick < o.Tick {
|
|
return false
|
|
}
|
|
if c.Ticker < o.Ticker {
|
|
return false
|
|
}
|
|
if c.Timer < o.Timer {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type waiting struct {
|
|
expected Calls
|
|
done chan struct{}
|
|
}
|