Skip to content

Commit d616464

Browse files
committed
fix: allow mock clock Timers to accept negative duration
1 parent 1de023a commit d616464

File tree

3 files changed

+97
-12
lines changed

3 files changed

+97
-12
lines changed

clock/mock.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ func (m *Mock) TickerFunc(ctx context.Context, d time.Duration, f func() error,
5252
}
5353

5454
func (m *Mock) NewTimer(d time.Duration, tags ...string) *Timer {
55-
if d < 0 {
56-
panic("duration must be positive or zero")
57-
}
5855
m.mu.Lock()
5956
defer m.mu.Unlock()
6057
c := newCall(clockFunctionNewTimer, tags, withDuration(d))
@@ -67,14 +64,17 @@ func (m *Mock) NewTimer(d time.Duration, tags ...string) *Timer {
6764
nxt: m.cur.Add(d),
6865
mock: m,
6966
}
67+
if d <= 0 {
68+
// zero or negative duration timer means we should immediately fire
69+
// it, rather than add it.
70+
go t.fire(t.mock.cur)
71+
return t
72+
}
7073
m.addTimerLocked(t)
7174
return t
7275
}
7376

7477
func (m *Mock) AfterFunc(d time.Duration, f func(), tags ...string) *Timer {
75-
if d < 0 {
76-
panic("duration must be positive or zero")
77-
}
7878
m.mu.Lock()
7979
defer m.mu.Unlock()
8080
c := newCall(clockFunctionAfterFunc, tags, withDuration(d))
@@ -85,6 +85,12 @@ func (m *Mock) AfterFunc(d time.Duration, f func(), tags ...string) *Timer {
8585
fn: f,
8686
mock: m,
8787
}
88+
if d <= 0 {
89+
// zero or negative duration timer means we should immediately fire
90+
// it, rather than add it.
91+
go t.fire(t.mock.cur)
92+
return t
93+
}
8894
m.addTimerLocked(t)
8995
return t
9096
}

clock/mock_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package clock_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/coder/coder/v2/clock"
9+
)
10+
11+
func TestTimer_NegativeDuration(t *testing.T) {
12+
t.Parallel()
13+
// nolint:gocritic // trying to avoid Coder-specific stuff with an eye toward spinning this out
14+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
15+
defer cancel()
16+
17+
mClock := clock.NewMock(t)
18+
start := mClock.Now()
19+
trap := mClock.Trap().NewTimer()
20+
defer trap.Close()
21+
22+
timers := make(chan *clock.Timer, 1)
23+
go func() {
24+
timers <- mClock.NewTimer(-time.Second)
25+
}()
26+
c := trap.MustWait(ctx)
27+
c.Release()
28+
// trap returns the actual passed value
29+
if c.Duration != -time.Second {
30+
t.Fatalf("expected -time.Second, got: %v", c.Duration)
31+
}
32+
33+
tmr := <-timers
34+
select {
35+
case <-ctx.Done():
36+
t.Fatal("timeout waiting for timer")
37+
case tme := <-tmr.C:
38+
// the tick is the current time, not the past
39+
if !tme.Equal(start) {
40+
t.Fatalf("expected time %v, got %v", start, tme)
41+
}
42+
}
43+
if tmr.Stop() {
44+
t.Fatal("timer still running")
45+
}
46+
}
47+
48+
func TestAfterFunc_NegativeDuration(t *testing.T) {
49+
t.Parallel()
50+
// nolint:gocritic // trying to avoid Coder-specific stuff with an eye toward spinning this out
51+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
52+
defer cancel()
53+
54+
mClock := clock.NewMock(t)
55+
trap := mClock.Trap().AfterFunc()
56+
defer trap.Close()
57+
58+
timers := make(chan *clock.Timer, 1)
59+
done := make(chan struct{})
60+
go func() {
61+
timers <- mClock.AfterFunc(-time.Second, func() {
62+
close(done)
63+
})
64+
}()
65+
c := trap.MustWait(ctx)
66+
c.Release()
67+
// trap returns the actual passed value
68+
if c.Duration != -time.Second {
69+
t.Fatalf("expected -time.Second, got: %v", c.Duration)
70+
}
71+
72+
tmr := <-timers
73+
select {
74+
case <-ctx.Done():
75+
t.Fatal("timeout waiting for timer")
76+
case <-done:
77+
// OK!
78+
}
79+
if tmr.Stop() {
80+
t.Fatal("timer still running")
81+
}
82+
}

clock/timer.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ func (t *Timer) Reset(d time.Duration, tags ...string) bool {
4444
if t.timer != nil {
4545
return t.timer.Reset(d)
4646
}
47-
if d < 0 {
48-
panic("duration must be positive or zero")
49-
}
5047
t.mock.mu.Lock()
5148
defer t.mock.mu.Unlock()
5249
c := newCall(clockFunctionTimerReset, tags, withDuration(d))
@@ -57,9 +54,9 @@ func (t *Timer) Reset(d time.Duration, tags ...string) bool {
5754
case <-t.c:
5855
default:
5956
}
60-
if d == 0 {
61-
// zero duration timer means we should immediately re-fire it, rather
62-
// than remove and re-add it.
57+
if d <= 0 {
58+
// zero or negative duration timer means we should immediately re-fire
59+
// it, rather than remove and re-add it.
6360
t.stopped = false
6461
go t.fire(t.mock.cur)
6562
return result

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy