7024f36a76
This commit adds Clock and DefaultClock and moves the private invoices.testClock under the clock package while adding basic unit tests for it. Clock is an interface currently encapsulating Now() and TickAfter(). It can be added as an external dependency to any class. This way tests can stub out time.Now() or time.After(). The DefaultClock class simply returns the real time.Now() and time.After().
76 lines
1.7 KiB
Go
76 lines
1.7 KiB
Go
package clock
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// TestClock can be used in tests to mock time.
|
|
type TestClock struct {
|
|
currentTime time.Time
|
|
timeChanMap map[time.Time][]chan time.Time
|
|
timeLock sync.Mutex
|
|
}
|
|
|
|
// NewTestClock returns a new test clock.
|
|
func NewTestClock(startTime time.Time) *TestClock {
|
|
return &TestClock{
|
|
currentTime: startTime,
|
|
timeChanMap: make(map[time.Time][]chan time.Time),
|
|
}
|
|
}
|
|
|
|
// Now returns the current (test) time.
|
|
func (c *TestClock) Now() time.Time {
|
|
c.timeLock.Lock()
|
|
defer c.timeLock.Unlock()
|
|
|
|
return c.currentTime
|
|
}
|
|
|
|
// TickAfter returns a channel that will receive a tick after the specified
|
|
// duration has passed passed by the user set test time.
|
|
func (c *TestClock) TickAfter(duration time.Duration) <-chan time.Time {
|
|
c.timeLock.Lock()
|
|
defer c.timeLock.Unlock()
|
|
|
|
triggerTime := c.currentTime.Add(duration)
|
|
ch := make(chan time.Time, 1)
|
|
|
|
// If already expired, tick immediately.
|
|
if !triggerTime.After(c.currentTime) {
|
|
ch <- c.currentTime
|
|
return ch
|
|
}
|
|
|
|
// Otherwise store the channel until the trigger time is there.
|
|
chans := c.timeChanMap[triggerTime]
|
|
chans = append(chans, ch)
|
|
c.timeChanMap[triggerTime] = chans
|
|
|
|
return ch
|
|
}
|
|
|
|
// SetTime sets the (test) time and triggers tick channels when they expire.
|
|
func (c *TestClock) SetTime(now time.Time) {
|
|
c.timeLock.Lock()
|
|
defer c.timeLock.Unlock()
|
|
|
|
c.currentTime = now
|
|
remainingChans := make(map[time.Time][]chan time.Time)
|
|
for triggerTime, chans := range c.timeChanMap {
|
|
// If the trigger time is still in the future, keep this channel
|
|
// in the channel map for later.
|
|
if triggerTime.After(now) {
|
|
remainingChans[triggerTime] = chans
|
|
continue
|
|
}
|
|
|
|
for _, c := range chans {
|
|
c <- now
|
|
}
|
|
}
|
|
|
|
c.timeChanMap = remainingChans
|
|
}
|