@@ -0,0 +1,380 @@
1+package clock_test
2+
3+import (
4+ "sync"
5+ "sync/atomic"
6+ "time"
7+
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
9+ . "github.com/onsi/ginkgo"
10+ . "github.com/onsi/gomega"
11+)
12+
13+const (
14+ testGlobalEpoch = 1000000000
15+ testLocalEpoch = 1234567890
16+)
17+
18+var _ = Describe("Clock suite", func() {
19+ var testClock = clock.Clock{}
20+
21+ AfterEach(func() {
22+ clock.Reset()
23+ testClock.Reset()
24+ })
25+
26+ Context("Global time not overridden", func() {
27+ It("errors when trying to Add", func() {
28+ err := clock.Add(123 * time.Hour)
29+ Expect(err).To(HaveOccurred())
30+ })
31+
32+ Context("Clock not mocked via Set", func() {
33+ const (
34+ outsideTolerance = int32(0)
35+ withinTolerance = int32(1)
36+ )
37+
38+ It("uses time.After for After", func() {
39+ var tolerance int32
40+ go func() {
41+ time.Sleep(10 * time.Millisecond)
42+ atomic.StoreInt32(&tolerance, withinTolerance)
43+ }()
44+ go func() {
45+ time.Sleep(30 * time.Millisecond)
46+ atomic.StoreInt32(&tolerance, outsideTolerance)
47+ }()
48+
49+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
50+
51+ <-testClock.After(20 * time.Millisecond)
52+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
53+
54+ <-testClock.After(20 * time.Millisecond)
55+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
56+ })
57+
58+ It("uses time.AfterFunc for AfterFunc", func() {
59+ var tolerance int32
60+ go func() {
61+ time.Sleep(10 * time.Millisecond)
62+ atomic.StoreInt32(&tolerance, withinTolerance)
63+ }()
64+ go func() {
65+ time.Sleep(30 * time.Millisecond)
66+ atomic.StoreInt32(&tolerance, outsideTolerance)
67+ }()
68+
69+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
70+
71+ var wg sync.WaitGroup
72+ wg.Add(1)
73+ testClock.AfterFunc(20*time.Millisecond, func() {
74+ wg.Done()
75+ })
76+ wg.Wait()
77+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
78+
79+ wg.Add(1)
80+ testClock.AfterFunc(20*time.Millisecond, func() {
81+ wg.Done()
82+ })
83+ wg.Wait()
84+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
85+ })
86+
87+ It("uses time.Now for Now", func() {
88+ a := time.Now()
89+ b := testClock.Now()
90+ Expect(b.Sub(a).Round(10 * time.Millisecond)).To(Equal(0 * time.Millisecond))
91+ })
92+
93+ It("uses time.Since for Since", func() {
94+ past := time.Now().Add(-60 * time.Second)
95+ Expect(time.Since(past).Round(10 * time.Millisecond)).
96+ To(Equal(60 * time.Second))
97+ })
98+
99+ It("uses time.Sleep for Sleep", func() {
100+ var tolerance int32
101+ go func() {
102+ time.Sleep(10 * time.Millisecond)
103+ atomic.StoreInt32(&tolerance, withinTolerance)
104+ }()
105+ go func() {
106+ time.Sleep(30 * time.Millisecond)
107+ atomic.StoreInt32(&tolerance, outsideTolerance)
108+ }()
109+
110+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
111+
112+ testClock.Sleep(20 * time.Millisecond)
113+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
114+
115+ testClock.Sleep(20 * time.Millisecond)
116+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
117+ })
118+
119+ It("uses time.Tick for Tick", func() {
120+ var tolerance int32
121+ go func() {
122+ time.Sleep(10 * time.Millisecond)
123+ atomic.StoreInt32(&tolerance, withinTolerance)
124+ }()
125+ go func() {
126+ time.Sleep(50 * time.Millisecond)
127+ atomic.StoreInt32(&tolerance, outsideTolerance)
128+ }()
129+
130+ ch := testClock.Tick(20 * time.Millisecond)
131+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
132+ <-ch
133+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
134+ <-ch
135+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
136+ <-ch
137+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
138+ })
139+
140+ It("uses time.Ticker for Ticker", func() {
141+ var tolerance int32
142+ go func() {
143+ time.Sleep(10 * time.Millisecond)
144+ atomic.StoreInt32(&tolerance, withinTolerance)
145+ }()
146+ go func() {
147+ time.Sleep(50 * time.Millisecond)
148+ atomic.StoreInt32(&tolerance, outsideTolerance)
149+ }()
150+
151+ ticker := testClock.Ticker(20 * time.Millisecond)
152+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
153+ <-ticker.C
154+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
155+ <-ticker.C
156+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
157+ <-ticker.C
158+ Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
159+ })
160+
161+ It("errors if Add is used", func() {
162+ err := testClock.Add(100 * time.Second)
163+ Expect(err).To(HaveOccurred())
164+ })
165+ })
166+
167+ Context("Clock mocked via Set", func() {
168+ var now = time.Unix(testLocalEpoch, 0)
169+
170+ BeforeEach(func() {
171+ testClock.Set(now)
172+ })
173+
174+ It("mocks After", func() {
175+ var after int32
176+ ready := make(chan struct{})
177+ ch := testClock.After(10 * time.Second)
178+ go func(ch <-chan time.Time) {
179+ close(ready)
180+ <-ch
181+ atomic.StoreInt32(&after, 1)
182+ }(ch)
183+ <-ready
184+
185+ err := testClock.Add(9 * time.Second)
186+ Expect(err).ToNot(HaveOccurred())
187+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
188+
189+ err = testClock.Add(1 * time.Second)
190+ Expect(err).ToNot(HaveOccurred())
191+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(1)))
192+ })
193+
194+ It("mocks AfterFunc", func() {
195+ var after int32
196+ testClock.AfterFunc(10*time.Second, func() {
197+ atomic.StoreInt32(&after, 1)
198+ })
199+
200+ err := testClock.Add(9 * time.Second)
201+ Expect(err).ToNot(HaveOccurred())
202+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
203+
204+ err = testClock.Add(1 * time.Second)
205+ Expect(err).ToNot(HaveOccurred())
206+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(1)))
207+ })
208+
209+ It("mocks AfterFunc with a stopped timer", func() {
210+ var after int32
211+ timer := testClock.AfterFunc(10*time.Second, func() {
212+ atomic.StoreInt32(&after, 1)
213+ })
214+ timer.Stop()
215+
216+ err := testClock.Add(11 * time.Second)
217+ Expect(err).ToNot(HaveOccurred())
218+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
219+ })
220+
221+ It("mocks Now", func() {
222+ Expect(testClock.Now()).To(Equal(now))
223+ err := testClock.Add(123 * time.Hour)
224+ Expect(err).ToNot(HaveOccurred())
225+ Expect(testClock.Now()).To(Equal(now.Add(123 * time.Hour)))
226+ })
227+
228+ It("mocks Since", func() {
229+ Expect(testClock.Since(time.Unix(testLocalEpoch-100, 0))).
230+ To(Equal(100 * time.Second))
231+ })
232+
233+ It("mocks Sleep", func() {
234+ var after int32
235+ ready := make(chan struct{})
236+ go func() {
237+ close(ready)
238+ testClock.Sleep(10 * time.Second)
239+ atomic.StoreInt32(&after, 1)
240+ }()
241+ <-ready
242+
243+ err := testClock.Add(9 * time.Second)
244+ Expect(err).ToNot(HaveOccurred())
245+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
246+
247+ err = testClock.Add(1 * time.Second)
248+ Expect(err).ToNot(HaveOccurred())
249+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(1)))
250+ })
251+
252+ It("mocks Tick", func() {
253+ var ticks int32
254+ ready := make(chan struct{})
255+ go func() {
256+ close(ready)
257+ tick := testClock.Tick(10 * time.Second)
258+ for ticks < 5 {
259+ <-tick
260+ atomic.AddInt32(&ticks, 1)
261+ }
262+ }()
263+ <-ready
264+
265+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(0)))
266+
267+ err := testClock.Add(9 * time.Second)
268+ Expect(err).ToNot(HaveOccurred())
269+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(0)))
270+
271+ err = testClock.Add(1 * time.Second)
272+ Expect(err).ToNot(HaveOccurred())
273+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(1)))
274+
275+ err = testClock.Add(30 * time.Second)
276+ Expect(err).ToNot(HaveOccurred())
277+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(4)))
278+
279+ err = testClock.Add(10 * time.Second)
280+ Expect(err).ToNot(HaveOccurred())
281+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(5)))
282+ })
283+
284+ It("mocks Ticker", func() {
285+ var ticks int32
286+ ready := make(chan struct{})
287+ go func() {
288+ ticker := testClock.Ticker(10 * time.Second)
289+ close(ready)
290+ for ticks < 5 {
291+ <-ticker.C
292+ atomic.AddInt32(&ticks, 1)
293+ }
294+ }()
295+ <-ready
296+
297+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(0)))
298+
299+ err := testClock.Add(9 * time.Second)
300+ Expect(err).ToNot(HaveOccurred())
301+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(0)))
302+
303+ err = testClock.Add(1 * time.Second)
304+ Expect(err).ToNot(HaveOccurred())
305+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(1)))
306+
307+ err = testClock.Add(30 * time.Second)
308+ Expect(err).ToNot(HaveOccurred())
309+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(4)))
310+
311+ err = testClock.Add(10 * time.Second)
312+ Expect(err).ToNot(HaveOccurred())
313+ Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(5)))
314+ })
315+
316+ It("mocks Timer", func() {
317+ var after int32
318+ ready := make(chan struct{})
319+ go func() {
320+ timer := testClock.Timer(10 * time.Second)
321+ close(ready)
322+ <-timer.C
323+ atomic.AddInt32(&after, 1)
324+ }()
325+ <-ready
326+
327+ err := testClock.Add(9 * time.Second)
328+ Expect(err).ToNot(HaveOccurred())
329+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
330+
331+ err = testClock.Add(1 * time.Second)
332+ Expect(err).ToNot(HaveOccurred())
333+ Expect(atomic.LoadInt32(&after)).To(Equal(int32(1)))
334+ })
335+ })
336+ })
337+
338+ Context("Global time overridden", func() {
339+ var (
340+ globalNow = time.Unix(testGlobalEpoch, 0)
341+ localNow = time.Unix(testLocalEpoch, 0)
342+ )
343+
344+ BeforeEach(func() {
345+ clock.Set(globalNow)
346+ })
347+
348+ Context("Clock not mocked via Set", func() {
349+ It("uses globally mocked Now", func() {
350+ Expect(testClock.Now()).To(Equal(globalNow))
351+ err := clock.Add(123 * time.Hour)
352+ Expect(err).ToNot(HaveOccurred())
353+ Expect(testClock.Now()).To(Equal(globalNow.Add(123 * time.Hour)))
354+ })
355+
356+ It("errors when Add is called on the local Clock", func() {
357+ err := testClock.Add(100 * time.Hour)
358+ Expect(err).To(HaveOccurred())
359+ })
360+ })
361+
362+ Context("Clock is mocked via Set", func() {
363+ BeforeEach(func() {
364+ testClock.Set(localNow)
365+ })
366+
367+ It("uses the local mock and ignores the global", func() {
368+ Expect(testClock.Now()).To(Equal(localNow))
369+
370+ err := clock.Add(456 * time.Hour)
371+ Expect(err).ToNot(HaveOccurred())
372+
373+ err = testClock.Add(123 * time.Hour)
374+ Expect(err).ToNot(HaveOccurred())
375+
376+ Expect(testClock.Now()).To(Equal(localNow.Add(123 * time.Hour)))
377+ })
378+ })
379+ })
380+})