Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for time.monotonic() #199

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ FreezeGun is a library that allows your python tests to travel through time by m
Usage
-----

Once the decorator or context manager have been invoked, all calls to datetime.datetime.now(), datetime.datetime.utcnow(), datetime.date.today(), time.time(), time.localtime(), time.gmtime(), and time.strftime() will return the time that has been frozen.
Once the decorator or context manager have been invoked, all calls to datetime.datetime.now(), datetime.datetime.utcnow(), datetime.date.today(), time.time(), time.localtime(), time.gmtime(), time.strftime() and time.monotonic() will return the time that has been frozen.

Decorator
~~~~~~~~~
Expand Down Expand Up @@ -173,6 +173,7 @@ Freezegun allows moving time to specific dates.

Parameter for ``move_to`` can be any valid ``freeze_time`` date (string, date, datetime).

Note that for ``tick``, manual tick and ``move_to`` there is by default moved frozen time.monotonic(). If you need to turn this off just add ``'time.monotonic'`` to ``ignore`` parameter.

Default Arguments
~~~~~~~~~~~~~~~~~
Expand Down
31 changes: 31 additions & 0 deletions freezegun/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
real_date = datetime.date
real_datetime = datetime.datetime

# monotonic is available since python 3.3
try:
real_monotonic = time.monotonic
except AttributeError:
real_monotonic = None

try:
real_uuid_generate_time = uuid._uuid_generate_time
except ImportError:
Expand Down Expand Up @@ -222,6 +228,18 @@ def _tz_offset(cls):
FakeDatetime.max = datetime_to_fakedatetime(real_datetime.max)


class FakeMonotonic(object):
def __init__(self, time_to_freeze, previous_monotonic_function):
self.time_to_freeze = time_to_freeze
self.previous_monotonic_function = previous_monotonic_function
self._start_freeze_time = self.time_to_freeze()
self._start_monotonic = self.previous_monotonic_function()

def __call__(self):
current_time = self.time_to_freeze()
return self._start_monotonic + (current_time - self._start_freeze_time).total_seconds()


