Skip to content

Commit

Permalink
Ruff format tests (#719)
Browse files Browse the repository at this point in the history
* ruff format tests

* log changes

* create missing pytz.UnknownTimeZoneError

---------

Co-authored-by: Steve Piercy <[email protected]>
  • Loading branch information
niccokunzmann and stevepiercy authored Oct 16, 2024
1 parent 48d471e commit 6efa2d3
Show file tree
Hide file tree
Showing 48 changed files with 1,926 additions and 1,351 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Changelog

Minor changes:

- Format test code with Ruff. See `Issue 672 <https://github.com/collective/icalendar/issues/672>`_.
- Document the Debian package. See `Issue 701 <https://github.com/collective/icalendar/issues/701>`_.

Breaking changes:
Expand Down
141 changes: 87 additions & 54 deletions src/icalendar/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@
except ImportError:
import zoneinfo
import pytest

import icalendar

try:
import pytz
except ImportError:
pytz = None
from datetime import datetime
from dateutil import tz
from icalendar.cal import Component, Calendar
from icalendar.timezone import tzp as _tzp
from icalendar.timezone import TZP
from pathlib import Path
import itertools
import sys
from pathlib import Path

from dateutil import tz

from icalendar.cal import Calendar, Component
from icalendar.timezone import TZP
from icalendar.timezone import tzp as _tzp

HAS_PYTZ = pytz is not None
if HAS_PYTZ:
PYTZ_UTC = [
pytz.utc,
pytz.timezone('UTC'),
pytz.timezone("UTC"),
]
PYTZ_IN_TIMEZONE = [
lambda dt, tzname: pytz.timezone(tzname).localize(dt),
Expand All @@ -34,26 +37,31 @@


class DataSource:
'''A collection of parsed ICS elements (e.g calendars, timezones, events)'''
def __init__(self, data_source_folder:Path, parser):
"""A collection of parsed ICS elements (e.g calendars, timezones, events)"""

def __init__(self, data_source_folder: Path, parser):
self._parser = parser
self._data_source_folder = data_source_folder

def keys(self):
"""Return all the files that could be used."""
return [p.stem for p in self._data_source_folder.iterdir() if p.suffix.lower() == ".ics"]
return [
p.stem
for p in self._data_source_folder.iterdir()
if p.suffix.lower() == ".ics"
]

def __getitem__(self, attribute):
"""Parse a file and return the result stored in the attribute."""
if attribute.endswith(".ics"):
source_file = attribute
attribute = attribute[:-4]
else:
source_file = attribute + '.ics'
source_file = attribute + ".ics"
source_path = self._data_source_folder / source_file
if not source_path.is_file():
raise AttributeError(f"{source_path} does not exist.")
with source_path.open('rb') as f:
with source_path.open("rb") as f:
raw_ics = f.read()
source = self._parser(raw_ics)
if not isinstance(source, list):
Expand All @@ -76,51 +84,67 @@ def __repr__(self):
@property
def multiple(self):
"""Return a list of all components parsed."""
return self.__class__(self._data_source_folder, lambda data: self._parser(data, multiple=True))
return self.__class__(
self._data_source_folder, lambda data: self._parser(data, multiple=True)
)


HERE = Path(__file__).parent
CALENDARS_FOLDER = HERE / 'calendars'
TIMEZONES_FOLDER = HERE / 'timezones'
EVENTS_FOLDER = HERE / 'events'
CALENDARS_FOLDER = HERE / "calendars"
TIMEZONES_FOLDER = HERE / "timezones"
EVENTS_FOLDER = HERE / "events"


@pytest.fixture(scope="module")
def calendars(tzp):
return DataSource(CALENDARS_FOLDER, icalendar.Calendar.from_ical)


@pytest.fixture(scope="module")
def timezones(tzp):
return DataSource(TIMEZONES_FOLDER, icalendar.Timezone.from_ical)


@pytest.fixture(scope="module")
def events(tzp):
return DataSource(EVENTS_FOLDER, icalendar.Event.from_ical)

@pytest.fixture(params=PYTZ_UTC + [
zoneinfo.ZoneInfo('UTC'),
tz.UTC,
tz.gettz('UTC')])

@pytest.fixture(params=PYTZ_UTC + [zoneinfo.ZoneInfo("UTC"), tz.UTC, tz.gettz("UTC")])
def utc(request, tzp):
return request.param

@pytest.fixture(params=PYTZ_IN_TIMEZONE + [
lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)),
lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname))
])

