From 061d23797e30cebc8df72e5ac068bd15225cf8b7 Mon Sep 17 00:00:00 2001 From: Zac-HD Date: Mon, 3 Jun 2019 21:02:15 +1000 Subject: [PATCH 1/2] Calculate number of days Some months have fewer than 31 days! Shocking but true. --- hypothesis-python/RELEASE.rst | 4 +++ .../src/hypothesis/searchstrategy/datetime.py | 22 ++++++---------- .../tests/cover/test_datetimes.py | 25 +++---------------- 3 files changed, 16 insertions(+), 35 deletions(-) create mode 100644 hypothesis-python/RELEASE.rst diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..1cf0f12c42 --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,4 @@ +RELEASE_TYPE: patch + +This patch makes :func:`~hypothesis.strategies.datetimes` more efficient, +as it now handles short months correctly by construction instead of filtering. diff --git a/hypothesis-python/src/hypothesis/searchstrategy/datetime.py b/hypothesis-python/src/hypothesis/searchstrategy/datetime.py index 60e99fed98..a43a0b93d0 100644 --- a/hypothesis-python/src/hypothesis/searchstrategy/datetime.py +++ b/hypothesis-python/src/hypothesis/searchstrategy/datetime.py @@ -18,6 +18,7 @@ from __future__ import absolute_import, division, print_function import datetime as dt +from calendar import monthrange from hypothesis.internal.conjecture import utils from hypothesis.searchstrategy.strategies import SearchStrategy @@ -44,12 +45,14 @@ def __init__(self, min_value, max_value, timezones_strat): self.max_dt = max_value self.tz_strat = timezones_strat - def _attempt_one_draw(self, data): + def do_draw(self, data): result = dict() cap_low, cap_high = True, True for name in ("year", "month", "day", "hour", "minute", "second", "microsecond"): low = getattr(self.min_dt if cap_low else dt.datetime.min, name) high = getattr(self.max_dt if cap_high else dt.datetime.max, name) + if name == "day" and not cap_high: + _, high = monthrange(**result) if name == "year": val = utils.integer_range(data, low, high, 2000) else: @@ -57,26 +60,17 @@ def _attempt_one_draw(self, data): result[name] = val cap_low = cap_low and val == low cap_high = cap_high and val == high + result = dt.datetime(**result) tz = data.draw(self.tz_strat) try: - result = dt.datetime(**result) if is_pytz_timezone(tz): # Can't just construct; see http://pytz.sourceforge.net return tz.normalize(tz.localize(result)) return result.replace(tzinfo=tz) except (ValueError, OverflowError): - return None - - def do_draw(self, data): - for _ in range(3): - result = self._attempt_one_draw(data) - if result is not None: - return result - data.note_event( - "3 attempts to create a datetime between %r and %r " - "with timezone from %r failed." % (self.min_dt, self.max_dt, self.tz_strat) - ) - data.mark_invalid() + msg = "Failed to draw a datetime between %r and %r with timezone from %r." + data.note_event(msg % (self.min_dt, self.max_dt, self.tz_strat)) + data.mark_invalid() class DateStrategy(SearchStrategy): diff --git a/hypothesis-python/tests/cover/test_datetimes.py b/hypothesis-python/tests/cover/test_datetimes.py index 26957b8265..598044e5bf 100644 --- a/hypothesis-python/tests/cover/test_datetimes.py +++ b/hypothesis-python/tests/cover/test_datetimes.py @@ -23,9 +23,7 @@ from hypothesis import given from hypothesis.internal.compat import hrange -from hypothesis.internal.conjecture.data import ConjectureData, Status, StopTest -from hypothesis.searchstrategy.datetime import DatetimeStrategy -from hypothesis.strategies import binary, dates, datetimes, none, timedeltas, times +from hypothesis.strategies import dates, datetimes, timedeltas, times from tests.common.debug import find_any, minimal @@ -88,21 +86,6 @@ def test_bordering_on_a_leap_year(): assert x.year == 2004 -def test_DatetimeStrategy_draw_may_fail(): - def is_failure_inducing(b): - try: - return strat._attempt_one_draw(ConjectureData.for_buffer(b)) is None - except StopTest: - return False - - strat = DatetimeStrategy(dt.datetime.min, dt.datetime.max, none()) - failure_inducing = minimal(binary(), is_failure_inducing, timeout_after=30) - data = ConjectureData.for_buffer(failure_inducing * 100) - with pytest.raises(StopTest): - data.draw(strat) - assert data.status == Status.INVALID - - def test_can_find_after_the_year_2000(): assert minimal(dates(), lambda x: x.year > 2000).year == 2001 @@ -111,9 +94,9 @@ 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 month in hrange(1, 13): - find_any(dates(), lambda x: x.month == month) +@pytest.mark.parametrize("month", hrange(1, 13)) +def test_can_find_each_month(month): + find_any(dates(), lambda x: x.month == month) def test_min_year_is_respected(): From 03a35feb4429c2dca8ae2d795daecdc9dfdb6d09 Mon Sep 17 00:00:00 2001 From: Zac-HD Date: Tue, 4 Jun 2019 23:25:26 +1000 Subject: [PATCH 2/2] De-flake verbosity test --- hypothesis-python/tests/cover/test_verbosity.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/hypothesis-python/tests/cover/test_verbosity.py b/hypothesis-python/tests/cover/test_verbosity.py index d0a8b1f67b..5f11f92a12 100644 --- a/hypothesis-python/tests/cover/test_verbosity.py +++ b/hypothesis-python/tests/cover/test_verbosity.py @@ -23,6 +23,7 @@ from hypothesis._settings import Verbosity, settings from hypothesis.reporting import default as default_reporter, with_reporter from hypothesis.strategies import booleans, integers, lists +from tests.common.debug import minimal from tests.common.utils import capture_out, fails @@ -60,16 +61,11 @@ def test_foo(x): def test_includes_progress_in_verbose_mode(): with capture_verbosity() as o: - - def foo(): - find( - lists(integers()), - lambda x: sum(x) >= 100, - settings=settings(verbosity=Verbosity.verbose, database=None), - ) - - foo() - + minimal( + lists(integers(), min_size=1), + lambda x: sum(x) >= 100, + settings(verbosity=Verbosity.verbose), + ) out = o.getvalue() assert out assert u"Shrunk example" in out