Skip to content

Commit fb21a14

Browse files
committed
Add new factory functions to testing.py
1 parent 59c194e commit fb21a14

File tree

6 files changed

+140
-15
lines changed

6 files changed

+140
-15
lines changed

__pkginfo__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
web = github_url = "https://github.com/domdfcoding/domdf_python_tools"
5252
repo_root = pathlib.Path(__file__).parent
5353
install_requires = (repo_root / "requirements.txt").read_text(encoding="utf-8").split('\n')
54-
extras_require = {'dates': ['pytz>=2019.1'], 'all': ['pytz>=2019.1']}
54+
extras_require = {'dates': ['pytz>=2019.1'], 'testing': ['pytest>=6.0.0'], 'all': ['pytest>=6.0.0', 'pytz>=2019.1']}
5555

5656

5757

domdf_python_tools/testing.py

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@
3131
# stdlib
3232
import itertools
3333
import random
34+
import sys
3435
from functools import lru_cache
35-
from typing import Any, Iterator, List, Sequence
36+
from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union
3637

3738
# 3rd party
39+
import _pytest
3840
import pytest
3941
from _pytest.mark import MarkDecorator
4042

4143
# this package
44+
from domdf_python_tools.doctools import PYPY
4245
from domdf_python_tools.utils import Len
46+
from domdf_python_tools.versions import Version
4347

4448
__all__ = [
4549
"generate_truthy_values",
@@ -49,6 +53,12 @@
4953
"whitespace_perms_list",
5054
"whitespace_perms",
5155
"count",
56+
"min_version",
57+
"max_version",
58+
"not_windows",
59+
"only_windows",
60+
"not_pypy",
61+
"only_pypy",
5262
]
5363

5464

