github.com/Laisky/zap@v1.27.0/internal/ztest/clock.go (about) 1 // Copyright (c) 2023 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package ztest 22 23 import ( 24 "sort" 25 "sync" 26 "time" 27 ) 28 29 // MockClock is a fake source of time. 30 // It implements standard time operations, 31 // but allows the user to control the passage of time. 32 // 33 // Use the [Add] method to progress time. 34 type MockClock struct { 35 mu sync.RWMutex 36 now time.Time 37 38 // The MockClock works by maintaining a list of waiters. 39 // Each waiter knows the time at which it should be resolved. 40 // When the clock advances, all waiters that are in range are resolved 41 // in chronological order. 42 waiters []waiter 43 } 44 45 // NewMockClock builds a new mock clock 46 // using the current actual time as the initial time. 47 func NewMockClock() *MockClock { 48 return &MockClock{ 49 now: time.Now(), 50 } 51 } 52 53 // Now reports the current time. 54 func (c *MockClock) Now() time.Time { 55 c.mu.RLock() 56 defer c.mu.RUnlock() 57 return c.now 58 } 59 60 // NewTicker returns a time.Ticker that ticks at the specified frequency. 61 // 62 // As with [time.NewTicker], 63 // the ticker will drop ticks if the receiver is slow, 64 // and the channel is never closed. 65 // 66 // Calling Stop on the returned ticker is a no-op. 67 // The ticker only runs when the clock is advanced. 68 func (c *MockClock) NewTicker(d time.Duration) *time.Ticker { 69 ch := make(chan time.Time, 1) 70 71 var tick func(time.Time) 72 tick = func(now time.Time) { 73 next := now.Add(d) 74 c.runAt(next, func() { 75 defer tick(next) 76 77 select { 78 case ch <- next: 79 // ok 80 default: 81 // The receiver is slow. 82 // Drop the tick and continue. 83 } 84 }) 85 } 86 tick(c.Now()) 87 88 return &time.Ticker{C: ch} 89 } 90 91 // runAt schedules the given function to be run at the given time. 92 // The function runs without a lock held, so it may schedule more work. 93 func (c *MockClock) runAt(t time.Time, fn func()) { 94 c.mu.Lock() 95 defer c.mu.Unlock() 96 c.waiters = append(c.waiters, waiter{until: t, fn: fn}) 97 } 98 99 type waiter struct { 100 until time.Time 101 fn func() 102 } 103 104 // Add progresses time by the given duration. 105 // Other operations waiting for the time to advance 106 // will be resolved if they are within range. 107 // 108 // Side effects of operations waiting for the time to advance 109 // will take effect on a best-effort basis. 110 // Avoid racing with operations that have side effects. 111 // 112 // Panics if the duration is negative. 113 func (c *MockClock) Add(d time.Duration) { 114 if d < 0 { 115 panic("cannot add negative duration") 116 } 117 118 c.mu.Lock() 119 defer c.mu.Unlock() 120 121 sort.Slice(c.waiters, func(i, j int) bool { 122 return c.waiters[i].until.Before(c.waiters[j].until) 123 }) 124 125 newTime := c.now.Add(d) 126 // newTime won't be recorded until the end of this method. 127 // This ensures that any waiters that are resolved 128 // are resolved at the time they were expecting. 129 130 for len(c.waiters) > 0 { 131 w := c.waiters[0] 132 if w.until.After(newTime) { 133 break 134 } 135 c.waiters[0] = waiter{} // avoid memory leak 136 c.waiters = c.waiters[1:] 137 138 // The waiter is within range. 139 // Travel to the time of the waiter and resolve it. 140 c.now = w.until 141 142 // The waiter may schedule more work 143 // so we must release the lock. 144 c.mu.Unlock() 145 w.fn() 146 // Sleeping here is necessary to let the side effects of waiters 147 // take effect before we continue. 148 time.Sleep(1 * time.Millisecond) 149 c.mu.Lock() 150 } 151 152 c.now = newTime 153 }