From eeab500de710cfbffdfb478cab8ae78a6e23015d Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 2 Aug 2018 02:14:16 -0700 Subject: [PATCH] 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. --- ticker/mock.go | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 ticker/mock.go diff --git a/ticker/mock.go b/ticker/mock.go new file mode 100644 index 00000000..2fae9e51 --- /dev/null +++ b/ticker/mock.go @@ -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() +}