1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
|
package quartz_test
import (
"context"
"errors"
"testing"
"time"
"github.com/coder/quartz"
)
func TestTimer_NegativeDuration(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
mClock := quartz.NewMock(t)
start := mClock.Now()
trap := mClock.Trap().NewTimer()
defer trap.Close()
timers := make(chan *quartz.Timer, 1)
go func() {
timers <- mClock.NewTimer(-time.Second)
}()
c := trap.MustWait(ctx)
c.Release()
// trap returns the actual passed value
if c.Duration != -time.Second {
t.Fatalf("expected -time.Second, got: %v", c.Duration)
}
tmr := <-timers
select {
case <-ctx.Done():
t.Fatal("timeout waiting for timer")
case tme := <-tmr.C:
// the tick is the current time, not the past
if !tme.Equal(start) {
t.Fatalf("expected time %v, got %v", start, tme)
}
}
if tmr.Stop() {
t.Fatal("timer still running")
}
}
func TestAfterFunc_NegativeDuration(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
mClock := quartz.NewMock(t)
trap := mClock.Trap().AfterFunc()
defer trap.Close()
timers := make(chan *quartz.Timer, 1)
done := make(chan struct{})
go func() {
timers <- mClock.AfterFunc(-time.Second, func() {
close(done)
})
}()
c := trap.MustWait(ctx)
c.Release()
// trap returns the actual passed value
if c.Duration != -time.Second {
t.Fatalf("expected -time.Second, got: %v", c.Duration)
}
tmr := <-timers
select {
case <-ctx.Done():
t.Fatal("timeout waiting for timer")
case <-done:
// OK!
}
if tmr.Stop() {
t.Fatal("timer still running")
}
}
func TestNewTicker(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
mClock := quartz.NewMock(t)
start := mClock.Now()
trapNT := mClock.Trap().NewTicker("new")
defer trapNT.Close()
trapStop := mClock.Trap().TickerStop("stop")
defer trapStop.Close()
trapReset := mClock.Trap().TickerReset("reset")
defer trapReset.Close()
tickers := make(chan *quartz.Ticker, 1)
go func() {
tickers <- mClock.NewTicker(time.Hour, "new")
}()
c := trapNT.MustWait(ctx)
c.Release()
if c.Duration != time.Hour {
t.Fatalf("expected time.Hour, got: %v", c.Duration)
}
tkr := <-tickers
for i := 0; i < 3; i++ {
mClock.Advance(time.Hour).MustWait(ctx)
}
// should get first tick, rest dropped
tTime := start.Add(time.Hour)
select {
case <-ctx.Done():
t.Fatal("timeout waiting for ticker")
case tick := <-tkr.C:
if !tick.Equal(tTime) {
t.Fatalf("expected time %v, got %v", tTime, tick)
}
}
go tkr.Reset(time.Minute, "reset")
c = trapReset.MustWait(ctx)
mClock.Advance(time.Second).MustWait(ctx)
c.Release()
if c.Duration != time.Minute {
t.Fatalf("expected time.Minute, got: %v", c.Duration)
}
mClock.Advance(time.Minute).MustWait(ctx)
// tick should show present time, ensuring the 2 hour ticks got dropped when
// we didn't read from the channel.
tTime = mClock.Now()
select {
case <-ctx.Done():
t.Fatal("timeout waiting for ticker")
case tick := <-tkr.C:
if !tick.Equal(tTime) {
t.Fatalf("expected time %v, got %v", tTime, tick)
}
}
go tkr.Stop("stop")
trapStop.MustWait(ctx).Release()
mClock.Advance(time.Hour).MustWait(ctx)
select {
case <-tkr.C:
t.Fatal("ticker still running")
default:
// OK
}
// Resetting after stop
go tkr.Reset(time.Minute, "reset")
trapReset.MustWait(ctx).Release()
mClock.Advance(time.Minute).MustWait(ctx)
tTime = mClock.Now()
select {
case <-ctx.Done():
t.Fatal("timeout waiting for ticker")
case tick := <-tkr.C:
if !tick.Equal(tTime) {
t.Fatalf("expected time %v, got %v", tTime, tick)
}
}
}
func TestPeek(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
mClock := quartz.NewMock(t)
d, ok := mClock.Peek()
if d != 0 {
t.Fatal("expected Peek() to return 0")
}
if ok {
t.Fatal("expected Peek() to return false")
}
tmr := mClock.NewTimer(time.Second)
d, ok = mClock.Peek()
if d != time.Second {
t.Fatal("expected Peek() to return 1s")
}
if !ok {
t.Fatal("expected Peek() to return true")
}
mClock.Advance(999 * time.Millisecond).MustWait(ctx)
d, ok = mClock.Peek()
if d != time.Millisecond {
t.Fatal("expected Peek() to return 1ms")
}
if !ok {
t.Fatal("expected Peek() to return true")
}
stopped := tmr.Stop()
if !stopped {
t.Fatal("expected Stop() to return true")
}
d, ok = mClock.Peek()
if d != 0 {
t.Fatal("expected Peek() to return 0")
}
if ok {
t.Fatal("expected Peek() to return false")
}
}
// TestTickerFunc_ContextDoneDuringTick tests that TickerFunc.Wait() can't return while the tick
// function callback is in progress.
func TestTickerFunc_ContextDoneDuringTick(t *testing.T) {
t.Parallel()
testCtx, testCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer testCancel()
mClock := quartz.NewMock(t)
tickStart := make(chan struct{})
tickDone := make(chan struct{})
ctx, cancel := context.WithCancel(testCtx)
defer cancel()
tkr := mClock.TickerFunc(ctx, time.Second, func() error {
close(tickStart)
select {
case <-tickDone:
case <-testCtx.Done():
t.Error("timeout waiting for tickDone")
}
return nil
})
w := mClock.Advance(time.Second)
select {
case <-tickStart:
// OK
case <-testCtx.Done():
t.Fatal("timeout waiting for tickStart")
}
waitErr := make(chan error, 1)
go func() {
waitErr <- tkr.Wait()
}()
cancel()
select {
case <-waitErr:
t.Fatal("wait should not return while tick callback in progress")
case <-time.After(time.Millisecond * 100):
// OK
}
close(tickDone)
select {
case err := <-waitErr:
if !errors.Is(err, context.Canceled) {
t.Fatal("expected context.Canceled error")
}
case <-testCtx.Done():
t.Fatal("timed out waiting for wait to finish")
}
w.MustWait(testCtx)
}
// TestTickerFunc_LongCallback tests that we don't call the ticker func a second time while the
// first is still executing.
func TestTickerFunc_LongCallback(t *testing.T) {
t.Parallel()
testCtx, testCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer testCancel()
mClock := quartz.NewMock(t)
expectedErr := errors.New("callback error")
tickStart := make(chan struct{})
tickDone := make(chan struct{})
ctx, cancel := context.WithCancel(testCtx)
defer cancel()
tkr := mClock.TickerFunc(ctx, time.Second, func() error {
close(tickStart)
select {
case <-tickDone:
case <-testCtx.Done():
t.Error("timeout waiting for tickDone")
}
return expectedErr
})
w := mClock.Advance(time.Second)
select {
case <-tickStart:
// OK
case <-testCtx.Done():
t.Fatal("timeout waiting for tickStart")
}
// additional ticks complete immediately.
elapsed := time.Duration(0)
for elapsed < 5*time.Second {
d, wt := mClock.AdvanceNext()
elapsed += d
wt.MustWait(testCtx)
}
waitErr := make(chan error, 1)
go func() {
waitErr <- tkr.Wait()
}()
cancel()
close(tickDone)
select {
case err := <-waitErr:
// we should get the function error, not the context error, since context was canceled while
// we were calling the function, and it returned an error.
if !errors.Is(err, expectedErr) {
t.Fatalf("wrong error: %s", err)
}
case <-testCtx.Done():
t.Fatal("timed out waiting for wait to finish")
}
w.MustWait(testCtx)
}
|