package clock
import (
"context"
"sort"
"sync"
"time"
)
type Duration = time .Duration
type Clock interface {
After (d time .Duration ) <-chan time .Time
AfterFunc (d time .Duration , f func ()) *Timer
Now () time .Time
Since (t time .Time ) time .Duration
Until (t time .Time ) time .Duration
Sleep (d time .Duration )
Tick (d time .Duration ) <-chan time .Time
Ticker (d time .Duration ) *Ticker
Timer (d time .Duration ) *Timer
WithDeadline (parent context .Context , d time .Time ) (context .Context , context .CancelFunc )
WithTimeout (parent context .Context , t time .Duration ) (context .Context , context .CancelFunc )
}
func New () Clock {
return &clock {}
}
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 ) Since (t time .Time ) time .Duration { return time .Since (t ) }
func (c *clock ) Until (t time .Time ) time .Duration { return time .Until (t ) }
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 }
}
func (c *clock ) WithDeadline (parent context .Context , d time .Time ) (context .Context , context .CancelFunc ) {
return context .WithDeadline (parent , d )
}
func (c *clock ) WithTimeout (parent context .Context , t time .Duration ) (context .Context , context .CancelFunc ) {
return context .WithTimeout (parent , t )
}
type Mock struct {
mu sync .Mutex
now time .Time
timers clockTimers
}
func NewMock () *Mock {
return &Mock {now : time .Unix (0 , 0 )}
}
func (m *Mock ) Add (d time .Duration ) {
m .mu .Lock ()
t := m .now .Add (d )
m .mu .Unlock ()
for {
if !m .runNextTimer (t ) {
break
}
}
m .mu .Lock ()
m .now = t
m .mu .Unlock ()
gosched ()
}
func (m *Mock ) Set (t time .Time ) {
for {
if !m .runNextTimer (t ) {
break
}
}
m .mu .Lock ()
m .now = t
m .mu .Unlock ()
gosched ()
}
func (m *Mock ) WaitForAllTimers () time .Time {
for {
m .mu .Lock ()
if len (m .timers ) == 0 {
m .mu .Unlock ()
return m .Now ()
}
sort .Sort (m .timers )
next := m .timers [len (m .timers )-1 ].Next ()
m .mu .Unlock ()
m .Set (next )
}
}
func (m *Mock ) runNextTimer (max time .Time ) bool {
m .mu .Lock ()
sort .Sort (m .timers )
if len (m .timers ) == 0 {
m .mu .Unlock ()
return false
}
t := m .timers [0 ]
if t .Next ().After (max ) {
m .mu .Unlock ()
return false
}
m .now = t .Next ()
now := m .now
m .mu .Unlock ()
t .Tick (now )
return true
}
func (m *Mock ) After (d time .Duration ) <-chan time .Time {
return m .Timer (d ).C
}
func (m *Mock ) AfterFunc (d time .Duration , f func ()) *Timer {
m .mu .Lock ()
defer m .mu .Unlock ()
ch := make (chan time .Time , 1 )
t := &Timer {
c : ch ,
fn : f ,
mock : m ,
next : m .now .Add (d ),
stopped : false ,
}
m .timers = append (m .timers , (*internalTimer )(t ))
return t
}
func (m *Mock ) Now () time .Time {
m .mu .Lock ()
defer m .mu .Unlock ()
return m .now
}
func (m *Mock ) Since (t time .Time ) time .Duration {
return m .Now ().Sub (t )
}
func (m *Mock ) Until (t time .Time ) time .Duration {
return t .Sub (m .Now ())
}
func (m *Mock ) Sleep (d time .Duration ) {
<-m .After (d )
}
func (m *Mock ) Tick (d time .Duration ) <-chan time .Time {
return m .Ticker (d ).C
}
func (m *Mock ) Ticker (d time .Duration ) *Ticker {
m .mu .Lock ()
defer m .mu .Unlock ()
ch := make (chan time .Time , 1 )
t := &Ticker {
C : ch ,
c : ch ,
mock : m ,
d : d ,
next : m .now .Add (d ),
}
m .timers = append (m .timers , (*internalTicker )(t ))
return t
}
func (m *Mock ) Timer (d time .Duration ) *Timer {
m .mu .Lock ()
ch := make (chan time .Time , 1 )
t := &Timer {
C : ch ,
c : ch ,
mock : m ,
next : m .now .Add (d ),
stopped : false ,
}
m .timers = append (m .timers , (*internalTimer )(t ))
now := m .now
m .mu .Unlock ()
m .runNextTimer (now )
return t
}
func (m *Mock ) removeClockTimer (t clockTimer ) {
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 )
}
type clockTimer interface {
Next() time .Time
Tick(time .Time )
}
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 ()) }
type Timer struct {
C <-chan time .Time
c chan time .Time
timer *time .Timer
next time .Time
mock *Mock
fn func ()
stopped bool
}
func (t *Timer ) Stop () bool {
if t .timer != nil {
return t .timer .Stop ()
}
t .mock .mu .Lock ()
registered := !t .stopped
t .mock .removeClockTimer ((*internalTimer )(t ))
t .stopped = true
t .mock .mu .Unlock ()
return registered
}
func (t *Timer ) Reset (d time .Duration ) bool {
if t .timer != nil {
return t .timer .Reset (d )
}
t .mock .mu .Lock ()
t .next = t .mock .now .Add (d )
defer t .mock .mu .Unlock ()
registered := !t .stopped
if t .stopped {
t .mock .timers = append (t .mock .timers , (*internalTimer )(t ))
}
t .stopped = false
return registered
}
type internalTimer Timer
func (t *internalTimer ) Next () time .Time { return t .next }
func (t *internalTimer ) Tick (now time .Time ) {
defer gosched ()
t .mock .mu .Lock ()
if t .fn != nil {
defer func () { go t .fn () }()
} else {
t .c <- now
}
t .mock .removeClockTimer ((*internalTimer )(t ))
t .stopped = true
t .mock .mu .Unlock ()
}
type Ticker struct {
C <-chan time .Time
c chan time .Time
ticker *time .Ticker
next time .Time
mock *Mock
d time .Duration
stopped bool
}
func (t *Ticker ) Stop () {
if t .ticker != nil {
t .ticker .Stop ()
} else {
t .mock .mu .Lock ()
t .mock .removeClockTimer ((*internalTicker )(t ))
t .stopped = true
t .mock .mu .Unlock ()
}
}
func (t *Ticker ) Reset (dur time .Duration ) {
if t .ticker != nil {
t .ticker .Reset (dur )
return
}
t .mock .mu .Lock ()
defer t .mock .mu .Unlock ()
if t .stopped {
t .mock .timers = append (t .mock .timers , (*internalTicker )(t ))
t .stopped = false
}
t .d = dur
t .next = t .mock .now .Add (dur )
}
type internalTicker Ticker
func (t *internalTicker ) Next () time .Time { return t .next }
func (t *internalTicker ) Tick (now time .Time ) {
select {
case t .c <- now :
default :
}
t .mock .mu .Lock ()
t .next = now .Add (t .d )
t .mock .mu .Unlock ()
gosched ()
}
func gosched() { time .Sleep (1 * time .Millisecond ) }
var (
_ Clock = &Mock {}
)
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .