blob: 47b0b7f96524cd77dcb39d6a37bd707e93eae06a [file] [log] [blame]
Abhay Kumara61c5222025-11-10 07:32:50 +00001// Copyright (c) 2023 Uber Technologies, Inc.
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 deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// 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 FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21package ztest
22
23import (
24 "sort"
25 "sync"
26 "time"
27)
28
29// MockClock is a fake source of time.
30// It implements standard time operations,
31// but allows the user to control the passage of time.
32//
33// Use the [Add] method to progress time.
34type MockClock struct {
35 mu sync.RWMutex
36 now time.Time
37
38 // The MockClock works by maintaining a list of waiters.
39 // Each waiter knows the time at which it should be resolved.
40 // When the clock advances, all waiters that are in range are resolved
41 // in chronological order.
42 waiters []waiter
43}
44
45// NewMockClock builds a new mock clock
46// using the current actual time as the initial time.
47func NewMockClock() *MockClock {
48 return &MockClock{
49 now: time.Now(),
50 }
51}
52
53// Now reports the current time.
54func (c *MockClock) Now() time.Time {
55 c.mu.RLock()
56 defer c.mu.RUnlock()
57 return c.now
58}
59
60// NewTicker returns a time.Ticker that ticks at the specified frequency.
61//
62// As with [time.NewTicker],
63// the ticker will drop ticks if the receiver is slow,
64// and the channel is never closed.
65//
66// Calling Stop on the returned ticker is a no-op.
67// The ticker only runs when the clock is advanced.
68func (c *MockClock) NewTicker(d time.Duration) *time.Ticker {
69 ch := make(chan time.Time, 1)
70
71 var tick func(time.Time)
72 tick = func(now time.Time) {
73 next := now.Add(d)
74 c.runAt(next, func() {
75 defer tick(next)
76
77 select {
78 case ch <- next:
79 // ok
80 default:
81 // The receiver is slow.
82 // Drop the tick and continue.
83 }
84 })
85 }
86 tick(c.Now())
87
88 return &time.Ticker{C: ch}
89}
90
91// runAt schedules the given function to be run at the given time.
92// The function runs without a lock held, so it may schedule more work.
93func (c *MockClock) runAt(t time.Time, fn func()) {
94 c.mu.Lock()
95 defer c.mu.Unlock()
96 c.waiters = append(c.waiters, waiter{until: t, fn: fn})
97}
98
99type waiter struct {
100 until time.Time
101 fn func()
102}
103
104// Add progresses time by the given duration.
105// Other operations waiting for the time to advance
106// will be resolved if they are within range.
107//
108// Side effects of operations waiting for the time to advance
109// will take effect on a best-effort basis.
110// Avoid racing with operations that have side effects.
111//
112// Panics if the duration is negative.
113func (c *MockClock) Add(d time.Duration) {
114 if d < 0 {
115 panic("cannot add negative duration")
116 }
117
118 c.mu.Lock()
119 defer c.mu.Unlock()
120
121 sort.Slice(c.waiters, func(i, j int) bool {
122 return c.waiters[i].until.Before(c.waiters[j].until)
123 })
124
125 newTime := c.now.Add(d)
126 // newTime won't be recorded until the end of this method.
127 // This ensures that any waiters that are resolved
128 // are resolved at the time they were expecting.
129
130 for len(c.waiters) > 0 {
131 w := c.waiters[0]
132 if w.until.After(newTime) {
133 break
134 }
135 c.waiters[0] = waiter{} // avoid memory leak
136 c.waiters = c.waiters[1:]
137
138 // The waiter is within range.
139 // Travel to the time of the waiter and resolve it.
140 c.now = w.until
141
142 // The waiter may schedule more work
143 // so we must release the lock.
144 c.mu.Unlock()
145 w.fn()
146 // Sleeping here is necessary to let the side effects of waiters
147 // take effect before we continue.
148 time.Sleep(1 * time.Millisecond)
149 c.mu.Lock()
150 }
151
152 c.now = newTime
153}