1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# 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 https://mozilla.org/MPL/2.0/.
import datetime as dt
import sys
import warnings
import pytest
from hypothesis import assume, given, strategies as st
from hypothesis.errors import InvalidArgument, StopTest
from hypothesis.strategies import data, datetimes, just, sampled_from, times
from hypothesis.strategies._internal.datetime import datetime_does_not_exist
from tests.common.debug import assert_all_examples, find_any, minimal
from tests.common.utils import Why, xfail_on_crosshair
with warnings.catch_warnings():
if sys.version_info[:2] >= (3, 12):
# See https://github.com/stub42/pytz/issues/105 and
# https://github.com/dateutil/dateutil/pull/1285/
warnings.simplefilter("ignore", DeprecationWarning)
import pytz
from dateutil.tz import datetime_exists
from hypothesis.extra.pytz import timezones
def test_utc_is_minimal():
assert pytz.UTC is minimal(timezones())
def test_can_generate_non_naive_time():
assert minimal(times(timezones=timezones()), lambda d: d.tzinfo).tzinfo == pytz.UTC
def test_can_generate_non_naive_datetime():
assert (
minimal(datetimes(timezones=timezones()), lambda d: d.tzinfo).tzinfo == pytz.UTC
)
@given(datetimes(timezones=timezones()))
def test_timezone_aware_datetimes_are_timezone_aware(dt):
assert dt.tzinfo is not None
@given(sampled_from(["min_value", "max_value"]), datetimes(timezones=timezones()))
def test_datetime_bounds_must_be_naive(name, val):
with pytest.raises(InvalidArgument):
datetimes(**{name: val}).validate()
def test_underflow_in_simplify():
# we shouldn't trigger a pytz bug when we're simplifying
minimal(
datetimes(
max_value=dt.datetime.min + dt.timedelta(days=3), timezones=timezones()
),
lambda x: x.tzinfo != pytz.UTC,
)
def test_overflow_in_simplify():
# we shouldn't trigger a pytz bug when we're simplifying
minimal(
datetimes(
min_value=dt.datetime.max - dt.timedelta(days=3), timezones=timezones()
),
lambda x: x.tzinfo != pytz.UTC,
)
def test_timezones_arg_to_datetimes_must_be_search_strategy():
with pytest.raises(InvalidArgument):
datetimes(timezones=pytz.all_timezones).validate()
tz = [pytz.timezone(t) for t in pytz.all_timezones]
with pytest.raises(InvalidArgument):
datetimes(timezones=tz).validate()
@given(times(timezones=timezones()))
def test_timezone_aware_times_are_timezone_aware(dt):
assert dt.tzinfo is not None
def test_can_generate_non_utc():
times(timezones=timezones()).filter(
lambda d: assume(d.tzinfo) and d.tzinfo.zone != "UTC"
).validate()
@given(sampled_from(["min_value", "max_value"]), times(timezones=timezones()))
def test_time_bounds_must_be_naive(name, val):
with pytest.raises(InvalidArgument):
times(**{name: val}).validate()
@pytest.mark.parametrize(
"bound",
[
{"min_value": dt.datetime.max - dt.timedelta(days=3)},
{"max_value": dt.datetime.min + dt.timedelta(days=3)},
],
)
def test_can_trigger_error_in_draw_near_boundary(bound):
found = False
# this would be better written with find_any, but I couldn't get rewriting
# with st.composite and assuming the event condition to work.
# https://github.com/HypothesisWorks/hypothesis/pull/4229#discussion_r1907993831
@given(st.data())
def f(data):
try:
data.draw(datetimes(**bound, timezones=timezones()))
except StopTest:
pass
if "Failed to draw a datetime" in data.conjecture_data.events.get(
"invalid because", ""
):
nonlocal found
found = True
f()
assert found
@given(data(), datetimes(), datetimes())
def test_datetimes_stay_within_naive_bounds(data, lo, hi):
if lo > hi:
lo, hi = hi, lo
out = data.draw(datetimes(lo, hi, timezones=timezones()))
assert lo <= out.replace(tzinfo=None) <= hi
@pytest.mark.parametrize(
"kw",
[
# Ireland uses *negative* offset DST, which means that our sloppy interpretation
# of "is_dst=not fold" bypasses the filter for imaginary times. This is basically
# unfixable without redesigning pytz per PEP-495, and it's much more likely to be
# replaced by dateutil or PEP-615 zoneinfo in the standard library instead.
{
"min_value": dt.datetime(2019, 3, 31),
"max_value": dt.datetime(2019, 4, 1),
"timezones": just(pytz.timezone("Europe/Dublin")),
},
# The day of a spring-forward transition in Australia; 2am is imaginary
# (the common case so an optimistic `is_dst=bool(fold)` also fails the test)
{
"min_value": dt.datetime(2020, 10, 4),
"max_value": dt.datetime(2020, 10, 5),
"timezones": just(pytz.timezone("Australia/Sydney")),
},
],
)
@xfail_on_crosshair(Why.symbolic_outside_context, strict=False)
def test_datetimes_can_exclude_imaginary(kw):
# Sanity check: fail unless those days contain an imaginary hour to filter out
find_any(datetimes(**kw, allow_imaginary=True), lambda x: not datetime_exists(x))
# Assert that with allow_imaginary=False we only generate existing datetimes.
assert_all_examples(datetimes(**kw, allow_imaginary=False), datetime_exists)
def test_really_weird_tzinfo_case():
x = dt.datetime(2019, 3, 31, 2, 30, tzinfo=pytz.timezone("Europe/Dublin"))
assert x.tzinfo is not x.astimezone(dt.timezone.utc).astimezone(x.tzinfo)
# And that weird case exercises the rare branch in our helper:
assert datetime_does_not_exist(x)
|