diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 367e62e4f..936a21233 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.720 + rev: v0.740 hooks: - id: mypy exclude: '^(docs|tasks|tests)|setup\.py' diff --git a/docs/tags.rst b/docs/tags.rst index cabe33b21..0851d7893 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -38,6 +38,7 @@ Reference A dictionary mapping interpreter names to their `abbreviation codes`_ (e.g. ``"cpython"`` is ``"cp"``). All interpreter names are lower-case. + .. class:: Tag(interpreter, abi, platform) A representation of the tag triple for a wheel. Instances are considered @@ -65,17 +66,18 @@ Reference .. function:: parse_tag(tag) - Parse the provided *tag* into a set of :class:`Tag` instances. + Parses the provided ``tag`` into a set of :class:`Tag` instances. - The returning of a set is required due to the possibility that the tag is a - `compressed tag set`_, e.g. ``"py2.py3-none-any"``. + Returning a set is required due to the possibility that the tag is a + `compressed tag set`_, e.g. ``"py2.py3-none-any"`` which supports both + Python 2 and Python 3. :param str tag: The tag to parse, e.g. ``"py3-none-any"``. -.. function:: sys_tags(warn=False) +.. function:: sys_tags(*, warn=False) - Create an iterable of tags that the running interpreter supports. + Yields the tags that the running interpreter supports. The iterable is ordered so that the best-matching tag is first in the sequence. The exact preferential order to tags is interpreter-specific, but @@ -92,11 +94,108 @@ Reference The function returns an iterable in order to allow for the possible short-circuiting of tag generation if the entire sequence is not necessary - and calculating some tags happens to be expensive. + and tag calculation happens to be expensive. :param bool warn: Whether warnings should be logged. Defaults to ``False``. +.. function:: interpreter_name() + + Returns the running interpreter's name. + + This typically acts as the prefix to the :attr:`~Tag.interpreter` tag. + + +.. function:: interpreter_version(*, warn=False) + + Returns the running interpreter's version. + + This typically acts as the suffix to the :attr:`~Tag.interpreter` tag. + + +.. function:: mac_platforms(version=None, arch=None) + + Yields the :attr:`~Tag.platform` tags for macOS. + + :param tuple version: A two-item tuple presenting the version of macOS. + Defaults to the current system's version. + :param str arch: The CPU architecture. Defaults to the architecture of the + current system, e.g. ``"x86_64"``. + + .. note:: + Equivalent support for the other major platforms is purposefully not + provided: + + - On Windows, platform compatibility is statically specified + - On Linux, code must be run on the system itself to determine + compatibility + + +.. function:: compatible_tags(python_version=None, interpreter=None, platforms=None) + + Yields the tags for an interpreter compatible with the Python version + specified by ``python_version``. + + The specific tags generated are: + + - ``py*-none-`` + - ``-none-any`` if ``interpreter`` is provided + - ``py*-none-any`` + + :param Sequence python_version: A one- or two-item sequence representing the + compatible version of Python. Defaults to + ``sys.version_info[:2]``. + :param str interpreter: The name of the interpreter (if known), e.g. + ``"cp38"``. Defaults to the current interpreter. + :param Iterable platforms: Iterable of compatible platforms. Defaults to the + platforms compatible with the current system. + +.. function:: cpython_tags(python_version=None, abis=None, platforms=None, *, warn=False) + + Yields the tags for the CPython interpreter. + + The specific tags generated are: + + - ``cp--`` + - ``cp-abi3-`` + - ``cp-none-`` + - ``cp-abi3-`` where "older version" is all older + minor versions down to Python 3.2 (when ``abi3`` was introduced) + + If ``python_version`` only provides a major-only version then only + user-provided ABIs via ``abis`` and the ``none`` ABI will be used. + + :param Sequence python_version: A one- or two-item sequence representing the + targetted Python version. Defaults to + ``sys.version_info[:2]``. + :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs + compatible with the current system. + :param Iterable platforms: Iterable of compatible platforms. Defaults to the + platforms compatible with the current system. + :param bool warn: Whether warnings should be logged. Defaults to ``False``. + +.. function:: generic_tags(interpreter=None, abis=None, platforms=None, *, warn=False) + + Yields the tags for an interpreter which requires no specialization. + + This function should be used if one of the other interpreter-specific + functions provided by this module is not appropriate (i.e. not calculating + tags for a CPython interpreter). + + The specific tags generated are: + + - ``--`` + + The ``"none"`` ABI will be added if it was not explicitly provided. + + :param str interpreter: The name of the interpreter. Defaults to being + calculated. + :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs + compatible with the current system. + :param Iterable platforms: Iterable of compatible platforms. Defaults to the + platforms compatible with the current system. + :param bool warn: Whether warnings should be logged. Defaults to ``False``. + .. _abbreviation codes: https://www.python.org/dev/peps/pep-0425/#python-tag .. _compressed tag set: https://www.python.org/dev/peps/pep-0425/#compressed-tag-sets .. _platform compatibility tags: https://packaging.python.org/specifications/platform-compatibility-tags/ diff --git a/packaging/tags.py b/packaging/tags.py index 73e253147..a719d4e73 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -24,9 +24,19 @@ from ._typing import MYPY_CHECK_RUNNING, cast if MYPY_CHECK_RUNNING: # pragma: no cover - from typing import Dict, FrozenSet, Iterable, Iterator, List, Optional, Tuple, Union + from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + ) - PythonVersion = Tuple[int, int] + PythonVersion = Sequence[int] MacVersion = Tuple[int, int] GlibcVersion = Tuple[int, int] @@ -105,8 +115,24 @@ def parse_tag(tag): return frozenset(tags) +def _warn_keyword_parameter(func_name, kwargs): + # type: (str, Dict[str, bool]) -> bool + """ + Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only. + """ + if not kwargs: + return False + elif len(kwargs) > 1 or "warn" not in kwargs: + kwargs.pop("warn", None) + arg = next(iter(kwargs.keys())) + raise TypeError( + "{}() got an unexpected keyword argument {!r}".format(func_name, arg) + ) + return kwargs["warn"] + + def _get_config_var(name, warn=False): - # type: (str, Optional[bool]) -> Union[int, str, None] + # type: (str, bool) -> Union[int, str, None] value = sysconfig.get_config_var(name) if value is None and warn: logger.debug( @@ -120,14 +146,9 @@ def _normalize_string(string): return string.replace(".", "_").replace("-", "_") -def _cpython_interpreter(py_version): - # type: (PythonVersion) -> str - # TODO: Is using py_version_nodot for interpreter version critical? - return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1]) - - def _cpython_abis(py_version, warn=False): - # type: (PythonVersion, Optional[bool]) -> List[str] + # type: (PythonVersion, bool) -> List[str] + py_version = tuple(py_version) # To allow for version comparison. abis = [] version = "{}{}".format(*py_version[:2]) debug = pymalloc = ucs4 = "" @@ -162,91 +183,150 @@ def _cpython_abis(py_version, warn=False): return abis -def _cpython_tags(py_version, interpreter, abis, platforms): - # type: (PythonVersion, str, Iterable[str], Iterable[str]) -> Iterator[Tag] +def cpython_tags( + python_version=None, # type: Optional[PythonVersion] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + warn = _warn_keyword_parameter("cpython_tags", kwargs) + if not python_version: + python_version = sys.version_info[:2] + + if len(python_version) < 2: + interpreter = "cp{}".format(python_version[0]) + else: + interpreter = "cp{}{}".format(*python_version[:2]) + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or _platform_tags()) for abi in abis: for platform_ in platforms: yield Tag(interpreter, abi, platform_) - for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): - yield tag + # Not worrying about the case of Python 3.2 or older being specified and + # thus having redundant tags thanks to the abi3 in-fill later on as + # 'packaging' doesn't directly support Python that far back. + if len(python_version) > 1: + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): yield tag # PEP 384 was first implemented in Python 3.2. - for minor_version in range(py_version[1] - 1, 1, -1): - for platform_ in platforms: - interpreter = "cp{major}{minor}".format( - major=py_version[0], minor=minor_version - ) - yield Tag(interpreter, "abi3", platform_) - - -def _pypy_interpreter(): - # type: () -> str - # Ignoring sys.pypy_version_info for type checking due to typeshed lacking - # the reference to the attribute. - return "pp{py_major}{pypy_major}{pypy_minor}".format( - py_major=sys.version_info[0], - pypy_major=sys.pypy_version_info.major, # type: ignore - pypy_minor=sys.pypy_version_info.minor, # type: ignore - ) + if len(python_version) > 1: + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{major}{minor}".format( + major=python_version[0], minor=minor_version + ) + yield Tag(interpreter, "abi3", platform_) def _generic_abi(): - # type: () -> str + # type: () -> Iterator[str] abi = sysconfig.get_config_var("SOABI") if abi: - return _normalize_string(abi) - else: - return "none" + yield _normalize_string(abi) -def _pypy_tags(py_version, interpreter, abi, platforms): - # type: (PythonVersion, str, str, Iterable[str]) -> Iterator[Tag] - for tag in (Tag(interpreter, abi, platform) for platform in platforms): - yield tag - for tag in (Tag(interpreter, "none", platform) for platform in platforms): - yield tag +def generic_tags( + interpreter=None, # type: Optional[str] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] + """ + Yields the tags for a generic interpreter. + The tags consist of: + - -- -def _generic_tags(interpreter, py_version, abi, platforms): - # type: (str, PythonVersion, str, Iterable[str]) -> Iterator[Tag] - for tag in (Tag(interpreter, abi, platform) for platform in platforms): - yield tag - if abi != "none": - tags = (Tag(interpreter, "none", platform_) for platform_ in platforms) - for tag in tags: - yield tag + The "none" ABI will be added if it was not explicitly provided. + """ + warn = _warn_keyword_parameter("generic_tags", kwargs) + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + platforms = list(platforms or _platform_tags()) + abis = list(abis) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) def _py_interpreter_range(py_version): # type: (PythonVersion) -> Iterator[str] """ - Yield Python versions in descending order. + Yields Python versions in descending order. After the latest version, the major-only version will be yielded, and then - all following versions up to 'end'. + all previous versions of that major version. """ - yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) + if len(py_version) > 1: + yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) yield "py{major}".format(major=py_version[0]) - for minor in range(py_version[1] - 1, -1, -1): - yield "py{major}{minor}".format(major=py_version[0], minor=minor) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{major}{minor}".format(major=py_version[0], minor=minor) -def _independent_tags(interpreter, py_version, platforms): - # type: (str, PythonVersion, Iterable[str]) -> Iterator[Tag] +def compatible_tags( + python_version=None, # type: Optional[PythonVersion] + interpreter=None, # type: Optional[str] + platforms=None, # type: Optional[Iterator[str]] +): + # type: (...) -> Iterator[Tag] """ - Return the sequence of tags that are consistent across implementations. + Yields the sequence of tags that are compatible with a specific version of Python. The tags consist of: - py*-none- - - -none-any + - -none-any # ... if `interpreter` is provided. - py*-none-any """ - for version in _py_interpreter_range(py_version): + if not python_version: + python_version = sys.version_info[:2] + if not platforms: + platforms = _platform_tags() + for version in _py_interpreter_range(python_version): for platform_ in platforms: yield Tag(version, "none", platform_) - yield Tag(interpreter, "none", "any") - for version in _py_interpreter_range(py_version): + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): yield Tag(version, "none", "any") @@ -289,11 +369,16 @@ def _mac_binary_formats(version, cpu_arch): return formats -def _mac_platforms( - version=None, # type: Optional[MacVersion] - arch=None, # type: Optional[str] -): - # type: (...) -> List[str] +def mac_platforms(version=None, arch=None): + # type: (Optional[MacVersion], Optional[str]) -> Iterator[str] + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ version_str, _, cpu_arch = platform.mac_ver() # type: ignore if version is None: version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) @@ -303,19 +388,15 @@ def _mac_platforms( arch = _mac_arch(cpu_arch) else: arch = arch - platforms = [] for minor_version in range(version[1], -1, -1): compat_version = version[0], minor_version binary_formats = _mac_binary_formats(compat_version, arch) for binary_format in binary_formats: - platforms.append( - "macosx_{major}_{minor}_{binary_format}".format( - major=compat_version[0], - minor=compat_version[1], - binary_format=binary_format, - ) + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, ) - return platforms # From PEP 513. @@ -323,7 +404,7 @@ def _is_manylinux_compatible(name, glibc_version): # type: (str, GlibcVersion) -> bool # Check for presence of _manylinux module. try: - import _manylinux + import _manylinux # noqa return bool(getattr(_manylinux, name + "_compatible")) except (ImportError, AttributeError): @@ -341,7 +422,9 @@ def _glibc_version_string(): def _glibc_version_string_confstr(): # type: () -> Optional[str] - "Primary implementation of glibc_version_string using os.confstr." + """ + Primary implementation of glibc_version_string using os.confstr. + """ # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely # to be broken or missing. This strategy is used in the standard library # platform module. @@ -359,7 +442,9 @@ def _glibc_version_string_confstr(): def _glibc_version_string_ctypes(): # type: () -> Optional[str] - "Fallback implementation of glibc_version_string using ctypes." + """ + Fallback implementation of glibc_version_string using ctypes. + """ try: import ctypes except ImportError: @@ -421,7 +506,7 @@ def _have_compatible_glibc(required_major, minimum_minor): def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): - # type: (bool) -> List[str] + # type: (bool) -> Iterator[str] linux = _normalize_string(distutils.util.get_platform()) if linux == "linux_x86_64" and is_32bit: linux = "linux_i686" @@ -433,71 +518,76 @@ def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): manylinux_support_iter = iter(manylinux_support) for name, glibc_version in manylinux_support_iter: if _is_manylinux_compatible(name, glibc_version): - platforms = [linux.replace("linux", name)] + yield linux.replace("linux", name) break - else: - platforms = [] # Support for a later manylinux implies support for an earlier version. - platforms += [linux.replace("linux", name) for name, _ in manylinux_support_iter] - platforms.append(linux) - return platforms + for name, _ in manylinux_support_iter: + yield linux.replace("linux", name) + yield linux def _generic_platforms(): - # type: () -> List[str] - platform = _normalize_string(distutils.util.get_platform()) - return [platform] + # type: () -> Iterator[str] + yield _normalize_string(distutils.util.get_platform()) + +def _platform_tags(): + # type: () -> Iterator[str] + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() -def _interpreter_name(): + +def interpreter_name(): # type: () -> str + """ + Returns the name of the running interpreter. + """ try: - name = sys.implementation.name + name = sys.implementation.name # type: ignore except AttributeError: # pragma: no cover # Python 2.7 compatibility. name = platform.python_implementation().lower() return INTERPRETER_SHORT_NAMES.get(name) or name -def _generic_interpreter(name, py_version, warn=False): - # type: (str, PythonVersion, Optional[bool]) -> str - version = _get_config_var("py_version_nodot", warn) - if not version: - version = "".join(map(str, py_version[:2])) - return "{name}{version}".format(name=name, version=version) +def interpreter_version(**kwargs): + # type: (bool) -> str + """ + Returns the version of the running interpreter. + """ + warn = _warn_keyword_parameter("interpreter_version", kwargs) + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = "".join(map(str, sys.version_info[:2])) + return version -def sys_tags(warn=False): - # type: (Optional[bool]) -> Iterator[Tag] +def sys_tags(**kwargs): + # type: (bool) -> Iterator[Tag] """ Returns the sequence of tag triples for the running interpreter. The order of the sequence corresponds to priority order for the interpreter, from most to least important. """ - py_version = sys.version_info[:2] - interpreter_name = _interpreter_name() - if platform.system() == "Darwin": - platforms = _mac_platforms() - elif platform.system() == "Linux": - platforms = _linux_platforms() - else: - platforms = _generic_platforms() + warn = _warn_keyword_parameter("sys_tags", kwargs) - if interpreter_name == "cp": - interpreter = _cpython_interpreter(py_version) - abis = _cpython_abis(py_version, warn) - for tag in _cpython_tags(py_version, interpreter, abis, platforms): - yield tag - elif interpreter_name == "pp": - interpreter = _pypy_interpreter() - abi = _generic_abi() - for tag in _pypy_tags(py_version, interpreter, abi, platforms): + interp_name = interpreter_name() + if interp_name == "cp": + for tag in cpython_tags(warn=warn): yield tag else: - interpreter = _generic_interpreter(interpreter_name, py_version, warn) - abi = _generic_abi() - for tag in _generic_tags(interpreter, py_version, abi, platforms): + for tag in generic_tags(): yield tag - for tag in _independent_tags(interpreter, py_version, platforms): + + for tag in compatible_tags(): yield tag diff --git a/tests/test_tags.py b/tests/test_tags.py index c45ccd3a1..0a89991e1 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -2,7 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import collections +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc try: import ctypes @@ -39,6 +42,15 @@ def is_64bit_os(): return platform.architecture()[0] == "64bit" +@pytest.fixture +def manylinux_module(monkeypatch): + monkeypatch.setattr(tags, "_have_compatible_glibc", lambda *args: False) + module_name = "_manylinux" + module = types.ModuleType(module_name) + monkeypatch.setitem(sys.modules, module_name, module) + return module + + @pytest.fixture def mock_interpreter_name(monkeypatch): def mock(name): @@ -53,630 +65,777 @@ def mock(name): return mock -def test_tag_lowercasing(): - tag = tags.Tag("PY3", "None", "ANY") - assert tag.interpreter == "py3" - assert tag.abi == "none" - assert tag.platform == "any" - - -def test_tag_equality(): - args = "py3", "none", "any" - assert tags.Tag(*args) == tags.Tag(*args) - - -def test_tag_equality_fails_with_non_tag(): - assert not tags.Tag("py3", "none", "any") == "non-tag" - - -def test_tag_hashing(example_tag): - tags = {example_tag} # Should not raise TypeError. - assert example_tag in tags - - -def test_tag_hash_equality(example_tag): - equal_tag = tags.Tag("py3", "none", "any") - assert example_tag == equal_tag - assert example_tag.__hash__() == equal_tag.__hash__() - +class TestTag: + def test_lowercasing(self): + tag = tags.Tag("PY3", "None", "ANY") + assert tag.interpreter == "py3" + assert tag.abi == "none" + assert tag.platform == "any" -def test_tag_str(example_tag): - assert str(example_tag) == "py3-none-any" + def test_equality(self): + args = "py3", "none", "any" + assert tags.Tag(*args) == tags.Tag(*args) + def test_equality_fails_with_non_tag(self): + assert not tags.Tag("py3", "none", "any") == "non-tag" -def test_tag_repr(example_tag): - assert repr(example_tag) == "".format( - tag_id=id(example_tag) - ) - - -def test_tag_attribute_access(example_tag): - assert example_tag.interpreter == "py3" - assert example_tag.abi == "none" - assert example_tag.platform == "any" - - -def test_parse_tag_simple(example_tag): - parsed_tags = tags.parse_tag(str(example_tag)) - assert parsed_tags == {example_tag} + def test_hashing(self, example_tag): + tags = {example_tag} # Should not raise TypeError. + assert example_tag in tags + def test_hash_equality(self, example_tag): + equal_tag = tags.Tag("py3", "none", "any") + assert example_tag == equal_tag # Sanity check. + assert example_tag.__hash__() == equal_tag.__hash__() -def test_parse_tag_multi_interpreter(example_tag): - expected = {example_tag, tags.Tag("py2", "none", "any")} - given = tags.parse_tag("py2.py3-none-any") - assert given == expected + def test_str(self, example_tag): + assert str(example_tag) == "py3-none-any" - -def test_parse_tag_multi_platform(): - expected = { - tags.Tag("cp37", "cp37m", platform) - for platform in ( - "macosx_10_6_intel", - "macosx_10_9_intel", - "macosx_10_9_x86_64", - "macosx_10_10_intel", - "macosx_10_10_x86_64", + def test_repr(self, example_tag): + assert repr(example_tag) == "".format( + tag_id=id(example_tag) ) - } - given = tags.parse_tag( - "cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64." - "macosx_10_10_intel.macosx_10_10_x86_64" - ) - assert given == expected - - -@pytest.mark.parametrize( - "name,expected", - [("CPython", "cp"), ("PyPy", "pp"), ("Jython", "jy"), ("IronPython", "ip")], -) -def test__interpreter_name_cpython(name, expected, mock_interpreter_name): - mock_interpreter_name(name) - assert tags._interpreter_name() == expected + def test_attribute_access(self, example_tag): + assert example_tag.interpreter == "py3" + assert example_tag.abi == "none" + assert example_tag.platform == "any" -@pytest.mark.parametrize( - "arch, is_32bit, expected", - [ - ("i386", True, "i386"), - ("ppc", True, "ppc"), - ("x86_64", False, "x86_64"), - ("x86_64", True, "i386"), - ("ppc64", False, "ppc64"), - ("ppc64", True, "ppc"), - ], -) -def test_macos_architectures(arch, is_32bit, expected): - assert tags._mac_arch(arch, is_32bit=is_32bit) == expected +class TestWarnKeywordOnlyParameter: + def test_no_argument(self): + assert not tags._warn_keyword_parameter("test_warn_keyword_parameters", {}) -@pytest.mark.parametrize( - "version,arch,expected", - [ - ((10, 17), "x86_64", ["x86_64", "intel", "fat64", "fat32", "universal"]), - ((10, 4), "x86_64", ["x86_64", "intel", "fat64", "fat32", "universal"]), - ((10, 3), "x86_64", []), - ((10, 17), "i386", ["i386", "intel", "fat32", "fat", "universal"]), - ((10, 4), "i386", ["i386", "intel", "fat32", "fat", "universal"]), - ((10, 3), "i386", []), - ((10, 17), "ppc64", []), - ((10, 6), "ppc64", []), - ((10, 5), "ppc64", ["ppc64", "fat64", "universal"]), - ((10, 3), "ppc64", []), - ((10, 17), "ppc", []), - ((10, 7), "ppc", []), - ((10, 6), "ppc", ["ppc", "fat32", "fat", "universal"]), - ((10, 0), "ppc", ["ppc", "fat32", "fat", "universal"]), - ((11, 0), "riscv", ["riscv", "universal"]), - ], -) -def test_macos_binary_formats(version, arch, expected): - assert tags._mac_binary_formats(version, arch) == expected - - -def test_mac_platforms(): - platforms = tags._mac_platforms((10, 5), "x86_64") - assert platforms == [ - "macosx_10_5_x86_64", - "macosx_10_5_intel", - "macosx_10_5_fat64", - "macosx_10_5_fat32", - "macosx_10_5_universal", - "macosx_10_4_x86_64", - "macosx_10_4_intel", - "macosx_10_4_fat64", - "macosx_10_4_fat32", - "macosx_10_4_universal", - ] - - assert len(tags._mac_platforms((10, 17), "x86_64")) == 14 * 5 - - assert not tags._mac_platforms((10, 0), "x86_64") - - -def test_macos_version_detection(monkeypatch): - if platform.system() != "Darwin": - monkeypatch.setattr( - platform, "mac_ver", lambda: ("10.14", ("", "", ""), "x86_64") + def test_false(self): + assert not tags._warn_keyword_parameter( + "test_warn_keyword_parameters", {"warn": False} ) - version = platform.mac_ver()[0].split(".") - expected = "macosx_{major}_{minor}".format(major=version[0], minor=version[1]) - platforms = tags._mac_platforms(arch="x86_64") - assert platforms[0].startswith(expected) - - -@pytest.mark.parametrize("arch", ["x86_64", "i386"]) -def test_macos_arch_detection(arch, monkeypatch): - if platform.system() != "Darwin" or platform.mac_ver()[2] != arch: - monkeypatch.setattr(platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch)) - monkeypatch.setattr(tags, "_mac_arch", lambda *args: arch) - assert tags._mac_platforms((10, 14))[0].endswith(arch) - - -@pytest.mark.parametrize( - "py_debug,gettotalrefcount,result", - [(1, False, True), (0, False, False), (None, True, True)], -) -def test_cpython_abis_debug(py_debug, gettotalrefcount, result, monkeypatch): - config = {"Py_DEBUG": py_debug, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": 2} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - if gettotalrefcount: - monkeypatch.setattr(sys, "gettotalrefcount", 1, raising=False) - expected = ["cp37d" if result else "cp37"] - assert tags._cpython_abis((3, 7)) == expected - - -def test_cpython_abis_debug_file_extension(monkeypatch): - config = {"Py_DEBUG": None} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - monkeypatch.delattr(sys, "gettotalrefcount", raising=False) - monkeypatch.setattr(tags, "EXTENSION_SUFFIXES", {"_d.pyd"}) - assert tags._cpython_abis((3, 8)) == ["cp38d", "cp38"] - - -@pytest.mark.parametrize( - "debug,expected", [(True, ["cp38d", "cp38"]), (False, ["cp38"])] -) -def test_cpython_abis_debug_38(debug, expected, monkeypatch): - config = {"Py_DEBUG": debug} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - assert tags._cpython_abis((3, 8)) == expected - - -@pytest.mark.parametrize( - "pymalloc,version,result", - [(1, (3, 7), True), (0, (3, 7), False), (None, (3, 7), True), (1, (3, 8), False)], -) -def test_cpython_abis_pymalloc(pymalloc, version, result, monkeypatch): - config = {"Py_DEBUG": 0, "WITH_PYMALLOC": pymalloc, "Py_UNICODE_SIZE": 2} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - base_abi = "cp{}{}".format(version[0], version[1]) - expected = [base_abi + "m" if result else base_abi] - assert tags._cpython_abis(version) == expected + def test_true(self): + assert tags._warn_keyword_parameter( + "test_warn_keyword_parameters", {"warn": True} + ) -@pytest.mark.parametrize( - "unicode_size,maxunicode,version,result", - [ - (4, 0x10FFFF, (3, 2), True), - (2, 0xFFFF, (3, 2), False), - (None, 0x10FFFF, (3, 2), True), - (None, 0xFFFF, (3, 2), False), - (4, 0x10FFFF, (3, 3), False), - ], -) -def test_cpython_abis_wide_unicode( - unicode_size, maxunicode, version, result, monkeypatch -): - config = {"Py_DEBUG": 0, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": unicode_size} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - monkeypatch.setattr(sys, "maxunicode", maxunicode) - base_abi = "cp{}{}".format(version[0], version[1]) - expected = [base_abi + "u" if result else base_abi] - assert tags._cpython_abis(version) == expected - - -def test_independent_tags(): - result = list(tags._independent_tags("cp33", (3, 3), ["plat1", "plat2"])) - assert result == [ - tags.Tag("py33", "none", "plat1"), - tags.Tag("py33", "none", "plat2"), - tags.Tag("py3", "none", "plat1"), - tags.Tag("py3", "none", "plat2"), - tags.Tag("py32", "none", "plat1"), - tags.Tag("py32", "none", "plat2"), - tags.Tag("py31", "none", "plat1"), - tags.Tag("py31", "none", "plat2"), - tags.Tag("py30", "none", "plat1"), - tags.Tag("py30", "none", "plat2"), - tags.Tag("cp33", "none", "any"), - tags.Tag("py33", "none", "any"), - tags.Tag("py3", "none", "any"), - tags.Tag("py32", "none", "any"), - tags.Tag("py31", "none", "any"), - tags.Tag("py30", "none", "any"), - ] - - -def test_cpython_tags(): - result = list( - tags._cpython_tags((3, 8), "cp38", ["cp38d", "cp38"], ["plat1", "plat2"]) + def test_too_many_arguments(self): + message_re = re.compile(r"too_many.+{!r}".format("whatever")) + with pytest.raises(TypeError, match=message_re): + tags._warn_keyword_parameter("too_many", {"warn": True, "whatever": True}) + + def test_wrong_argument(self): + message_re = re.compile(r"missing.+{!r}".format("unexpected")) + with pytest.raises(TypeError, match=message_re): + tags._warn_keyword_parameter("missing", {"unexpected": True}) + + +class TestParseTag: + def test_simple(self, example_tag): + parsed_tags = tags.parse_tag(str(example_tag)) + assert parsed_tags == {example_tag} + + def test_multi_interpreter(self, example_tag): + expected = {example_tag, tags.Tag("py2", "none", "any")} + given = tags.parse_tag("py2.py3-none-any") + assert given == expected + + def test_multi_platform(self): + expected = { + tags.Tag("cp37", "cp37m", platform) + for platform in ( + "macosx_10_6_intel", + "macosx_10_9_intel", + "macosx_10_9_x86_64", + "macosx_10_10_intel", + "macosx_10_10_x86_64", + ) + } + given = tags.parse_tag( + "cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64." + "macosx_10_10_intel.macosx_10_10_x86_64" + ) + assert given == expected + + +class TestInterpreterName: + def test_sys_implementation_name(self, monkeypatch): + class MockImplementation(object): + pass + + mock_implementation = MockImplementation() + mock_implementation.name = "sillywalk" + monkeypatch.setattr(sys, "implementation", mock_implementation, raising=False) + assert tags.interpreter_name() == "sillywalk" + + def test_platform(self, monkeypatch): + monkeypatch.delattr(sys, "implementation", raising=False) + name = "SillyWalk" + monkeypatch.setattr(platform, "python_implementation", lambda: name) + assert tags.interpreter_name() == name.lower() + + def test_interpreter_short_names(self, mock_interpreter_name, monkeypatch): + mock_interpreter_name("cpython") + assert tags.interpreter_name() == "cp" + + +class TestInterpreterVersion: + def test_warn(self, monkeypatch): + class MockConfigVar(object): + def __init__(self, return_): + self.warn = None + self._return = return_ + + def __call__(self, name, warn): + self.warn = warn + return self._return + + mock_config_var = MockConfigVar("38") + monkeypatch.setattr(tags, "_get_config_var", mock_config_var) + tags.interpreter_version(warn=True) + assert mock_config_var.warn + + def test_python_version_nodot(self, monkeypatch): + monkeypatch.setattr(tags, "_get_config_var", lambda var, warn: "NN") + assert tags.interpreter_version() == "NN" + + def test_sys_version_info(self, monkeypatch): + monkeypatch.setattr(tags, "_get_config_var", lambda *args, **kwargs: None) + monkeypatch.setattr(sys, "version_info", ("L", "M", "N")) + assert tags.interpreter_version() == "LM" + + +class TestMacOSPlatforms: + @pytest.mark.parametrize( + "arch, is_32bit, expected", + [ + ("i386", True, "i386"), + ("ppc", True, "ppc"), + ("x86_64", False, "x86_64"), + ("x86_64", True, "i386"), + ("ppc64", False, "ppc64"), + ("ppc64", True, "ppc"), + ], ) - assert result == [ - tags.Tag("cp38", "cp38d", "plat1"), - tags.Tag("cp38", "cp38d", "plat2"), - tags.Tag("cp38", "cp38", "plat1"), - tags.Tag("cp38", "cp38", "plat2"), - tags.Tag("cp38", "abi3", "plat1"), - tags.Tag("cp38", "abi3", "plat2"), - tags.Tag("cp38", "none", "plat1"), - tags.Tag("cp38", "none", "plat2"), - tags.Tag("cp37", "abi3", "plat1"), - tags.Tag("cp37", "abi3", "plat2"), - tags.Tag("cp36", "abi3", "plat1"), - tags.Tag("cp36", "abi3", "plat2"), - tags.Tag("cp35", "abi3", "plat1"), - tags.Tag("cp35", "abi3", "plat2"), - tags.Tag("cp34", "abi3", "plat1"), - tags.Tag("cp34", "abi3", "plat2"), - tags.Tag("cp33", "abi3", "plat1"), - tags.Tag("cp33", "abi3", "plat2"), - tags.Tag("cp32", "abi3", "plat1"), - tags.Tag("cp32", "abi3", "plat2"), - ] - result = list(tags._cpython_tags((3, 3), "cp33", ["cp33m"], ["plat1", "plat2"])) - assert result == [ - tags.Tag("cp33", "cp33m", "plat1"), - tags.Tag("cp33", "cp33m", "plat2"), - tags.Tag("cp33", "abi3", "plat1"), - tags.Tag("cp33", "abi3", "plat2"), - tags.Tag("cp33", "none", "plat1"), - tags.Tag("cp33", "none", "plat2"), - tags.Tag("cp32", "abi3", "plat1"), - tags.Tag("cp32", "abi3", "plat2"), - ] - - -def test_sys_tags_on_mac_cpython(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("CPython"): - monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) - if platform.system() != "Darwin": - monkeypatch.setattr(platform, "system", lambda: "Darwin") - monkeypatch.setattr(tags, "_mac_platforms", lambda: ["macosx_10_5_x86_64"]) - abis = tags._cpython_abis(sys.version_info[:2]) - platforms = tags._mac_platforms() - result = list(tags.sys_tags()) - assert len(abis) == 1 - assert result[0] == tags.Tag( - "cp{major}{minor}".format(major=sys.version_info[0], minor=sys.version_info[1]), - abis[0], - platforms[0], + def test_architectures(self, arch, is_32bit, expected): + assert tags._mac_arch(arch, is_32bit=is_32bit) == expected + + @pytest.mark.parametrize( + "version,arch,expected", + [ + ((10, 17), "x86_64", ["x86_64", "intel", "fat64", "fat32", "universal"]), + ((10, 4), "x86_64", ["x86_64", "intel", "fat64", "fat32", "universal"]), + ((10, 3), "x86_64", []), + ((10, 17), "i386", ["i386", "intel", "fat32", "fat", "universal"]), + ((10, 4), "i386", ["i386", "intel", "fat32", "fat", "universal"]), + ((10, 3), "i386", []), + ((10, 17), "ppc64", []), + ((10, 6), "ppc64", []), + ((10, 5), "ppc64", ["ppc64", "fat64", "universal"]), + ((10, 3), "ppc64", []), + ((10, 17), "ppc", []), + ((10, 7), "ppc", []), + ((10, 6), "ppc", ["ppc", "fat32", "fat", "universal"]), + ((10, 0), "ppc", ["ppc", "fat32", "fat", "universal"]), + ((11, 0), "riscv", ["riscv", "universal"]), + ], ) - assert result[-1] == tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - - -def test_generic_abi(monkeypatch): - abi = sysconfig.get_config_var("SOABI") - if abi: - abi = abi.replace(".", "_").replace("-", "_") - else: - abi = "none" - assert abi == tags._generic_abi() - - monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "cpython-37m-darwin") - assert tags._generic_abi() == "cpython_37m_darwin" - - monkeypatch.setattr(sysconfig, "get_config_var", lambda key: None) - assert tags._generic_abi() == "none" - - -def test_pypy_interpreter(monkeypatch): - if hasattr(sys, "pypy_version_info"): - major, minor = sys.pypy_version_info[:2] - else: - attributes = ["major", "minor", "micro", "releaselevel", "serial"] - PyPyVersion = collections.namedtuple("version_info", attributes) - major, minor = 6, 0 - pypy_version = PyPyVersion( - major=major, minor=minor, micro=1, releaselevel="final", serial=0 + def test_binary_formats(self, version, arch, expected): + assert tags._mac_binary_formats(version, arch) == expected + + def test_version_detection(self, monkeypatch): + if platform.system() != "Darwin": + monkeypatch.setattr( + platform, "mac_ver", lambda: ("10.14", ("", "", ""), "x86_64") + ) + version = platform.mac_ver()[0].split(".") + expected = "macosx_{major}_{minor}".format(major=version[0], minor=version[1]) + platforms = list(tags.mac_platforms(arch="x86_64")) + assert platforms[0].startswith(expected) + + @pytest.mark.parametrize("arch", ["x86_64", "i386"]) + def test_arch_detection(self, arch, monkeypatch): + if platform.system() != "Darwin" or platform.mac_ver()[2] != arch: + monkeypatch.setattr( + platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch) + ) + monkeypatch.setattr(tags, "_mac_arch", lambda *args: arch) + assert next(tags.mac_platforms((10, 14))).endswith(arch) + + def test_mac_platforms(self): + platforms = list(tags.mac_platforms((10, 5), "x86_64")) + assert platforms == [ + "macosx_10_5_x86_64", + "macosx_10_5_intel", + "macosx_10_5_fat64", + "macosx_10_5_fat32", + "macosx_10_5_universal", + "macosx_10_4_x86_64", + "macosx_10_4_intel", + "macosx_10_4_fat64", + "macosx_10_4_fat32", + "macosx_10_4_universal", + ] + + assert len(list(tags.mac_platforms((10, 17), "x86_64"))) == 14 * 5 + + assert not list(tags.mac_platforms((10, 0), "x86_64")) + + +class TestManylinuxPlatform: + def test_module_declaration_true(self, manylinux_module): + manylinux_module.manylinux1_compatible = True + assert tags._is_manylinux_compatible("manylinux1", (2, 5)) + + def test_module_declaration_false(self, manylinux_module): + manylinux_module.manylinux1_compatible = False + assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) + + def test_module_declaration_missing_attribute(self, manylinux_module): + try: + del manylinux_module.manylinux1_compatible + except AttributeError: + pass + assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) + + def test_is_manylinux_compatible_module_support( + self, manylinux_module, monkeypatch + ): + monkeypatch.setitem(sys.modules, manylinux_module.__name__, None) + assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) + + @pytest.mark.parametrize( + "version,compatible", (((2, 0), True), ((2, 5), True), ((2, 10), False)) + ) + def test_is_manylinux_compatible_glibc_support( + self, version, compatible, monkeypatch + ): + monkeypatch.setitem(sys.modules, "_manylinux", None) + monkeypatch.setattr( + tags, + "_have_compatible_glibc", + lambda major, minor: (major, minor) <= (2, 5), ) - monkeypatch.setattr(sys, "pypy_version_info", pypy_version, raising=False) - expected = "pp{}{}{}".format(sys.version_info[0], major, minor) - assert expected == tags._pypy_interpreter() - - -def test_pypy_tags(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("PyPy"): - monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp360") - interpreter = tags._pypy_interpreter() - result = list(tags._pypy_tags((3, 3), interpreter, "pypy3_60", ["plat1", "plat2"])) - assert result == [ - tags.Tag(interpreter, "pypy3_60", "plat1"), - tags.Tag(interpreter, "pypy3_60", "plat2"), - tags.Tag(interpreter, "none", "plat1"), - tags.Tag(interpreter, "none", "plat2"), - ] - - -def test_sys_tags_on_mac_pypy(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("PyPy"): - monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp360") - if platform.system() != "Darwin": - monkeypatch.setattr(platform, "system", lambda: "Darwin") - monkeypatch.setattr(tags, "_mac_platforms", lambda: ["macosx_10_5_x86_64"]) - interpreter = tags._pypy_interpreter() - abi = tags._generic_abi() - platforms = tags._mac_platforms() - result = list(tags.sys_tags()) - assert result[0] == tags.Tag(interpreter, abi, platforms[0]) - assert result[-1] == tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - - -def test_generic_interpreter(): - version = sysconfig.get_config_var("py_version_nodot") - if not version: - version = "".join(sys.version_info[:2]) - result = tags._generic_interpreter("sillywalk", sys.version_info[:2]) - assert result == "sillywalk{version}".format(version=version) - - -def test_generic_interpreter_no_config_var(monkeypatch): - monkeypatch.setattr(sysconfig, "get_config_var", lambda _: None) - assert tags._generic_interpreter("sillywalk", (3, 6)) == "sillywalk36" - - -def test_generic_platforms(): - platform = distutils.util.get_platform().replace("-", "_") - platform = platform.replace(".", "_") - assert tags._generic_platforms() == [platform] - - -def test_generic_tags(): - result = list(tags._generic_tags("sillywalk33", (3, 3), "abi", ["plat1", "plat2"])) - assert result == [ - tags.Tag("sillywalk33", "abi", "plat1"), - tags.Tag("sillywalk33", "abi", "plat2"), - tags.Tag("sillywalk33", "none", "plat1"), - tags.Tag("sillywalk33", "none", "plat2"), - ] - - no_abi = tags._generic_tags("sillywalk34", (3, 4), "none", ["plat1", "plat2"]) - assert list(no_abi) == [ - tags.Tag("sillywalk34", "none", "plat1"), - tags.Tag("sillywalk34", "none", "plat2"), - ] - - -def test_sys_tags_on_windows_cpython(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("CPython"): - monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) - if platform.system() != "Windows": - monkeypatch.setattr(platform, "system", lambda: "Windows") - monkeypatch.setattr(tags, "_generic_platforms", lambda: ["win_amd64"]) - abis = tags._cpython_abis(sys.version_info[:2]) - platforms = tags._generic_platforms() - result = list(tags.sys_tags()) - interpreter = "cp{major}{minor}".format( - major=sys.version_info[0], minor=sys.version_info[1] + assert bool(tags._is_manylinux_compatible("manylinux1", version)) == compatible + + @pytest.mark.parametrize( + "version_str,major,minor,expected", + [ + ("2.4", 2, 4, True), + ("2.4", 2, 5, False), + ("2.4", 2, 3, True), + ("3.4", 2, 4, False), + ], ) - assert len(abis) == 1 - expected = tags.Tag(interpreter, abis[0], platforms[0]) - assert result[0] == expected - expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - assert result[-1] == expected - - -def test_is_manylinux_compatible_module_support(monkeypatch): - monkeypatch.setattr(tags, "_have_compatible_glibc", lambda *args: False) - module_name = "_manylinux" - module = types.ModuleType(module_name) - module.manylinux1_compatible = True - monkeypatch.setitem(sys.modules, module_name, module) - assert tags._is_manylinux_compatible("manylinux1", (2, 5)) - module.manylinux1_compatible = False - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - del module.manylinux1_compatible - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - monkeypatch.setitem(sys.modules, module_name, None) - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - - -def test_is_manylinux_compatible_glibc_support(monkeypatch): - monkeypatch.setitem(sys.modules, "_manylinux", None) - monkeypatch.setattr( - tags, "_have_compatible_glibc", lambda major, minor: (major, minor) <= (2, 5) + def test_check_glibc_version(self, version_str, major, minor, expected): + assert expected == tags._check_glibc_version(version_str, major, minor) + + @pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"]) + def test_check_glibc_version_warning(self, version_str): + with warnings.catch_warnings(record=True) as w: + tags._check_glibc_version(version_str, 2, 4) + assert len(w) == 1 + assert issubclass(w[0].category, RuntimeWarning) + + @pytest.mark.skipif(not ctypes, reason="requires ctypes") + @pytest.mark.parametrize( + "version_str,expected", + [ + # Be very explicit about bytes and Unicode for Python 2 testing. + (b"2.4", "2.4"), + (u"2.4", "2.4"), + ], ) - assert tags._is_manylinux_compatible("manylinux1", (2, 0)) - assert tags._is_manylinux_compatible("manylinux1", (2, 5)) - assert not tags._is_manylinux_compatible("manylinux1", (2, 10)) - - -@pytest.mark.parametrize( - "version_str,major,minor,expected", - [ - ("2.4", 2, 4, True), - ("2.4", 2, 5, False), - ("2.4", 2, 3, True), - ("3.4", 2, 4, False), - ], -) -def test_check_glibc_version(version_str, major, minor, expected): - assert expected == tags._check_glibc_version(version_str, major, minor) - - -@pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"]) -def test_check_glibc_version_warning(version_str): - with warnings.catch_warnings(record=True) as w: - tags._check_glibc_version(version_str, 2, 4) - assert len(w) == 1 - assert issubclass(w[0].category, RuntimeWarning) - + def test_glibc_version_string(self, version_str, expected, monkeypatch): + class LibcVersion: + def __init__(self, version_str): + self.version_str = version_str -@pytest.mark.skipif(not ctypes, reason="requires ctypes") -@pytest.mark.parametrize( - "version_str,expected", - [ - # Be very explicit about bytes and Unicode for Python 2 testing. - (b"2.4", "2.4"), - (u"2.4", "2.4"), - ], -) -def test_glibc_version_string(version_str, expected, monkeypatch): - class LibcVersion: - def __init__(self, version_str): - self.version_str = version_str - - def __call__(self): - return version_str - - class ProcessNamespace: - def __init__(self, libc_version): - self.gnu_get_libc_version = libc_version - - process_namespace = ProcessNamespace(LibcVersion(version_str)) - monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace) - monkeypatch.setattr(tags, "_glibc_version_string_confstr", lambda: False) - - assert tags._glibc_version_string() == expected - - del process_namespace.gnu_get_libc_version - assert tags._glibc_version_string() is None + def __call__(self): + return version_str + class ProcessNamespace: + def __init__(self, libc_version): + self.gnu_get_libc_version = libc_version -def test_glibc_version_string_confstr(monkeypatch): - monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) - assert tags._glibc_version_string_confstr() == "2.20" + process_namespace = ProcessNamespace(LibcVersion(version_str)) + monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace) + monkeypatch.setattr(tags, "_glibc_version_string_confstr", lambda: False) + assert tags._glibc_version_string() == expected -@pytest.mark.parametrize( - "failure", [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"] -) -def test_glibc_version_string_confstr_fail(monkeypatch, failure): - monkeypatch.setattr(os, "confstr", failure, raising=False) - assert tags._glibc_version_string_confstr() is None - - -def test_glibc_version_string_confstr_missing(monkeypatch): - monkeypatch.delattr(os, "confstr", raising=False) - assert tags._glibc_version_string_confstr() is None - + del process_namespace.gnu_get_libc_version + assert tags._glibc_version_string() is None -def test_glibc_version_string_ctypes_missing(monkeypatch): - monkeypatch.setitem(sys.modules, "ctypes", None) - assert tags._glibc_version_string_ctypes() is None + def test_glibc_version_string_confstr(self, monkeypatch): + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) + assert tags._glibc_version_string_confstr() == "2.20" - -def test_get_config_var_does_not_log(monkeypatch): - debug = pretend.call_recorder(lambda *a: None) - monkeypatch.setattr(tags.logger, "debug", debug) - tags._get_config_var("missing") - assert debug.calls == [] - - -def test_get_config_var_does_log(monkeypatch): - debug = pretend.call_recorder(lambda *a: None) - monkeypatch.setattr(tags.logger, "debug", debug) - tags._get_config_var("missing", warn=True) - assert debug.calls == [ - pretend.call( - "Config variable '%s' is unset, Python ABI tag may be incorrect", "missing" - ) - ] - - -def test_have_compatible_glibc(monkeypatch): - if platform.system() == "Linux": + @pytest.mark.parametrize( + "failure", + [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"], + ) + def test_glibc_version_string_confstr_fail(self, monkeypatch, failure): + monkeypatch.setattr(os, "confstr", failure, raising=False) + assert tags._glibc_version_string_confstr() is None + + def test_glibc_version_string_confstr_missing(self, monkeypatch): + monkeypatch.delattr(os, "confstr", raising=False) + assert tags._glibc_version_string_confstr() is None + + def test_glibc_version_string_ctypes_missing(self, monkeypatch): + monkeypatch.setitem(sys.modules, "ctypes", None) + assert tags._glibc_version_string_ctypes() is None + + def test_get_config_var_does_not_log(self, monkeypatch): + debug = pretend.call_recorder(lambda *a: None) + monkeypatch.setattr(tags.logger, "debug", debug) + tags._get_config_var("missing") + assert debug.calls == [] + + def test_get_config_var_does_log(self, monkeypatch): + debug = pretend.call_recorder(lambda *a: None) + monkeypatch.setattr(tags.logger, "debug", debug) + tags._get_config_var("missing", warn=True) + assert debug.calls == [ + pretend.call( + "Config variable '%s' is unset, Python ABI tag may be incorrect", + "missing", + ) + ] + + @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") + def test_have_compatible_glibc_linux(self): # Assuming no one is running this test with a version of glibc released in # 1997. assert tags._have_compatible_glibc(2, 0) - else: + + def test_have_compatible_glibc(self, monkeypatch): monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.4") assert tags._have_compatible_glibc(2, 4) - monkeypatch.setattr(tags, "_glibc_version_string", lambda: None) - assert not tags._have_compatible_glibc(2, 4) - -def test_linux_platforms_64bit_on_64bit_os(is_64bit_os, is_x86, monkeypatch): - if platform.system() != "Linux" or not is_64bit_os or not is_x86: + def test_glibc_version_string_none(self, monkeypatch): + monkeypatch.setattr(tags, "_glibc_version_string", lambda: None) + assert not tags._have_compatible_glibc(2, 4) + + def test_linux_platforms_64bit_on_64bit_os(self, is_64bit_os, is_x86, monkeypatch): + if platform.system() != "Linux" or not is_64bit_os or not is_x86: + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) + linux_platform = list(tags._linux_platforms(is_32bit=False))[-1] + assert linux_platform == "linux_x86_64" + + def test_linux_platforms_32bit_on_64bit_os(self, is_64bit_os, is_x86, monkeypatch): + if platform.system() != "Linux" or not is_64bit_os or not is_x86: + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) + linux_platform = list(tags._linux_platforms(is_32bit=True))[-1] + assert linux_platform == "linux_i686" + + def test_linux_platforms_manylinux_unsupported(self, monkeypatch): monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = tags._linux_platforms(is_32bit=False)[-1] - assert linux_platform == "linux_x86_64" + linux_platform = list(tags._linux_platforms(is_32bit=False)) + assert linux_platform == ["linux_x86_64"] + def test_linux_platforms_manylinux1(self, monkeypatch): + monkeypatch.setattr( + tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux1" + ) + if platform.system() != "Linux": + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + platforms = list(tags._linux_platforms(is_32bit=False)) + assert platforms == ["manylinux1_x86_64", "linux_x86_64"] -def test_linux_platforms_32bit_on_64bit_os(is_64bit_os, is_x86, monkeypatch): - if platform.system() != "Linux" or not is_64bit_os or not is_x86: - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = tags._linux_platforms(is_32bit=True)[-1] - assert linux_platform == "linux_i686" - + def test_linux_platforms_manylinux2010(self, monkeypatch): + monkeypatch.setattr( + tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2010" + ) + if platform.system() != "Linux": + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + platforms = list(tags._linux_platforms(is_32bit=False)) + expected = ["manylinux2010_x86_64", "manylinux1_x86_64", "linux_x86_64"] + assert platforms == expected -def test_linux_platforms_manylinux_unsupported(monkeypatch): - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = tags._linux_platforms(is_32bit=False) - assert linux_platform == ["linux_x86_64"] + def test_linux_platforms_manylinux2014(self, monkeypatch): + monkeypatch.setattr( + tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014" + ) + if platform.system() != "Linux": + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + platforms = list(tags._linux_platforms(is_32bit=False)) + expected = [ + "manylinux2014_x86_64", + "manylinux2010_x86_64", + "manylinux1_x86_64", + "linux_x86_64", + ] + assert platforms == expected -def test_linux_platforms_manylinux1(monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux1" - ) - if platform.system() != "Linux": - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = tags._linux_platforms(is_32bit=False) - assert platforms == ["manylinux1_x86_64", "linux_x86_64"] +@pytest.mark.parametrize( + "platform_name,dispatch_func", + [ + ("Darwin", "mac_platforms"), + ("Linux", "_linux_platforms"), + ("Generic", "_generic_platforms"), + ], +) +def test__platform_tags(platform_name, dispatch_func, monkeypatch): + expected = ["sillywalk"] + monkeypatch.setattr(platform, "system", lambda: platform_name) + monkeypatch.setattr(tags, dispatch_func, lambda: expected) + assert tags._platform_tags() == expected -def test_linux_platforms_manylinux2010(monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2010" +class TestCPythonABI: + @pytest.mark.parametrize( + "py_debug,gettotalrefcount,result", + [(1, False, True), (0, False, False), (None, True, True)], ) - if platform.system() != "Linux": - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = tags._linux_platforms(is_32bit=False) - expected = ["manylinux2010_x86_64", "manylinux1_x86_64", "linux_x86_64"] - assert platforms == expected - - -def test_linux_platforms_manylinux2014(monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014" + def test_debug(self, py_debug, gettotalrefcount, result, monkeypatch): + config = {"Py_DEBUG": py_debug, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": 2} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + if gettotalrefcount: + monkeypatch.setattr(sys, "gettotalrefcount", 1, raising=False) + expected = ["cp37d" if result else "cp37"] + assert tags._cpython_abis((3, 7)) == expected + + def test_debug_file_extension(self, monkeypatch): + config = {"Py_DEBUG": None} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + monkeypatch.delattr(sys, "gettotalrefcount", raising=False) + monkeypatch.setattr(tags, "EXTENSION_SUFFIXES", {"_d.pyd"}) + assert tags._cpython_abis((3, 8)) == ["cp38d", "cp38"] + + @pytest.mark.parametrize( + "debug,expected", [(True, ["cp38d", "cp38"]), (False, ["cp38"])] ) - if platform.system() != "Linux": - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = tags._linux_platforms(is_32bit=False) - expected = [ - "manylinux2014_x86_64", - "manylinux2010_x86_64", - "manylinux1_x86_64", - "linux_x86_64", - ] - assert platforms == expected - - -def test_sys_tags_linux_cpython(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("CPython"): - monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) - if platform.system() != "Linux": - monkeypatch.setattr(platform, "system", lambda: "Linux") - monkeypatch.setattr(tags, "_linux_platforms", lambda: ["linux_x86_64"]) - abis = tags._cpython_abis(sys.version_info[:2]) - platforms = tags._linux_platforms() - result = list(tags.sys_tags()) - expected_interpreter = "cp{major}{minor}".format( - major=sys.version_info[0], minor=sys.version_info[1] + def test__debug_cp38(self, debug, expected, monkeypatch): + config = {"Py_DEBUG": debug} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + assert tags._cpython_abis((3, 8)) == expected + + @pytest.mark.parametrize( + "pymalloc,version,result", + [ + (1, (3, 7), True), + (0, (3, 7), False), + (None, (3, 7), True), + (1, (3, 8), False), + ], + ) + def test_pymalloc(self, pymalloc, version, result, monkeypatch): + config = {"Py_DEBUG": 0, "WITH_PYMALLOC": pymalloc, "Py_UNICODE_SIZE": 2} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + base_abi = "cp{}{}".format(version[0], version[1]) + expected = [base_abi + "m" if result else base_abi] + assert tags._cpython_abis(version) == expected + + @pytest.mark.parametrize( + "unicode_size,maxunicode,version,result", + [ + (4, 0x10FFFF, (3, 2), True), + (2, 0xFFFF, (3, 2), False), + (None, 0x10FFFF, (3, 2), True), + (None, 0xFFFF, (3, 2), False), + (4, 0x10FFFF, (3, 3), False), + ], ) - assert len(abis) == 1 - assert result[0] == tags.Tag(expected_interpreter, abis[0], platforms[0]) - expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - assert result[-1] == expected + def test_wide_unicode(self, unicode_size, maxunicode, version, result, monkeypatch): + config = {"Py_DEBUG": 0, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": unicode_size} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + monkeypatch.setattr(sys, "maxunicode", maxunicode) + base_abi = "cp{}{}".format(version[0], version[1]) + expected = [base_abi + "u" if result else base_abi] + assert tags._cpython_abis(version) == expected + + +class TestCPythonTags: + def test_iterator_returned(self): + result_iterator = tags.cpython_tags( + (3, 8), ["cp38d", "cp38"], ["plat1", "plat2"] + ) + isinstance(result_iterator, collections_abc.Iterator) + def test_all_args(self): + result_iterator = tags.cpython_tags( + (3, 8), ["cp38d", "cp38"], ["plat1", "plat2"] + ) + result = list(result_iterator) + assert result == [ + tags.Tag("cp38", "cp38d", "plat1"), + tags.Tag("cp38", "cp38d", "plat2"), + tags.Tag("cp38", "cp38", "plat1"), + tags.Tag("cp38", "cp38", "plat2"), + tags.Tag("cp38", "abi3", "plat1"), + tags.Tag("cp38", "abi3", "plat2"), + tags.Tag("cp38", "none", "plat1"), + tags.Tag("cp38", "none", "plat2"), + tags.Tag("cp37", "abi3", "plat1"), + tags.Tag("cp37", "abi3", "plat2"), + tags.Tag("cp36", "abi3", "plat1"), + tags.Tag("cp36", "abi3", "plat2"), + tags.Tag("cp35", "abi3", "plat1"), + tags.Tag("cp35", "abi3", "plat2"), + tags.Tag("cp34", "abi3", "plat1"), + tags.Tag("cp34", "abi3", "plat2"), + tags.Tag("cp33", "abi3", "plat1"), + tags.Tag("cp33", "abi3", "plat2"), + tags.Tag("cp32", "abi3", "plat1"), + tags.Tag("cp32", "abi3", "plat2"), + ] + result = list(tags.cpython_tags((3, 3), ["cp33m"], ["plat1", "plat2"])) + assert result == [ + tags.Tag("cp33", "cp33m", "plat1"), + tags.Tag("cp33", "cp33m", "plat2"), + tags.Tag("cp33", "abi3", "plat1"), + tags.Tag("cp33", "abi3", "plat2"), + tags.Tag("cp33", "none", "plat1"), + tags.Tag("cp33", "none", "plat2"), + tags.Tag("cp32", "abi3", "plat1"), + tags.Tag("cp32", "abi3", "plat2"), + ] + + def test_python_version_defaults(self): + tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"])) + interpreter = "cp{}{}".format(*sys.version_info[:2]) + assert tag == tags.Tag(interpreter, "abi3", "any") + + def test_abi_defaults(self, monkeypatch): + monkeypatch.setattr(tags, "_cpython_abis", lambda _1, _2: ["cp38"]) + result = list(tags.cpython_tags((3, 8), platforms=["any"])) + assert tags.Tag("cp38", "cp38", "any") in result + assert tags.Tag("cp38", "abi3", "any") in result + assert tags.Tag("cp38", "none", "any") in result + + def test_platforms_defaults(self, monkeypatch): + monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat1"]) + result = list(tags.cpython_tags((3, 8), abis=["whatever"])) + assert tags.Tag("cp38", "whatever", "plat1") in result + + def test_major_only_python_version(self): + result = list(tags.cpython_tags((3,), ["abi"], ["plat"])) + assert result == [ + tags.Tag("cp3", "abi", "plat"), + tags.Tag("cp3", "none", "plat"), + ] + + def test_major_only_python_version_with_default_abis(self): + result = list(tags.cpython_tags((3,), platforms=["plat"])) + assert result == [tags.Tag("cp3", "none", "plat")] + + @pytest.mark.parametrize("abis", [[], ["abi3"], ["none"]]) + def test_skip_redundant_abis(self, abis): + results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) + assert results == [ + tags.Tag("cp30", "abi3", "any"), + tags.Tag("cp30", "none", "any"), + ] + + +class TestGenericTags: + @pytest.mark.skipif( + not sysconfig.get_config_var("SOABI"), reason="SOABI not defined" + ) + def test__generic_abi_soabi_provided(self): + abi = sysconfig.get_config_var("SOABI").replace(".", "_").replace("-", "_") + assert [abi] == list(tags._generic_abi()) -def test_generic_sys_tags(monkeypatch): - monkeypatch.setattr(platform, "system", lambda: "Generic") - monkeypatch.setattr(tags, "_interpreter_name", lambda: "generic") + def test__generic_abi(self, monkeypatch): + monkeypatch.setattr( + sysconfig, "get_config_var", lambda key: "cpython-37m-darwin" + ) + assert list(tags._generic_abi()) == ["cpython_37m_darwin"] + + def test__generic_abi_no_soabi(self, monkeypatch): + monkeypatch.setattr(sysconfig, "get_config_var", lambda key: None) + assert not list(tags._generic_abi()) + + def test_generic_platforms(self): + platform = distutils.util.get_platform().replace("-", "_") + platform = platform.replace(".", "_") + assert list(tags._generic_platforms()) == [platform] + + def test_iterator_returned(self): + result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) + assert isinstance(result_iterator, collections_abc.Iterator) + + def test_all_args(self): + result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) + result = list(result_iterator) + assert result == [ + tags.Tag("sillywalk33", "abi", "plat1"), + tags.Tag("sillywalk33", "abi", "plat2"), + tags.Tag("sillywalk33", "none", "plat1"), + tags.Tag("sillywalk33", "none", "plat2"), + ] + + @pytest.mark.parametrize("abi", [[], ["none"]]) + def test_abi_unspecified(self, abi): + no_abi = list(tags.generic_tags("sillywalk34", abi, ["plat1", "plat2"])) + assert no_abi == [ + tags.Tag("sillywalk34", "none", "plat1"), + tags.Tag("sillywalk34", "none", "plat2"), + ] + + def test_interpreter_default(self, monkeypatch): + monkeypatch.setattr(tags, "interpreter_name", lambda: "sillywalk") + monkeypatch.setattr(tags, "interpreter_version", lambda warn: "NN") + result = list(tags.generic_tags(abis=["none"], platforms=["any"])) + assert result == [tags.Tag("sillywalkNN", "none", "any")] + + def test_abis_default(self, monkeypatch): + monkeypatch.setattr(tags, "_generic_abi", lambda: iter(["abi"])) + result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) + assert result == [ + tags.Tag("sillywalk", "abi", "any"), + tags.Tag("sillywalk", "none", "any"), + ] + + def test_platforms_default(self, monkeypatch): + monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat"]) + result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) + assert result == [tags.Tag("sillywalk", "none", "plat")] + + +class TestCompatibleTags: + def test_all_args(self): + result = list(tags.compatible_tags((3, 3), "cp33", ["plat1", "plat2"])) + assert result == [ + tags.Tag("py33", "none", "plat1"), + tags.Tag("py33", "none", "plat2"), + tags.Tag("py3", "none", "plat1"), + tags.Tag("py3", "none", "plat2"), + tags.Tag("py32", "none", "plat1"), + tags.Tag("py32", "none", "plat2"), + tags.Tag("py31", "none", "plat1"), + tags.Tag("py31", "none", "plat2"), + tags.Tag("py30", "none", "plat1"), + tags.Tag("py30", "none", "plat2"), + tags.Tag("cp33", "none", "any"), + tags.Tag("py33", "none", "any"), + tags.Tag("py3", "none", "any"), + tags.Tag("py32", "none", "any"), + tags.Tag("py31", "none", "any"), + tags.Tag("py30", "none", "any"), + ] + + def test_major_only_python_version(self): + result = list(tags.compatible_tags((3,), "cp33", ["plat"])) + assert result == [ + tags.Tag("py3", "none", "plat"), + tags.Tag("cp33", "none", "any"), + tags.Tag("py3", "none", "any"), + ] + + def test_default_python_version(self, monkeypatch): + monkeypatch.setattr(sys, "version_info", (3, 1)) + result = list(tags.compatible_tags(interpreter="cp31", platforms=["plat"])) + assert result == [ + tags.Tag("py31", "none", "plat"), + tags.Tag("py3", "none", "plat"), + tags.Tag("py30", "none", "plat"), + tags.Tag("cp31", "none", "any"), + tags.Tag("py31", "none", "any"), + tags.Tag("py3", "none", "any"), + tags.Tag("py30", "none", "any"), + ] + + def test_default_interpreter(self): + result = list(tags.compatible_tags((3, 1), platforms=["plat"])) + assert result == [ + tags.Tag("py31", "none", "plat"), + tags.Tag("py3", "none", "plat"), + tags.Tag("py30", "none", "plat"), + tags.Tag("py31", "none", "any"), + tags.Tag("py3", "none", "any"), + tags.Tag("py30", "none", "any"), + ] + + def test_default_platforms(self, monkeypatch): + monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat"]) + result = list(tags.compatible_tags((3, 1), "cp31")) + assert result == [ + tags.Tag("py31", "none", "plat"), + tags.Tag("py3", "none", "plat"), + tags.Tag("py30", "none", "plat"), + tags.Tag("cp31", "none", "any"), + tags.Tag("py31", "none", "any"), + tags.Tag("py3", "none", "any"), + tags.Tag("py30", "none", "any"), + ] + + +class TestSysTags: + @pytest.mark.parametrize( + "name,expected", + [("CPython", "cp"), ("PyPy", "pp"), ("Jython", "jy"), ("IronPython", "ip")], + ) + def test_interpreter_name(self, name, expected, mock_interpreter_name): + mock_interpreter_name(name) + assert tags.interpreter_name() == expected + + def test_iterator(self): + assert isinstance(tags.sys_tags(), collections_abc.Iterator) + + def test_mac_cpython(self, mock_interpreter_name, monkeypatch): + if mock_interpreter_name("CPython"): + monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) + if platform.system() != "Darwin": + monkeypatch.setattr(platform, "system", lambda: "Darwin") + monkeypatch.setattr(tags, "mac_platforms", lambda: ["macosx_10_5_x86_64"]) + abis = tags._cpython_abis(sys.version_info[:2]) + platforms = list(tags.mac_platforms()) + result = list(tags.sys_tags()) + assert len(abis) == 1 + assert result[0] == tags.Tag( + "cp{major}{minor}".format( + major=sys.version_info[0], minor=sys.version_info[1] + ), + abis[0], + platforms[0], + ) + assert result[-1] == tags.Tag( + "py{}0".format(sys.version_info[0]), "none", "any" + ) - result = list(tags.sys_tags()) - expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - assert result[-1] == expected + def test_windows_cpython(self, mock_interpreter_name, monkeypatch): + if mock_interpreter_name("CPython"): + monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) + if platform.system() != "Windows": + monkeypatch.setattr(platform, "system", lambda: "Windows") + monkeypatch.setattr(tags, "_generic_platforms", lambda: ["win_amd64"]) + abis = tags._cpython_abis(sys.version_info[:2]) + platforms = tags._generic_platforms() + result = list(tags.sys_tags()) + interpreter = "cp{major}{minor}".format( + major=sys.version_info[0], minor=sys.version_info[1] + ) + assert len(abis) == 1 + expected = tags.Tag(interpreter, abis[0], platforms[0]) + assert result[0] == expected + expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") + assert result[-1] == expected + + def test_linux_cpython(self, mock_interpreter_name, monkeypatch): + if mock_interpreter_name("CPython"): + monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) + if platform.system() != "Linux": + monkeypatch.setattr(platform, "system", lambda: "Linux") + monkeypatch.setattr(tags, "_linux_platforms", lambda: ["linux_x86_64"]) + abis = list(tags._cpython_abis(sys.version_info[:2])) + platforms = list(tags._linux_platforms()) + result = list(tags.sys_tags()) + expected_interpreter = "cp{major}{minor}".format( + major=sys.version_info[0], minor=sys.version_info[1] + ) + assert len(abis) == 1 + assert result[0] == tags.Tag(expected_interpreter, abis[0], platforms[0]) + expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") + assert result[-1] == expected + + def test_generic(self, monkeypatch): + monkeypatch.setattr(platform, "system", lambda: "Generic") + monkeypatch.setattr(tags, "interpreter_name", lambda: "generic") + + result = list(tags.sys_tags()) + expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") + assert result[-1] == expected