@@ -199,3 +209,110 @@ def count(stop: int, start: int = 0, step: int = 1) -> MarkDecorator:
199209
"""
200210

201211
return pytest.mark.parametrize("count", range(start, stop, step))
212+
213+
214+
def _make_version(version: Union[str, float, Tuple[int, ...]]) -> Version:
215+
if isinstance(version, float):
216+
return Version.from_float(version)
217+
elif isinstance(version, str):
218+
return Version.from_str(version)
219+
else:
220+
return Version.from_tuple(version)
221+
222+
223+
def min_version(
224+
version: Union[str, float, Tuple[int]],
225+
reason: Optional[str] = None,
226+
) -> _pytest.mark.structures.MarkDecorator:
227+
"""
228+
Factory function to return a ``@pytest.mark.skipif`` decorator that will
229+
skip a test if the current Python version is less than the required one.
230+
231+
:param version: The version number to compare to :py:data:`sys.version_info`.
232+
:param reason: The reason to display when skipping.
233+
:default reason: ``'Requires Python <version> or greater.'``
234+
235+
.. versionadded:: 0.9.0
236+
"""
237+
238+
version_ = _make_version(version)
239+
240+
if reason is None:
241+
reason = f"Requires Python {version_} or greater."
242+
243+
return pytest.mark.skipif(condition=sys.version_info[:3] < version_, reason=reason)
244+
245+
246+
def max_version(
247+
version: Union[str, float, Tuple[int]],
248+
reason: Optional[str] = None,
249+
) -> _pytest.mark.structures.MarkDecorator:
250+
"""
251+
Factory function to return a ``@pytest.mark.skipif`` decorator that will
252+
skip a test if the current Python version is greater than the required one.
253+
254+
:param version: The version number to compare to :py:data:`sys.version_info`.
255+
:param reason: The reason to display when skipping.
256+
:default reason: ``'Not needed after Python <version>.'``
257+
258+
.. versionadded:: 0.9.0
259+
"""
260+
261+
version_ = _make_version(version)
262+
263+
if reason is None:
264+
reason = f"Not needed after Python {version_}."
265+
266+
return pytest.mark.skipif(condition=sys.version_info[:3] > version_, reason=reason)
267+
268+
269+
def not_windows(reason: str = "Not required on Windows.", ) -> _pytest.mark.structures.MarkDecorator:
270+
"""
271+
Factory function to return a ``@pytest.mark.skipif`` decorator that will
272+
skip a test if the current platform is Windows.
273+
274+
:param reason: The reason to display when skipping.
275+
276+
.. versionadded:: 0.9.0
277+
"""
278+
279+
return pytest.mark.skipif(condition=sys.platform == "win32", reason=reason)
280+
281+
282+
def only_windows(reason: str = "Only required on Windows.", ) -> _pytest.mark.structures.MarkDecorator:
283+
"""
284+
Factory function to return a ``@pytest.mark.skipif`` decorator that will
285+
skip a test if the current platform is **not** Windows.
286+
287+
:param reason: The reason to display when skipping.
288+
289+
.. versionadded:: 0.9.0
290+
"""
291+
292+
return pytest.mark.skipif(condition=sys.platform != "win32", reason=reason)
293+
294+
295+
def not_pypy(reason: str = "Not required on PyPy.", ) -> _pytest.mark.structures.MarkDecorator:
296+
"""
297+
Factory function to return a ``@pytest.mark.skipif`` decorator that will
298+
skip a test if the current Python implementation is PyPy.
299+
300+
:param reason: The reason to display when skipping.
301+
302+
.. versionadded:: 0.9.0
303+
"""
304+
305+
return pytest.mark.skipif(condition=PYPY, reason=reason)
306+
307+
308+
def only_pypy(reason: str = "Only required on PyPy.", ) -> _pytest.mark.structures.MarkDecorator:
309+
"""
310+
Factory function to return a ``@pytest.mark.skipif`` decorator that will
311+
skip a test if the current Python implementation is not PyPy.
312+
313+
:param reason: The reason to display when skipping.
314+
315+
.. versionadded:: 0.9.0
316+
"""
317+
318+
return pytest.mark.skipif(condition=PYPY, reason=reason)

domdf_python_tools/versions.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,15 @@ def from_tuple(cls, version_tuple: Tuple[Union[str, int], ...]) -> "Version":
189189
190190
:param version_tuple: The version number.
191191
192-
:return: The created Version
192+
:return: The created :class:`~domdf_python_tools.versions.Version`.
193+
194+
.. versionchanged:: 0.9.0
195+
196+
Tuples with more than three elements are truncated.
197+
Previously a :exc:`TypeError` was raised.
193198
"""
194199

195-
return cls(*(int(x) for x in version_tuple))
200+
return cls(*(int(x) for x in version_tuple[:3]))
196201

