Documentation ¶
Overview ¶
Package abtime provides abstracted time functionality that can be swapped between testing and real without changing application code.
In any code that seriously uses time, such as billing or scheduling code, best software engineering practices are that you should not directly access the operating system time.
Other people's discussions: http://blog.plover.com/prog/Moonpig.html#testing-sucks http://stackoverflow.com/questions/5622194/time-dependent-unit-tests/5622222#5622222 http://jim-mcbeath.blogspot.com/2009/02/unit-testing-with-dates-and-times.html
This module wraps the parts of the time module of Go that do access the OS time directly, as it stands at Go 1.2 and 1.3 (which are both the same.) Unfortunately, due to the fact I can not re-export types, you'll still need to import "time" for its types.
This module declares an interface for time functions AbstractTime, provides an implementation that simply backs to the "real" time functions "RealTime", and provides an implementation that allows you to fully control the time "ManualTime", including setting "now", and requiring you to manually trigger all time-based events, such as alerts and alarms.
Since there is no way to distinguish between different calls to the standard time functions, each of the methods in the AbstractTime interface adds an "id". The RealTime implementation simply ignores them. The ManualTime implementations uses these to trigger specific time events. Be sure to see the example for usage of the ManualTime implementation.
Avoid re-using IDs on the Tick functions; it becomes confusing which .Trigger is affecting which Tick.
Be sure to see the Example below.
Quality: At the moment I would call this beta code. Go lint clean, go vet clean, 100% coverage in the tests. You and I both know that doesn't prove this is bug-free, but at least it shows I care. And bear in mind what this really provides is a structure, rather than a whackload of code; should the code prove not quite correct for your project, it will be easy for you to fix it.
Index ¶
- type AbstractTime
- type ManualTime
- func (mt *ManualTime) Advance(d time.Duration)
- func (mt *ManualTime) After(d time.Duration, id int) <-chan time.Time
- func (mt *ManualTime) AfterFunc(d time.Duration, f func(), id int) Timer
- func (mt *ManualTime) NewTicker(d time.Duration, id int) Ticker
- func (mt *ManualTime) NewTimer(d time.Duration, id int) Timer
- func (mt *ManualTime) Now() time.Time
- func (mt *ManualTime) QueueNows(times ...time.Time)
- func (mt *ManualTime) Sleep(d time.Duration, id int)
- func (mt *ManualTime) Tick(d time.Duration, id int) <-chan time.Time
- func (mt *ManualTime) Trigger(ids ...int)
- func (mt *ManualTime) Unregister(ids ...int)
- func (mt *ManualTime) UnregisterAll()
- func (mt *ManualTime) WithDeadline(parent context.Context, deadline time.Time, id int) (context.Context, context.CancelFunc)
- func (mt *ManualTime) WithTimeout(parent context.Context, timeout time.Duration, id int) (context.Context, context.CancelFunc)
- type RealTime
- func (rt RealTime) After(d time.Duration, token int) <-chan time.Time
- func (rt RealTime) AfterFunc(d time.Duration, f func(), token int) Timer
- func (rt RealTime) NewTicker(d time.Duration, token int) Ticker
- func (rt RealTime) NewTimer(d time.Duration, token int) Timer
- func (rt RealTime) Now() time.Time
- func (rt RealTime) Sleep(d time.Duration, token int)
- func (rt RealTime) Tick(d time.Duration, token int) <-chan time.Time
- func (rt RealTime) WithDeadline(parent context.Context, deadline time.Time, _ int) (context.Context, context.CancelFunc)
- func (rt RealTime) WithTimeout(parent context.Context, timeout time.Duration, _ int) (context.Context, context.CancelFunc)
- type Ticker
- type Timer
- type TimerWrap
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AbstractTime ¶
type AbstractTime interface { Now() time.Time After(time.Duration, int) <-chan time.Time Sleep(time.Duration, int) Tick(time.Duration, int) <-chan time.Time NewTicker(time.Duration, int) Ticker AfterFunc(time.Duration, func(), int) Timer NewTimer(time.Duration, int) Timer WithDeadline(context.Context, time.Time, int) (context.Context, context.CancelFunc) WithTimeout(context.Context, time.Duration, int) (context.Context, context.CancelFunc) }
The AbstractTime interface abstracts the time module into an interface.
Example ¶
package main import ( "time" ) // It's best to allocate IDs like this for your time usages. const ( timeoutID = iota ) func main() { // Suppose you have a goroutine feeding you something from a socket, // and you want to do something if that times out. You can test this // with: manualTime := NewManual() timedOut := make(chan struct{}) go ReadSocket(manualTime, timedOut) manualTime.Trigger(timeoutID) // This will read the struct{}{} from above. Getting here asserts // that we did what we wanted when we timed out. <-timedOut } // In production code, at would be a RealTime, and thus use the "real" // time.After function, ignoring the ID. func ReadSocket(at AbstractTime, timedOut chan struct{}) { timeout := at.After(time.Second, timeoutID) // in this example, this will never be filled fromSocket := make(chan []byte) select { case <-fromSocket: // handle socketData case <-timeout: timedOut <- struct{}{} } }
Output:
type ManualTime ¶
The ManualTime object implements a time object you directly control.
This allows you to manipulate "now", and control when events occur.
func NewManual ¶
func NewManual() *ManualTime
NewManual returns a new ManualTime object, with the Now populated from the time.Now().
func NewManualAtTime ¶
func NewManualAtTime(now time.Time) *ManualTime
NewManualAtTime returns a new ManualTime object, with the Now set to the time.Time you pass in.
func (*ManualTime) Advance ¶
func (mt *ManualTime) Advance(d time.Duration)
Advance advances the manual time's idea of "now" by the given duration.
If there is a queue of "Nows" from QueueNows, note this won't affect any of them.
func (*ManualTime) AfterFunc ¶
func (mt *ManualTime) AfterFunc(d time.Duration, f func(), id int) Timer
AfterFunc fires the function in its own goroutine when the id is .Trigger()ed. The resulting Timer object will return nil for its Channel().
func (*ManualTime) NewTicker ¶
func (mt *ManualTime) NewTicker(d time.Duration, id int) Ticker
NewTicker wraps time.NewTicker. It takes a snapshot of "now" at the point of the TickToken call, and will increment the time it returns by the Duration of the tick.
Note that this can cause times to arrive out of order relative to each other if you have many of these going at once, if you manually trigger the ticks in such a way that they will be out of order.
func (*ManualTime) NewTimer ¶
func (mt *ManualTime) NewTimer(d time.Duration, id int) Timer
NewTimer allows you to create a Ticker, which can be triggered via the given id, and also supports the Stop operation *time.Tickers have.
func (*ManualTime) Now ¶
func (mt *ManualTime) Now() time.Time
Now returns the ManualTime's current idea of "Now".
If you have used QueueNow, this will advance to the next queued Now.
func (*ManualTime) QueueNows ¶
func (mt *ManualTime) QueueNows(times ...time.Time)
QueueNows allows you to set a number of times to be retrieved by successive calls to "Now". Once the queue is consumed by calls to Now(), the last time in the queue "sticks" as the new Now.
This is useful if you have code that is timing how long something took by successive calls to .Now, with no other place for the test code to intercede.
If multiple threads are accessing the Manual, it is of course non-deterministic who gets what time. However this could still be useful.
func (*ManualTime) Sleep ¶
func (mt *ManualTime) Sleep(d time.Duration, id int)
Sleep halts execution until you release it via Trigger.
func (*ManualTime) Trigger ¶
func (mt *ManualTime) Trigger(ids ...int)
Trigger takes the given ids for time events, and causes them to "occur": triggering messages on channels, ending sleeps, etc.
Note this is the ONLY way to "trigger" such events. While this package allows you to manipulate "Now" in a couple of different ways, advancing "now" past a Trigger's set time will NOT trigger it. First, this keeps it simple to understand when things are triggered, and second, reality isn't so deterministic anyhow....
func (*ManualTime) Unregister ¶ added in v1.0.2
func (mt *ManualTime) Unregister(ids ...int)
Unregister will unregister a particular ID from the system. Normally the first one sticks, which means if you've got code that creates multiple timers in a loop or in multiple function calls, only the first one will work.
NOTE: This method indicates a design flaw in abtime. It is not yet clear to me how to fix it in any reasonable way.
func (*ManualTime) UnregisterAll ¶ added in v1.0.2
func (mt *ManualTime) UnregisterAll()
UnregisterAll will unregister all current IDs from the manual time, returning you to a fresh view of the created channels and timers and such.
func (*ManualTime) WithDeadline ¶ added in v1.0.7
func (mt *ManualTime) WithDeadline(parent context.Context, deadline time.Time, id int) (context.Context, context.CancelFunc)
WithDeadline is a valid Context that is meant to drop in over a regular context.WithDeadline invocation. Instead of being canceled when reaching an actual deadline the context is canceled either by Trigger or by the returned CancelFunc.
func (*ManualTime) WithTimeout ¶ added in v1.0.7
func (mt *ManualTime) WithTimeout(parent context.Context, timeout time.Duration, id int) (context.Context, context.CancelFunc)
WithTimeout is equivalent to WithDeadline invoked on a deadline equal to the current time plus the timeout.
type RealTime ¶
type RealTime struct{}
The RealTime object implements the direct calls to the time module.
func NewRealTime ¶
func NewRealTime() RealTime
NewRealTime returns a AbTime-conforming object that backs to the standard time module.
func (RealTime) AfterFunc ¶
AfterFunc wraps time.AfterFunc. It returns something conforming to the abtime.Timer interface.
func (RealTime) NewTicker ¶
NewTicker wraps time.NewTicker. It returns something conforming to the abtime.Ticker interface.
func (RealTime) NewTimer ¶
NewTimer wraps time.NewTimer. It returns something conforming to the abtime.Timer interface.
type Ticker ¶
Ticker defines an interface for the functions that return *time.Ticker in the original Time module.
type Timer ¶
Timer defines an interface for the functions that return *time.Timer in the original Time module.