#!/usr/bin/env python
#
# selectors.py
"""
Pytest decorators for selectively running tests.
"""
#
# Copyright © 2020-2021 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
#
# stdlib
import inspect
import sys
from textwrap import dedent
from typing import Callable, Optional, Tuple, Union, cast
# 3rd party
import pytest
from _pytest.mark import MarkDecorator # nodep
from domdf_python_tools.compat import PYPY
from domdf_python_tools.versions import Version
# this package
from coincidence.utils import is_docker
__all__ = (
"min_version",
"max_version",
"only_version",
"not_windows",
"only_windows",
"not_pypy",
"only_pypy",
"not_macos",
"only_macos",
"not_docker",
"not_linux",
"only_linux",
"only_docker",
"platform_boolean_factory",
)
def _make_version(version: Union[str, float, Tuple[int, ...]]) -> Version:
if isinstance(version, float):
return Version.from_float(version)
elif isinstance(version, str):
return Version.from_str(version)
else:
return Version.from_tuple(version)
[docs]def min_version(
version: Union[str, float, Tuple[int, ...]],
reason: Optional[str] = None,
) -> MarkDecorator:
"""
Factory function to return a ``@pytest.mark.skipif`` decorator which will
skip a test if the current Python version is less than the required one.
:param version: The version number to compare to :py:data:`sys.version_info`.
:param reason: The reason to display when skipping.
:default reason: :file:`'Requires Python {<version>} or greater.'`
""" # noqa: D400
version_ = _make_version(version)
if reason is None: # pragma: no cover
reason = f"Requires Python {version_} or greater."
return pytest.mark.skipif(condition=sys.version_info[:3] < version_, reason=reason)
[docs]def max_version(
version: Union[str, float, Tuple[int, ...]],
reason: Optional[str] = None,
) -> MarkDecorator:
"""
Factory function to return a ``@pytest.mark.skipif`` decorator which will
skip a test if the current Python version is greater than the required one.
:param version: The version number to compare to :py:data:`sys.version_info`.
:param reason: The reason to display when skipping.
:default reason: :file:`'Not needed after Python {<version>}.'`
""" # noqa: D400
version_ = _make_version(version)
if reason is None: # pragma: no cover
reason = f"Not needed after Python {version_}."
return pytest.mark.skipif(condition=sys.version_info[:3] > version_, reason=reason)
[docs]def only_version(
version: Union[str, float, Tuple[int, ...]],
reason: Optional[str] = None,
) -> MarkDecorator:
"""
Factory function to return a ``@pytest.mark.skipif`` decorator which will
skip a test if the current Python version not the required one.
:param version: The version number to compare to :py:data:`sys.version_info`.
:param reason: The reason to display when skipping.
:default reason: :file:`'Not needed on Python {<version>}.'`
""" # noqa: D400
version_ = _make_version(version)
if reason is None: # pragma: no cover
reason = f"Not needed on Python {version_}."
return pytest.mark.skipif(condition=sys.version_info[:2] != version_[:2], reason=reason)
not_windows, only_windows = platform_boolean_factory(condition=sys.platform == "win32", platform="Windows")
only_windows.__doc__ = f"""\
{inspect.cleandoc(only_windows.__doc__ or '')}
:rtype:
.. latex:clearpage::
"""
not_macos, only_macos = platform_boolean_factory(condition=sys.platform == "darwin", platform="macOS")
not_linux, only_linux = platform_boolean_factory(
condition=sys.platform == "linux",
platform="Linux",
versionadded="0.2.0"
)
not_linux.__doc__ = f"""\
{inspect.cleandoc(not_linux.__doc__ or '')}
:rtype:
.. latex:clearpage::
"""
not_docker, only_docker = platform_boolean_factory(condition=is_docker(), platform="Docker")
not_docker.__doc__ = cast(str, not_docker.__doc__).replace("the current platform is", "running on")
only_docker.__doc__ = cast(str, only_docker.__doc__).replace("the current platform is", "running on")
not_pypy, only_pypy = platform_boolean_factory(condition=PYPY, platform="PyPy")
not_pypy.__doc__ = cast(str, not_pypy.__doc__).replace("current platform", "current Python implementation")
only_pypy.__doc__ = cast(str, only_pypy.__doc__).replace("current platform", "current Python implementation")