49 lines
1.4 KiB
Go
49 lines
1.4 KiB
Go
|
package testingx
|
||
|
|
||
|
import (
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// TimeDeterministic implements time.Now in a deterministic fashion
|
||
|
// such that every time.Time call returns a moment in time that occurs
|
||
|
// one second after the configured zeroTime.
|
||
|
//
|
||
|
// It's safe to use this struct from multiple goroutine contexts.
|
||
|
type TimeDeterministic struct {
|
||
|
// counter counts the number of "ticks" passed since the zero time: each
|
||
|
// call to Now increments this counter by one second.
|
||
|
counter time.Duration
|
||
|
|
||
|
// mu protects fields in this structure from concurrent access.
|
||
|
mu sync.Mutex
|
||
|
|
||
|
// zeroTime is the lazy-initialized zero time. The first call to Now
|
||
|
// will initialize this field with the current time.
|
||
|
zeroTime time.Time
|
||
|
}
|
||
|
|
||
|
// NewTimeDeterministic creates a new instance using the given zeroTime value.
|
||
|
func NewTimeDeterministic(zeroTime time.Time) *TimeDeterministic {
|
||
|
return &TimeDeterministic{
|
||
|
counter: 0,
|
||
|
mu: sync.Mutex{},
|
||
|
zeroTime: zeroTime,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Now is like time.Now but more deterministic. The first call returns the
|
||
|
// configured zeroTime and subsequent calls return moments in time that occur
|
||
|
// exactly one second after the time returned by the previous call.
|
||
|
func (td *TimeDeterministic) Now() time.Time {
|
||
|
td.mu.Lock()
|
||
|
if td.zeroTime.IsZero() {
|
||
|
td.zeroTime = time.Now()
|
||
|
}
|
||
|
offset := td.counter
|
||
|
td.counter += time.Second
|
||
|
res := td.zeroTime.Add(offset)
|
||
|
td.mu.Unlock()
|
||
|
return res
|
||
|
}
|