go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/clock/testclock/testclock.go (about) 1 // Copyright 2014 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package testclock 16 17 import ( 18 "container/heap" 19 "context" 20 "sync" 21 "time" 22 23 "go.chromium.org/luci/common/clock" 24 ) 25 26 // TestClock is a Clock interface with additional methods to help instrument it. 27 type TestClock interface { 28 clock.Clock 29 30 // Set sets the test clock's time. 31 Set(time.Time) 32 33 // Add advances the test clock's time. 34 Add(time.Duration) 35 36 // SetTimerCallback is a goroutine-safe method to set an instance-wide 37 // callback that is invoked when any timer begins. 38 SetTimerCallback(TimerCallback) 39 } 40 41 // TimerCallback that can be invoked when a timer has been set. This is useful 42 // for sychronizing state when testing. 43 type TimerCallback func(time.Duration, clock.Timer) 44 45 // testClock is a test-oriented implementation of the 'Clock' interface. 46 // 47 // This implementation's Clock responses are configurable by modifying its 48 // member variables. Time-based events are explicitly triggered by sending on a 49 // Timer instance's channel. 50 type testClock struct { 51 sync.Mutex 52 53 now time.Time // The current clock time. 54 55 timerCallback TimerCallback // Optional callback when a timer has been set. 56 pendingTimers pendingTimerHeap 57 } 58 59 var _ TestClock = (*testClock)(nil) 60 61 // New returns a TestClock instance set at the specified time. 62 func New(now time.Time) TestClock { 63 return &testClock{ 64 now: now, 65 } 66 } 67 68 func (c *testClock) Now() time.Time { 69 c.Lock() 70 defer c.Unlock() 71 72 return c.now 73 } 74 75 func (c *testClock) Sleep(ctx context.Context, d time.Duration) clock.TimerResult { 76 t := c.NewTimer(ctx) 77 t.Reset(d) 78 return <-t.GetC() 79 } 80 81 func (c *testClock) NewTimer(ctx context.Context) clock.Timer { 82 t := newTimer(ctx, c) 83 return t 84 } 85 86 func (c *testClock) Set(t time.Time) { 87 c.Lock() 88 defer c.Unlock() 89 90 c.setTimeLocked(t) 91 } 92 93 func (c *testClock) Add(d time.Duration) { 94 c.Lock() 95 defer c.Unlock() 96 97 c.setTimeLocked(c.now.Add(d)) 98 } 99 100 func (c *testClock) setTimeLocked(t time.Time) { 101 if t.Before(c.now) { 102 panic("Cannot go backwards in time. You're not Doc Brown.") 103 } 104 c.now = t 105 106 // Unblock any blocking timers that are waiting on our lock. 107 triggerTimersLocked(c.now, &c.pendingTimers) 108 } 109 110 func triggerTimersLocked(now time.Time, pendingTimers *pendingTimerHeap) { 111 for len(*pendingTimers) > 0 { 112 e := (*pendingTimers)[0] 113 if now.Before(e.deadline) { 114 break 115 } 116 117 heap.Pop(pendingTimers) 118 e.triggerC <- now 119 close(e.triggerC) 120 } 121 } 122 123 func (c *testClock) addPendingTimer(t *timer, d time.Duration, triggerC chan<- time.Time) { 124 deadline := c.Now().Add(d) 125 if callback := c.timerCallback; callback != nil { 126 callback(d, t) 127 } 128 129 c.Lock() 130 defer c.Unlock() 131 132 heap.Push(&c.pendingTimers, &pendingTimer{ 133 timer: t, 134 deadline: deadline, 135 triggerC: triggerC, 136 }) 137 triggerTimersLocked(c.now, &c.pendingTimers) 138 } 139 140 func (c *testClock) clearPendingTimer(t *timer) { 141 c.Lock() 142 defer c.Unlock() 143 144 for i := 0; i < len(c.pendingTimers); { 145 if e := c.pendingTimers[0]; e.timer == t { 146 heap.Remove(&c.pendingTimers, i) 147 close(e.triggerC) 148 } else { 149 i++ 150 } 151 } 152 } 153 154 func (c *testClock) SetTimerCallback(callback TimerCallback) { 155 c.Lock() 156 defer c.Unlock() 157 158 c.timerCallback = callback 159 } 160 161 // pendingTimer is a single registered timer instance along with its trigger 162 // deadline. 163 type pendingTimer struct { 164 *timer 165 166 deadline time.Time 167 triggerC chan<- time.Time 168 } 169 170 // pendingTimerHeap is a heap.Interface implementation for a slice of 171 // pendingTimer. 172 type pendingTimerHeap []*pendingTimer 173 174 func (h pendingTimerHeap) Less(i, j int) bool { return h[i].deadline.Before(h[j].deadline) } 175 func (h pendingTimerHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 176 func (h pendingTimerHeap) Len() int { return len(h) } 177 178 func (h *pendingTimerHeap) Push(x any) { *h = append(*h, x.(*pendingTimer)) } 179 func (h *pendingTimerHeap) Pop() (v any) { 180 idx := len(*h) - 1 181 v, *h = (*h)[idx], (*h)[:idx] 182 return 183 }