// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause package tstest import ( "slices" "sync/atomic" "testing" "time" "tailscale.com/tstime" ) func TestClockWithDefinedStartTime(t *testing.T) { t.Parallel() tests := []struct { name string start time.Time step time.Duration wants []time.Time // The return values of sequential calls to Now(). }{ { name: "increment ms", start: time.Unix(12345, 1000), step: 1000, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 2000), time.Unix(12345, 3000), time.Unix(12345, 4000), }, }, { name: "increment second", start: time.Unix(12345, 1000), step: time.Second, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12346, 1000), time.Unix(12347, 1000), time.Unix(12348, 1000), }, }, { name: "no increment", start: time.Unix(12345, 1000), wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 1000), time.Unix(12345, 1000), time.Unix(12345, 1000), }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clock := NewClock(ClockOpts{ Start: tt.start, Step: tt.step, }) if start := clock.GetStart(); !start.Equal(tt.start) { t.Errorf("clock has start %v, want %v", start, tt.start) } if step := clock.GetStep(); step != tt.step { t.Errorf("clock has step %v, want %v", step, tt.step) } for i := range tt.wants { if got := clock.Now(); !got.Equal(tt.wants[i]) { t.Errorf("step %v: clock.Now() = %v, want %v", i, got, tt.wants[i]) } if got := clock.PeekNow(); !got.Equal(tt.wants[i]) { t.Errorf("step %v: clock.PeekNow() = %v, want %v", i, got, tt.wants[i]) } } }) } } func TestClockWithDefaultStartTime(t *testing.T) { t.Parallel() tests := []struct { name string step time.Duration wants []time.Duration // The return values of sequential calls to Now() after added to Start() }{ { name: "increment ms", step: 1000, wants: []time.Duration{ 0, 1000, 2000, 3000, }, }, { name: "increment second", step: time.Second, wants: []time.Duration{ 0 * time.Second, 1 * time.Second, 2 * time.Second, 3 * time.Second, }, }, { name: "no increment", wants: []time.Duration{0, 0, 0, 0}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clock := NewClock(ClockOpts{ Step: tt.step, }) start := clock.GetStart() if step := clock.GetStep(); step != tt.step { t.Errorf("clock has step %v, want %v", step, tt.step) } for i := range tt.wants { want := start.Add(tt.wants[i]) if got := clock.Now(); !got.Equal(want) { t.Errorf("step %v: clock.Now() = %v, want %v", i, got, tt.wants[i]) } if got := clock.PeekNow(); !got.Equal(want) { t.Errorf("step %v: clock.PeekNow() = %v, want %v", i, got, tt.wants[i]) } } }) } } func TestZeroInitClock(t *testing.T) { t.Parallel() var clock Clock start := clock.GetStart() if step := clock.GetStep(); step != 0 { t.Errorf("clock has step %v, want 0", step) } for i := range 10 { if got := clock.Now(); !got.Equal(start) { t.Errorf("step %v: clock.Now() = %v, want %v", i, got, start) } if got := clock.PeekNow(); !got.Equal(start) { t.Errorf("step %v: clock.PeekNow() = %v, want %v", i, got, start) } } } func TestClockSetStep(t *testing.T) { t.Parallel() type stepInfo struct { when int step time.Duration } tests := []struct { name string start time.Time step time.Duration stepChanges []stepInfo wants []time.Time // The return values of sequential calls to Now(). }{ { name: "increment ms then s", start: time.Unix(12345, 1000), step: 1000, stepChanges: []stepInfo{ { when: 4, step: time.Second, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 2000), time.Unix(12345, 3000), time.Unix(12345, 4000), time.Unix(12346, 4000), time.Unix(12347, 4000), time.Unix(12348, 4000), time.Unix(12349, 4000), }, }, { name: "multiple changes over time", start: time.Unix(12345, 1000), step: 1, stepChanges: []stepInfo{ { when: 2, step: time.Second, }, { when: 4, step: 0, }, { when: 6, step: 1000, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 1001), time.Unix(12346, 1001), time.Unix(12347, 1001), time.Unix(12347, 1001), time.Unix(12347, 1001), time.Unix(12347, 2001), time.Unix(12347, 3001), }, }, { name: "multiple changes at once", start: time.Unix(12345, 1000), step: 1, stepChanges: []stepInfo{ { when: 2, step: time.Second, }, { when: 2, step: 0, }, { when: 2, step: 1000, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 1001), time.Unix(12345, 2001), time.Unix(12345, 3001), }, }, { name: "changes at start", start: time.Unix(12345, 1000), step: 0, stepChanges: []stepInfo{ { when: 0, step: time.Second, }, { when: 0, step: 1000, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 2000), time.Unix(12345, 3000), time.Unix(12345, 4000), }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clock := NewClock(ClockOpts{ Start: tt.start, Step: tt.step, }) wantStep := tt.step changeIndex := 0 for i := range tt.wants { for len(tt.stepChanges) > changeIndex && tt.stepChanges[changeIndex].when == i { wantStep = tt.stepChanges[changeIndex].step clock.SetStep(wantStep) changeIndex++ } if start := clock.GetStart(); !start.Equal(tt.start) { t.Errorf("clock has start %v, want %v", start, tt.start) } if step := clock.GetStep(); step != wantStep { t.Errorf("clock has step %v, want %v", step, tt.step) } if got := clock.Now(); !got.Equal(tt.wants[i]) { t.Errorf("step %v: clock.Now() = %v, want %v", i, got, tt.wants[i]) } if got := clock.PeekNow(); !got.Equal(tt.wants[i]) { t.Errorf("step %v: clock.PeekNow() = %v, want %v", i, got, tt.wants[i]) } } }) } } func TestClockAdvance(t *testing.T) { t.Parallel() type advanceInfo struct { when int advance time.Duration } tests := []struct { name string start time.Time step time.Duration advances []advanceInfo wants []time.Time // The return values of sequential calls to Now(). }{ { name: "increment ms then advance 1s", start: time.Unix(12345, 1000), step: 1000, advances: []advanceInfo{ { when: 4, advance: time.Second, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 2000), time.Unix(12345, 3000), time.Unix(12345, 4000), time.Unix(12346, 4000), time.Unix(12346, 5000), time.Unix(12346, 6000), time.Unix(12346, 7000), }, }, { name: "multiple advances over time", start: time.Unix(12345, 1000), step: 1, advances: []advanceInfo{ { when: 2, advance: time.Second, }, { when: 4, advance: 0, }, { when: 6, advance: 1000, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 1001), time.Unix(12346, 1001), time.Unix(12346, 1002), time.Unix(12346, 1002), time.Unix(12346, 1003), time.Unix(12346, 2003), time.Unix(12346, 2004), }, }, { name: "multiple advances at once", start: time.Unix(12345, 1000), step: 1, advances: []advanceInfo{ { when: 2, advance: time.Second, }, { when: 2, advance: 0, }, { when: 2, advance: 1000, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 1001), time.Unix(12346, 2001), time.Unix(12346, 2002), }, }, { name: "changes at start", start: time.Unix(12345, 1000), step: 5, advances: []advanceInfo{ { when: 0, advance: time.Second, }, { when: 0, advance: 1000, }, }, wants: []time.Time{ time.Unix(12346, 2000), time.Unix(12346, 2005), time.Unix(12346, 2010), time.Unix(12346, 2015), }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clock := NewClock(ClockOpts{ Start: tt.start, Step: tt.step, }) wantStep := tt.step changeIndex := 0 for i := range tt.wants { for len(tt.advances) > changeIndex && tt.advances[changeIndex].when == i { clock.Advance(tt.advances[changeIndex].advance) changeIndex++ } if start := clock.GetStart(); !start.Equal(tt.start) { t.Errorf("clock has start %v, want %v", start, tt.start) } if step := clock.GetStep(); step != wantStep { t.Errorf("clock has step %v, want %v", step, tt.step) } if got := clock.Now(); !got.Equal(tt.wants[i]) { t.Errorf("step %v: clock.Now() = %v, want %v", i, got, tt.wants[i]) } if got := clock.PeekNow(); !got.Equal(tt.wants[i]) { t.Errorf("step %v: clock.PeekNow() = %v, want %v", i, got, tt.wants[i]) } } }) } } func expectNoTicks(t *testing.T, tickC <-chan time.Time) { t.Helper() select { case tick := <-tickC: t.Errorf("wanted no ticks, got %v", tick) default: } } func TestSingleTicker(t *testing.T) { t.Parallel() type testStep struct { stop bool reset time.Duration resetAbsolute time.Time setStep time.Duration advance time.Duration advanceRealTime time.Duration wantTime time.Time wantTicks []time.Time } tests := []struct { name string realTimeOpts *ClockOpts start time.Time step time.Duration period time.Duration channelSize int steps []testStep }{ { name: "no tick advance", start: time.Unix(12345, 0), period: time.Second, steps: []testStep{ { advance: time.Second - 1, wantTime: time.Unix(12345, 999_999_999), }, }, }, { name: "no tick step", start: time.Unix(12345, 0), step: time.Second - 1, period: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12345, 999_999_999), }, }, }, { name: "single tick advance exact", start: time.Unix(12345, 0), period: time.Second, steps: []testStep{ { advance: time.Second, wantTime: time.Unix(12346, 0), wantTicks: []time.Time{time.Unix(12346, 0)}, }, }, }, { name: "single tick advance extra", start: time.Unix(12345, 0), period: time.Second, steps: []testStep{ { advance: time.Second + 1, wantTime: time.Unix(12346, 1), wantTicks: []time.Time{time.Unix(12346, 0)}, }, }, }, { name: "single tick step exact", start: time.Unix(12345, 0), step: time.Second, period: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), wantTicks: []time.Time{time.Unix(12346, 0)}, }, }, }, { name: "single tick step extra", start: time.Unix(12345, 0), step: time.Second + 1, period: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 1), wantTicks: []time.Time{time.Unix(12346, 0)}, }, }, }, { name: "single tick per advance", start: time.Unix(12345, 0), period: 3 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advance: 4 * time.Second, wantTime: time.Unix(12349, 0), wantTicks: []time.Time{time.Unix(12348, 0)}, }, { advance: 2 * time.Second, wantTime: time.Unix(12351, 0), wantTicks: []time.Time{time.Unix(12351, 0)}, }, { advance: 2 * time.Second, wantTime: time.Unix(12353, 0), }, { advance: 2 * time.Second, wantTime: time.Unix(12355, 0), wantTicks: []time.Time{time.Unix(12354, 0)}, }, }, }, { name: "single tick per step", start: time.Unix(12345, 0), step: 2 * time.Second, period: 3 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), }, { wantTime: time.Unix(12349, 0), wantTicks: []time.Time{time.Unix(12348, 0)}, }, { wantTime: time.Unix(12351, 0), wantTicks: []time.Time{time.Unix(12351, 0)}, }, { wantTime: time.Unix(12353, 0), }, { wantTime: time.Unix(12355, 0), wantTicks: []time.Time{time.Unix(12354, 0)}, }, }, }, { name: "multiple tick per advance", start: time.Unix(12345, 0), period: time.Second, channelSize: 3, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advance: 2 * time.Second, wantTime: time.Unix(12347, 0), wantTicks: []time.Time{ time.Unix(12346, 0), time.Unix(12347, 0), }, }, { advance: 4 * time.Second, wantTime: time.Unix(12351, 0), wantTicks: []time.Time{ time.Unix(12348, 0), time.Unix(12349, 0), time.Unix(12350, 0), // fourth tick dropped due to channel size }, }, }, }, { name: "multiple tick per step", start: time.Unix(12345, 0), step: 3 * time.Second, period: 2 * time.Second, channelSize: 3, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12348, 0), wantTicks: []time.Time{ time.Unix(12347, 0), }, }, { wantTime: time.Unix(12351, 0), wantTicks: []time.Time{ time.Unix(12349, 0), time.Unix(12351, 0), }, }, { wantTime: time.Unix(12354, 0), wantTicks: []time.Time{ time.Unix(12353, 0), }, }, { wantTime: time.Unix(12357, 0), wantTicks: []time.Time{ time.Unix(12355, 0), time.Unix(12357, 0), }, }, }, }, { name: "stop", start: time.Unix(12345, 0), step: 2 * time.Second, period: time.Second, channelSize: 3, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), wantTicks: []time.Time{ time.Unix(12346, 0), time.Unix(12347, 0), }, }, { stop: true, wantTime: time.Unix(12349, 0), }, { wantTime: time.Unix(12351, 0), }, { advance: 10 * time.Second, wantTime: time.Unix(12361, 0), }, }, }, { name: "reset while running", start: time.Unix(12345, 0), period: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advance: time.Second, wantTime: time.Unix(12346, 0), }, { advance: time.Second, wantTime: time.Unix(12347, 0), wantTicks: []time.Time{ time.Unix(12347, 0), }, }, { advance: time.Second, reset: time.Second, wantTime: time.Unix(12348, 0), wantTicks: []time.Time{ time.Unix(12348, 0), }, }, { setStep: 5 * time.Second, reset: 10 * time.Second, wantTime: time.Unix(12353, 0), }, { wantTime: time.Unix(12358, 0), wantTicks: []time.Time{ time.Unix(12358, 0), }, }, }, }, { name: "reset while stopped", start: time.Unix(12345, 0), step: time.Second, period: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), }, { wantTime: time.Unix(12347, 0), wantTicks: []time.Time{ time.Unix(12347, 0), }, }, { stop: true, wantTime: time.Unix(12348, 0), }, { wantTime: time.Unix(12349, 0), }, { reset: time.Second, wantTime: time.Unix(12350, 0), wantTicks: []time.Time{ time.Unix(12350, 0), }, }, { wantTime: time.Unix(12351, 0), wantTicks: []time.Time{ time.Unix(12351, 0), }, }, }, }, { name: "reset absolute", start: time.Unix(12345, 0), step: time.Second, period: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), }, { wantTime: time.Unix(12347, 0), wantTicks: []time.Time{ time.Unix(12347, 0), }, }, { reset: time.Second, resetAbsolute: time.Unix(12354, 50), advance: 7 * time.Second, wantTime: time.Unix(12354, 0), }, { wantTime: time.Unix(12355, 0), wantTicks: []time.Time{ time.Unix(12354, 50), }, }, { wantTime: time.Unix(12356, 0), wantTicks: []time.Time{ time.Unix(12355, 50), }, }, }, }, { name: "follow real time", realTimeOpts: new(ClockOpts), start: time.Unix(12345, 0), period: 2 * time.Second, channelSize: 3, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advanceRealTime: 5 * time.Second, wantTime: time.Unix(12350, 0), wantTicks: []time.Time{ time.Unix(12347, 0), time.Unix(12349, 0), }, }, { advance: 5 * time.Second, wantTime: time.Unix(12355, 0), wantTicks: []time.Time{ time.Unix(12351, 0), time.Unix(12353, 0), time.Unix(12355, 0), }, }, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() var realTimeClockForTestClock tstime.Clock var realTimeClock *Clock if tt.realTimeOpts != nil { realTimeClock = NewClock(*tt.realTimeOpts) // Passing realTimeClock into newClockInternal results in a // non-nil interface with a nil pointer, so this is necessary. realTimeClockForTestClock = realTimeClock } clock := newClockInternal(ClockOpts{ Start: tt.start, Step: tt.step, TimerChannelSize: tt.channelSize, FollowRealTime: realTimeClock != nil, }, realTimeClockForTestClock) tc, tickC := clock.NewTicker(tt.period) tickControl := tc.(*Ticker) t.Cleanup(tickControl.Stop) expectNoTicks(t, tickC) for i, step := range tt.steps { if step.stop { tickControl.Stop() } if !step.resetAbsolute.IsZero() { tickControl.ResetAbsolute(step.resetAbsolute, step.reset) } else if step.reset > 0 { tickControl.Reset(step.reset) } if step.setStep > 0 { clock.SetStep(step.setStep) } if step.advance > 0 { clock.Advance(step.advance) } if step.advanceRealTime > 0 { realTimeClock.Advance(step.advanceRealTime) } if now := clock.Now(); !step.wantTime.IsZero() && !now.Equal(step.wantTime) { t.Errorf("step %v now = %v, want %v", i, now, step.wantTime) } for j, want := range step.wantTicks { select { case tick := <-tickC: if tick.Equal(want) { continue } t.Errorf("step %v tick %v = %v, want %v", i, j, tick, want) default: t.Errorf("step %v tick %v missing", i, j) } } expectNoTicks(t, tickC) } }) } } func TestSingleTimer(t *testing.T) { t.Parallel() type testStep struct { stop bool stopReturn bool // The expected return value for Stop() if stop is true. reset time.Duration resetAbsolute time.Time resetReturn bool // The expected return value for Reset() or ResetAbsolute(). setStep time.Duration advance time.Duration advanceRealTime time.Duration wantTime time.Time wantTicks []time.Time } tests := []struct { name string realTimeOpts *ClockOpts start time.Time step time.Duration delay time.Duration steps []testStep }{ { name: "no tick advance", start: time.Unix(12345, 0), delay: time.Second, steps: []testStep{ { advance: time.Second - 1, wantTime: time.Unix(12345, 999_999_999), }, }, }, { name: "no tick step", start: time.Unix(12345, 0), step: time.Second - 1, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12345, 999_999_999), }, }, }, { name: "single tick advance exact", start: time.Unix(12345, 0), delay: time.Second, steps: []testStep{ { advance: time.Second, wantTime: time.Unix(12346, 0), wantTicks: []time.Time{time.Unix(12346, 0)}, }, { advance: time.Second, wantTime: time.Unix(12347, 0), }, }, }, { name: "single tick advance extra", start: time.Unix(12345, 0), delay: time.Second, steps: []testStep{ { advance: time.Second + 1, wantTime: time.Unix(12346, 1), wantTicks: []time.Time{time.Unix(12346, 0)}, }, { advance: time.Second, wantTime: time.Unix(12347, 1), }, }, }, { name: "single tick step exact", start: time.Unix(12345, 0), step: time.Second, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), wantTicks: []time.Time{time.Unix(12346, 0)}, }, { wantTime: time.Unix(12347, 0), }, }, }, { name: "single tick step extra", start: time.Unix(12345, 0), step: time.Second + 1, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 1), wantTicks: []time.Time{time.Unix(12346, 0)}, }, { wantTime: time.Unix(12347, 2), }, }, }, { name: "reset for single tick per advance", start: time.Unix(12345, 0), delay: 3 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advance: 4 * time.Second, wantTime: time.Unix(12349, 0), wantTicks: []time.Time{time.Unix(12348, 0)}, }, { resetAbsolute: time.Unix(12351, 0), advance: 2 * time.Second, wantTime: time.Unix(12351, 0), wantTicks: []time.Time{time.Unix(12351, 0)}, }, { reset: 3 * time.Second, advance: 2 * time.Second, wantTime: time.Unix(12353, 0), }, { advance: 2 * time.Second, wantTime: time.Unix(12355, 0), wantTicks: []time.Time{time.Unix(12354, 0)}, }, { advance: 10 * time.Second, wantTime: time.Unix(12365, 0), }, }, }, { name: "reset for single tick per step", start: time.Unix(12345, 0), step: 2 * time.Second, delay: 3 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), }, { wantTime: time.Unix(12349, 0), wantTicks: []time.Time{time.Unix(12348, 0)}, }, { reset: time.Second, wantTime: time.Unix(12351, 0), wantTicks: []time.Time{time.Unix(12350, 0)}, }, { resetAbsolute: time.Unix(12354, 0), wantTime: time.Unix(12353, 0), }, { wantTime: time.Unix(12355, 0), wantTicks: []time.Time{time.Unix(12354, 0)}, }, }, }, { name: "reset while active", start: time.Unix(12345, 0), step: 2 * time.Second, delay: 3 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), }, { reset: 3 * time.Second, resetReturn: true, wantTime: time.Unix(12349, 0), }, { resetAbsolute: time.Unix(12354, 0), resetReturn: true, wantTime: time.Unix(12351, 0), }, { wantTime: time.Unix(12353, 0), }, { wantTime: time.Unix(12355, 0), wantTicks: []time.Time{time.Unix(12354, 0)}, }, }, }, { name: "stop after fire", start: time.Unix(12345, 0), step: 2 * time.Second, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), wantTicks: []time.Time{time.Unix(12346, 0)}, }, { stop: true, wantTime: time.Unix(12349, 0), }, { wantTime: time.Unix(12351, 0), }, { advance: 10 * time.Second, wantTime: time.Unix(12361, 0), }, }, }, { name: "stop before fire", start: time.Unix(12345, 0), step: 2 * time.Second, delay: time.Second, steps: []testStep{ { stop: true, stopReturn: true, wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), }, { wantTime: time.Unix(12349, 0), }, { wantTime: time.Unix(12351, 0), }, { advance: 10 * time.Second, wantTime: time.Unix(12361, 0), }, }, }, { name: "stop after reset", start: time.Unix(12345, 0), step: 2 * time.Second, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), wantTicks: []time.Time{time.Unix(12346, 0)}, }, { reset: 10 * time.Second, wantTime: time.Unix(12349, 0), }, { stop: true, stopReturn: true, wantTime: time.Unix(12351, 0), }, { advance: 10 * time.Second, wantTime: time.Unix(12361, 0), }, }, }, { name: "reset while running", start: time.Unix(12345, 0), delay: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advance: time.Second, wantTime: time.Unix(12346, 0), }, { advance: time.Second, wantTime: time.Unix(12347, 0), wantTicks: []time.Time{ time.Unix(12347, 0), }, }, { advance: time.Second, reset: time.Second, wantTime: time.Unix(12348, 0), wantTicks: []time.Time{ time.Unix(12348, 0), }, }, { setStep: 5 * time.Second, reset: 10 * time.Second, wantTime: time.Unix(12353, 0), }, { wantTime: time.Unix(12358, 0), wantTicks: []time.Time{ time.Unix(12358, 0), }, }, }, }, { name: "reset while stopped", start: time.Unix(12345, 0), step: time.Second, delay: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), }, { stop: true, stopReturn: true, wantTime: time.Unix(12347, 0), }, { wantTime: time.Unix(12348, 0), }, { wantTime: time.Unix(12349, 0), }, { reset: time.Second, wantTime: time.Unix(12350, 0), wantTicks: []time.Time{ time.Unix(12350, 0), }, }, { wantTime: time.Unix(12351, 0), }, }, }, { name: "reset absolute", start: time.Unix(12345, 0), step: time.Second, delay: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), }, { wantTime: time.Unix(12347, 0), wantTicks: []time.Time{ time.Unix(12347, 0), }, }, { resetAbsolute: time.Unix(12354, 50), advance: 7 * time.Second, wantTime: time.Unix(12354, 0), }, { wantTime: time.Unix(12355, 0), wantTicks: []time.Time{ time.Unix(12354, 50), }, }, { wantTime: time.Unix(12356, 0), }, }, }, { name: "follow real time", realTimeOpts: new(ClockOpts), start: time.Unix(12345, 0), delay: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advanceRealTime: 5 * time.Second, wantTime: time.Unix(12350, 0), wantTicks: []time.Time{ time.Unix(12347, 0), }, }, { reset: 2 * time.Second, advance: 5 * time.Second, wantTime: time.Unix(12355, 0), wantTicks: []time.Time{ time.Unix(12352, 0), }, }, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() var realTimeClockForTestClock tstime.Clock var realTimeClock *Clock if tt.realTimeOpts != nil { realTimeClock = NewClock(*tt.realTimeOpts) // Passing realTimeClock into newClockInternal results in a // non-nil interface with a nil pointer, so this is necessary. realTimeClockForTestClock = realTimeClock } clock := newClockInternal(ClockOpts{ Start: tt.start, Step: tt.step, FollowRealTime: realTimeClock != nil, }, realTimeClockForTestClock) tc, tickC := clock.NewTimer(tt.delay) timerControl := tc.(*Timer) t.Cleanup(func() { timerControl.Stop() }) expectNoTicks(t, tickC) for i, step := range tt.steps { if step.stop { if got := timerControl.Stop(); got != step.stopReturn { t.Errorf("step %v Stop returned %v, want %v", i, got, step.stopReturn) } } if !step.resetAbsolute.IsZero() { if got := timerControl.ResetAbsolute(step.resetAbsolute); got != step.resetReturn { t.Errorf("step %v Reset returned %v, want %v", i, got, step.resetReturn) } } if step.reset > 0 { if got := timerControl.Reset(step.reset); got != step.resetReturn { t.Errorf("step %v Reset returned %v, want %v", i, got, step.resetReturn) } } if step.setStep > 0 { clock.SetStep(step.setStep) } if step.advance > 0 { clock.Advance(step.advance) } if step.advanceRealTime > 0 { realTimeClock.Advance(step.advanceRealTime) } if now := clock.Now(); !step.wantTime.IsZero() && !now.Equal(step.wantTime) { t.Errorf("step %v now = %v, want %v", i, now, step.wantTime) } for j, want := range step.wantTicks { select { case tick := <-tickC: if tick.Equal(want) { continue } t.Errorf("step %v tick %v = %v, want %v", i, j, tick, want) default: t.Errorf("step %v tick %v missing", i, j) } } expectNoTicks(t, tickC) } }) } } type testEvent struct { fireTimes []time.Time scheduleTimes []time.Time } func (te *testEvent) Fire(t time.Time) time.Time { var ret time.Time te.fireTimes = append(te.fireTimes, t) if len(te.scheduleTimes) > 0 { ret = te.scheduleTimes[0] te.scheduleTimes = te.scheduleTimes[1:] } return ret } func TestEventManager(t *testing.T) { t.Parallel() var em eventManager testEvents := []testEvent{ { scheduleTimes: []time.Time{ time.Unix(12300, 0), // step 1 time.Unix(12340, 0), // step 1 time.Unix(12345, 0), // step 1 time.Unix(12346, 0), // step 1 time.Unix(12347, 0), // step 3 time.Unix(12348, 0), // step 4 time.Unix(12349, 0), // step 4 }, }, { scheduleTimes: []time.Time{ time.Unix(12350, 0), // step 4 time.Unix(12360, 0), // step 5 time.Unix(12370, 0), // rescheduled time.Unix(12380, 0), // step 6 time.Unix(12381, 0), // step 6 time.Unix(12382, 0), // step 6 time.Unix(12393, 0), // stopped }, }, { scheduleTimes: []time.Time{ time.Unix(12350, 1), // step 4 time.Unix(12360, 1), // rescheduled time.Unix(12370, 1), // step 6 time.Unix(12380, 1), // step 6 time.Unix(12381, 1), // step 6 time.Unix(12382, 1), // step 6 time.Unix(12383, 1), // step 6 }, }, { scheduleTimes: []time.Time{ time.Unix(12355, 0), // step 5 time.Unix(12365, 0), // step 5 time.Unix(12370, 0), // step 6 time.Unix(12390, 0), // step 6 time.Unix(12391, 0), // step 7 time.Unix(12392, 0), // step 7 time.Unix(12393, 0), // step 7 }, }, { scheduleTimes: []time.Time{ time.Unix(100000, 0), // step 7 }, }, { scheduleTimes: []time.Time{ time.Unix(12346, 0), // step 1 }, }, { scheduleTimes: []time.Time{ time.Unix(12305, 0), // step 5 }, }, { scheduleTimes: []time.Time{ time.Unix(12372, 0), // step 6 time.Unix(12374, 0), // step 6 time.Unix(12376, 0), // step 6 time.Unix(12386, 0), // step 6 time.Unix(12396, 0), // step 7 }, }, } steps := []struct { reschedule []int stop []int advanceTo time.Time want map[int][]time.Time waitingEvents int }{ { advanceTo: time.Unix(12345, 0), }, { reschedule: []int{0, 1, 2, 3, 4, 5}, // add 0, 1, 2, 3, 4, 5 advanceTo: time.Unix(12346, 0), want: map[int][]time.Time{ 0: { time.Unix(12300, 0), time.Unix(12340, 0), time.Unix(12345, 0), time.Unix(12346, 0), }, 5: { time.Unix(12346, 0), }, }, waitingEvents: 5, // scheduled 0, 1, 2, 3, 4, 5; retired 5 }, { advanceTo: time.Unix(12346, 50), waitingEvents: 5, // no change }, { advanceTo: time.Unix(12347, 50), want: map[int][]time.Time{ 0: { time.Unix(12347, 0), }, }, waitingEvents: 5, // no change }, { advanceTo: time.Unix(12350, 50), want: map[int][]time.Time{ 0: { time.Unix(12348, 0), time.Unix(12349, 0), }, 1: { time.Unix(12350, 0), }, 2: { time.Unix(12350, 1), }, }, waitingEvents: 4, // retired 0 }, { reschedule: []int{6, 7}, // add 6, 7 stop: []int{2}, advanceTo: time.Unix(12365, 0), want: map[int][]time.Time{ 1: { time.Unix(12360, 0), }, 3: { time.Unix(12355, 0), time.Unix(12365, 0), }, 6: { time.Unix(12305, 0), }, }, waitingEvents: 4, // scheduled 6, 7; retired 2, 5 }, { reschedule: []int{1, 2}, // update 1; add 2 stop: []int{6}, advanceTo: time.Unix(12390, 0), want: map[int][]time.Time{ 1: { time.Unix(12380, 0), time.Unix(12381, 0), time.Unix(12382, 0), }, 2: { time.Unix(12370, 1), time.Unix(12380, 1), time.Unix(12381, 1), time.Unix(12382, 1), time.Unix(12383, 1), }, 3: { time.Unix(12370, 0), time.Unix(12390, 0), }, 7: { time.Unix(12372, 0), time.Unix(12374, 0), time.Unix(12376, 0), time.Unix(12386, 0), }, }, waitingEvents: 3, // scheduled 2, retired 2, stopped 6 }, { stop: []int{1}, // no-op: already stopped advanceTo: time.Unix(200000, 0), want: map[int][]time.Time{ 3: { time.Unix(12391, 0), time.Unix(12392, 0), time.Unix(12393, 0), }, 4: { time.Unix(100000, 0), }, 7: { time.Unix(12396, 0), }, }, waitingEvents: 0, // retired 3, 4, 7 }, { advanceTo: time.Unix(300000, 0), }, } for i, step := range steps { for _, idx := range step.reschedule { ev := &testEvents[idx] t := ev.scheduleTimes[0] ev.scheduleTimes = ev.scheduleTimes[1:] em.Reschedule(ev, t) } for _, idx := range step.stop { ev := &testEvents[idx] em.Reschedule(ev, time.Time{}) } em.AdvanceTo(step.advanceTo) for j := range testEvents { if !slices.Equal(testEvents[j].fireTimes, step.want[j]) { t.Errorf("step %v event %v fire times = %v, want %v", i, j, testEvents[j].fireTimes, step.want[j]) } testEvents[j].fireTimes = nil } } } func TestClockFollowRealTime(t *testing.T) { t.Parallel() type advanceInfo struct { when int advanceTestClock time.Duration advanceTestClockTo time.Time advanceRealTimeClock time.Duration } tests := []struct { name string start time.Time wantStart time.Time // This may differ from start when start.IsZero(). realTimeClockOpts ClockOpts advances []advanceInfo wants []time.Time // The return values of sequential calls to Now(). }{ { name: "increment ms then advance 1s", start: time.Unix(12345, 1000), wantStart: time.Unix(12345, 1000), advances: []advanceInfo{ { when: 1, advanceRealTimeClock: 1000, }, { when: 2, advanceRealTimeClock: 1000, }, { when: 3, advanceRealTimeClock: 1000, }, { when: 4, advanceTestClock: time.Second, }, { when: 5, advanceRealTimeClock: 1000, }, { when: 6, advanceRealTimeClock: 1000, }, { when: 7, advanceRealTimeClock: 1000, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 2000), time.Unix(12345, 3000), time.Unix(12345, 4000), time.Unix(12346, 4000), time.Unix(12346, 5000), time.Unix(12346, 6000), time.Unix(12346, 7000), }, }, { name: "multiple advances over time", start: time.Unix(12345, 1000), wantStart: time.Unix(12345, 1000), advances: []advanceInfo{ { when: 1, advanceRealTimeClock: 1, }, { when: 2, advanceTestClock: time.Second, }, { when: 3, advanceRealTimeClock: 1, }, { when: 4, advanceTestClock: 0, }, { when: 5, advanceRealTimeClock: 1, }, { when: 6, advanceTestClock: 1000, }, { when: 7, advanceRealTimeClock: 1, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 1001), time.Unix(12346, 1001), time.Unix(12346, 1002), time.Unix(12346, 1002), time.Unix(12346, 1003), time.Unix(12346, 2003), time.Unix(12346, 2004), }, }, { name: "multiple advances at once", start: time.Unix(12345, 1000), wantStart: time.Unix(12345, 1000), advances: []advanceInfo{ { when: 1, advanceRealTimeClock: 1, }, { when: 2, advanceTestClock: time.Second, }, { when: 2, advanceTestClock: 0, }, { when: 2, advanceTestClock: 1000, }, { when: 3, advanceRealTimeClock: 1, }, }, wants: []time.Time{ time.Unix(12345, 1000), time.Unix(12345, 1001), time.Unix(12346, 2001), time.Unix(12346, 2002), }, }, { name: "changes at start", start: time.Unix(12345, 1000), wantStart: time.Unix(12345, 1000), advances: []advanceInfo{ { when: 0, advanceTestClock: time.Second, }, { when: 0, advanceTestClock: 1000, }, { when: 1, advanceRealTimeClock: 5, }, { when: 2, advanceRealTimeClock: 5, }, { when: 3, advanceRealTimeClock: 5, }, }, wants: []time.Time{ time.Unix(12346, 2000), time.Unix(12346, 2005), time.Unix(12346, 2010), time.Unix(12346, 2015), }, }, { name: "start from current time", realTimeClockOpts: ClockOpts{ Start: time.Unix(12345, 0), }, wantStart: time.Unix(12345, 0), advances: []advanceInfo{ { when: 1, advanceTestClock: time.Second, }, { when: 2, advanceRealTimeClock: 10 * time.Second, }, { when: 3, advanceTestClock: time.Minute, }, { when: 4, advanceRealTimeClock: time.Hour, }, { when: 5, advanceTestClockTo: time.Unix(100, 0), }, { when: 6, advanceRealTimeClock: time.Hour, }, }, wants: []time.Time{ time.Unix(12345, 0), time.Unix(12346, 0), time.Unix(12356, 0), time.Unix(12416, 0), time.Unix(16016, 0), time.Unix(100, 0), time.Unix(3700, 0), }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() realTimeClock := NewClock(tt.realTimeClockOpts) clock := newClockInternal(ClockOpts{ Start: tt.start, FollowRealTime: true, }, realTimeClock) changeIndex := 0 for i := range tt.wants { for len(tt.advances) > changeIndex && tt.advances[changeIndex].when == i { advance := tt.advances[changeIndex] if advance.advanceTestClockTo.IsZero() { clock.Advance(advance.advanceTestClock) } else { clock.AdvanceTo(advance.advanceTestClockTo) } realTimeClock.Advance(advance.advanceRealTimeClock) changeIndex++ } if start := clock.GetStart(); !start.Equal(tt.wantStart) { t.Errorf("clock has start %v, want %v", start, tt.wantStart) } if got := clock.Now(); !got.Equal(tt.wants[i]) { t.Errorf("step %v: clock.Now() = %v, want %v", i, got, tt.wants[i]) } if got := clock.PeekNow(); !got.Equal(tt.wants[i]) { t.Errorf("step %v: clock.PeekNow() = %v, want %v", i, got, tt.wants[i]) } } }) } } func TestAfterFunc(t *testing.T) { t.Parallel() type testStep struct { stop bool stopReturn bool // The expected return value for Stop() if stop is true. reset time.Duration resetAbsolute time.Time resetReturn bool // The expected return value for Reset() or ResetAbsolute(). setStep time.Duration advance time.Duration advanceRealTime time.Duration wantTime time.Time wantTick bool } tests := []struct { name string realTimeOpts *ClockOpts start time.Time step time.Duration delay time.Duration steps []testStep }{ { name: "no tick advance", start: time.Unix(12345, 0), delay: time.Second, steps: []testStep{ { advance: time.Second - 1, wantTime: time.Unix(12345, 999_999_999), }, }, }, { name: "no tick step", start: time.Unix(12345, 0), step: time.Second - 1, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12345, 999_999_999), }, }, }, { name: "single tick advance exact", start: time.Unix(12345, 0), delay: time.Second, steps: []testStep{ { advance: time.Second, wantTime: time.Unix(12346, 0), wantTick: true, }, { advance: time.Second, wantTime: time.Unix(12347, 0), }, }, }, { name: "single tick advance extra", start: time.Unix(12345, 0), delay: time.Second, steps: []testStep{ { advance: time.Second + 1, wantTime: time.Unix(12346, 1), wantTick: true, }, { advance: time.Second, wantTime: time.Unix(12347, 1), }, }, }, { name: "single tick step exact", start: time.Unix(12345, 0), step: time.Second, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), wantTick: true, }, { wantTime: time.Unix(12347, 0), }, }, }, { name: "single tick step extra", start: time.Unix(12345, 0), step: time.Second + 1, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 1), wantTick: true, }, { wantTime: time.Unix(12347, 2), }, }, }, { name: "reset for single tick per advance", start: time.Unix(12345, 0), delay: 3 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advance: 4 * time.Second, wantTime: time.Unix(12349, 0), wantTick: true, }, { resetAbsolute: time.Unix(12351, 0), advance: 2 * time.Second, wantTime: time.Unix(12351, 0), wantTick: true, }, { reset: 3 * time.Second, advance: 2 * time.Second, wantTime: time.Unix(12353, 0), }, { advance: 2 * time.Second, wantTime: time.Unix(12355, 0), wantTick: true, }, { advance: 10 * time.Second, wantTime: time.Unix(12365, 0), }, }, }, { name: "reset for single tick per step", start: time.Unix(12345, 0), step: 2 * time.Second, delay: 3 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), }, { wantTime: time.Unix(12349, 0), wantTick: true, }, { reset: time.Second, wantTime: time.Unix(12351, 0), wantTick: true, }, { resetAbsolute: time.Unix(12354, 0), wantTime: time.Unix(12353, 0), }, { wantTime: time.Unix(12355, 0), wantTick: true, }, }, }, { name: "reset while active", start: time.Unix(12345, 0), step: 2 * time.Second, delay: 3 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), }, { reset: 3 * time.Second, resetReturn: true, wantTime: time.Unix(12349, 0), }, { resetAbsolute: time.Unix(12354, 0), resetReturn: true, wantTime: time.Unix(12351, 0), }, { wantTime: time.Unix(12353, 0), }, { wantTime: time.Unix(12355, 0), wantTick: true, }, }, }, { name: "stop after fire", start: time.Unix(12345, 0), step: 2 * time.Second, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), wantTick: true, }, { stop: true, wantTime: time.Unix(12349, 0), }, { wantTime: time.Unix(12351, 0), }, { advance: 10 * time.Second, wantTime: time.Unix(12361, 0), }, }, }, { name: "stop before fire", start: time.Unix(12345, 0), step: 2 * time.Second, delay: time.Second, steps: []testStep{ { stop: true, stopReturn: true, wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), }, { wantTime: time.Unix(12349, 0), }, { wantTime: time.Unix(12351, 0), }, { advance: 10 * time.Second, wantTime: time.Unix(12361, 0), }, }, }, { name: "stop after reset", start: time.Unix(12345, 0), step: 2 * time.Second, delay: time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12347, 0), wantTick: true, }, { reset: 10 * time.Second, wantTime: time.Unix(12349, 0), }, { stop: true, stopReturn: true, wantTime: time.Unix(12351, 0), }, { advance: 10 * time.Second, wantTime: time.Unix(12361, 0), }, }, }, { name: "reset while running", start: time.Unix(12345, 0), delay: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advance: time.Second, wantTime: time.Unix(12346, 0), }, { advance: time.Second, wantTime: time.Unix(12347, 0), wantTick: true, }, { advance: time.Second, reset: time.Second, wantTime: time.Unix(12348, 0), wantTick: true, }, { setStep: 5 * time.Second, reset: 10 * time.Second, wantTime: time.Unix(12353, 0), }, { wantTime: time.Unix(12358, 0), wantTick: true, }, }, }, { name: "reset while stopped", start: time.Unix(12345, 0), step: time.Second, delay: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), }, { stop: true, stopReturn: true, wantTime: time.Unix(12347, 0), }, { wantTime: time.Unix(12348, 0), }, { wantTime: time.Unix(12349, 0), }, { reset: time.Second, wantTime: time.Unix(12350, 0), wantTick: true, }, { wantTime: time.Unix(12351, 0), }, }, }, { name: "reset absolute", start: time.Unix(12345, 0), step: time.Second, delay: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { wantTime: time.Unix(12346, 0), }, { wantTime: time.Unix(12347, 0), wantTick: true, }, { resetAbsolute: time.Unix(12354, 50), advance: 7 * time.Second, wantTime: time.Unix(12354, 0), }, { wantTime: time.Unix(12355, 0), wantTick: true, }, { wantTime: time.Unix(12356, 0), }, }, }, { name: "follow real time", realTimeOpts: new(ClockOpts), start: time.Unix(12345, 0), delay: 2 * time.Second, steps: []testStep{ { wantTime: time.Unix(12345, 0), }, { advanceRealTime: 5 * time.Second, wantTime: time.Unix(12350, 0), wantTick: true, }, { reset: 2 * time.Second, advance: 5 * time.Second, wantTime: time.Unix(12355, 0), wantTick: true, }, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() var realTimeClockForTestClock tstime.Clock var realTimeClock *Clock if tt.realTimeOpts != nil { realTimeClock = NewClock(*tt.realTimeOpts) // Passing realTimeClock into newClockInternal results in a // non-nil interface with a nil pointer, so this is necessary. realTimeClockForTestClock = realTimeClock } var gotTick atomic.Bool clock := newClockInternal(ClockOpts{ Start: tt.start, Step: tt.step, FollowRealTime: realTimeClock != nil, }, realTimeClockForTestClock) tc := clock.AfterFunc(tt.delay, func() { if gotTick.Swap(true) == true { t.Error("multiple ticks detected") } }) timerControl := tc.(*Timer) t.Cleanup(func() { timerControl.Stop() }) if gotTick.Load() { t.Error("initial tick detected, want none") } for i, step := range tt.steps { if step.stop { if got := timerControl.Stop(); got != step.stopReturn { t.Errorf("step %v Stop returned %v, want %v", i, got, step.stopReturn) } } if !step.resetAbsolute.IsZero() { if got := timerControl.ResetAbsolute(step.resetAbsolute); got != step.resetReturn { t.Errorf("step %v Reset returned %v, want %v", i, got, step.resetReturn) } } if step.reset > 0 { if got := timerControl.Reset(step.reset); got != step.resetReturn { t.Errorf("step %v Reset returned %v, want %v", i, got, step.resetReturn) } } if step.setStep > 0 { clock.SetStep(step.setStep) } if step.advance > 0 { clock.Advance(step.advance) } if step.advanceRealTime > 0 { realTimeClock.Advance(step.advanceRealTime) } if now := clock.Now(); !step.wantTime.IsZero() && !now.Equal(step.wantTime) { t.Errorf("step %v now = %v, want %v", i, now, step.wantTime) } if got := gotTick.Swap(false); got != step.wantTick { t.Errorf("step %v tick %v, want %v", i, got, step.wantTick) } } }) } } func TestSince(t *testing.T) { t.Parallel() tests := []struct { name string start time.Time since time.Time want time.Duration }{ { name: "positive", start: time.Unix(12345, 1000), since: time.Unix(11111, 1000), want: 1234 * time.Second, }, { name: "negative", start: time.Unix(12345, 1000), since: time.Unix(15436, 1000), want: -3091 * time.Second, }, { name: "zero", start: time.Unix(12345, 1000), since: time.Unix(12345, 1000), want: 0, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clock := NewClock(ClockOpts{ Start: tt.start, }) got := clock.Since(tt.since) if got != tt.want { t.Errorf("Since duration %v, want %v", got, tt.want) } }) } }