go.mway.dev/chrono@v0.6.1-0.20240126030049-189c5aef20d2/clock/throttled_clock_test.go (about) 1 // Copyright (c) 2023 Matt Way 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 5 // deal in the Software without restriction, including without limitation the 6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 // sell 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 18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 // IN THE THE SOFTWARE. 20 21 package clock_test 22 23 import ( 24 "runtime" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/stretchr/testify/require" 30 "go.mway.dev/chrono/clock" 31 "go.mway.dev/math" 32 "go.uber.org/atomic" 33 ) 34 35 func TestThrottledClock_Constructors(t *testing.T) { 36 cases := map[string]struct { 37 clockFn func(time.Duration) *clock.ThrottledClock 38 }{ 39 "NewThrottledMonotonicClock": { 40 clockFn: func(d time.Duration) *clock.ThrottledClock { 41 return clock.NewThrottledMonotonicClock(d) 42 }, 43 }, 44 "NewThrottledWallClock": { 45 clockFn: func(d time.Duration) *clock.ThrottledClock { 46 return clock.NewThrottledWallClock(d) 47 }, 48 }, 49 } 50 51 for name, tt := range cases { 52 t.Run(name, func(t *testing.T) { 53 var ( 54 clock = tt.clockFn(time.Millisecond) 55 prevNanos = clock.Nanotime() 56 prevTime = clock.Now() 57 ) 58 defer clock.Stop() 59 60 waitForChange(t, clock, prevNanos) 61 62 require.True(t, clock.Nanotime() > prevNanos, "nanotime did not increase") 63 require.True(t, clock.Now().After(prevTime), "time did not increase") 64 }) 65 } 66 } 67 68 //nolint:gocyclo 69 func TestThrottledClock_Timers(t *testing.T) { 70 clk := clock.NewThrottledClock(func() int64 { return 0 }, time.Minute) 71 defer clk.Stop() 72 73 var ( 74 first = clk.Nanotime() 75 wg sync.WaitGroup 76 ) 77 78 // ThrottledClock.NewTimer 79 wg.Add(1) 80 go func() { 81 defer wg.Done() 82 83 timer := clk.NewTimer(time.Millisecond) 84 defer timer.Stop() 85 86 select { 87 case _, ok := <-timer.C: 88 require.True(t, ok, "zero time returned") 89 case <-time.After(time.Second): 90 require.FailNow(t, "timer did not fire") 91 } 92 }() 93 94 // ThrottledClock.NewTicker 95 wg.Add(1) 96 go func() { 97 defer wg.Done() 98 99 ticker := clk.NewTicker(time.Millisecond) 100 defer ticker.Stop() 101 102 for i := 0; i < 10; i++ { 103 select { 104 case <-ticker.C: 105 case <-time.After(time.Second): 106 require.FailNow(t, "timer did not fire") 107 } 108 } 109 }() 110 111 // ThrottledClock.Tick 112 wg.Add(1) 113 go func() { 114 defer wg.Done() 115 116 tickerC := clk.Tick(time.Millisecond) 117 118 for i := 0; i < 10; i++ { 119 select { 120 case <-tickerC: 121 case <-time.After(time.Second): 122 require.FailNow(t, "timer did not fire") 123 } 124 } 125 }() 126 127 // ThrottledClock.After 128 wg.Add(1) 129 go func() { 130 defer wg.Done() 131 132 timerC := clk.After(time.Millisecond) 133 134 select { 135 case <-timerC: 136 case <-time.After(time.Second): 137 require.FailNow(t, "timer did not fire") 138 } 139 }() 140 141 // ThrottledClock.AfterFunc 142 wg.Add(1) 143 go func() { 144 defer wg.Done() 145 146 timerC := make(chan struct{}) 147 clk.AfterFunc(time.Millisecond, func() { 148 close(timerC) 149 }) 150 151 select { 152 case <-timerC: 153 case <-time.After(time.Second): 154 require.FailNow(t, "timer did not fire") 155 } 156 }() 157 158 // ThrottledClock.Sleep 159 wg.Add(1) 160 go func() { 161 defer wg.Done() 162 163 timerC := make(chan struct{}) 164 go func() { 165 clk.Sleep(10 * time.Millisecond) 166 close(timerC) 167 }() 168 169 select { 170 case <-timerC: 171 case <-time.After(time.Second): 172 require.FailNow(t, "timer did not fire") 173 } 174 }() 175 176 wg.Wait() 177 require.Equal(t, first, clk.Nanotime()) 178 } 179 180 func TestThrottledClock_Since(t *testing.T) { 181 clk := clock.NewThrottledClock(func() int64 { return 123 }, time.Minute) 182 defer clk.Stop() 183 184 require.Equal(t, 23*time.Nanosecond, clk.Since(time.Unix(0, 100))) 185 require.Equal(t, 23*time.Nanosecond, clk.SinceNanotime(100)) 186 } 187 188 func TestThrottledClock_Internals(t *testing.T) { 189 var ( 190 now = atomic.NewInt64(123) 191 nowfn = func() int64 { 192 return now.Load() 193 } 194 ) 195 196 clk := clock.NewThrottledClock(nowfn, time.Microsecond) 197 defer clk.Stop() 198 199 require.Equal(t, now.Load(), clk.Nanotime()) 200 require.True(t, clk.Now().Equal(time.Unix(0, now.Load()))) 201 202 prev := now.Load() 203 now.Store(456) 204 waitForChange(t, clk, prev) 205 206 require.Equal(t, now.Load(), clk.Nanotime()) 207 require.True(t, clk.Now().Equal(time.Unix(0, now.Load()))) 208 209 prev = now.Load() 210 now.Store(1) 211 waitForChange(t, clk, prev) 212 213 require.Equal(t, now.Load(), clk.Nanotime()) 214 require.True(t, clk.Now().Equal(time.Unix(0, now.Load()))) 215 216 clk.Stop() 217 218 prev = now.Load() 219 now.Store(1) 220 221 // The clock should no longer update once it is stopped. 222 time.Sleep(100 * time.Millisecond) 223 require.Equal(t, prev, clk.Nanotime()) 224 } 225 226 func TestThrottledClock_Stopwatch(t *testing.T) { 227 var ( 228 now = atomic.NewInt64(0) 229 nowfn = func() int64 { 230 return now.Load() 231 } 232 ) 233 234 clk := clock.NewThrottledClock(nowfn, time.Microsecond) 235 defer clk.Stop() 236 237 stopwatch := clk.NewStopwatch() 238 require.Equal(t, 0*time.Second, stopwatch.Elapsed()) 239 240 now.Add(int64(time.Second)) 241 waitForChange(t, clk, 0) 242 require.Equal(t, time.Second, stopwatch.Elapsed()) 243 244 now.Add(int64(time.Second)) 245 waitForChange(t, clk, int64(time.Second)) 246 require.Equal(t, 2*time.Second, stopwatch.Elapsed()) 247 require.Equal(t, 2*time.Second, stopwatch.Reset()) 248 require.Equal(t, 0*time.Second, stopwatch.Elapsed()) 249 250 now.Add(int64(time.Second)) 251 waitForChange(t, clk, int64(2*time.Second)) 252 require.Equal(t, time.Second, stopwatch.Elapsed()) 253 } 254 255 func waitForChange(t *testing.T, clk *clock.ThrottledClock, prev int64) { 256 var ( 257 done = make(chan struct{}) 258 stop atomic.Bool 259 ) 260 261 go func() { 262 defer close(done) 263 264 for !stop.Load() && clk.Nanotime() == prev { 265 time.Sleep(clk.Interval() / 2) 266 runtime.Gosched() 267 } 268 }() 269 270 wait := math.Max(time.Second, 2*clk.Interval()) 271 select { 272 case <-time.After(wait): 273 stop.Store(true) 274 case <-done: 275 } 276 277 <-done 278 require.NotEqual(t, prev, clk.Nanotime(), "clock did not update") 279 }