Tutorial¶
Why should I use time-travel?¶
Writing good tests can sometimes be a bit tricky, especially when you are testing code that uses a lot of I/O and has hard timing constraints.
The naïve approach for testing such code is to actually wait for the time to pass. This is bad. Horribly bad. Why?
- Tests shouldn’t take long.
- Time is not accurate. When you wait for timeouts, there’s always a threshold.
If your code expects exactly 5 seconds to pass, there’s no guarantee that
time.sleep
will wait exactly that long.
If you rely on timing in your tests - your build will never be reliable.
How does it work?¶
When loaded, the library mocks modules that access the machine’s time
(e.g. time
, datetime
) and I/O event handlers (e.g. poll
, select
)
and replaces them with an internal event-pool implementation that lets the user
choose when time moves forward and which I/O event will happen next.
The TimeTravel Context Manager¶
-
class
time_travel.
TimeTravel
(start_time=86400.0, **kwargs)[source]¶ Context-manager for patching time and I/O libraries.
Note
The initial time for the clock is set to 86,400 seconds since epoc. This is because Windows does not support any lower values. Sorry UNIX users!
Performance¶
The way the context manager works is that it changes references to patched
objects in loaded modules. By default time-travel
searches through
every loaded module in sys.modules
. This takes around 2 seconds.
Wait!! Don’t leave yet!! We managed to solve this!!!
To minimize search time, time_travel.TimeTravel
gets a keyword argument
named modules_to_patch
, which is a list of module names to search in.
For example, let’s say you’re testing a module named foobar:
import foobar
with TimeTravel(modules_to_patch=['foobar']) as t:
foobar.dostuff()
This will reduce the replace time to the bare minimum.
Note
When the default search method is used (without the modules_to_patch
argument) the following modules are skipped and not patched:
pytest
unittest
mock
threading
Moving Through Time¶
-
time.
time
()¶ Return the time stored in
time-travel
’s internal clock.
-
time.
sleep
(secs)¶ Move
time-travel
’s internal clock forward by secs seconds.
-
datetime.date.
today
()¶ Return a
datetime.date
object initialized to the day thattime-travel
’s internal clock is set to.
-
datetime.datetime.
today
()¶ Return a
datetime.datetime
object initialized to the day thattime-travel
’s internal clock is set to.
-
datetime.datetime.
now
()¶ Return a
datetime.datetime
object initialized to the time thattime-travel
’s internal clock is set to.
-
datetime.datetime.
utcnow
()¶ Return a
datetime.datetime
object initialized to the time thattime-travel
’s internal clock is set to (timezone naive).
Faking I/O Events¶
To mock I/O events, the user must tell time-travel
which event will happen,
for which file descriptor, and when. For that we have:
-
TimeTravel.
add_future_event
(time_from_now, fd, event)[source]¶ Add an event to the event pool.
Parameters: - time_from_now – When will the event happen.
- fd – The descriptor (usually a socket object) that the event will happen for.
- event – The event that will happen (implementation specific).
For example:
with TimeTravel() as t:
sock = socket.socket()
t.add_future_event(2, sock, EVENT)
Note
EVENT
is implementation specific for every event handler (select
,
poll
, etc.) and will be described in the corresponding handler’s
documentation.
-
select.
select
(rlist, wlist, xlist, timeout=None)¶ Mimics the behaviour of
select.select
.select
has no event types, it uses positional lists in order to distinguish between read-ready, write-ready and exception.TimeTravel.add_future_event()
requires an event type, so the following consts are provided:TimeTravel.event_types.select.READ
TimeTravel.event_types.select.WRITE
TimeTravel.event_types.select.EXCEPTIONAL
The mock returns the first event(s) that expire in the event pool and moves time forward to that point in time. For example, if the user added 2 events:
t.add_future_event(1, sock1, t.event_types.select.READ) t.add_future_event(2, sock2, t.event_types.select.READ)
Calling
select.select([sock1, sock2], [], [])
will return an rlist containing onlysock1
and the time will move forward by 1 second.
-
select.
poll
()¶ Return a
MockPollObject
that behaves exactly like the realPoll
object.Note
This patcher is not supported on Windows.
-
class
MockPollObject
(clock, event_pool)¶ A mock poll object.
-
MockPollObject.
modify
(fd, eventmask)¶ Modify an already registered fd’s event mask.
-
MockPollObject.
poll
(timeout=None)¶ Poll the set of registered file descriptors.
timeout is a value in milliseconds.
-
MockPollObject.
register
(fd, eventmask=None)¶ Register a file descriptor with the fake polling object.
-
MockPollObject.
unregister
(fd)¶ Remove a file descriptor tracked by the fake polling object.
-
The event type supplied to
TimeTravel.add_future_event()
is the event mask that is required by poll.poll() (select.POLLIN
,select.POLLOUT
, etc.).-
class