197202
@classmethod
198203
def from_float(cls, version_float: float) -> "Version":
@@ -216,7 +221,7 @@ def _iter_string(version_string: str) -> Generator[int, None, None]:
216221
:return: Iterable elements of the version.
217222
"""
218223

219-
return (int(x) for x in version_string.split("."))
224+
return (int(x) for x in re.split("[.,]", version_string))
220225

221226

222227
def _iter_float(version_float: float) -> Generator[int, None, None]:

tests/test_paths.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# this package
2323
from domdf_python_tools import paths
2424
from domdf_python_tools.paths import PathPlus, clean_writer, copytree
25+
from domdf_python_tools.testing import not_pypy, not_windows
2526

2627

2728
def test_maybe_make():
@@ -156,7 +157,7 @@ def test_parent_path():
156157
assert str(paths.parent_path("spam/spam/spam")) == os.path.join("spam", "spam")
157158

158159

159-
@pytest.mark.skipif(sys.platform == "win32", reason="Needs special-casing for Windows")
160+
@not_windows("Needs special-casing for Windows")
160161
@pytest.mark.parametrize(
161162
"relto, relpath",
162163
[
@@ -288,7 +289,7 @@ def test_pathplus_write_clean(input_string, output_string):
288289
assert tempfile.read_text() == "\n".join(output_string)
289290

290291

291-
@pytest.mark.xfail(reason="Unsupported on PyPy3 <7.2", condition=(platform.python_implementation() == "PyPy"))
292+
@not_pypy()
292293
def test_make_executable():
293294
with TemporaryDirectory() as tmpdir:
294295
tempfile = pathlib.Path(tmpdir) / "tmpfile.sh"

tests/test_paths_stdlib.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
# this package
2828
from domdf_python_tools.paths import PathPlus, PosixPathPlus, WindowsPathPlus
29+
from domdf_python_tools.testing import min_version
2930

3031
try:
3132
# stdlib
@@ -236,7 +237,7 @@ def test_unlink(self):
236237
self.assertFileNotFound(p.stat)
237238
self.assertFileNotFound(p.unlink)
238239

239-
@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9 or higher")
240+
@min_version(3.9, "Requires Python 3.9 or higher")
240241
def test_unlink_missing_ok(self): # pragma: no cover (<py37)
241242
p = PathPlus(BASE) / 'fileAAA'
242243
self.assertFileNotFound(p.unlink)
@@ -250,7 +251,7 @@ def test_rmdir(self):
250251
self.assertFileNotFound(p.stat)
251252
self.assertFileNotFound(p.unlink)
252253

253-
@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9 or higher")
254+
@min_version(3.9, "Requires Python 3.9 or higher")
254255
@unittest.skipUnless(hasattr(os, "link"), "os.link() is not present")
255256
def test_link_to(self): # pragma: no cover (<py37)
256257
P = PathPlus(BASE)
@@ -340,7 +341,7 @@ def test_replace(self):
340341
self.assertEqual(os.stat(r).st_size, size)
341342
self.assertFileNotFound(q.stat)
342343

343-
@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9 or higher")
344+
@min_version(3.9, "Requires Python 3.9 or higher")
344345
@with_symlinks
345346
def test_readlink(self): # pragma: no cover (<py39)
346347
P = PathPlus(BASE)
@@ -544,7 +545,7 @@ def test_is_file(self):
544545
self.assertFalse((P / 'linkB').is_file())
545546
self.assertFalse((P / 'brokenLink').is_file())
546547

547-
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
548+
@min_version(3.9, "Requires Python 3.9 or higher")
548549
@only_posix
549550
def test_is_mount(self): # pragma: no cover (<py37)
550551
P = PathPlus(BASE)

tests/test_terminal.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# this package
1313
from domdf_python_tools import terminal_colours
1414
from domdf_python_tools.terminal import Echo, br, clear, interrupt, overtype
15+
from domdf_python_tools.testing import not_windows, only_windows
1516

1617
fake = Faker()
1718
fake.add_provider(internet)
@@ -44,7 +45,7 @@ def test_br(capsys):
4445
assert stdout == ["foo", '', "bar", '']
4546

4647

47-
@pytest.mark.skipif(condition=os.name != "nt", reason="Different test used for POSIX")
48+
@only_windows(reason="Different test used for POSIX")
4849
def test_interrupt_windows(capsys):
4950
interrupt()
5051

@@ -53,7 +54,7 @@ def test_interrupt_windows(capsys):
5354
assert stdout == ["(Press Ctrl-C to quit at any time.)", '']
5455

5556

56-
@pytest.mark.skipif(condition=os.name == "nt", reason="Different test used for Windows")
57+
@not_windows(reason="Different test used for Windows")
5758
def test_interrupt_posix(capsys):
5859
interrupt()
5960

@@ -62,7 +63,7 @@ def test_interrupt_posix(capsys):
6263
assert stdout == ["(Press Ctrl-D to quit at any time.)", '']
6364

6465

65-
# @pytest.mark.skipif(condition=os.name != "nt", reason="Different test used for POSIX")
66+
# @only_windows(reason="Different test used for POSIX")
6667
# def test_clear_windows(capsys):
6768
# clear()
6869
#
@@ -72,7 +73,7 @@ def test_interrupt_posix(capsys):
7273
#
7374

7475

75-
@pytest.mark.skipif(condition=os.name == "nt", reason="Different test used for Windows")
76+
@not_windows(reason="Different test used for Windows")
7677
def test_clear_posix(capsys):
7778
clear()
7879

0 commit comments

Comments
 (0)