Skip to content

Commit

Permalink
add hypothesis tests for TimeStamp and TimeSpan
Browse files Browse the repository at this point in the history
  • Loading branch information
cleder committed Nov 5, 2024
1 parent bfb5158 commit 45bbc7b
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 27 deletions.
23 changes: 19 additions & 4 deletions fastkml/times.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,30 @@ def __init__(
resolution: Optional[DateTimeResolution] = None,
) -> None:
"""Initialize a KmlDateTime object."""
self.dt = dt
self.resolution = resolution
if resolution is None:
# sourcery skip: swap-if-expression
self.resolution = (
resolution = (
DateTimeResolution.date
if not isinstance(dt, datetime)
else DateTimeResolution.datetime
)
dt = (
dt.date()
if isinstance(dt, datetime) and resolution != DateTimeResolution.datetime
else dt
)
if resolution == DateTimeResolution.year:
self.dt = date(dt.year, 1, 1)
elif resolution == DateTimeResolution.year_month:
self.dt = date(dt.year, dt.month, 1)
else:
self.dt = dt
self.resolution = (
DateTimeResolution.date
if not isinstance(self.dt, datetime)
and resolution == DateTimeResolution.datetime
else resolution
)

def __repr__(self) -> str:
"""Create a string (c)representation for KmlDateTime."""
Expand Down Expand Up @@ -142,7 +157,7 @@ def parse(cls, datestr: str) -> Optional["KmlDateTime"]:
year = int(year_month_day_match.group("year"))
month = int(year_month_day_match.group("month") or 1)
day = int(year_month_day_match.group("day") or 1)
dt = arrow.get(year, month, day).datetime
dt = date(year, month, day)
resolution = DateTimeResolution.date
if year_month_day_match.group("day") is None:
resolution = DateTimeResolution.year_month
Expand Down
14 changes: 2 additions & 12 deletions tests/hypothesis/gx_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Test gx Track and MultiTrack."""
import datetime
import typing

from hypothesis import given
from hypothesis import strategies as st
from hypothesis.extra.dateutil import timezones
from pygeoif.hypothesis.strategies import epsg4326
from pygeoif.hypothesis.strategies import points

Expand All @@ -29,12 +27,12 @@
import fastkml.types
from fastkml.gx import Angle
from fastkml.gx import TrackItem
from fastkml.times import KmlDateTime
from tests.base import Lxml
from tests.hypothesis.common import assert_repr_roundtrip
from tests.hypothesis.common import assert_str_roundtrip
from tests.hypothesis.common import assert_str_roundtrip_terse
from tests.hypothesis.common import assert_str_roundtrip_verbose
from tests.hypothesis.strategies import kml_datetimes
from tests.hypothesis.strategies import nc_name

track_items = st.builds(
Expand All @@ -51,15 +49,7 @@
),
),
coord=points(srs=epsg4326),
when=st.builds(
KmlDateTime,
dt=st.datetimes(
allow_imaginary=False,
timezones=timezones(),
min_value=datetime.datetime(2000, 1, 1), # noqa: DTZ001
max_value=datetime.datetime(2050, 1, 1), # noqa: DTZ001
),
),
when=kml_datetimes(),
)


Expand Down
26 changes: 26 additions & 0 deletions tests/hypothesis/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

"""Custom hypothesis strategies for testing."""
import datetime
import re
import string
from functools import partial
from typing import Final
from urllib.parse import urlencode

from hypothesis import strategies as st
from hypothesis.extra.dateutil import timezones

import fastkml.enums
from fastkml.times import KmlDateTime

ID_TEXT: Final = string.ascii_letters + string.digits + ".-_"
nc_name = partial(
Expand All @@ -48,6 +53,27 @@
alphabet=st.characters(min_codepoint=1, blacklist_categories=("Cc", "Cs")),
)

kml_datetimes = partial(
st.builds,
KmlDateTime,
dt=st.one_of(
st.dates(
min_value=datetime.date(2000, 1, 1),
max_value=datetime.date(2050, 1, 1),
),
st.datetimes(
allow_imaginary=False,
timezones=timezones(),
min_value=datetime.datetime(2000, 1, 1), # noqa: DTZ001
max_value=datetime.datetime(2050, 1, 1), # noqa: DTZ001
),
),
resolution=st.one_of(
st.none(),
st.one_of(st.none(), st.sampled_from(fastkml.enums.DateTimeResolution)),
),
)


@st.composite
def query_strings(draw: st.DrawFn) -> str:
Expand Down
76 changes: 76 additions & 0 deletions tests/hypothesis/times_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright (C) 2024 Christian Ledermann
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
Property-based tests for the times module.
These tests use the hypothesis library to generate random input for the
functions under test. The tests are run with pytest.
"""
import typing

from hypothesis import given
from hypothesis import strategies as st

import fastkml
import fastkml.enums
import fastkml.times
from tests.base import Lxml
from tests.hypothesis.common import assert_repr_roundtrip
from tests.hypothesis.common import assert_str_roundtrip
from tests.hypothesis.common import assert_str_roundtrip_terse
from tests.hypothesis.common import assert_str_roundtrip_verbose
from tests.hypothesis.strategies import kml_datetimes
from tests.hypothesis.strategies import nc_name


class TestTimes(Lxml):
@given(
id=st.one_of(st.none(), nc_name()),
target_id=st.one_of(st.none(), nc_name()),
timestamp=kml_datetimes(),
)
def test_fuzz_time_stamp(
self,
id: typing.Optional[str],
target_id: typing.Optional[str],
timestamp: typing.Optional[fastkml.times.KmlDateTime],
) -> None:
time_stamp = fastkml.TimeStamp(id=id, target_id=target_id, timestamp=timestamp)

assert_repr_roundtrip(time_stamp)
assert_str_roundtrip(time_stamp)
assert_str_roundtrip_terse(time_stamp)
assert_str_roundtrip_verbose(time_stamp)

@given(
id=st.one_of(st.none(), nc_name()),
target_id=st.one_of(st.none(), nc_name()),
begin=kml_datetimes(),
end=kml_datetimes(),
)
def test_fuzz_time_span(
self,
id: typing.Optional[str],
target_id: typing.Optional[str],
begin: typing.Optional[fastkml.times.KmlDateTime],
end: typing.Optional[fastkml.times.KmlDateTime],
) -> None:
time_span = fastkml.TimeSpan(id=id, target_id=target_id, begin=begin, end=end)

assert_repr_roundtrip(time_span)
assert_str_roundtrip(time_span)
assert_str_roundtrip_terse(time_span)
assert_str_roundtrip_verbose(time_span)
22 changes: 11 additions & 11 deletions tests/times_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_parse_year(self) -> None:

assert dt
assert dt.resolution == DateTimeResolution.year
assert dt.dt == datetime.datetime(2000, 1, 1, tzinfo=tzutc())
assert dt.dt == datetime.date(2000, 1, 1)

def test_parse_year_0(self) -> None:
with pytest.raises(
Expand All @@ -116,14 +116,14 @@ def test_parse_year_month(self) -> None:

assert dt
assert dt.resolution == DateTimeResolution.year_month
assert dt.dt == datetime.datetime(2000, 3, 1, tzinfo=tzutc())
assert dt.dt == datetime.date(2000, 3, 1)

def test_parse_year_month_no_dash(self) -> None:
dt = KmlDateTime.parse("200004")

assert dt
assert dt.resolution == DateTimeResolution.year_month
assert dt.dt == datetime.datetime(2000, 4, 1, tzinfo=tzutc())
assert dt.dt == datetime.date(2000, 4, 1)

def test_parse_year_month_0(self) -> None:
with pytest.raises(ValueError, match="month must be in 1..12"):
Expand All @@ -138,14 +138,14 @@ def test_parse_year_month_day(self) -> None:

assert dt
assert dt.resolution == DateTimeResolution.date
assert dt.dt == datetime.datetime(2000, 3, 1, tzinfo=tzutc())
assert dt.dt == datetime.date(2000, 3, 1)

def test_parse_year_month_day_no_dash(self) -> None:
dt = KmlDateTime.parse("20000401")

assert dt
assert dt.resolution == DateTimeResolution.date
assert dt.dt == datetime.datetime(2000, 4, 1, tzinfo=tzutc())
assert dt.dt == datetime.date(2000, 4, 1)

def test_parse_year_month_day_0(self) -> None:
with pytest.raises(
Expand Down Expand Up @@ -295,7 +295,7 @@ def test_read_timestamp_year(self) -> None:

assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.year
assert ts.timestamp.dt == datetime.datetime(1997, 1, 1, 0, 0, tzinfo=tzutc())
assert ts.timestamp.dt == datetime.date(1997, 1, 1)

def test_read_timestamp_year_month(self) -> None:
doc = """
Expand All @@ -308,7 +308,7 @@ def test_read_timestamp_year_month(self) -> None:

assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.year_month
assert ts.timestamp.dt == datetime.datetime(1997, 7, 1, 0, 0, tzinfo=tzutc())
assert ts.timestamp.dt == datetime.date(1997, 7, 1)

def test_read_timestamp_ym_no_hyphen(self) -> None:
doc = """
Expand All @@ -321,7 +321,7 @@ def test_read_timestamp_ym_no_hyphen(self) -> None:

assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.year_month
assert ts.timestamp.dt == datetime.datetime(1998, 8, 1, 0, 0, tzinfo=tzutc())
assert ts.timestamp.dt == datetime.date(1998, 8, 1)

def test_read_timestamp_ymd(self) -> None:
doc = """
Expand All @@ -334,7 +334,7 @@ def test_read_timestamp_ymd(self) -> None:

assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.date
assert ts.timestamp.dt == datetime.datetime(1997, 7, 16, 0, 0, tzinfo=tzutc())
assert ts.timestamp.dt == datetime.date(1997, 7, 16)

def test_read_timestamp_utc(self) -> None:
# dateTime (YYYY-MM-DDThh:mm:ssZ)
Expand Down Expand Up @@ -393,7 +393,7 @@ def test_read_timespan(self) -> None:

assert ts.begin
assert ts.begin.resolution == DateTimeResolution.date
assert ts.begin.dt == datetime.datetime(1876, 8, 1, 0, 0, tzinfo=tzutc())
assert ts.begin.dt == datetime.date(1876, 8, 1)
assert ts.end
assert ts.end.resolution == DateTimeResolution.datetime
assert ts.end.dt == datetime.datetime(1997, 7, 16, 7, 30, 15, tzinfo=tzutc())
Expand All @@ -415,7 +415,7 @@ def test_feature_fromstring(self) -> None:

assert d.time_stamp is None
assert d.begin
assert d.begin.dt == datetime.datetime(1876, 8, 1, 0, 0, tzinfo=tzutc())
assert d.begin.dt == datetime.date(1876, 8, 1)
assert d.end
assert d.end.dt == datetime.datetime(1997, 7, 16, 7, 30, 15, tzinfo=tzutc())

Expand Down

0 comments on commit 45bbc7b

Please sign in to comment.