diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ce212cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ben Johnson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a3f2580 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +clock +===== + +Clock is a small library for mocking time in Go. It provides an interface +around the standard library's [`time`][time] package so that the application +can use the realtime clock while tests can use the mock clock. + +[time]: http://golang.org/pkg/time/ + + +## Usage + +### Realtime Clock + +Your application can maintain a `Clock` variable that will allow realtime and +mock clocks to be interchangable. For example, if you had an `Application` type: + +```go +import "github.com/benbjohnson/clock" + +type Application struct { + Clock clock.Clock +} +``` + +You could initialize it to use the realtime clock like this: + +```go +var app Application +app.Clock = clock.New() +... +``` + +Then all timers and time-related functionality should be performed from the +`Clock` variable. + + +### Mocking time + +In your tests, you will want to use a `Mock` clock: + +```go +import ( + "testing" + + "github.com/benbjohnson/clock" +) + +func TestApplication_DoSomething(t *testing.T) { + mock := clock.NewMock() + app := Application{Clock: mock} + ... +} +``` + +Now that you've initialized your application to use the mock clock, you can +adjust the time programmatically. The mock clock always starts from the Unix +epoch (midnight, Jan 1, 1970 UTC). + + +### Controlling time + +The mock clock provides the same functions that the standard library's `time` +package provides. For example, to find the current time, you use the `Now()` +function: + +```go +mock := clock.NewMock() + +// Find the current time. +mock.Now().UTC() // 1970-01-01 00:00:00 +0000 UTC + +// Move the clock forward. +mock.Add(2 * time.Hour) + +// Check the time again. It's 2 hours later! +mock.Now().UTC() // 1970-01-01 02:00:00 +0000 UTC +``` + +Timers and Tickers are also controlled by this same mock clock. They will only +execute when the clock is moved forward: + +``` +mock := clock.NewMock() +count := 0 + +// Kick off a timer to increment every 1 mock second. +go func() { + ticker := clock.Ticker(1 * time.Second) + for { + <-ticker.C + count++ + } +}() +runtime.Gosched() + +// Move the clock forward 10 second. +mock.Add(10 * time.Second) + +// This prints 10. +fmt.Println(count) +``` + + diff --git a/clock.go b/clock.go new file mode 100644 index 0000000..cc109d4 --- /dev/null +++ b/clock.go @@ -0,0 +1,360 @@ +package clock + +import ( + "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 + Since(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 +} + +// 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) Since(t time.Time) time.Duration { return time.Since(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} +} + +// 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 + gosched func() +} + +type MockOpt struct { + // Gosched is a function which is called to schedule other (app-specific) + // goroutines. Default implementation just sleeps for 1 millisecond. It is + // often not acceptable because it slows down tests significantly and is also + // potentialy fragile. + Gosched func() +} + +// NewMock returns an instance of a mock clock with the default options. +// See also NewMockOpt +func NewMock() *Mock { + return NewMockOpt(MockOpt{}) +} + +// NewMockOpt returns an instance of a mock clock. +// The current time of the mock clock on initialization is the Unix epoch. +// See MockOpt for description of available options. +func NewMockOpt(opt MockOpt) *Mock { + mock := &Mock{ + now: time.Unix(0, 0), + gosched: opt.Gosched, + } + if mock.gosched == nil { + mock.gosched = func() { + time.Sleep(1 * time.Millisecond) + } + } + + return mock +} + +// 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. + m.gosched() +} + +// Set sets the current time of the mock clock to a specific one. +// This should only be called from a single goroutine at a time. +func (m *Mock) Set(t time.Time) { + // 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. + m.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 { + 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 { + 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 { + m.mu.Lock() + defer m.mu.Unlock() + return m.now +} + +// Since returns time since the mock clocks wall time. +func (m *Mock) Since(t time.Time) time.Duration { + return m.Now().Sub(t) +} + +// 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) { + <-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 { + return m.Ticker(d).C +} + +// Ticker creates a new instance of Ticker. +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 +} + +// Timer creates a new instance of Timer. +func (m *Mock) Timer(d time.Duration) *Timer { + m.mu.Lock() + defer m.mu.Unlock() + 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)) + 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) +} + +// 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 + stopped bool // True if stopped, false if running +} + +// Stop turns off the ticker. +func (t *Timer) Stop() bool { + if t.timer != nil { + return t.timer.Stop() + } + + t.mock.mu.Lock() + registered := !t.stopped + t.mock.mu.Unlock() + + t.mock.removeClockTimer((*internalTimer)(t)) + t.mock.mu.Lock() + defer t.mock.mu.Unlock() + t.stopped = true + return registered +} + +// Reset changes the expiry time of the timer +func (t *Timer) Reset(d time.Duration) bool { + if t.timer != nil { + return t.timer.Reset(d) + } + + t.next = t.mock.now.Add(d) + t.mock.mu.Lock() + 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) { + if t.fn != nil { + t.fn() + } else { + t.c <- now + } + t.mock.removeClockTimer((*internalTimer)(t)) + t.mock.mu.Lock() + defer t.mock.mu.Unlock() + + t.stopped = true + t.mock.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: + default: + } + t.next = now.Add(t.d) + t.mock.gosched() +} diff --git a/clock_test.go b/clock_test.go new file mode 100644 index 0000000..322b742 --- /dev/null +++ b/clock_test.go @@ -0,0 +1,599 @@ +package clock + +import ( + "fmt" + "os" + "sync" + "sync/atomic" + "testing" + "time" +) + +// Ensure that the clock's After channel sends at the correct time. +func TestClock_After(t *testing.T) { + var ok bool + go func() { + time.Sleep(10 * time.Millisecond) + ok = true + }() + go func() { + time.Sleep(30 * time.Millisecond) + t.Fatal("too late") + }() + gosched() + + <-New().After(20 * time.Millisecond) + if !ok { + t.Fatal("too early") + } +} + +// Ensure that the clock's AfterFunc executes at the correct time. +func TestClock_AfterFunc(t *testing.T) { + var ok bool + go func() { + time.Sleep(10 * time.Millisecond) + ok = true + }() + go func() { + time.Sleep(30 * time.Millisecond) + t.Fatal("too late") + }() + gosched() + + var wg sync.WaitGroup + wg.Add(1) + New().AfterFunc(20*time.Millisecond, func() { + wg.Done() + }) + wg.Wait() + if !ok { + t.Fatal("too early") + } +} + +// Ensure that the clock's time matches the standary library. +func TestClock_Now(t *testing.T) { + a := time.Now().Round(time.Second) + b := New().Now().Round(time.Second) + if !a.Equal(b) { + t.Errorf("not equal: %s != %s", a, b) + } +} + +// Ensure that the clock sleeps for the appropriate amount of time. +func TestClock_Sleep(t *testing.T) { + var ok bool + go func() { + time.Sleep(10 * time.Millisecond) + ok = true + }() + go func() { + time.Sleep(30 * time.Millisecond) + t.Fatal("too late") + }() + gosched() + + New().Sleep(20 * time.Millisecond) + if !ok { + t.Fatal("too early") + } +} + +// Ensure that the clock ticks correctly. +func TestClock_Tick(t *testing.T) { + var ok bool + go func() { + time.Sleep(10 * time.Millisecond) + ok = true + }() + go func() { + time.Sleep(50 * time.Millisecond) + t.Fatal("too late") + }() + gosched() + + c := New().Tick(20 * time.Millisecond) + <-c + <-c + if !ok { + t.Fatal("too early") + } +} + +// Ensure that the clock's ticker ticks correctly. +func TestClock_Ticker(t *testing.T) { + var ok bool + go func() { + time.Sleep(100 * time.Millisecond) + ok = true + }() + go func() { + time.Sleep(200 * time.Millisecond) + t.Fatal("too late") + }() + gosched() + + ticker := New().Ticker(50 * time.Millisecond) + <-ticker.C + <-ticker.C + if !ok { + t.Fatal("too early") + } +} + +// Ensure that the clock's ticker can stop correctly. +func TestClock_Ticker_Stp(t *testing.T) { + var ok bool + go func() { + time.Sleep(10 * time.Millisecond) + ok = true + }() + gosched() + + ticker := New().Ticker(20 * time.Millisecond) + <-ticker.C + ticker.Stop() + select { + case <-ticker.C: + t.Fatal("unexpected send") + case <-time.After(30 * time.Millisecond): + } +} + +// Ensure that the clock's timer waits correctly. +func TestClock_Timer(t *testing.T) { + var ok bool + go func() { + time.Sleep(10 * time.Millisecond) + ok = true + }() + go func() { + time.Sleep(30 * time.Millisecond) + t.Fatal("too late") + }() + gosched() + + timer := New().Timer(20 * time.Millisecond) + <-timer.C + if !ok { + t.Fatal("too early") + } + + if timer.Stop() { + t.Fatal("timer still running") + } +} + +// Ensure that the clock's timer can be stopped. +func TestClock_Timer_Stop(t *testing.T) { + var ok bool + go func() { + time.Sleep(10 * time.Millisecond) + ok = true + }() + + timer := New().Timer(20 * time.Millisecond) + if !timer.Stop() { + t.Fatal("timer not running") + } + if timer.Stop() { + t.Fatal("timer wasn't cancelled") + } + select { + case <-timer.C: + t.Fatal("unexpected send") + case <-time.After(30 * time.Millisecond): + } +} + +// Ensure that the clock's timer can be reset. +func TestClock_Timer_Reset(t *testing.T) { + var ok bool + go func() { + time.Sleep(20 * time.Millisecond) + ok = true + }() + go func() { + time.Sleep(30 * time.Millisecond) + t.Fatal("too late") + }() + gosched() + + timer := New().Timer(10 * time.Millisecond) + if !timer.Reset(20 * time.Millisecond) { + t.Fatal("timer not running") + } + + <-timer.C + if !ok { + t.Fatal("too early") + } +} + +// Ensure that the mock's After channel sends at the correct time. +func TestMock_After(t *testing.T) { + var ok int32 + clock := NewMock() + + // Create a channel to execute after 10 mock seconds. + ch := clock.After(10 * time.Second) + go func(ch <-chan time.Time) { + <-ch + atomic.StoreInt32(&ok, 1) + }(ch) + + // Move clock forward to just before the time. + clock.Add(9 * time.Second) + if atomic.LoadInt32(&ok) == 1 { + t.Fatal("too early") + } + + // Move clock forward to the after channel's time. + clock.Add(1 * time.Second) + if atomic.LoadInt32(&ok) == 0 { + t.Fatal("too late") + } +} + +// Ensure that the mock's After channel doesn't block on write. +func TestMock_UnusedAfter(t *testing.T) { + mock := NewMock() + mock.After(1 * time.Millisecond) + + done := make(chan bool, 1) + go func() { + mock.Add(1 * time.Second) + done <- true + }() + + select { + case <-done: + case <-time.After(1 * time.Second): + t.Fatal("mock.Add hung") + } +} + +// Ensure that the mock's AfterFunc executes at the correct time. +func TestMock_AfterFunc(t *testing.T) { + var ok int32 + clock := NewMock() + + // Execute function after duration. + clock.AfterFunc(10*time.Second, func() { + atomic.StoreInt32(&ok, 1) + }) + + // Move clock forward to just before the time. + clock.Add(9 * time.Second) + if atomic.LoadInt32(&ok) == 1 { + t.Fatal("too early") + } + + // Move clock forward to the after channel's time. + clock.Add(1 * time.Second) + if atomic.LoadInt32(&ok) == 0 { + t.Fatal("too late") + } +} + +// Ensure that the mock's AfterFunc doesn't execute if stopped. +func TestMock_AfterFunc_Stop(t *testing.T) { + // Execute function after duration. + clock := NewMock() + timer := clock.AfterFunc(10*time.Second, func() { + t.Fatal("unexpected function execution") + }) + gosched() + + // Stop timer & move clock forward. + timer.Stop() + clock.Add(10 * time.Second) + gosched() +} + +// Ensure that the mock's current time can be changed. +func TestMock_Now(t *testing.T) { + clock := NewMock() + if now := clock.Now(); !now.Equal(time.Unix(0, 0)) { + t.Fatalf("expected epoch, got: %v", now) + } + + // Add 10 seconds and check the time. + clock.Add(10 * time.Second) + if now := clock.Now(); !now.Equal(time.Unix(10, 0)) { + t.Fatalf("expected epoch, got: %v", now) + } +} + +func TestMock_Since(t *testing.T) { + clock := NewMock() + + beginning := clock.Now() + clock.Add(500 * time.Second) + if since := clock.Since(beginning); since.Seconds() != 500 { + t.Fatalf("expected 500 since beginning, actually: %v", since.Seconds()) + } +} + +// Ensure that the mock can sleep for the correct time. +func TestMock_Sleep(t *testing.T) { + var ok int32 + clock := NewMock() + + // Create a channel to execute after 10 mock seconds. + go func() { + clock.Sleep(10 * time.Second) + atomic.StoreInt32(&ok, 1) + }() + gosched() + + // Move clock forward to just before the sleep duration. + clock.Add(9 * time.Second) + if atomic.LoadInt32(&ok) == 1 { + t.Fatal("too early") + } + + // Move clock forward to the after the sleep duration. + clock.Add(1 * time.Second) + if atomic.LoadInt32(&ok) == 0 { + t.Fatal("too late") + } +} + +// Ensure that the mock's Tick channel sends at the correct time. +func TestMock_Tick(t *testing.T) { + var n int32 + clock := NewMock() + + // Create a channel to increment every 10 seconds. + go func() { + tick := clock.Tick(10 * time.Second) + for { + <-tick + atomic.AddInt32(&n, 1) + } + }() + gosched() + + // Move clock forward to just before the first tick. + clock.Add(9 * time.Second) + if atomic.LoadInt32(&n) != 0 { + t.Fatalf("expected 0, got %d", n) + } + + // Move clock forward to the start of the first tick. + clock.Add(1 * time.Second) + if atomic.LoadInt32(&n) != 1 { + t.Fatalf("expected 1, got %d", n) + } + + // Move clock forward over several ticks. + clock.Add(30 * time.Second) + if atomic.LoadInt32(&n) != 4 { + t.Fatalf("expected 4, got %d", n) + } +} + +// Ensure that the mock's Ticker channel sends at the correct time. +func TestMock_Ticker(t *testing.T) { + var n int32 + clock := NewMock() + + // Create a channel to increment every microsecond. + go func() { + ticker := clock.Ticker(1 * time.Microsecond) + for { + <-ticker.C + atomic.AddInt32(&n, 1) + } + }() + gosched() + + // Move clock forward. + clock.Add(10 * time.Microsecond) + if atomic.LoadInt32(&n) != 10 { + t.Fatalf("unexpected: %d", n) + } +} + +// Ensure that the mock's Ticker channel won't block if not read from. +func TestMock_Ticker_Overflow(t *testing.T) { + clock := NewMock() + ticker := clock.Ticker(1 * time.Microsecond) + clock.Add(10 * time.Microsecond) + ticker.Stop() +} + +// Ensure that the mock's Ticker can be stopped. +func TestMock_Ticker_Stop(t *testing.T) { + var n int32 + clock := NewMock() + + // Create a channel to increment every second. + ticker := clock.Ticker(1 * time.Second) + go func() { + for { + <-ticker.C + atomic.AddInt32(&n, 1) + } + }() + gosched() + + // Move clock forward. + clock.Add(5 * time.Second) + if atomic.LoadInt32(&n) != 5 { + t.Fatalf("expected 5, got: %d", n) + } + + ticker.Stop() + + // Move clock forward again. + clock.Add(5 * time.Second) + if atomic.LoadInt32(&n) != 5 { + t.Fatalf("still expected 5, got: %d", n) + } +} + +// Ensure that multiple tickers can be used together. +func TestMock_Ticker_Multi(t *testing.T) { + var n int32 + clock := NewMock() + + go func() { + a := clock.Ticker(1 * time.Microsecond) + b := clock.Ticker(3 * time.Microsecond) + + for { + select { + case <-a.C: + atomic.AddInt32(&n, 1) + case <-b.C: + atomic.AddInt32(&n, 100) + } + } + }() + gosched() + + // Move clock forward. + clock.Add(10 * time.Microsecond) + gosched() + if atomic.LoadInt32(&n) != 310 { + t.Fatalf("unexpected: %d", n) + } +} + +func ExampleMock_After() { + // Create a new mock clock. + clock := NewMock() + count := 0 + + ready := make(chan struct{}) + // Create a channel to execute after 10 mock seconds. + go func() { + ch := clock.After(10 * time.Second) + close(ready) + <-ch + count = 100 + }() + <-ready + + // Print the starting value. + fmt.Printf("%s: %d\n", clock.Now().UTC(), count) + + // Move the clock forward 5 seconds and print the value again. + clock.Add(5 * time.Second) + fmt.Printf("%s: %d\n", clock.Now().UTC(), count) + + // Move the clock forward 5 seconds to the tick time and check the value. + clock.Add(5 * time.Second) + fmt.Printf("%s: %d\n", clock.Now().UTC(), count) + + // Output: + // 1970-01-01 00:00:00 +0000 UTC: 0 + // 1970-01-01 00:00:05 +0000 UTC: 0 + // 1970-01-01 00:00:10 +0000 UTC: 100 +} + +func ExampleMock_AfterFunc() { + // Create a new mock clock. + clock := NewMock() + count := 0 + + // Execute a function after 10 mock seconds. + clock.AfterFunc(10*time.Second, func() { + count = 100 + }) + gosched() + + // Print the starting value. + fmt.Printf("%s: %d\n", clock.Now().UTC(), count) + + // Move the clock forward 10 seconds and print the new value. + clock.Add(10 * time.Second) + fmt.Printf("%s: %d\n", clock.Now().UTC(), count) + + // Output: + // 1970-01-01 00:00:00 +0000 UTC: 0 + // 1970-01-01 00:00:10 +0000 UTC: 100 +} + +func ExampleMock_Sleep() { + // Create a new mock clock. + clock := NewMock() + count := 0 + + // Execute a function after 10 mock seconds. + go func() { + clock.Sleep(10 * time.Second) + count = 100 + }() + gosched() + + // Print the starting value. + fmt.Printf("%s: %d\n", clock.Now().UTC(), count) + + // Move the clock forward 10 seconds and print the new value. + clock.Add(10 * time.Second) + fmt.Printf("%s: %d\n", clock.Now().UTC(), count) + + // Output: + // 1970-01-01 00:00:00 +0000 UTC: 0 + // 1970-01-01 00:00:10 +0000 UTC: 100 +} + +func ExampleMock_Ticker() { + // Create a new mock clock. + clock := NewMock() + count := 0 + + ready := make(chan struct{}) + // Increment count every mock second. + go func() { + ticker := clock.Ticker(1 * time.Second) + close(ready) + for { + <-ticker.C + count++ + } + }() + <-ready + + // Move the clock forward 10 seconds and print the new value. + clock.Add(10 * time.Second) + fmt.Printf("Count is %d after 10 seconds\n", count) + + // Move the clock forward 5 more seconds and print the new value. + clock.Add(5 * time.Second) + fmt.Printf("Count is %d after 15 seconds\n", count) + + // Output: + // Count is 10 after 10 seconds + // Count is 15 after 15 seconds +} + +func ExampleMock_Timer() { + // Create a new mock clock. + clock := NewMock() + count := 0 + + ready := make(chan struct{}) + // Increment count after a mock second. + go func() { + timer := clock.Timer(1 * time.Second) + close(ready) + <-timer.C + count++ + }() + <-ready + + // Move the clock forward 10 seconds and print the new value. + clock.Add(10 * time.Second) + fmt.Printf("Count is %d after 10 seconds\n", count) + + // Output: + // Count is 1 after 10 seconds +} + +func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) } +func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9bf6444 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/dimonomid/clock + +go 1.13