@pytest.fixture(
params=PYTZ_IN_TIMEZONE
+ [
lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)),
lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname)),
]
)
def in_timezone(request, tzp):
return request.param


# exclude broken calendars here
ICS_FILES_EXCLUDE = (
"big_bad_calendar.ics", "issue_104_broken_calendar.ics", "small_bad_calendar.ics",
"multiple_calendar_components.ics", "pr_480_summary_with_colon.ics",
"parsing_error_in_UTC_offset.ics", "parsing_error.ics",
"big_bad_calendar.ics",
"issue_104_broken_calendar.ics",
"small_bad_calendar.ics",
"multiple_calendar_components.ics",
"pr_480_summary_with_colon.ics",
"parsing_error_in_UTC_offset.ics",
"parsing_error.ics",
)
ICS_FILES = [
file.name for file in
itertools.chain(CALENDARS_FOLDER.iterdir(), TIMEZONES_FOLDER.iterdir(), EVENTS_FOLDER.iterdir())
file.name
for file in itertools.chain(
CALENDARS_FOLDER.iterdir(), TIMEZONES_FOLDER.iterdir(), EVENTS_FOLDER.iterdir()
)
if file.name not in ICS_FILES_EXCLUDE
]


@pytest.fixture(params=ICS_FILES)
def ics_file(tzp, calendars, timezones, events, request):
"""An example ICS file."""
Expand All @@ -133,71 +157,76 @@ def ics_file(tzp, calendars, timezones, events, request):


FUZZ_V1 = [key for key in CALENDARS_FOLDER.iterdir() if "fuzz-testcase" in str(key)]


@pytest.fixture(params=FUZZ_V1)
def fuzz_v1_calendar(request):
"""Clusterfuzz calendars."""
return request.param


@pytest.fixture()
@pytest.fixture
def x_sometime():
"""Map x_sometime to time"""
icalendar.cal.types_factory.types_map['X-SOMETIME'] = 'time'
icalendar.cal.types_factory.types_map["X-SOMETIME"] = "time"
yield
icalendar.cal.types_factory.types_map.pop('X-SOMETIME')
icalendar.cal.types_factory.types_map.pop("X-SOMETIME")


@pytest.fixture()
@pytest.fixture
def factory():
"""Return a new component factory."""
return icalendar.ComponentFactory()


@pytest.fixture()
@pytest.fixture
def vUTCOffset_ignore_exceptions():
icalendar.vUTCOffset.ignore_exceptions = True
yield
icalendar.vUTCOffset.ignore_exceptions = False


@pytest.fixture()
@pytest.fixture
def event_component(tzp):
"""Return an event component."""
c = Component()
c.name = 'VEVENT'
c.name = "VEVENT"
return c


@pytest.fixture()
@pytest.fixture
def c(tzp):
"""Return an empty component."""
c = Component()
return c


comp = c

@pytest.fixture()

@pytest.fixture
def calendar_component(tzp):
"""Return an empty component."""
c = Component()
c.name = 'VCALENDAR'
c.name = "VCALENDAR"
return c


@pytest.fixture()
@pytest.fixture
def filled_event_component(c, calendar_component):
"""Return an event with some values and add it to calendar_component."""
e = Component(summary='A brief history of time')
e.name = 'VEVENT'
e.add('dtend', '20000102T000000', encode=0)
e.add('dtstart', '20000101T000000', encode=0)
e = Component(summary="A brief history of time")
e.name = "VEVENT"
e.add("dtend", "20000102T000000", encode=0)
e.add("dtstart", "20000101T000000", encode=0)
calendar_component.add_component(e)
return e


@pytest.fixture()
@pytest.fixture
def calendar_with_resources(tzp):
c = Calendar()
c['resources'] = 'Chair, Table, "Room: 42"'
c["resources"] = 'Chair, Table, "Room: 42"'
return c


Expand All @@ -220,13 +249,13 @@ def other_tzp(request, tzp):
return tzp


@pytest.fixture()
@pytest.fixture
def pytz_only(tzp):
"""Skip tests that are not running under pytz."""
assert tzp.uses_pytz()


