tailscale/tstest/clock.go
Adrian Dewhurst cd4c71c122 tstest: prepare for Clock API changes
This change introduces tstime.NewClock and tstime.ClockOpts as a new way
to construct tstime.Clock. This is a subset of #8464 as a stepping stone
so that we can update our internal code to use the new API before making
the second round of changes.

Updates #8463

Change-Id: Ib26edb60e5355802aeca83ed60e4fdf806c90e27
Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
2023-07-06 17:03:19 -04:00

135 lines
3.7 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tstest
import (
"sync"
"time"
)
// ClockOpts is used to configure the initial settings for a Clock. Once the
// settings are configured as desired, call NewClock to get the resulting Clock.
type ClockOpts struct {
// Start is the starting time for the Clock. When FollowRealTime is false,
// Start is also the value that will be returned by the first call
// to Clock.Now.
Start time.Time
// Step is the amount of time the Clock will advance whenever Clock.Now is
// called. If set to zero, the Clock will only advance when Clock.Advance is
// called and/or if FollowRealTime is true.
//
// FollowRealTime and Step cannot be enabled at the same time.
Step time.Duration
// TimerChannelSize configures the maximum buffered ticks that are
// permitted in the channel of any Timer and Ticker created by this Clock.
// The special value 0 means to use the default of 1. The buffer may need to
// be increased if time is advanced by more than a single tick and proper
// functioning of the test requires that the ticks are not lost.
TimerChannelSize int
// FollowRealTime makes the simulated time increment along with real time.
// It is a compromise between determinism and the difficulty of explicitly
// managing the simulated time via Step or Clock.Advance. When
// FollowRealTime is set, calls to Now() and PeekNow() will add the
// elapsed real-world time to the simulated time.
//
// FollowRealTime and Step cannot be enabled at the same time.
FollowRealTime bool
}
// NewClock creates a Clock with the specified settings. To create a
// Clock with only the default settings, new(Clock) is equivalent, except that
// the start time will not be computed until one of the receivers is called.
func NewClock(co ClockOpts) *Clock {
if co.TimerChannelSize != 0 || co.FollowRealTime {
panic("TimerChannelSize and FollowRealTime are not implemented yet")
}
clock := &Clock{
Start: co.Start,
Step: co.Step,
}
clock.Lock()
defer clock.Unlock()
clock.initLocked()
return clock
}
// Clock is a testing clock that advances every time its Now method is
// called, beginning at Start.
//
// The zero value starts virtual time at an arbitrary value recorded
// in Start on the first call to Now, and time never advances.
type Clock struct {
// Start is the first value returned by Now.
Start time.Time
// Step is how much to advance with each Now call.
Step time.Duration
// Present is the time that the next Now call will receive.
Present time.Time
sync.Mutex
}
// Now returns the virtual clock's current time, and advances it
// according to its step configuration.
func (c *Clock) Now() time.Time {
c.Lock()
defer c.Unlock()
c.initLocked()
step := c.Step
ret := c.Present
c.Present = c.Present.Add(step)
return ret
}
func (c *Clock) Advance(d time.Duration) {
c.Lock()
defer c.Unlock()
c.initLocked()
c.Present = c.Present.Add(d)
}
func (c *Clock) initLocked() {
if c.Start.IsZero() {
c.Start = time.Now()
}
if c.Present.Before(c.Start) {
c.Present = c.Start
}
}
// Reset rewinds the virtual clock to its start time.
func (c *Clock) Reset() {
c.Lock()
defer c.Unlock()
c.Present = c.Start
}
// GetStart returns the initial simulated time when this Clock was created.
func (c *Clock) GetStart() time.Time {
c.Lock()
defer c.Unlock()
c.initLocked()
return c.Start
}
// GetStep returns the amount that simulated time advances on every call to Now.
func (c *Clock) GetStep() time.Duration {
c.Lock()
defer c.Unlock()
c.initLocked()
return c.Step
}
// SetStep updates the amount that simulated time advances on every call to Now.
func (c *Clock) SetStep(d time.Duration) {
c.Lock()
defer c.Unlock()
c.initLocked()
c.Step = d
}