From 2c1a7243ed19b9b9a036dbafaa9095d221fa2a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0ebek?= Date: Thu, 13 Jul 2017 12:17:07 +0200 Subject: [PATCH] Add support for time.monotonic() --- freezegun/api.py | 25 +++++++++++++++++++ tests/test_datetimes.py | 54 +++++++++++++++++++++++++++++++++++++++++ tests/test_ticking.py | 14 +++++++++++ 3 files changed, 93 insertions(+) diff --git a/freezegun/api.py b/freezegun/api.py index e7776145..57b10a44 100644 --- a/freezegun/api.py +++ b/freezegun/api.py @@ -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: @@ -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 @@ -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: + 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) @@ -490,6 +513,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: + time.monotonic = time.monotonic.previous_monotonic_function uuid._uuid_generate_time = real_uuid_generate_time uuid._UuidCreate = real_uuid_create diff --git a/tests/test_datetimes.py b/tests/test_datetimes.py index 68102d28..667b8613 100644 --- a/tests/test_datetimes.py +++ b/tests/test_datetimes.py @@ -169,6 +169,43 @@ 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_bad_time_argument(): try: freeze_time("2012-13-14", tz_offset=-4) @@ -394,6 +431,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() diff --git a/tests/test_ticking.py b/tests/test_ticking.py index d77d60cf..2129472c 100644 --- a/tests/test_ticking.py +++ b/tests/test_ticking.py @@ -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 @@ -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