def convert_to_timezone_naive(time_to_freeze):
"""
Converts a potentially timezone-aware datetime to be a naive UTC datetime
Expand Down Expand Up @@ -404,6 +422,11 @@ def start(self):
('real_strftime', real_strftime, 'FakeStrfTime', fake_strftime),
('real_time', real_time, 'FakeTime', fake_time),
]
if real_monotonic is not None and 'time.monotonic' not in self.ignore:
fake_monotonic = FakeMonotonic(time_to_freeze, time.monotonic)
time.monotonic = fake_monotonic
to_patch.append(('real_monotonic', real_monotonic, 'FakeMonotonic', fake_monotonic))

real_names = tuple(real_name for real_name, real, fake_name, fake in to_patch)
self.fake_names = tuple(fake_name for real_name, real, fake_name, fake in to_patch)
self.reals = dict((id(fake), real) for real_name, real, fake_name, fake in to_patch)
Expand All @@ -426,6 +449,9 @@ def start(self):
for module_attribute in dir(module):
if module_attribute in real_names:
continue
if '{}.{}'.format(mod_name, module_attribute) in self.ignore:
continue

try:
attribute_value = getattr(module, module_attribute)
except (ImportError, AttributeError, TypeError):
Expand Down Expand Up @@ -476,6 +502,9 @@ def stop(self):

if module_attribute in self.fake_names:
continue
if '{}.{}'.format(mod_name, module_attribute) in self.ignore:
continue

try:
attribute_value = getattr(module, module_attribute)
except (ImportError, AttributeError, TypeError):
Expand All @@ -490,6 +519,8 @@ def stop(self):
time.gmtime = time.gmtime.previous_gmtime_function
time.localtime = time.localtime.previous_localtime_function
time.strftime = time.strftime.previous_strftime_function
if real_monotonic is not None and 'time.monotonic' not in self.ignore:
time.monotonic = time.monotonic.previous_monotonic_function

uuid._uuid_generate_time = real_uuid_generate_time
uuid._UuidCreate = real_uuid_create
Expand Down
75 changes: 75 additions & 0 deletions tests/test_datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,64 @@ def test_move_to():
assert frozen_datetime() == initial_datetime


def test_manual_increment_monotonic():
if sys.version_info[0] != 3:
raise skip.SkipTest("test target is Python3")

initial_datetime = datetime.datetime(year=1, month=7, day=12,
hour=15, minute=6, second=3)
with freeze_time(initial_datetime) as frozen_datetime:
initial_monotonic = time.monotonic()

frozen_datetime.tick()
initial_monotonic += 1
assert time.monotonic() == initial_monotonic

frozen_datetime.tick(delta=datetime.timedelta(seconds=10))
initial_monotonic += 10
assert time.monotonic() == initial_monotonic


def test_move_to_monotonic():
if sys.version_info[0] != 3:
raise skip.SkipTest("test target is Python3")

initial_datetime = datetime.datetime(year=1, month=7, day=12,
hour=15, minute=6, second=3)

other_datetime = datetime.datetime(year=2, month=8, day=13,
hour=14, minute=5, second=0)
with freeze_time(initial_datetime) as frozen_datetime:
initial_monotonic = time.monotonic()

frozen_datetime.move_to(other_datetime)
assert time.monotonic() - initial_monotonic == (other_datetime - initial_datetime).total_seconds()

frozen_datetime.move_to(initial_datetime)
assert time.monotonic() == initial_monotonic


def test_monotonic_can_be_ignored():
if sys.version_info[0] != 3:
raise skip.SkipTest("test target is Python3")

initial_datetime = datetime.datetime(year=1, month=7, day=12,
hour=15, minute=6, second=3)

other_datetime = datetime.datetime(year=2, month=8, day=13,
hour=14, minute=5, second=0)
# time.monotonic() will not affected by freeze_time
with freeze_time(initial_datetime, ignore=['time.monotonic']) as frozen_datetime:
initial_monotonic = time.monotonic()

frozen_datetime.move_to(other_datetime)
second_monotonic = time.monotonic()
assert second_monotonic > initial_monotonic

frozen_datetime.move_to(initial_datetime)
assert time.monotonic() > second_monotonic


def test_bad_time_argument():
try:
freeze_time("2012-13-14", tz_offset=-4)
Expand Down Expand Up @@ -394,6 +452,23 @@ def test_nested_context_manager():
assert datetime.datetime.now() > datetime.datetime(2013, 1, 1)


def test_nested_monotonic():
if sys.version_info[0] != 3:
raise skip.SkipTest("test target is Python3")

with freeze_time('2012-01-14') as frozen_datetime_1:
initial_monotonic_1 = time.monotonic()
with freeze_time('2012-12-25') as frozen_datetime_2:
initial_monotonic_2 = time.monotonic()
frozen_datetime_2.tick()
initial_monotonic_2 += 1
assert time.monotonic() == initial_monotonic_2
assert time.monotonic() == initial_monotonic_1
frozen_datetime_1.tick()
initial_monotonic_1 += 1
assert time.monotonic() == initial_monotonic_1


def _assert_datetime_date_and_time_are_all_equal(expected_datetime):
assert datetime.datetime.now() == expected_datetime
assert datetime.date.today() == expected_datetime.date()
Expand Down
14 changes: 14 additions & 0 deletions tests/test_ticking.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import datetime
import time
import mock
import sys

from nose.plugins import skip

from freezegun import freeze_time
from tests import utils
Expand Down Expand Up @@ -43,3 +46,14 @@ def test_non_pypy_compat():
freeze_time("Jan 14th, 2012, 23:59:59", tick=True)
except Exception:
raise AssertionError("tick=True should not error on CPython")


@utils.cpython_only
def test_ticking_datetime_monotonic():
if sys.version_info[0] != 3:
raise skip.SkipTest("test target is Python3")

with freeze_time("Jan 14th, 2012", tick=True):
initial_monotonic = time.monotonic()
time.sleep(0.001) # Deal with potential clock resolution problems
assert time.monotonic() > initial_monotonic