Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
363 lines
8.6 KiB
363 lines
8.6 KiB
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{} |
|
}
|
|
|