@pytest.fixture()
@pytest.fixture
def zoneinfo_only(tzp, request, tzp_name):
"""Skip tests that are not running under zoneinfo."""
assert tzp.uses_zoneinfo()
Expand All @@ -247,14 +276,18 @@ def pytest_generate_tests(metafunc):
tzp_names = ["zoneinfo"]
if "pytz_only" in metafunc.fixturenames:
tzp_names = PYTZ_TZP
assert not ("zoneinfo_only" in metafunc.fixturenames and "pytz_only" in metafunc.fixturenames), "Use pytz_only or zoneinfo_only but not both!"
assert not (
"zoneinfo_only" in metafunc.fixturenames
and "pytz_only" in metafunc.fixturenames
), "Use pytz_only or zoneinfo_only but not both!"
metafunc.parametrize("tzp_name", tzp_names, scope="module")


class DoctestZoneInfo(zoneinfo.ZoneInfo):
"""Constent ZoneInfo representation for tests."""

def __repr__(self):
return f"ZoneInfo(key={repr(self.key)})"
return f"ZoneInfo(key={self.key!r})"


def doctest_print(obj):
Expand All @@ -270,13 +303,13 @@ def doctest_import(name, *args, **kw):
return pytz
return __import__(name, *args, **kw)

@pytest.fixture()

@pytest.fixture
def env_for_doctest(monkeypatch):
"""Modify the environment to make doctests run."""
monkeypatch.setitem(sys.modules, "zoneinfo", zoneinfo)
monkeypatch.setattr(zoneinfo, "ZoneInfo", DoctestZoneInfo)
from icalendar.timezone.zoneinfo import ZONEINFO

monkeypatch.setattr(ZONEINFO, "utc", zoneinfo.ZoneInfo("UTC"))
return {
"print": doctest_print
}
return {"print": doctest_print}
7 changes: 5 additions & 2 deletions src/icalendar/tests/fuzzed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
Some more tests can be added to make sure that the behavior works properly.
"""

def fuzz_calendar_v1(from_ical, calendar_string: str, multiple: bool, should_walk: bool):

def fuzz_calendar_v1(
from_ical, calendar_string: str, multiple: bool, should_walk: bool
):
"""Take a from_ical function and reproduce the error.
The calendar_string is a fuzzed input.
Expand All @@ -16,7 +19,7 @@ def fuzz_calendar_v1(from_ical, calendar_string: str, multiple: bool, should_wal
cal = [cal]
for c in cal:
if should_walk:
for event in cal.walk('VEVENT'):
for event in cal.walk("VEVENT"):
event.to_ical()
else:
cal.to_ical()
9 changes: 4 additions & 5 deletions src/icalendar/tests/fuzzed/test_fuzzed_calendars.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""This test tests all fuzzed calendars."""
from icalendar.tests.fuzzed import fuzz_calendar_v1

import icalendar
from icalendar.tests.fuzzed import fuzz_calendar_v1


def test_fuzz_v1(fuzz_v1_calendar):
"""Test a calendar."""
with open(fuzz_v1_calendar, "rb") as f:
fuzz_calendar_v1(
icalendar.Calendar.from_ical,
f.read(),
multiple=True,
should_walk=True
icalendar.Calendar.from_ical, f.read(), multiple=True, should_walk=True
)
24 changes: 10 additions & 14 deletions src/icalendar/tests/hypothesis/test_fuzzing.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
import string
import unittest

from hypothesis import given, settings
import hypothesis.strategies as st
from hypothesis import given, settings

from icalendar.parser import Contentline, Contentlines, Parameters
import unittest


def printable_characters(**kw):
return st.text(
st.characters(blacklist_categories=(
'Cc', 'Cs'
), **kw)
)
return st.text(st.characters(blacklist_categories=("Cc", "Cs"), **kw))


key = st.text(string.ascii_letters + string.digits, min_size=1)
value = printable_characters(blacklist_characters='\\;:\"')
value = printable_characters(blacklist_characters='\\;:"')

class TestFuzzing(unittest.TestCase):

@given(lines=st.lists(
st.tuples(key, st.dictionaries(key, value), value),
min_size=1
))
class TestFuzzing(unittest.TestCase):
@given(
lines=st.lists(st.tuples(key, st.dictionaries(key, value), value), min_size=1)
)
@settings(max_examples=10**3)
def test_main(self, lines):
cl = Contentlines()
Expand All @@ -33,6 +29,6 @@ def test_main(self, lines):
# Happens when there is a random parameter 'self'...
continue
cl.append(Contentline.from_parts(key, params, value))
cl.append('')
cl.append("")

assert Contentlines.from_ical(cl.to_ical()) == cl
Empty file.
Loading

0 comments on commit 6efa2d3

Please sign in to comment.