ticker/mock: adds debug Mock Ticker

This commit adds a Mock Ticker, implementing the
Ticker interface. The ticker abides by the
behavioral requirements of the production
implementation, but also includes the ability to
force feed ticks while the Ticker is paused.

This commit also places the mock.go file behind
the debug build flag. This allows us to import
the MockTicker into our unit tests.
This commit is contained in:
Conner Fromknecht 2018-08-02 02:14:16 -07:00
parent f8fe26fb06
commit eeab500de7
No known key found for this signature in database
GPG Key ID: E7D737B67FA592C7

104
ticker/mock.go Normal file

@ -0,0 +1,104 @@
// +build debug
package ticker
import (
"sync"
"sync/atomic"
"time"
)
// Mock implements the Ticker interface, and provides a method of
// force-feeding ticks, even while paused.
type Mock struct {
isActive uint32 // used atomically
// Force is used to force-feed a ticks into the ticker. Useful for
// debugging when trying to wake an event.
Force chan time.Time
ticker <-chan time.Time
skip chan struct{}
wg sync.WaitGroup
quit chan struct{}
}
// MockNew returns a Mock Ticker, used for testing and debugging. It supports
// the ability to force-feed events that get output by the
func MockNew(interval time.Duration) *Mock {
m := &Mock{
ticker: time.NewTicker(interval).C,
Force: make(chan time.Time),
skip: make(chan struct{}),
quit: make(chan struct{}),
}
// Proxy the real ticks to our Force channel if we are active.
m.wg.Add(1)
go func() {
defer m.wg.Done()
for {
select {
case t := <-m.ticker:
if atomic.LoadUint32(&m.isActive) == 0 {
continue
}
select {
case m.Force <- t:
case <-m.skip:
case <-m.quit:
return
}
case <-m.quit:
return
}
}
}()
return m
}
// Ticks returns a receive-only channel that delivers times at the ticker's
// prescribed interval when active. Force-fed ticks can be delivered at any
// time.
//
// NOTE: Part of the Ticker interface.
func (m *Mock) Ticks() <-chan time.Time {
return m.Force
}
// Resumes starts underlying time.Ticker and causes the ticker to begin
// delivering scheduled events.
//
// NOTE: Part of the Ticker interface.
func (m *Mock) Resume() {
atomic.StoreUint32(&m.isActive, 1)
}
// Pause suspends the underlying ticker, such that Ticks() stops signaling at
// regular intervals.
//
// NOTE: Part of the Ticker interface.
func (m *Mock) Pause() {
atomic.StoreUint32(&m.isActive, 0)
// If the ticker fired and read isActive as true, it may still send the
// tick. We'll try to send on the skip channel to drop it.
select {
case m.skip <- struct{}{}:
default:
}
}
// Stop suspends the underlying ticker, such that Ticks() stops signaling at
// regular intervals, and permanently frees up any resources.
//
// NOTE: Part of the Ticker interface.
func (m *Mock) Stop() {
m.Pause()
close(m.quit)
m.wg.Wait()
}