blob: abaa97f68ae473fa501d7197b16bcf4f88bb3947 [file] [log] [blame] [edit]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package clock
import (
"context"
"time"
)
type clock interface {
Now() time.Time
After(d time.Duration) <-chan time.Time
}
type clockKeyType string
// clockKey is the key we use to associate a clock with a Context.
const clockKey = clockKeyType("clock")
// Now returns the current time for the clock associated with the given context,
// or the real current time if there is no clock associated with the context.
// Any code that needs to be tested with a mocked-out time should use
// `clock.Now()` instead of `time.Now()`.
func Now(ctx context.Context) time.Time {
if c, ok := ctx.Value(clockKey).(clock); ok && c != nil {
return c.Now()
}
return time.Now()
}
// After returns time.After() or the equivalent for the clock associated with
// the given context.
func After(ctx context.Context, d time.Duration) <-chan time.Time {
if c, ok := ctx.Value(clockKey).(clock); ok && c != nil {
return c.After(d)
}
return time.After(d)
}
// NewContext returns a new context with the given clock attached.
//
// This should generally only be used in tests; production code should always
// use the real time.
func NewContext(ctx context.Context, c clock) context.Context {
return context.WithValue(ctx, clockKey, c)
}
type timer struct {
endTime time.Time
ch chan time.Time
}
func (t *timer) advanceTo(newTime time.Time) {
if newTime.After(t.endTime) {
t.ch <- newTime
}
}
// FakeClock provides support for mocking the current time in tests.
type FakeClock struct {
now time.Time
timer *timer
afterCalled chan struct{}
}
func NewFakeClock() *FakeClock {
return &FakeClock{now: time.Now(), afterCalled: make(chan struct{}, 1)}
}
func (c *FakeClock) Now() time.Time {
return c.now
}
func (c *FakeClock) After(d time.Duration) <-chan time.Time {
t := &timer{c.now.Add(d), make(chan time.Time, 1)}
c.timer = t
if len(c.afterCalled) == 0 {
c.afterCalled <- struct{}{}
}
return t.ch
}
func (c *FakeClock) Advance(d time.Duration) {
c.now = c.now.Add(d)
// Notify timer that the time has changed
if c.timer != nil {
c.timer.advanceTo(c.now)
}
}
// AfterCalledChan returns the channel to wait for the clock's timer to be set from a
// call to After().
func (c *FakeClock) AfterCalledChan() chan struct{} {
return c.afterCalled
}