diff options
author | Raphaël Barrois <raphael.barrois@polytechnique.org> | 2013-04-21 22:29:46 +0200 |
---|---|---|
committer | Raphaël Barrois <raphael.barrois@polytechnique.org> | 2013-04-21 22:29:46 +0200 |
commit | 3b75e132fd1f302b604e76f4c1a0361c32ba50d4 (patch) | |
tree | a285557ceda0d30fe85aa264f6578b1826d0bbb4 | |
parent | b5f0579a9050b38392f45eb8e29b51e46928584a (diff) | |
download | factory-boy-3b75e132fd1f302b604e76f4c1a0361c32ba50d4.tar factory-boy-3b75e132fd1f302b604e76f4c1a0361c32ba50d4.tar.gz |
Add tests.alter_time and fix time altering on pypy.
-rw-r--r-- | tests/alter_time.py | 113 | ||||
-rw-r--r-- | tests/utils.py | 48 |
2 files changed, 121 insertions, 40 deletions
diff --git a/tests/alter_time.py b/tests/alter_time.py new file mode 100644 index 0000000..f426e03 --- /dev/null +++ b/tests/alter_time.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This code is in the public domain +# Author: Raphaël Barrois + + +from __future__ import print_function + +import datetime +import mock + + +real_datetime_class = datetime.datetime + +def mock_datetime_now(target, datetime_module): + """Override ``datetime.datetime.now()`` with a custom target value. + + This creates a new datetime.datetime class, and alters its now()/utcnow() + methods. + + Returns: + A mock.patch context, can be used as a decorator or in a with. + """ + + # See http://bugs.python.org/msg68532 + # And http://docs.python.org/reference/datamodel.html#customizing-instance-and-subclass-checks + class DatetimeSubclassMeta(type): + """We need to customize the __instancecheck__ method for isinstance(). + + This must be performed at a metaclass level. + """ + + @classmethod + def __instancecheck__(mcs, obj): + return isinstance(obj, real_datetime_class) + + class BaseMockedDatetime(real_datetime_class): + @classmethod + def now(cls, tz=None): + return target.replace(tzinfo=tz) + + @classmethod + def utcnow(cls): + return target + + # Python2 & Python3-compatible metaclass + MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {}) + + return mock.patch.object(datetime_module, 'datetime', MockedDatetime) + +real_date_class = datetime.date + +def mock_date_today(target, datetime_module): + """Override ``datetime.date.today()`` with a custom target value. + + This creates a new datetime.date class, and alters its today() method. + + Returns: + A mock.patch context, can be used as a decorator or in a with. + """ + + # See http://bugs.python.org/msg68532 + # And http://docs.python.org/reference/datamodel.html#customizing-instance-and-subclass-checks + class DateSubclassMeta(type): + """We need to customize the __instancecheck__ method for isinstance(). + + This must be performed at a metaclass level. + """ + + @classmethod + def __instancecheck__(mcs, obj): + return isinstance(obj, real_date_class) + + class BaseMockedDate(real_date_class): + @classmethod + def today(cls): + return target + + # Python2 & Python3-compatible metaclass + MockedDate = DateSubclassMeta('date', (BaseMockedDate,), {}) + + return mock.patch.object(datetime_module, 'date', MockedDate) + + +def main(): + """Run a couple of tests""" + target_dt = real_datetime_class(2009, 1, 1) + target_date = real_date_class(2009, 1, 1) + + print("Entering mock") + with mock_datetime_now(target_dt, datetime): + print("- now ->", datetime.datetime.now()) + print("- isinstance(now, dt) ->", isinstance(datetime.datetime.now(), datetime.datetime)) + print("- isinstance(target, dt) ->", isinstance(target_dt, datetime.datetime)) + + with mock_date_today(target_date, datetime): + print("- today ->", datetime.date.today()) + print("- isinstance(now, date) ->", isinstance(datetime.date.today(), datetime.date)) + print("- isinstance(target, date) ->", isinstance(target_date, datetime.date)) + + + print("Outside mock") + print("- now ->", datetime.datetime.now()) + print("- isinstance(now, dt) ->", isinstance(datetime.datetime.now(), datetime.datetime)) + print("- isinstance(target, dt) ->", isinstance(target_dt, datetime.datetime)) + + print("- today ->", datetime.date.today()) + print("- isinstance(now, date) ->", isinstance(datetime.date.today(), datetime.date)) + print("- isinstance(target, date) ->", isinstance(target_date, datetime.date)) + + +if __name__ == '__main__': + main() diff --git a/tests/utils.py b/tests/utils.py index 823e82b..0111df3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -23,50 +23,23 @@ import datetime from .compat import mock +from . import alter_time class MultiModulePatcher(object): """An abstract context processor for patching multiple modules.""" - replaced_symbol = None - replaced_symbol_module = None - module_imported_as = '' - def __init__(self, *target_modules, **kwargs): super(MultiModulePatcher, self).__init__(**kwargs) - if not self.module_imported_as: - self.module_imported_as = replaced_symbol.__module__.__name__ self.patchers = [self._build_patcher(mod) for mod in target_modules] - def _check_module(self, target_module): - if not self.replaced_symbol_module: - # No check to perform - return - - replaced_import = getattr(target_module, self.module_imported_as) - assert replaced_import is self.replaced_symbol_module, ( - "Cannot patch import %s.%s (%r != %r)" % ( - target_module.__name__, self.module_imported_as, - replaced_import, self.replaced_symbol_module)) - def _build_patcher(self, target_module): """Build a mock patcher for the target module.""" - self._check_module(target_module) - - return mock.patch.object( - getattr(target_module, self.module_imported_as), - self.replaced_symbol.__name__, - mock.Mock(wraps=self.replaced_symbol), - ) - - def setup_mocked_symbol(self, mocked_symbol): - """Setup a mocked symbol for later use.""" - pass + raise NotImplementedError() def __enter__(self): for patcher in self.patchers: mocked_symbol = patcher.start() - self.setup_mocked_symbol(mocked_symbol) def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): for patcher in self.patchers: @@ -75,26 +48,21 @@ class MultiModulePatcher(object): class mocked_date_today(MultiModulePatcher): """A context processor changing the value of date.today().""" - replaced_symbol = datetime.date - replaced_symbol_module = datetime - module_imported_as = 'datetime' def __init__(self, target_date, *target_modules, **kwargs): self.target_date = target_date super(mocked_date_today, self).__init__(*target_modules, **kwargs) - def setup_mocked_symbol(self, mocked_date): - mocked_date.today.return_value = self.target_date + def _build_patcher(self, target_module): + module_datetime = getattr(target_module, 'datetime') + return alter_time.mock_date_today(self.target_date, module_datetime) class mocked_datetime_now(MultiModulePatcher): - replaced_symbol = datetime.datetime - replaced_symbol_module = datetime - module_imported_as = 'datetime' - def __init__(self, target_dt, *target_modules, **kwargs): self.target_dt = target_dt super(mocked_datetime_now, self).__init__(*target_modules, **kwargs) - def setup_mocked_symbol(self, mocked_datetime): - mocked_datetime.now.return_value = self.target_dt + def _build_patcher(self, target_module): + module_datetime = getattr(target_module, 'datetime') + return alter_time.mock_datetime_now(self.target_dt, module_datetime) |