diff --git a/.gitignore b/.gitignore index a23926c786..a9d46c4591 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ *.swp *.pyc venv* +.cache +.hypothesis +docs/_build +*.egg-info diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0a38169fad..db78d79dfa 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -69,6 +69,7 @@ The primary author for most of Hypothesis is David R. MacIver (me). However the people have also contributed work. As well as my thanks, they also have copyright over their individual contributions. +* `Adam Johnson `_ * `Adam Sven Johnson `_ * `Alex Stapleton `_ * `Charles O'Farrell `_ diff --git a/docs/Makefile b/docs/Makefile index c35bcfb9e2..53eeb2d955 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -56,6 +56,9 @@ html: @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." +livehtml: + sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo diff --git a/docs/extras.rst b/docs/extras.rst index 981d49cae3..b021f71ebf 100644 --- a/docs/extras.rst +++ b/docs/extras.rst @@ -6,7 +6,7 @@ Hypothesis itself does not have any dependencies, but there are some packages th need additional things installed in order to work. You can install these dependencies using the setuptools extra feature as e.g. -pip install hypothesis[django]. This will check installation of compatible versions. +``pip install hypothesis[django]``. This will check installation of compatible versions. You can also just install hypothesis into a project using them, ignore the version constraints, and hope for the best. @@ -20,62 +20,99 @@ you run into a bug with any of these please specify the dependency version. hypothesis[datetime] -------------------- -As might be expected, this provides a strategy which generates instances of -datetime. It depends on pytz to work. +As might be expected, this provides strategies for which generating instances +of objects from the ``datetime`` module: ``datetime``\s, ``date``\s, and +``time``\s. It depends on ``pytz`` to work. -It should work with just about any version of pytz. pytz has a very stable API -and Hypothesis works around a bug or two in older versions. +It should work with just about any version of ``pytz``. ``pytz`` has a very +stable API and Hypothesis works around a bug or two in older versions. -It lives in the hypothesis.extra.datetime package: +It lives in the ``hypothesis.extra.datetime`` package. -.. code-block:: pycon - >>> from hypothesis.extra.datetime import datetimes - >>> datetimes().example() - datetime.datetime(1705, 1, 20, 0, 32, 0, 973139, tzinfo=>> datetimes().example() - datetime.datetime(7274, 6, 9, 23, 0, 31, 75498, tzinfo=>> datetimes(min_year=2001, max_year=2010).example() - datetime.datetime(2010, 7, 7, 0, 15, 0, 614034, tzinfo=>> datetimes(min_year=2001, max_year=2010).example() - datetime.datetime(2006, 9, 26, 22, 0, 0, 220365, tzinfo=>> from hypothesis.extra.datetime import datetimes + >>> datetimes().example() + datetime.datetime(1705, 1, 20, 0, 32, 0, 973139, tzinfo=>> datetimes().example() + datetime.datetime(7274, 6, 9, 23, 0, 31, 75498, tzinfo=>> import pytz - >>> pytz.all_timezones[:3] - ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa'] - >>> datetimes(timezones=pytz.all_timezones[:3]).example() - datetime.datetime(6257, 8, 21, 13, 6, 24, 8751, tzinfo=) - >>> datetimes(timezones=pytz.all_timezones[:3]).example() - datetime.datetime(7851, 2, 3, 0, 0, 0, 767400, tzinfo=) - >>> datetimes(timezones=pytz.all_timezones[:3]).example() - datetime.datetime(8262, 6, 22, 16, 0, 0, 154235, tzinfo=) + >>> datetimes(min_year=2001, max_year=2010).example() + datetime.datetime(2010, 7, 7, 0, 15, 0, 614034, tzinfo=>> datetimes(min_year=2001, max_year=2010).example() + datetime.datetime(2006, 9, 26, 22, 0, 0, 220365, tzinfo=>> datetimes(timezones=[]).example() - datetime.datetime(918, 11, 26, 2, 0, 35, 916439) + >>> import pytz + >>> pytz.all_timezones[:3] + ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa'] + >>> datetimes(timezones=pytz.all_timezones[:3]).example() + datetime.datetime(6257, 8, 21, 13, 6, 24, 8751, tzinfo=) + >>> datetimes(timezones=pytz.all_timezones[:3]).example() + datetime.datetime(7851, 2, 3, 0, 0, 0, 767400, tzinfo=) + >>> datetimes(timezones=pytz.all_timezones[:3]).example() + datetime.datetime(8262, 6, 22, 16, 0, 0, 154235, tzinfo=) -You can also explicitly get a mix of naive and non-naive datetimes if you -want: + If the set of timezones is empty you will get a naive datetime: -.. code-block:: pycon + .. code-block:: pycon + + >>> datetimes(timezones=[]).example() + datetime.datetime(918, 11, 26, 2, 0, 35, 916439) + + You can also explicitly get a mix of naive and non-naive datetimes if you + want: + + .. code-block:: pycon + + >>> datetimes(allow_naive=True).example() + datetime.datetime(2433, 3, 20, 0, 0, 44, 460383, tzinfo=) + >>> datetimes(allow_naive=True).example() + datetime.datetime(7003, 1, 22, 0, 0, 52, 401259) + + +.. method:: dates(min_year=None, max_year=None) + + This strategy generates ``date`` objects. For example: + + .. code-block:: pycon + + >>> from hypothesis.extra.datetime import dates + >>> dates().example() + datetime.date(1687, 3, 23) + >>> dates().example() + datetime.date(9565, 5, 2) + + Again, you can restrict the range with the ``min_year`` and ``max_year`` + arguments. + + +.. method:: times() + + This strategy generates ``time`` objects. For example: + + .. code-block:: pycon + + >>> from hypothesis.extra.datetime import times + >>> times().example() + datetime.time(0, 15, 55, 188712) + >>> times().example() + datetime.time(9, 0, 47, 959374) - >>> datetimes(allow_naive=True).example() - datetime.datetime(2433, 3, 20, 0, 0, 44, 460383, tzinfo=) - >>> datetimes(allow_naive=True).example() - datetime.datetime(7003, 1, 22, 0, 0, 52, 401259) ----------------------- hypothesis[fakefactory] diff --git a/src/hypothesis/extra/datetime.py b/src/hypothesis/extra/datetime.py index 7741057808..6225778178 100644 --- a/src/hypothesis/extra/datetime.py +++ b/src/hypothesis/extra/datetime.py @@ -278,6 +278,31 @@ def datetimes(allow_naive=None, timezones=None, min_year=None, max_year=None): ) +@defines_strategy +def dates(min_year=None, max_year=None): + """Return a strategy for generating dates.""" + return datetimes( + allow_naive=True, timezones=[], + min_year=min_year, max_year=max_year, + ).map(datetime_to_date) + + +def datetime_to_date(dt): + return dt.date() + + +@defines_strategy +def times(allow_naive=None, timezones=None): + """Return a strategy for generating times.""" + return datetimes( + allow_naive=allow_naive, timezones=timezones, + ).map(datetime_to_time) + + +def datetime_to_time(dt): + return dt.timetz() + + @strategy.extend_static(dt.datetime) def datetime_strategy(cls, settings): return datetimes() diff --git a/tests/datetime/test_dates.py b/tests/datetime/test_dates.py new file mode 100644 index 0000000000..33c581cbaf --- /dev/null +++ b/tests/datetime/test_dates.py @@ -0,0 +1,75 @@ +# coding=utf-8 + +# This file is part of Hypothesis (https://github.com/DRMacIver/hypothesis) + +# Most of this work is copyright (C) 2013-2015 David R. MacIver +# (david@drmaciver.com), but it contains contributions by others. See +# https://github.com/DRMacIver/hypothesis/blob/master/CONTRIBUTING.rst for a +# full list of people who may hold copyright, and consult the git log if you +# need to determine who owns an individual contribution. + +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at http://mozilla.org/MPL/2.0/. + +# END HEADER + +from __future__ import division, print_function, absolute_import + +from random import Random +from datetime import MAXYEAR + +import pytest +import hypothesis.settings as hs +from hypothesis.strategytests import strategy_test_suite +from hypothesis.extra.datetime import dates +from hypothesis.internal.debug import minimal +from hypothesis.internal.compat import hrange +from hypothesis.searchstrategy.strategies import BadData + +hs.Settings.default.max_examples = 1000 + + +TestStandardDescriptorFeatures1 = strategy_test_suite(dates()) + + +def test_can_find_after_the_year_2000(): + assert minimal(dates(), lambda x: x.year > 2000).year == 2001 + + +def test_can_find_before_the_year_2000(): + assert minimal(dates(), lambda x: x.year < 2000).year == 1999 + + +def test_can_find_each_month(): + for i in hrange(1, 12): + minimal(dates(), lambda x: x.month == i) + + +def test_min_year_is_respected(): + assert minimal(dates(min_year=2003)).year == 2003 + + +def test_max_year_is_respected(): + assert minimal(dates(max_year=1998)).year == 1998 + + +def test_year_bounds_are_respected_in_deserialization(): + s = dates() + r = Random(1) + template = s.draw_template(r, s.draw_parameter(r)) + year = s.reify(template).year + basic = s.to_basic(template) + above = dates(min_year=year + 1) + below = dates(max_year=year - 1) + with pytest.raises(BadData): + above.from_basic(basic) + with pytest.raises(BadData): + below.from_basic(basic) + + +def test_can_draw_times_in_the_final_year(): + last_year = dates(min_year=MAXYEAR) + r = Random(1) + for _ in hrange(1000): + last_year.reify(last_year.draw_and_produce(r)) diff --git a/tests/datetime/test_times.py b/tests/datetime/test_times.py new file mode 100644 index 0000000000..3abf384592 --- /dev/null +++ b/tests/datetime/test_times.py @@ -0,0 +1,99 @@ +# coding=utf-8 + +# This file is part of Hypothesis (https://github.com/DRMacIver/hypothesis) + +# Most of this work is copyright (C) 2013-2015 David R. MacIver +# (david@drmaciver.com), but it contains contributions by others. See +# https://github.com/DRMacIver/hypothesis/blob/master/CONTRIBUTING.rst for a +# full list of people who may hold copyright, and consult the git log if you +# need to determine who owns an individual contribution. + +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at http://mozilla.org/MPL/2.0/. + +# END HEADER + +from __future__ import division, print_function, absolute_import + +from random import Random + +import pytz +import pytest +import hypothesis.settings as hs +from hypothesis import given, assume +from hypothesis.strategytests import strategy_test_suite +from hypothesis.extra.datetime import times +from hypothesis.internal.debug import minimal +from hypothesis.searchstrategy.strategies import BadData + +hs.Settings.default.max_examples = 1000 + + +TestStandardDescriptorFeatures1 = strategy_test_suite(times()) + + +def test_can_find_midnight(): + minimal( + times(), + lambda x: (x.hour == 0 and x.minute == 0 and x.second == 0), + ) + + +def test_can_find_non_midnight(): + assert minimal(times(), lambda x: x.hour != 0).hour == 1 + + +def test_can_find_off_the_minute(): + minimal(times(), lambda x: x.second == 0) + + +def test_can_find_on_the_minute(): + minimal(times(), lambda x: x.second != 0) + + +def test_simplifies_towards_midnight(): + d = minimal(times()) + assert d.hour == 0 + assert d.minute == 0 + assert d.second == 0 + assert d.microsecond == 0 + + +def test_can_generate_naive_time(): + minimal(times(allow_naive=True), lambda d: not d.tzinfo) + + +def test_can_generate_non_naive_time(): + assert minimal( + times(allow_naive=True), lambda d: d.tzinfo).tzinfo == pytz.UTC + + +def test_can_generate_non_utc(): + minimal( + times(), + lambda d: assume(d.tzinfo) and d.tzinfo.zone != u'UTC') + + +@given(times(timezones=[])) +def test_naive_times_are_naive(dt): + assert not dt.tzinfo + + +@given(times(allow_naive=False)) +def test_timezone_aware_times_are_timezone_aware(dt): + assert dt.tzinfo + + +def test_restricts_to_allowed_set_of_timezones(): + timezones = list(map(pytz.timezone, list(pytz.all_timezones)[:3])) + x = minimal(times(timezones=timezones)) + assert any(tz.zone == x.tzinfo.zone for tz in timezones) + + +def test_timezones_are_checked_in_deserialization(): + s = times() + r = Random(1) + basic = s.to_basic(s.draw_template(r, s.draw_parameter(r))) + with pytest.raises(BadData): + times(timezones=[]).from_basic(basic)