diff --git a/adafruit_itertools/__init__.py b/adafruit_itertools/__init__.py index b3ec117..6617ba4 100644 --- a/adafruit_itertools/__init__.py +++ b/adafruit_itertools/__init__.py @@ -342,17 +342,34 @@ def islice(p, start, stop=(), step=1): if stop == (): stop = start start = 0 + if stop is not None and stop < 0: + raise ValueError("stop must be None or >= 0") + if start < 0: + raise ValueError("start must be >= 0") + if step <= 0: + raise ValueError("step must be > 0") + # TODO: optimizing or breaking semantics? if stop is not None and start >= stop: return it = iter(p) for _ in range(start): - next(it) + try: + next(it) + except StopIteration: + return while True: - yield next(it) + try: + val = next(it) + except StopIteration: + return + yield val for _ in range(step - 1): - next(it) + try: + next(it) + except StopIteration: + return start += step if stop is not None and start >= stop: return diff --git a/tests/README.rst b/tests/README.rst new file mode 100644 index 0000000..ab25406 --- /dev/null +++ b/tests/README.rst @@ -0,0 +1,24 @@ +.. + SPDX-FileCopyrightText: KB Sriram + SPDX-License-Identifier: MIT +.. + +Itertools Tests +=============== + +These tests run under CPython, and are intended to verify that the +Adafruit library functions return the same outputs compared to ones in +the standard `itertools` module. + +These tests run automatically from the standard `circuitpython github +workflow `_. To run them manually, first install these packages +if necessary:: + + $ pip3 install pytest + +Then ensure you're in the *root* directory of the repository and run +the following command:: + + $ python -m pytest + +.. _wf: https://github.com/adafruit/workflows-circuitpython-libs/blob/6e1562eaabced4db1bd91173b698b1cc1dfd35ab/build/action.yml#L78-L84 diff --git a/tests/test_itertools.py b/tests/test_itertools.py new file mode 100644 index 0000000..9a52b5b --- /dev/null +++ b/tests/test_itertools.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: KB Sriram +# SPDX-License-Identifier: MIT + +from typing import Iterator, Optional, Sequence, TypeVar +import itertools as it +import pytest +import adafruit_itertools as ait + +_T = TypeVar("_T") + + +@pytest.mark.parametrize( + "seq, start", + [ + ("", 0), + ("", 2), + ("ABCDEFG", 0), + ("ABCDEFG", 2), + ("ABCDEFG", 20), + ], +) +def test_islice_start(seq: Sequence[_T], start: int) -> None: + x: Iterator[_T] = ait.islice(seq, start) + y: Iterator[_T] = it.islice(seq, start) + assert list(x) == list(y) + + +@pytest.mark.parametrize( + "seq, start, stop", + [ + ("", 0, 5), + ("", 2, 5), + ("", 0, 0), + ("ABCDEFG", 2, 2), + ("ABCDEFG", 2, 6), + ("ABCDEFG", 2, None), + ("ABCDEFG", 2, 17), + ("ABCDEFG", 20, 30), + ], +) +def test_islice_start_stop(seq: Sequence[_T], start: int, stop: Optional[int]) -> None: + x: Iterator[_T] = ait.islice(seq, start, stop) + y: Iterator[_T] = it.islice(seq, start, stop) + assert list(x) == list(y) + + +@pytest.mark.parametrize( + "seq, start, stop, step", + [ + ("", 0, 5, 3), + ("", 2, 5, 2), + ("", 0, 0, 1), + ("ABCDEFG", 2, 2, 2), + ("ABCDEFG", 2, 6, 3), + ("ABCDEFG", 2, 17, 2), + ("ABCDEFG", 0, None, 2), + ("ABCDEFG", 20, 30, 3), + ("ABCDEFG", 0, None, 3), + ], +) +def test_islice_start_stop_step( + seq: Sequence[_T], start: int, stop: Optional[int], step: int +) -> None: + x: Iterator[_T] = ait.islice(seq, start, stop, step) + y: Iterator[_T] = it.islice(seq, start, stop, step) + assert list(x) == list(y) + + +def test_islice_error() -> None: + with pytest.raises(ValueError): + list(ait.islice("abc", -1)) + with pytest.raises(ValueError): + list(ait.islice("abc", 0, -1)) + with pytest.raises(ValueError): + list(ait.islice("abc", 0, 0, 0))