Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add NewTicker to clock testing library#13593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
spikecurtis merged 1 commit intomainfromspike/clock-testing-new-ticker
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletionclock/clock.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,16 @@ import (
)

type Clock interface {
// NewTicker returns a new Ticker containing a channel that will send the current time on the
// channel after each tick. The period of the ticks is specified by the duration argument. The
// ticker will adjust the time interval or drop ticks to make up for slow receivers. The
// duration d must be greater than zero; if not, NewTicker will panic. Stop the ticker to
// release associated resources.
NewTicker(d time.Duration, tags ...string) *Ticker
// TickerFunc is a convenience function that calls f on the interval d until either the given
// context expires or f returns an error. Callers may call Wait() on the returned Waiter to
// wait until this happens and obtain the error.
// wait until this happens and obtain the error. The duration d must be greater than zero; if
// not, TickerFunc will panic.
TickerFunc(ctx context.Context, d time.Duration, f func() error, tags ...string) Waiter
// NewTimer creates a new Timer that will send the current time on its channel after at least
// duration d.
Expand Down
62 changes: 47 additions & 15 deletionsclock/mock.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,9 @@ type event interface {
}

func (m *Mock) TickerFunc(ctx context.Context, d time.Duration, f func() error, tags ...string) Waiter {
if d <= 0 {
panic("TickerFunc called with negative or zero duration")
}
m.mu.Lock()
defer m.mu.Unlock()
c := newCall(clockFunctionTickerFunc, tags, withDuration(d))
Expand All@@ -51,6 +54,28 @@ func (m *Mock) TickerFunc(ctx context.Context, d time.Duration, f func() error,
return t
}

func (m *Mock) NewTicker(d time.Duration, tags ...string) *Ticker {
if d <= 0 {
panic("NewTicker called with negative or zero duration")
}
m.mu.Lock()
defer m.mu.Unlock()
c := newCall(clockFunctionNewTicker, tags, withDuration(d))
m.matchCallLocked(c)
defer close(c.complete)
// 1 element buffer follows standard library implementation
ticks := make(chan time.Time, 1)
t := &Ticker{
C: ticks,
c: ticks,
d: d,
nxt: m.cur.Add(d),
mock: m,
}
m.addEventLocked(t)
return t
}

func (m *Mock) NewTimer(d time.Duration, tags ...string) *Timer {
m.mu.Lock()
defer m.mu.Unlock()
Expand All@@ -70,7 +95,7 @@ func (m *Mock) NewTimer(d time.Duration, tags ...string) *Timer {
go t.fire(t.mock.cur)
return t
}
m.addTimerLocked(t)
m.addEventLocked(t)
return t
}

Expand All@@ -91,7 +116,7 @@ func (m *Mock) AfterFunc(d time.Duration, f func(), tags ...string) *Timer {
go t.fire(t.mock.cur)
return t
}
m.addTimerLocked(t)
m.addEventLocked(t)
return t
}

Expand DownExpand Up@@ -122,8 +147,8 @@ func (m *Mock) Until(t time.Time, tags ...string) time.Duration {
return t.Sub(m.cur)
}

func (m *Mock)addTimerLocked(t *Timer) {
m.all = append(m.all,t)
func (m *Mock)addEventLocked(e event) {
m.all = append(m.all,e)
m.recomputeNextLocked()
}

Expand DownExpand Up@@ -152,20 +177,12 @@ func (m *Mock) removeTimer(t *Timer) {
}

func (m *Mock) removeTimerLocked(t *Timer) {
defer m.recomputeNextLocked()
t.stopped = true
var e event = t
for i := range m.all {
if m.all[i] == e {
m.all = append(m.all[:i], m.all[i+1:]...)
return
}
}
m.removeEventLocked(t)
}

func (m *Mock)removeTickerFuncLocked(ct *mockTickerFunc) {
func (m *Mock)removeEventLocked(e event) {
defer m.recomputeNextLocked()
var e event = ct
for i := range m.all {
if m.all[i] == e {
m.all = append(m.all[:i], m.all[i+1:]...)
Expand DownExpand Up@@ -371,6 +388,18 @@ func (t Trapper) TickerFuncWait(tags ...string) *Trap {
return t.mock.newTrap(clockFunctionTickerFuncWait, tags)
}

func (t Trapper) NewTicker(tags ...string) *Trap {
return t.mock.newTrap(clockFunctionNewTicker, tags)
}

func (t Trapper) TickerStop(tags ...string) *Trap {
return t.mock.newTrap(clockFunctionTickerStop, tags)
}

func (t Trapper) TickerReset(tags ...string) *Trap {
return t.mock.newTrap(clockFunctionTickerReset, tags)
}

func (t Trapper) Now(tags ...string) *Trap {
return t.mock.newTrap(clockFunctionNow, tags)
}
Expand DownExpand Up@@ -459,7 +488,7 @@ func (m *mockTickerFunc) exitLocked(err error) {
}
m.done = true
m.err = err
m.mock.removeTickerFuncLocked(m)
m.mock.removeEventLocked(m)
m.cond.Broadcast()
}

Expand DownExpand Up@@ -493,6 +522,9 @@ const (
clockFunctionTimerReset
clockFunctionTickerFunc
clockFunctionTickerFuncWait
clockFunctionNewTicker
clockFunctionTickerReset
clockFunctionTickerStop
clockFunctionNow
clockFunctionSince
clockFunctionUntil
Expand Down
87 changes: 87 additions & 0 deletionsclock/mock_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,3 +80,90 @@ func TestAfterFunc_NegativeDuration(t *testing.T) {
t.Fatal("timer still running")
}
}

func TestNewTicker(t *testing.T) {
t.Parallel()
// nolint:gocritic // trying to avoid Coder-specific stuff with an eye toward spinning this out
ctx, cancel := context.WithTimeout(context.Background(), 1000*time.Second)
defer cancel()

mClock := clock.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 *clock.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)
}
}
}
5 changes: 5 additions & 0 deletionsclock/real.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,11 @@ func NewReal() Clock {
return realClock{}
}

func (realClock) NewTicker(d time.Duration, _ ...string) *Ticker {
tkr := time.NewTicker(d)
return &Ticker{ticker: tkr, C: tkr.C}
}

func (realClock) TickerFunc(ctx context.Context, d time.Duration, f func() error, _ ...string) Waiter {
ct := &realContextTicker{
ctx: ctx,
Expand Down
68 changes: 68 additions & 0 deletionsclock/ticker.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
package clock

import"time"

typeTickerstruct {
C<-chan time.Time
//nolint: revive
cchan time.Time
ticker*time.Ticker// realtime impl, if set
d time.Duration// period, if set
nxt time.Time// next tick time
mock*Mock// mock clock, if set
stoppedbool// true if the ticker is not running
}

func (t*Ticker)fire(tt time.Time) {
t.mock.mu.Lock()
defert.mock.mu.Unlock()
ift.stopped {
return
}
for!t.nxt.After(t.mock.cur) {
t.nxt=t.nxt.Add(t.d)
}
t.mock.recomputeNextLocked()
select {
caset.c<-tt:
default:
}
}

func (t*Ticker)next() time.Time {
returnt.nxt
}

func (t*Ticker)Stop(tags...string) {
ift.ticker!=nil {
t.ticker.Stop()
return
}
t.mock.mu.Lock()
defert.mock.mu.Unlock()
c:=newCall(clockFunctionTickerStop,tags)
t.mock.matchCallLocked(c)
deferclose(c.complete)
t.mock.removeEventLocked(t)
t.stopped=true
}

func (t*Ticker)Reset(d time.Duration,tags...string) {
ift.ticker!=nil {
t.ticker.Reset(d)
return
}
t.mock.mu.Lock()
defert.mock.mu.Unlock()
c:=newCall(clockFunctionTickerReset,tags,withDuration(d))
t.mock.matchCallLocked(c)
deferclose(c.complete)
t.nxt=t.mock.cur.Add(d)
t.d=d
ift.stopped {
t.stopped=false
t.mock.addEventLocked(t)
}else {
t.mock.recomputeNextLocked()
}
}
2 changes: 1 addition & 1 deletionclock/timer.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,6 +64,6 @@ func (t *Timer) Reset(d time.Duration, tags ...string) bool {
t.mock.removeTimerLocked(t)
t.stopped = false
t.nxt = t.mock.cur.Add(d)
t.mock.addTimerLocked(t)
t.mock.addEventLocked(t)
return result
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp