From 11e0cff40cb070d196ab61fb86e2fce22facbf46 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Tue, 3 Dec 2019 10:00:15 -0500 Subject: [PATCH] Factor safe Clock creation into a strategy for reuse --- src/_zkapauthorizer/tests/strategies.py | 39 ++++++++++++++++++++ src/_zkapauthorizer/tests/test_controller.py | 30 ++++----------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index 223a7ec..4402065 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -19,6 +19,9 @@ Hypothesis strategies for property testing. from base64 import ( urlsafe_b64encode, ) +from datetime import ( + datetime, +) import attr @@ -39,6 +42,9 @@ from hypothesis.strategies import ( datetimes, ) +from twisted.internet.task import ( + Clock, +) from twisted.web.test.requesthelper import ( DummyRequest, ) @@ -541,3 +547,36 @@ def announcements(): return just({ u"ristretto-issuer-root-url": u"https://issuer.example.invalid/", }) + + +_POSIX_EPOCH = datetime.utcfromtimestamp(0) + +def posix_safe_datetimes(): + """ + Build datetime instances in a range that can be represented as floats + without losing microsecond precision. + """ + return datetimes( + # I don't know that time-based parts of the system break down + # before the POSIX epoch but I don't know that they work, either. + # Don't time travel with this code. + min_value=_POSIX_EPOCH, + # Once we get far enough into the future we lose the ability to + # represent a timestamp with microsecond precision in a floating point + # number, which we do with any POSIX timestamp-like API (eg + # twisted.internet.task.Clock). So don't go far enough into the + # future. + max_value=datetime(2200, 1, 1), + ) + + +def clocks(now=posix_safe_datetimes()): + """ + Build ``twisted.internet.task.Clock`` instances set to a time built by + ``now``. + """ + def clock_at_time(when): + c = Clock() + c.advance((when - _POSIX_EPOCH).total_seconds()) + return c + return now.map(clock_at_time) diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index 672c95f..5485b16 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -64,9 +64,6 @@ from hypothesis.strategies import ( from twisted.python.url import ( URL, ) -from twisted.internet.task import ( - Clock, -) from twisted.internet.defer import ( fail, ) @@ -119,6 +116,7 @@ from .strategies import ( tahoe_configs, vouchers, voucher_objects, + clocks, ) from .matchers import ( Provides, @@ -127,8 +125,6 @@ from .fixtures import ( TemporaryVoucherStore, ) -POSIX_EPOCH = datetime.utcfromtimestamp(0) - class PaymentControllerTests(TestCase): """ Tests for ``PaymentController``. @@ -232,32 +228,20 @@ class PaymentControllerTests(TestCase): @given( tahoe_configs(), - datetimes( - # I don't know that time-based parts of the system break down - # before the POSIX epoch but I don't know that they work, either. - # Don't time travel with this code. - min_value=POSIX_EPOCH, - # Once we get far enough into the future we lose the ability to - # represent a timestamp with microsecond precision in a floating - # point number, which we do with Clock. So don't go far enough - # into the future. - max_value=datetime(2200, 1, 1), - ), + clocks(), vouchers(), ) - def test_redeem_error_after_delay(self, get_config, now, voucher): + def test_redeem_error_after_delay(self, get_config, clock, voucher): """ When ``PaymentController`` receives a non-terminal error trying to redeem a voucher, after some time passes it tries to redeem the voucher again. """ - clock = Clock() - clock.advance((now - POSIX_EPOCH).total_seconds()) - + datetime_now = lambda: datetime.utcfromtimestamp(clock.seconds()) store = self.useFixture( TemporaryVoucherStore( get_config, - lambda: datetime.utcfromtimestamp(clock.seconds()), + datetime_now, ), ).store controller = PaymentController( @@ -272,7 +256,7 @@ class PaymentControllerTests(TestCase): MatchesAll( IsInstance(model_Unpaid), MatchesStructure( - finished=Equals(now), + finished=Equals(datetime_now()), ), ) ) @@ -288,7 +272,7 @@ class PaymentControllerTests(TestCase): IsInstance(model_Unpaid), MatchesStructure( # At the new time, demonstrating the retry was performed. - finished=Equals(now + interval), + finished=Equals(datetime_now()), ), ), ) -- GitLab