summaryrefslogtreecommitdiff
path: root/vendor/github.com/benbjohnson/clock
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/benbjohnson/clock')
-rw-r--r--vendor/github.com/benbjohnson/clock/LICENSE21
-rw-r--r--vendor/github.com/benbjohnson/clock/README.md105
-rw-r--r--vendor/github.com/benbjohnson/clock/clock.go422
-rw-r--r--vendor/github.com/benbjohnson/clock/context.go86
4 files changed, 634 insertions, 0 deletions
diff --git a/vendor/github.com/benbjohnson/clock/LICENSE b/vendor/github.com/benbjohnson/clock/LICENSE
new file mode 100644
index 0000000..ce212cb
--- /dev/null
+++ b/vendor/github.com/benbjohnson/clock/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/vendor/github.com/benbjohnson/clock/README.md b/vendor/github.com/benbjohnson/clock/README.md
new file mode 100644
index 0000000..4f1f82f
--- /dev/null
+++ b/vendor/github.com/benbjohnson/clock/README.md
@@ -0,0 +1,105 @@
+clock
+=====
+
+[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/benbjohnson/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.
+
+The module is currently maintained by @djmitche.
+
+[time]: https://pkg.go.dev/github.com/benbjohnson/clock
+
+## Usage
+
+### Realtime Clock
+
+Your application can maintain a `Clock` variable that will allow realtime and
+mock clocks to be interchangeable. 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 UTC on Jan 1, 1970).
+
+
+### 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:
+
+```go
+mock := clock.NewMock()
+count := 0
+
+// Kick off a timer to increment every 1 mock second.
+go func() {
+ ticker := mock.Ticker(1 * time.Second)
+ for {
+ <-ticker.C
+ count++
+ }
+}()
+runtime.Gosched()
+
+// Move the clock forward 10 seconds.
+mock.Add(10 * time.Second)
+
+// This prints 10.
+fmt.Println(count)
+```
diff --git a/vendor/github.com/benbjohnson/clock/clock.go b/vendor/github.com/benbjohnson/clock/clock.go
new file mode 100644
index 0000000..14ddc07
--- /dev/null
+++ b/vendor/github.com/benbjohnson/clock/clock.go
@@ -0,0 +1,422 @@
+package clock
+
+import (
+ "context"
+ "sort"
+ "sync"
+ "time"
+)
+
+// Re-export of time.Duration
+type Duration = time.Duration
+
+// 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 change 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
+ 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)
+}
+
+// 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) 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)
+}
+
+// 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 protects all other fields in this struct, and the data that they
+ // point to.
+ mu sync.Mutex
+
+ now time.Time // current time
+ timers clockTimers // tickers & timers
+}
+
+// NewMock returns an instance of a mock clock.
+// The current time of the mock clock on initialization is the Unix epoch.
+func NewMock() *Mock {
+ return &Mock{now: time.Unix(0, 0)}
+}
+
+// Add moves the current time of the mock clock forward by the specified 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.
+ m.mu.Lock()
+ t := m.now.Add(d)
+ m.mu.Unlock()
+
+ // 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 that other goroutines get handled.
+ 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 that other goroutines get handled.
+ gosched()
+}
+
+// WaitForAllTimers sets the clock until all timers are expired
+func (m *Mock) WaitForAllTimers() time.Time {
+ // Continue to execute timers until there are no more
+ 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)
+ }
+}
+
+// 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
+// its next time is after the max time. Returns true if a timer was 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()
+ now := m.now
+ m.mu.Unlock()
+
+ // Execute timer.
+ t.Tick(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 in its own goroutine.
+// A Timer is returned that can be stopped.
+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
+}
+
+// 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 `t` using the mock clock's wall time.
+func (m *Mock) Since(t time.Time) time.Duration {
+ return m.Now().Sub(t)
+}
+
+// Until returns time until `t` using the mock clock's wall time.
+func (m *Mock) Until(t time.Time) time.Duration {
+ return t.Sub(m.Now())
+}
+
+// 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()
+ 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
+}
+
+// removeClockTimer removes a timer from m.timers. m.mu MUST be held
+// when this method is called.
+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)
+}
+
+// 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.removeClockTimer((*internalTimer)(t))
+ t.stopped = true
+ t.mock.mu.Unlock()
+ 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.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) {
+ // a gosched() after ticking, to allow any consequences of the
+ // tick to complete
+ defer gosched()
+
+ t.mock.mu.Lock()
+ if t.fn != nil {
+ // defer function execution until the lock is released, and
+ defer func() { go t.fn() }()
+ } else {
+ t.c <- now
+ }
+ t.mock.removeClockTimer((*internalTimer)(t))
+ t.stopped = true
+ t.mock.mu.Unlock()
+}
+
+// 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
+ stopped bool // True if stopped, false if running
+}
+
+// Stop turns off the ticker.
+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()
+ }
+}
+
+// Reset resets the ticker to a new duration.
+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()
+}
+
+// Sleep momentarily so that other goroutines can process.
+func gosched() { time.Sleep(1 * time.Millisecond) }
+
+var (
+ // type checking
+ _ Clock = &Mock{}
+)
diff --git a/vendor/github.com/benbjohnson/clock/context.go b/vendor/github.com/benbjohnson/clock/context.go
new file mode 100644
index 0000000..eb67594
--- /dev/null
+++ b/vendor/github.com/benbjohnson/clock/context.go
@@ -0,0 +1,86 @@
+package clock
+
+import (
+ "context"
+ "fmt"
+ "sync"
+ "time"
+)
+
+func (m *Mock) WithTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
+ return m.WithDeadline(parent, m.Now().Add(timeout))
+}
+
+func (m *Mock) WithDeadline(parent context.Context, deadline time.Time) (context.Context, context.CancelFunc) {
+ if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
+ // The current deadline is already sooner than the new one.
+ return context.WithCancel(parent)
+ }
+ ctx := &timerCtx{clock: m, parent: parent, deadline: deadline, done: make(chan struct{})}
+ propagateCancel(parent, ctx)
+ dur := m.Until(deadline)
+ if dur <= 0 {
+ ctx.cancel(context.DeadlineExceeded) // deadline has already passed
+ return ctx, func() {}
+ }
+ ctx.Lock()
+ defer ctx.Unlock()
+ if ctx.err == nil {
+ ctx.timer = m.AfterFunc(dur, func() {
+ ctx.cancel(context.DeadlineExceeded)
+ })
+ }
+ return ctx, func() { ctx.cancel(context.Canceled) }
+}
+
+// propagateCancel arranges for child to be canceled when parent is.
+func propagateCancel(parent context.Context, child *timerCtx) {
+ if parent.Done() == nil {
+ return // parent is never canceled
+ }
+ go func() {
+ select {
+ case <-parent.Done():
+ child.cancel(parent.Err())
+ case <-child.Done():
+ }
+ }()
+}
+
+type timerCtx struct {
+ sync.Mutex
+
+ clock Clock
+ parent context.Context
+ deadline time.Time
+ done chan struct{}
+
+ err error
+ timer *Timer
+}
+
+func (c *timerCtx) cancel(err error) {
+ c.Lock()
+ defer c.Unlock()
+ if c.err != nil {
+ return // already canceled
+ }
+ c.err = err
+ close(c.done)
+ if c.timer != nil {
+ c.timer.Stop()
+ c.timer = nil
+ }
+}
+
+func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { return c.deadline, true }
+
+func (c *timerCtx) Done() <-chan struct{} { return c.done }
+
+func (c *timerCtx) Err() error { return c.err }
+
+func (c *timerCtx) Value(key interface{}) interface{} { return c.parent.Value(key) }
+
+func (c *timerCtx) String() string {
+ return fmt.Sprintf("clock.WithDeadline(%s [%s])", c.deadline, c.deadline.Sub(c.clock.Now()))
+}