go.mway.dev/chrono@v0.6.1-0.20240126030049-189c5aef20d2/clock/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 "errors" 25 "testing" 26 "time" 27 28 "github.com/stretchr/testify/require" 29 "go.mway.dev/chrono/clock" 30 "go.uber.org/atomic" 31 ) 32 33 var ( 34 _withNanotimeFunc = clock.WithNanotimeFunc(clock.DefaultNanotimeFunc()) 35 _withTimeFunc = clock.WithTimeFunc(clock.DefaultTimeFunc()) 36 ) 37 38 func TestNewClock(t *testing.T) { 39 cases := map[string]struct { 40 opts []clock.Option 41 expectErr bool 42 }{ 43 "no opts": { 44 opts: nil, 45 expectErr: false, 46 }, 47 "default opts": { 48 opts: []clock.Option{clock.DefaultOptions()}, 49 expectErr: false, 50 }, 51 "with nanotime func": { 52 opts: []clock.Option{_withNanotimeFunc}, 53 expectErr: false, 54 }, 55 "with time func": { 56 opts: []clock.Option{_withTimeFunc}, 57 expectErr: false, 58 }, 59 "with nanotime and time funcs": { 60 opts: []clock.Option{ 61 _withNanotimeFunc, 62 _withTimeFunc, 63 }, 64 expectErr: false, 65 }, 66 } 67 68 for name, tt := range cases { 69 t.Run(name, func(t *testing.T) { 70 clk, err := clock.NewClock(tt.opts...) 71 if tt.expectErr { 72 require.Error(t, err) 73 require.Nil(t, clk) 74 } else { 75 require.NoError(t, err) 76 require.NotNil(t, clk) 77 } 78 }) 79 } 80 } 81 82 func TestNewClock_Funcs(t *testing.T) { 83 var ( 84 nanotimeFunc = func() int64 { return 123 } 85 timeFunc = func() time.Time { return time.Unix(0, 456) } 86 cases = map[string]struct { 87 opts []clock.Option 88 expectNanotime int64 89 }{ 90 "with nanotime func": { 91 opts: []clock.Option{clock.WithNanotimeFunc(nanotimeFunc)}, 92 expectNanotime: nanotimeFunc(), 93 }, 94 "with nanotime func options": { 95 opts: []clock.Option{clock.Options{ 96 NanotimeFunc: nanotimeFunc, 97 }}, 98 expectNanotime: nanotimeFunc(), 99 }, 100 "with time func": { 101 opts: []clock.Option{clock.WithTimeFunc(timeFunc)}, 102 expectNanotime: timeFunc().UnixNano(), 103 }, 104 "with time func options": { 105 opts: []clock.Option{clock.Options{ 106 TimeFunc: timeFunc, 107 }}, 108 expectNanotime: timeFunc().UnixNano(), 109 }, 110 "with nanotime and time funcs": { 111 opts: []clock.Option{ 112 clock.WithTimeFunc(timeFunc), 113 clock.WithNanotimeFunc(nanotimeFunc), 114 }, 115 expectNanotime: nanotimeFunc(), 116 }, 117 } 118 ) 119 120 for name, tt := range cases { 121 t.Run(name, func(t *testing.T) { 122 clk := newTestClock(t, tt.opts...) 123 require.Equal(t, tt.expectNanotime, clk.Nanotime()) 124 }) 125 } 126 } 127 128 func TestMustClock(t *testing.T) { 129 require.Panics(t, func() { 130 clock.MustClock(nil, errors.New("error")) 131 }) 132 133 require.NotPanics(t, func() { 134 clk := clock.NewFakeClock() 135 require.Equal(t, clk, clock.MustClock(clk, nil)) 136 }) 137 } 138 139 func TestSpecializedClockConstructors(t *testing.T) { 140 cases := map[string]struct { 141 clock clock.Clock 142 }{ 143 "NewMonotonicClock": { 144 clock: clock.NewMonotonicClock(), 145 }, 146 "NewWallClock": { 147 clock: clock.NewWallClock(), 148 }, 149 } 150 151 for name, tt := range cases { 152 t.Run(name, func(t *testing.T) { 153 cur := tt.clock.Nanotime() 154 for i := 0; i < 100; i++ { 155 time.Sleep(time.Millisecond) 156 now := tt.clock.Nanotime() 157 require.Greater(t, now, cur) 158 cur = now 159 } 160 }) 161 } 162 } 163 164 func TestClock_NewTimer(t *testing.T) { 165 cases := map[string]struct { 166 opts []clock.Option 167 }{ 168 "nanotime func": { 169 opts: []clock.Option{_withNanotimeFunc}, 170 }, 171 "time func": { 172 opts: []clock.Option{_withTimeFunc}, 173 }, 174 } 175 176 for name, tt := range cases { 177 t.Run(name, func(t *testing.T) { 178 var ( 179 clk = newTestClock(t, tt.opts...) 180 timer = clk.NewTimer(time.Millisecond) 181 ) 182 183 requireTick(t, timer.C) 184 require.False(t, timer.Stop()) 185 timer.Reset(time.Second) 186 require.True(t, timer.Stop()) 187 }) 188 } 189 } 190 191 func TestClock_NewTicker(t *testing.T) { 192 cases := map[string]struct { 193 opts []clock.Option 194 }{ 195 "nanotime func": { 196 opts: []clock.Option{_withNanotimeFunc}, 197 }, 198 "time func": { 199 opts: []clock.Option{_withTimeFunc}, 200 }, 201 } 202 203 for name, tt := range cases { 204 t.Run(name, func(t *testing.T) { 205 var ( 206 clk = newTestClock(t, tt.opts...) 207 ticker = clk.NewTicker(time.Millisecond) 208 ) 209 defer ticker.Stop() 210 211 requireTick(t, ticker.C) 212 ticker.Stop() 213 ticker.Reset(time.Millisecond) 214 requireTick(t, ticker.C) 215 }) 216 } 217 } 218 219 func TestClock_Since(t *testing.T) { 220 cases := map[string]struct { 221 opts []clock.Option 222 }{ 223 "nanotime func": { 224 opts: []clock.Option{_withNanotimeFunc}, 225 }, 226 "time func": { 227 opts: []clock.Option{_withTimeFunc}, 228 }, 229 } 230 231 for name, tt := range cases { 232 t.Run(name, func(t *testing.T) { 233 clk := newTestClock(t, tt.opts...) 234 require.InEpsilon( 235 t, 236 clk.Now().UnixNano(), 237 clk.Since(time.Unix(0, 0)), 238 float64(time.Second), 239 ) 240 require.InEpsilon( 241 t, 242 clk.Nanotime(), 243 clk.SinceNanotime(0), 244 float64(time.Second), 245 ) 246 }) 247 } 248 } 249 250 func TestClock_After(t *testing.T) { 251 cases := map[string]struct { 252 opts []clock.Option 253 }{ 254 "nanotime func": { 255 opts: []clock.Option{_withNanotimeFunc}, 256 }, 257 "time func": { 258 opts: []clock.Option{_withTimeFunc}, 259 }, 260 } 261 262 for name, tt := range cases { 263 t.Run(name, func(t *testing.T) { 264 clk := newTestClock(t, tt.opts...) 265 requireTick(t, clk.After(time.Millisecond)) 266 267 var called atomic.Bool 268 clk.AfterFunc(time.Millisecond, func() { called.Store(true) }) 269 waitFor(t, time.Second, called.Load) 270 }) 271 } 272 } 273 274 func TestClock_Tick(t *testing.T) { 275 cases := map[string]struct { 276 opts []clock.Option 277 }{ 278 "nanotime func": { 279 opts: []clock.Option{_withNanotimeFunc}, 280 }, 281 "time func": { 282 opts: []clock.Option{_withTimeFunc}, 283 }, 284 } 285 286 for name, tt := range cases { 287 t.Run(name, func(t *testing.T) { 288 var ( 289 clk = newTestClock(t, tt.opts...) 290 tickerC = clk.Tick(time.Millisecond) 291 ) 292 293 for i := 0; i < 10; i++ { 294 requireTick(t, tickerC) 295 } 296 }) 297 } 298 } 299 300 func TestClock_Sleep(t *testing.T) { 301 cases := map[string]struct { 302 name string 303 opts []clock.Option 304 }{ 305 "nanotime func": { 306 opts: []clock.Option{_withNanotimeFunc}, 307 }, 308 "time func": { 309 opts: []clock.Option{_withTimeFunc}, 310 }, 311 } 312 313 for _, tt := range cases { 314 t.Run(tt.name, func(t *testing.T) { 315 var ( 316 clk = newTestClock(t, tt.opts...) 317 sleepdone = make(chan struct{}) 318 ) 319 320 go func() { 321 defer close(sleepdone) 322 clk.Sleep(50 * time.Millisecond) 323 }() 324 325 start := clk.Nanotime() 326 select { 327 case <-time.After(time.Second): 328 require.Fail(t, "did not wake") 329 case <-sleepdone: 330 since := clk.SinceNanotime(start) 331 require.InEpsilon( 332 t, 333 50*time.Millisecond, 334 since, 335 float64(25*time.Millisecond), 336 ) 337 } 338 }) 339 } 340 } 341 342 func TestClock_Stopwatch(t *testing.T) { 343 var ( 344 cases = map[string]struct { 345 giveClock clock.Clock 346 }{ 347 "nanotime func": { 348 giveClock: clock.NewMonotonicClock(), 349 }, 350 "time func": { 351 giveClock: clock.NewWallClock(), 352 }, 353 } 354 waitElapse = func(s *clock.Stopwatch, d time.Duration) time.Duration { 355 for { 356 if x := s.Elapsed(); x >= d { 357 return d 358 } 359 time.Sleep(10 * time.Millisecond) 360 } 361 } 362 ) 363 364 for name, tt := range cases { 365 t.Run(name, func(t *testing.T) { 366 var ( 367 stopwatch = tt.giveClock.NewStopwatch() 368 elapsed = waitElapse(stopwatch, 10*time.Millisecond) 369 ) 370 371 require.GreaterOrEqual(t, elapsed, 10*time.Millisecond) 372 require.GreaterOrEqual(t, stopwatch.Reset(), elapsed) 373 require.Less(t, stopwatch.Elapsed(), 10*time.Millisecond) 374 375 elapsed = waitElapse(stopwatch, 10*time.Millisecond) 376 require.GreaterOrEqual(t, elapsed, 10*time.Millisecond) 377 require.GreaterOrEqual(t, stopwatch.Reset(), elapsed) 378 }) 379 } 380 } 381 382 func newTestClock(t *testing.T, opts ...clock.Option) clock.Clock { 383 clk, err := clock.NewClock(opts...) 384 require.NoError(t, err) 385 return clk 386 }