1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4
5try:
6    import collections.abc as collections_abc
7except ImportError:
8    import collections as collections_abc
9
10try:
11    import ctypes
12except ImportError:
13    ctypes = None
14import distutils.util
15
16import os
17import platform
18import re
19import sys
20import sysconfig
21import types
22import warnings
23
24import pretend
25import pytest
26
27from packaging import tags
28
29
30@pytest.fixture
31def example_tag():
32    return tags.Tag("py3", "none", "any")
33
34
35@pytest.fixture
36def is_x86():
37    return re.match(r"(i\d86|x86_64)", platform.machine()) is not None
38
39
40@pytest.fixture
41def manylinux_module(monkeypatch):
42    monkeypatch.setattr(tags, "_get_glibc_version", lambda *args: (2, 20))
43    module_name = "_manylinux"
44    module = types.ModuleType(module_name)
45    monkeypatch.setitem(sys.modules, module_name, module)
46    return module
47
48
49@pytest.fixture
50def mock_interpreter_name(monkeypatch):
51    def mock(name):
52        if hasattr(sys, "implementation") and sys.implementation.name != name.lower():
53            monkeypatch.setattr(sys.implementation, "name", name.lower())
54            return True
55        elif platform.python_implementation() != name:
56            monkeypatch.setattr(platform, "python_implementation", lambda: name)
57            return True
58        return False
59
60    return mock
61
62
63class TestTag:
64    def test_lowercasing(self):
65        tag = tags.Tag("PY3", "None", "ANY")
66        assert tag.interpreter == "py3"
67        assert tag.abi == "none"
68        assert tag.platform == "any"
69
70    def test_equality(self):
71        args = "py3", "none", "any"
72        assert tags.Tag(*args) == tags.Tag(*args)
73
74    def test_equality_fails_with_non_tag(self):
75        assert not tags.Tag("py3", "none", "any") == "non-tag"
76
77    def test_hashing(self, example_tag):
78        tags = {example_tag}  # Should not raise TypeError.
79        assert example_tag in tags
80
81    def test_hash_equality(self, example_tag):
82        equal_tag = tags.Tag("py3", "none", "any")
83        assert example_tag == equal_tag  # Sanity check.
84        assert example_tag.__hash__() == equal_tag.__hash__()
85
86    def test_str(self, example_tag):
87        assert str(example_tag) == "py3-none-any"
88
89    def test_repr(self, example_tag):
90        assert repr(example_tag) == "<py3-none-any @ {tag_id}>".format(
91            tag_id=id(example_tag)
92        )
93
94    def test_attribute_access(self, example_tag):
95        assert example_tag.interpreter == "py3"
96        assert example_tag.abi == "none"
97        assert example_tag.platform == "any"
98
99
100class TestWarnKeywordOnlyParameter:
101    def test_no_argument(self):
102        assert not tags._warn_keyword_parameter("test_warn_keyword_parameters", {})
103
104    def test_false(self):
105        assert not tags._warn_keyword_parameter(
106            "test_warn_keyword_parameters", {"warn": False}
107        )
108
109    def test_true(self):
110        assert tags._warn_keyword_parameter(
111            "test_warn_keyword_parameters", {"warn": True}
112        )
113
114    def test_too_many_arguments(self):
115        message_re = re.compile(r"too_many.+{!r}".format("whatever"))
116        with pytest.raises(TypeError, match=message_re):
117            tags._warn_keyword_parameter("too_many", {"warn": True, "whatever": True})
118
119    def test_wrong_argument(self):
120        message_re = re.compile(r"missing.+{!r}".format("unexpected"))
121        with pytest.raises(TypeError, match=message_re):
122            tags._warn_keyword_parameter("missing", {"unexpected": True})
123
124
125class TestParseTag:
126    def test_simple(self, example_tag):
127        parsed_tags = tags.parse_tag(str(example_tag))
128        assert parsed_tags == {example_tag}
129
130    def test_multi_interpreter(self, example_tag):
131        expected = {example_tag, tags.Tag("py2", "none", "any")}
132        given = tags.parse_tag("py2.py3-none-any")
133        assert given == expected
134
135    def test_multi_platform(self):
136        expected = {
137            tags.Tag("cp37", "cp37m", platform)
138            for platform in (
139                "macosx_10_6_intel",
140                "macosx_10_9_intel",
141                "macosx_10_9_x86_64",
142                "macosx_10_10_intel",
143                "macosx_10_10_x86_64",
144            )
145        }
146        given = tags.parse_tag(
147            "cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64."
148            "macosx_10_10_intel.macosx_10_10_x86_64"
149        )
150        assert given == expected
151
152
153class TestInterpreterName:
154    def test_sys_implementation_name(self, monkeypatch):
155        class MockImplementation(object):
156            pass
157
158        mock_implementation = MockImplementation()
159        mock_implementation.name = "sillywalk"
160        monkeypatch.setattr(sys, "implementation", mock_implementation, raising=False)
161        assert tags.interpreter_name() == "sillywalk"
162
163    def test_platform(self, monkeypatch):
164        monkeypatch.delattr(sys, "implementation", raising=False)
165        name = "SillyWalk"
166        monkeypatch.setattr(platform, "python_implementation", lambda: name)
167        assert tags.interpreter_name() == name.lower()
168
169    def test_interpreter_short_names(self, mock_interpreter_name, monkeypatch):
170        mock_interpreter_name("cpython")
171        assert tags.interpreter_name() == "cp"
172
173
174class TestInterpreterVersion:
175    def test_warn(self, monkeypatch):
176        class MockConfigVar(object):
177            def __init__(self, return_):
178                self.warn = None
179                self._return = return_
180
181            def __call__(self, name, warn):
182                self.warn = warn
183                return self._return
184
185        mock_config_var = MockConfigVar("38")
186        monkeypatch.setattr(tags, "_get_config_var", mock_config_var)
187        tags.interpreter_version(warn=True)
188        assert mock_config_var.warn
189
190    def test_python_version_nodot(self, monkeypatch):
191        monkeypatch.setattr(tags, "_get_config_var", lambda var, warn: "NN")
192        assert tags.interpreter_version() == "NN"
193
194    @pytest.mark.parametrize(
195        "version_info,version_str",
196        [
197            ((1, 2, 3), "12"),
198            ((1, 12, 3), "1_12"),
199            ((11, 2, 3), "11_2"),
200            ((11, 12, 3), "11_12"),
201            ((1, 2, 13), "12"),
202        ],
203    )
204    def test_sys_version_info(self, version_info, version_str, monkeypatch):
205        monkeypatch.setattr(tags, "_get_config_var", lambda *args, **kwargs: None)
206        monkeypatch.setattr(sys, "version_info", version_info)
207        assert tags.interpreter_version() == version_str
208
209
210class TestMacOSPlatforms:
211    @pytest.mark.parametrize(
212        "arch, is_32bit, expected",
213        [
214            ("i386", True, "i386"),
215            ("ppc", True, "ppc"),
216            ("x86_64", False, "x86_64"),
217            ("x86_64", True, "i386"),
218            ("ppc64", False, "ppc64"),
219            ("ppc64", True, "ppc"),
220        ],
221    )
222    def test_architectures(self, arch, is_32bit, expected):
223        assert tags._mac_arch(arch, is_32bit=is_32bit) == expected
224
225    @pytest.mark.parametrize(
226        "version,arch,expected",
227        [
228            (
229                (10, 15),
230                "x86_64",
231                ["x86_64", "intel", "fat64", "fat32", "universal2", "universal"],
232            ),
233            (
234                (10, 4),
235                "x86_64",
236                ["x86_64", "intel", "fat64", "fat32", "universal2", "universal"],
237            ),
238            ((10, 3), "x86_64", []),
239            ((10, 15), "i386", ["i386", "intel", "fat32", "fat", "universal"]),
240            ((10, 4), "i386", ["i386", "intel", "fat32", "fat", "universal"]),
241            ((10, 3), "i386", []),
242            ((10, 15), "ppc64", []),
243            ((10, 6), "ppc64", []),
244            ((10, 5), "ppc64", ["ppc64", "fat64", "universal"]),
245            ((10, 3), "ppc64", []),
246            ((10, 15), "ppc", []),
247            ((10, 7), "ppc", []),
248            ((10, 6), "ppc", ["ppc", "fat32", "fat", "universal"]),
249            ((10, 0), "ppc", ["ppc", "fat32", "fat", "universal"]),
250            ((11, 0), "riscv", ["riscv"]),
251            (
252                (11, 0),
253                "x86_64",
254                ["x86_64", "intel", "fat64", "fat32", "universal2", "universal"],
255            ),
256            ((11, 0), "arm64", ["arm64", "universal2"]),
257            ((11, 1), "arm64", ["arm64", "universal2"]),
258            ((12, 0), "arm64", ["arm64", "universal2"]),
259        ],
260    )
261    def test_binary_formats(self, version, arch, expected):
262        assert tags._mac_binary_formats(version, arch) == expected
263
264    def test_version_detection(self, monkeypatch):
265        if platform.system() != "Darwin":
266            monkeypatch.setattr(
267                platform, "mac_ver", lambda: ("10.14", ("", "", ""), "x86_64")
268            )
269        version = platform.mac_ver()[0].split(".")
270        expected = "macosx_{major}_{minor}".format(major=version[0], minor=version[1])
271        platforms = list(tags.mac_platforms(arch="x86_64"))
272        assert platforms[0].startswith(expected)
273
274    @pytest.mark.parametrize("arch", ["x86_64", "i386"])
275    def test_arch_detection(self, arch, monkeypatch):
276        if platform.system() != "Darwin" or platform.mac_ver()[2] != arch:
277            monkeypatch.setattr(
278                platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch)
279            )
280            monkeypatch.setattr(tags, "_mac_arch", lambda *args: arch)
281        assert next(tags.mac_platforms((10, 14))).endswith(arch)
282
283    def test_mac_platforms(self):
284        platforms = list(tags.mac_platforms((10, 5), "x86_64"))
285        assert platforms == [
286            "macosx_10_5_x86_64",
287            "macosx_10_5_intel",
288            "macosx_10_5_fat64",
289            "macosx_10_5_fat32",
290            "macosx_10_5_universal2",
291            "macosx_10_5_universal",
292            "macosx_10_4_x86_64",
293            "macosx_10_4_intel",
294            "macosx_10_4_fat64",
295            "macosx_10_4_fat32",
296            "macosx_10_4_universal2",
297            "macosx_10_4_universal",
298        ]
299
300        assert len(list(tags.mac_platforms((10, 17), "x86_64"))) == 14 * 6
301
302        assert not list(tags.mac_platforms((10, 0), "x86_64"))
303
304    @pytest.mark.parametrize("major,minor", [(11, 0), (11, 3), (12, 0), (12, 3)])
305    def test_macos_11(self, major, minor):
306        platforms = list(tags.mac_platforms((major, minor), "x86_64"))
307        assert "macosx_11_0_arm64" not in platforms
308        assert "macosx_11_0_x86_64" in platforms
309        assert "macosx_11_3_x86_64" not in platforms
310        assert "macosx_11_0_universal" in platforms
311        assert "macosx_11_0_universal2" in platforms
312        # Mac OS "10.16" is the version number that binaries compiled against an old
313        # (pre 11.0) SDK will see.   It can also be enabled explicitly for a process
314        # with the environment variable SYSTEM_VERSION_COMPAT=1.
315        assert "macosx_10_16_x86_64" in platforms
316        assert "macosx_10_15_x86_64" in platforms
317        assert "macosx_10_4_x86_64" in platforms
318        assert "macosx_10_3_x86_64" not in platforms
319        if major >= 12:
320            assert "macosx_12_0_x86_64" in platforms
321            assert "macosx_12_0_universal" in platforms
322            assert "macosx_12_0_universal2" in platforms
323
324        platforms = list(tags.mac_platforms((major, minor), "arm64"))
325        assert "macosx_11_0_arm64" in platforms
326        assert "macosx_11_3_arm64" not in platforms
327        assert "macosx_11_0_universal" not in platforms
328        assert "macosx_11_0_universal2" in platforms
329        assert "macosx_10_15_x86_64" not in platforms
330        assert "macosx_10_4_x86_64" not in platforms
331        assert "macosx_10_3_x86_64" not in platforms
332        if major >= 12:
333            assert "macosx_12_0_arm64" in platforms
334            assert "macosx_12_0_universal2" in platforms
335
336
337class TestManylinuxPlatform:
338    def teardown_method(self):
339        # Clear the version cache
340        tags._glibc_version = []
341
342    @pytest.mark.parametrize("tf", (True, False))
343    @pytest.mark.parametrize(
344        "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17)))
345    )
346    def test_module_declaration(
347        self, monkeypatch, manylinux_module, attribute, glibc, tf
348    ):
349        manylinux = "manylinux{}_compatible".format(attribute)
350        monkeypatch.setattr(manylinux_module, manylinux, tf, raising=False)
351        res = tags._is_manylinux_compatible(manylinux, "x86_64", glibc)
352        assert tf is res
353
354    @pytest.mark.parametrize(
355        "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17)))
356    )
357    def test_module_declaration_missing_attribute(
358        self, monkeypatch, manylinux_module, attribute, glibc
359    ):
360        manylinux = "manylinux{}_compatible".format(attribute)
361        monkeypatch.delattr(manylinux_module, manylinux, raising=False)
362        assert tags._is_manylinux_compatible(manylinux, "x86_64", glibc)
363
364    @pytest.mark.parametrize(
365        "version,compatible", (((2, 0), True), ((2, 5), True), ((2, 10), False))
366    )
367    def test_is_manylinux_compatible_glibc_support(
368        self, version, compatible, monkeypatch
369    ):
370        monkeypatch.setitem(sys.modules, "_manylinux", None)
371        monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 5))
372        assert (
373            bool(tags._is_manylinux_compatible("manylinux1", "any", version))
374            == compatible
375        )
376
377    @pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"])
378    def test_check_glibc_version_warning(self, version_str):
379        with warnings.catch_warnings(record=True) as w:
380            tags._parse_glibc_version(version_str)
381            assert len(w) == 1
382            assert issubclass(w[0].category, RuntimeWarning)
383
384    @pytest.mark.skipif(not ctypes, reason="requires ctypes")
385    @pytest.mark.parametrize(
386        "version_str,expected",
387        [
388            # Be very explicit about bytes and Unicode for Python 2 testing.
389            (b"2.4", "2.4"),
390            (u"2.4", "2.4"),
391        ],
392    )
393    def test_glibc_version_string(self, version_str, expected, monkeypatch):
394        class LibcVersion:
395            def __init__(self, version_str):
396                self.version_str = version_str
397
398            def __call__(self):
399                return version_str
400
401        class ProcessNamespace:
402            def __init__(self, libc_version):
403                self.gnu_get_libc_version = libc_version
404
405        process_namespace = ProcessNamespace(LibcVersion(version_str))
406        monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace)
407        monkeypatch.setattr(tags, "_glibc_version_string_confstr", lambda: False)
408
409        assert tags._glibc_version_string() == expected
410
411        del process_namespace.gnu_get_libc_version
412        assert tags._glibc_version_string() is None
413
414    def test_glibc_version_string_confstr(self, monkeypatch):
415        monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
416        assert tags._glibc_version_string_confstr() == "2.20"
417
418    def test_glibc_version_string_fail(self, monkeypatch):
419        monkeypatch.setattr(os, "confstr", lambda x: None, raising=False)
420        monkeypatch.setitem(sys.modules, "ctypes", None)
421        assert tags._glibc_version_string() is None
422        assert tags._get_glibc_version() == (-1, -1)
423
424    @pytest.mark.parametrize(
425        "failure",
426        [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"],
427    )
428    def test_glibc_version_string_confstr_fail(self, monkeypatch, failure):
429        monkeypatch.setattr(os, "confstr", failure, raising=False)
430        assert tags._glibc_version_string_confstr() is None
431
432    def test_glibc_version_string_confstr_missing(self, monkeypatch):
433        monkeypatch.delattr(os, "confstr", raising=False)
434        assert tags._glibc_version_string_confstr() is None
435
436    def test_glibc_version_string_ctypes_missing(self, monkeypatch):
437        monkeypatch.setitem(sys.modules, "ctypes", None)
438        assert tags._glibc_version_string_ctypes() is None
439
440    def test_glibc_version_string_ctypes_raise_oserror(self, monkeypatch):
441        def patched_cdll(name):
442            raise OSError("Dynamic loading not supported")
443
444        monkeypatch.setattr(ctypes, "CDLL", patched_cdll)
445        assert tags._glibc_version_string_ctypes() is None
446
447    def test_get_config_var_does_not_log(self, monkeypatch):
448        debug = pretend.call_recorder(lambda *a: None)
449        monkeypatch.setattr(tags.logger, "debug", debug)
450        tags._get_config_var("missing")
451        assert debug.calls == []
452
453    def test_get_config_var_does_log(self, monkeypatch):
454        debug = pretend.call_recorder(lambda *a: None)
455        monkeypatch.setattr(tags.logger, "debug", debug)
456        tags._get_config_var("missing", warn=True)
457        assert debug.calls == [
458            pretend.call(
459                "Config variable '%s' is unset, Python ABI tag may be incorrect",
460                "missing",
461            )
462        ]
463
464    @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux")
465    def test_is_manylinux_compatible_old(self):
466        # Assuming no one is running this test with a version of glibc released in
467        # 1997.
468        assert tags._is_manylinux_compatible("any", "any", (2, 0))
469
470    def test_is_manylinux_compatible(self, monkeypatch):
471        monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.4")
472        assert tags._is_manylinux_compatible("", "any", (2, 4))
473
474    def test_glibc_version_string_none(self, monkeypatch):
475        monkeypatch.setattr(tags, "_glibc_version_string", lambda: None)
476        assert not tags._is_manylinux_compatible("any", "any", (2, 4))
477
478    @pytest.mark.parametrize(
479        "arch,is_32bit,expected",
480        [
481            ("linux-x86_64", False, "linux_x86_64"),
482            ("linux-x86_64", True, "linux_i686"),
483            ("linux-aarch64", False, "linux_aarch64"),
484            ("linux-aarch64", True, "linux_armv7l"),
485        ],
486    )
487    def test_linux_platforms_32_64bit_on_64bit_os(
488        self, arch, is_32bit, expected, monkeypatch
489    ):
490        monkeypatch.setattr(distutils.util, "get_platform", lambda: arch)
491        monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
492        monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False)
493        linux_platform = list(tags._linux_platforms(is_32bit=is_32bit))[-1]
494        assert linux_platform == expected
495
496    def test_linux_platforms_manylinux_unsupported(self, monkeypatch):
497        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
498        monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
499        monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False)
500        linux_platform = list(tags._linux_platforms(is_32bit=False))
501        assert linux_platform == ["linux_x86_64"]
502
503    def test_linux_platforms_manylinux1(self, is_x86, monkeypatch):
504        monkeypatch.setattr(
505            tags, "_is_manylinux_compatible", lambda name, *args: name == "manylinux1"
506        )
507        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
508        monkeypatch.setattr(platform, "machine", lambda: "x86_64")
509        monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
510        platforms = list(tags._linux_platforms(is_32bit=False))
511        arch = platform.machine()
512        assert platforms == ["manylinux1_" + arch, "linux_" + arch]
513
514    def test_linux_platforms_manylinux2010(self, is_x86, monkeypatch):
515        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
516        monkeypatch.setattr(platform, "machine", lambda: "x86_64")
517        monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.12", raising=False)
518        platforms = list(tags._linux_platforms(is_32bit=False))
519        arch = platform.machine()
520        expected = [
521            "manylinux_2_12_" + arch,
522            "manylinux2010_" + arch,
523            "manylinux_2_11_" + arch,
524            "manylinux_2_10_" + arch,
525            "manylinux_2_9_" + arch,
526            "manylinux_2_8_" + arch,
527            "manylinux_2_7_" + arch,
528            "manylinux_2_6_" + arch,
529            "manylinux_2_5_" + arch,
530            "manylinux1_" + arch,
531            "linux_" + arch,
532        ]
533        assert platforms == expected
534
535    def test_linux_platforms_manylinux2014(self, is_x86, monkeypatch):
536        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
537        monkeypatch.setattr(platform, "machine", lambda: "x86_64")
538        monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.17", raising=False)
539        platforms = list(tags._linux_platforms(is_32bit=False))
540        arch = platform.machine()
541        expected = [
542            "manylinux_2_17_" + arch,
543            "manylinux2014_" + arch,
544            "manylinux_2_16_" + arch,
545            "manylinux_2_15_" + arch,
546            "manylinux_2_14_" + arch,
547            "manylinux_2_13_" + arch,
548            "manylinux_2_12_" + arch,
549            "manylinux2010_" + arch,
550            "manylinux_2_11_" + arch,
551            "manylinux_2_10_" + arch,
552            "manylinux_2_9_" + arch,
553            "manylinux_2_8_" + arch,
554            "manylinux_2_7_" + arch,
555            "manylinux_2_6_" + arch,
556            "manylinux_2_5_" + arch,
557            "manylinux1_" + arch,
558            "linux_" + arch,
559        ]
560        assert platforms == expected
561
562    def test_linux_platforms_manylinux2014_armhf_abi(self, monkeypatch):
563        monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.30")
564        monkeypatch.setattr(
565            tags,
566            "_is_manylinux_compatible",
567            lambda name, *args: name == "manylinux2014",
568        )
569        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv7l")
570        monkeypatch.setattr(
571            sys,
572            "executable",
573            os.path.join(os.path.dirname(__file__), "hello-world-armv7l-armhf"),
574        )
575        platforms = list(tags._linux_platforms(is_32bit=True))
576        expected = ["manylinux2014_armv7l", "linux_armv7l"]
577        assert platforms == expected
578
579    def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch):
580        monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.17")
581        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
582        monkeypatch.setattr(
583            sys,
584            "executable",
585            os.path.join(os.path.dirname(__file__), "hello-world-x86_64-i386"),
586        )
587        platforms = list(tags._linux_platforms(is_32bit=True))
588        expected = [
589            "manylinux_2_17_i686",
590            "manylinux2014_i686",
591            "manylinux_2_16_i686",
592            "manylinux_2_15_i686",
593            "manylinux_2_14_i686",
594            "manylinux_2_13_i686",
595            "manylinux_2_12_i686",
596            "manylinux2010_i686",
597            "manylinux_2_11_i686",
598            "manylinux_2_10_i686",
599            "manylinux_2_9_i686",
600            "manylinux_2_8_i686",
601            "manylinux_2_7_i686",
602            "manylinux_2_6_i686",
603            "manylinux_2_5_i686",
604            "manylinux1_i686",
605            "linux_i686",
606        ]
607        assert platforms == expected
608
609    def test_linux_platforms_manylinux_glibc3(self, monkeypatch):
610        # test for a future glic 3.x version
611        monkeypatch.setattr(tags, "_glibc_version_string", lambda: "3.2")
612        monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, *args: True)
613        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_aarch64")
614        monkeypatch.setattr(
615            sys,
616            "executable",
617            os.path.join(os.path.dirname(__file__), "hello-world-aarch64"),
618        )
619        platforms = list(tags._linux_platforms(is_32bit=False))
620        expected = (
621            ["manylinux_3_2_aarch64", "manylinux_3_1_aarch64", "manylinux_3_0_aarch64"]
622            + ["manylinux_2_{}_aarch64".format(i) for i in range(50, 16, -1)]
623            + ["manylinux2014_aarch64", "linux_aarch64"]
624        )
625        assert platforms == expected
626
627    def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch):
628        monkeypatch.setattr(
629            tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014"
630        )
631        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l")
632        monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
633        platforms = list(tags._linux_platforms(is_32bit=True))
634        expected = ["linux_armv6l"]
635        assert platforms == expected
636
637    @pytest.mark.parametrize(
638        "machine, abi, alt_machine",
639        [("x86_64", "x32", "i686"), ("armv7l", "armel", "armv7l")],
640    )
641    def test_linux_platforms_not_manylinux_abi(
642        self, monkeypatch, machine, abi, alt_machine
643    ):
644        monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, _: False)
645        monkeypatch.setattr(
646            distutils.util, "get_platform", lambda: "linux_{}".format(machine)
647        )
648        monkeypatch.setattr(
649            sys,
650            "executable",
651            os.path.join(
652                os.path.dirname(__file__), "hello-world-{}-{}".format(machine, abi)
653            ),
654        )
655        platforms = list(tags._linux_platforms(is_32bit=True))
656        expected = ["linux_{}".format(alt_machine)]
657        assert platforms == expected
658
659    @pytest.mark.parametrize(
660        "machine, abi, elf_class, elf_data, elf_machine",
661        [
662            (
663                "x86_64",
664                "x32",
665                tags._ELFFileHeader.ELFCLASS32,
666                tags._ELFFileHeader.ELFDATA2LSB,
667                tags._ELFFileHeader.EM_X86_64,
668            ),
669            (
670                "x86_64",
671                "i386",
672                tags._ELFFileHeader.ELFCLASS32,
673                tags._ELFFileHeader.ELFDATA2LSB,
674                tags._ELFFileHeader.EM_386,
675            ),
676            (
677                "x86_64",
678                "amd64",
679                tags._ELFFileHeader.ELFCLASS64,
680                tags._ELFFileHeader.ELFDATA2LSB,
681                tags._ELFFileHeader.EM_X86_64,
682            ),
683            (
684                "armv7l",
685                "armel",
686                tags._ELFFileHeader.ELFCLASS32,
687                tags._ELFFileHeader.ELFDATA2LSB,
688                tags._ELFFileHeader.EM_ARM,
689            ),
690            (
691                "armv7l",
692                "armhf",
693                tags._ELFFileHeader.ELFCLASS32,
694                tags._ELFFileHeader.ELFDATA2LSB,
695                tags._ELFFileHeader.EM_ARM,
696            ),
697            (
698                "s390x",
699                "s390x",
700                tags._ELFFileHeader.ELFCLASS64,
701                tags._ELFFileHeader.ELFDATA2MSB,
702                tags._ELFFileHeader.EM_S390,
703            ),
704        ],
705    )
706    def test_get_elf_header(
707        self, monkeypatch, machine, abi, elf_class, elf_data, elf_machine
708    ):
709        path = os.path.join(
710            os.path.dirname(__file__), "hello-world-{}-{}".format(machine, abi)
711        )
712        monkeypatch.setattr(sys, "executable", path)
713        elf_header = tags._get_elf_header()
714        assert elf_header.e_ident_class == elf_class
715        assert elf_header.e_ident_data == elf_data
716        assert elf_header.e_machine == elf_machine
717
718    @pytest.mark.parametrize(
719        "content", [None, "invalid-magic", "invalid-class", "invalid-data", "too-short"]
720    )
721    def test_get_elf_header_bad_excutable(self, monkeypatch, content):
722        if content:
723            path = os.path.join(
724                os.path.dirname(__file__), "hello-world-{}".format(content)
725            )
726        else:
727            path = None
728        monkeypatch.setattr(sys, "executable", path)
729        assert tags._get_elf_header() is None
730
731    def test_is_linux_armhf_not_elf(self, monkeypatch):
732        monkeypatch.setattr(tags, "_get_elf_header", lambda: None)
733        assert not tags._is_linux_armhf()
734
735    def test_is_linux_i686_not_elf(self, monkeypatch):
736        monkeypatch.setattr(tags, "_get_elf_header", lambda: None)
737        assert not tags._is_linux_i686()
738
739
740@pytest.mark.parametrize(
741    "platform_name,dispatch_func",
742    [
743        ("Darwin", "mac_platforms"),
744        ("Linux", "_linux_platforms"),
745        ("Generic", "_generic_platforms"),
746    ],
747)
748def test__platform_tags(platform_name, dispatch_func, monkeypatch):
749    expected = ["sillywalk"]
750    monkeypatch.setattr(platform, "system", lambda: platform_name)
751    monkeypatch.setattr(tags, dispatch_func, lambda: expected)
752    assert tags._platform_tags() == expected
753
754
755class TestCPythonABI:
756    @pytest.mark.parametrize(
757        "py_debug,gettotalrefcount,result",
758        [(1, False, True), (0, False, False), (None, True, True)],
759    )
760    def test_debug(self, py_debug, gettotalrefcount, result, monkeypatch):
761        config = {"Py_DEBUG": py_debug, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": 2}
762        monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__)
763        if gettotalrefcount:
764            monkeypatch.setattr(sys, "gettotalrefcount", 1, raising=False)
765        expected = ["cp37d" if result else "cp37"]
766        assert tags._cpython_abis((3, 7)) == expected
767
768    def test_debug_file_extension(self, monkeypatch):
769        config = {"Py_DEBUG": None}
770        monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__)
771        monkeypatch.delattr(sys, "gettotalrefcount", raising=False)
772        monkeypatch.setattr(tags, "EXTENSION_SUFFIXES", {"_d.pyd"})
773        assert tags._cpython_abis((3, 8)) == ["cp38d", "cp38"]
774
775    @pytest.mark.parametrize(
776        "debug,expected", [(True, ["cp38d", "cp38"]), (False, ["cp38"])]
777    )
778    def test__debug_cp38(self, debug, expected, monkeypatch):
779        config = {"Py_DEBUG": debug}
780        monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__)
781        assert tags._cpython_abis((3, 8)) == expected
782
783    @pytest.mark.parametrize(
784        "pymalloc,version,result",
785        [
786            (1, (3, 7), True),
787            (0, (3, 7), False),
788            (None, (3, 7), True),
789            (1, (3, 8), False),
790        ],
791    )
792    def test_pymalloc(self, pymalloc, version, result, monkeypatch):
793        config = {"Py_DEBUG": 0, "WITH_PYMALLOC": pymalloc, "Py_UNICODE_SIZE": 2}
794        monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__)
795        base_abi = "cp{}{}".format(version[0], version[1])
796        expected = [base_abi + "m" if result else base_abi]
797        assert tags._cpython_abis(version) == expected
798
799    @pytest.mark.parametrize(
800        "unicode_size,maxunicode,version,result",
801        [
802            (4, 0x10FFFF, (3, 2), True),
803            (2, 0xFFFF, (3, 2), False),
804            (None, 0x10FFFF, (3, 2), True),
805            (None, 0xFFFF, (3, 2), False),
806            (4, 0x10FFFF, (3, 3), False),
807        ],
808    )
809    def test_wide_unicode(self, unicode_size, maxunicode, version, result, monkeypatch):
810        config = {"Py_DEBUG": 0, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": unicode_size}
811        monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__)
812        monkeypatch.setattr(sys, "maxunicode", maxunicode)
813        base_abi = "cp" + tags._version_nodot(version)
814        expected = [base_abi + "u" if result else base_abi]
815        assert tags._cpython_abis(version) == expected
816
817
818class TestCPythonTags:
819    def test_iterator_returned(self):
820        result_iterator = tags.cpython_tags(
821            (3, 8), ["cp38d", "cp38"], ["plat1", "plat2"]
822        )
823        assert isinstance(result_iterator, collections_abc.Iterator)
824
825    def test_all_args(self):
826        result_iterator = tags.cpython_tags(
827            (3, 11), ["cp3_11d", "cp3_11"], ["plat1", "plat2"]
828        )
829        result = list(result_iterator)
830        assert result == [
831            tags.Tag("cp3_11", "cp3_11d", "plat1"),
832            tags.Tag("cp3_11", "cp3_11d", "plat2"),
833            tags.Tag("cp3_11", "cp3_11", "plat1"),
834            tags.Tag("cp3_11", "cp3_11", "plat2"),
835            tags.Tag("cp3_11", "abi3", "plat1"),
836            tags.Tag("cp3_11", "abi3", "plat2"),
837            tags.Tag("cp3_11", "none", "plat1"),
838            tags.Tag("cp3_11", "none", "plat2"),
839            tags.Tag("cp3_10", "abi3", "plat1"),
840            tags.Tag("cp3_10", "abi3", "plat2"),
841            tags.Tag("cp39", "abi3", "plat1"),
842            tags.Tag("cp39", "abi3", "plat2"),
843            tags.Tag("cp38", "abi3", "plat1"),
844            tags.Tag("cp38", "abi3", "plat2"),
845            tags.Tag("cp37", "abi3", "plat1"),
846            tags.Tag("cp37", "abi3", "plat2"),
847            tags.Tag("cp36", "abi3", "plat1"),
848            tags.Tag("cp36", "abi3", "plat2"),
849            tags.Tag("cp35", "abi3", "plat1"),
850            tags.Tag("cp35", "abi3", "plat2"),
851            tags.Tag("cp34", "abi3", "plat1"),
852            tags.Tag("cp34", "abi3", "plat2"),
853            tags.Tag("cp33", "abi3", "plat1"),
854            tags.Tag("cp33", "abi3", "plat2"),
855            tags.Tag("cp32", "abi3", "plat1"),
856            tags.Tag("cp32", "abi3", "plat2"),
857        ]
858        result_iterator = tags.cpython_tags(
859            (3, 8), ["cp38d", "cp38"], ["plat1", "plat2"]
860        )
861        result = list(result_iterator)
862        assert result == [
863            tags.Tag("cp38", "cp38d", "plat1"),
864            tags.Tag("cp38", "cp38d", "plat2"),
865            tags.Tag("cp38", "cp38", "plat1"),
866            tags.Tag("cp38", "cp38", "plat2"),
867            tags.Tag("cp38", "abi3", "plat1"),
868            tags.Tag("cp38", "abi3", "plat2"),
869            tags.Tag("cp38", "none", "plat1"),
870            tags.Tag("cp38", "none", "plat2"),
871            tags.Tag("cp37", "abi3", "plat1"),
872            tags.Tag("cp37", "abi3", "plat2"),
873            tags.Tag("cp36", "abi3", "plat1"),
874            tags.Tag("cp36", "abi3", "plat2"),
875            tags.Tag("cp35", "abi3", "plat1"),
876            tags.Tag("cp35", "abi3", "plat2"),
877            tags.Tag("cp34", "abi3", "plat1"),
878            tags.Tag("cp34", "abi3", "plat2"),
879            tags.Tag("cp33", "abi3", "plat1"),
880            tags.Tag("cp33", "abi3", "plat2"),
881            tags.Tag("cp32", "abi3", "plat1"),
882            tags.Tag("cp32", "abi3", "plat2"),
883        ]
884
885        result = list(tags.cpython_tags((3, 3), ["cp33m"], ["plat1", "plat2"]))
886        assert result == [
887            tags.Tag("cp33", "cp33m", "plat1"),
888            tags.Tag("cp33", "cp33m", "plat2"),
889            tags.Tag("cp33", "abi3", "plat1"),
890            tags.Tag("cp33", "abi3", "plat2"),
891            tags.Tag("cp33", "none", "plat1"),
892            tags.Tag("cp33", "none", "plat2"),
893            tags.Tag("cp32", "abi3", "plat1"),
894            tags.Tag("cp32", "abi3", "plat2"),
895        ]
896
897    def test_python_version_defaults(self):
898        tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"]))
899        interpreter = "cp" + tags._version_nodot(sys.version_info[:2])
900        assert interpreter == tag.interpreter
901
902    def test_abi_defaults(self, monkeypatch):
903        monkeypatch.setattr(tags, "_cpython_abis", lambda _1, _2: ["cp38"])
904        result = list(tags.cpython_tags((3, 8), platforms=["any"]))
905        assert tags.Tag("cp38", "cp38", "any") in result
906        assert tags.Tag("cp38", "abi3", "any") in result
907        assert tags.Tag("cp38", "none", "any") in result
908
909    def test_abi_defaults_needs_underscore(self, monkeypatch):
910        monkeypatch.setattr(tags, "_cpython_abis", lambda _1, _2: ["cp3_11"])
911        result = list(tags.cpython_tags((3, 11), platforms=["any"]))
912        assert tags.Tag("cp3_11", "cp3_11", "any") in result
913        assert tags.Tag("cp3_11", "abi3", "any") in result
914        assert tags.Tag("cp3_11", "none", "any") in result
915
916    def test_platforms_defaults(self, monkeypatch):
917        monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat1"])
918        result = list(tags.cpython_tags((3, 8), abis=["whatever"]))
919        assert tags.Tag("cp38", "whatever", "plat1") in result
920
921    def test_platforms_defaults_needs_underscore(self, monkeypatch):
922        monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat1"])
923        result = list(tags.cpython_tags((3, 11), abis=["whatever"]))
924        assert tags.Tag("cp3_11", "whatever", "plat1") in result
925
926    def test_major_only_python_version(self):
927        result = list(tags.cpython_tags((3,), ["abi"], ["plat"]))
928        assert result == [
929            tags.Tag("cp3", "abi", "plat"),
930            tags.Tag("cp3", "none", "plat"),
931        ]
932
933    def test_major_only_python_version_with_default_abis(self):
934        result = list(tags.cpython_tags((3,), platforms=["plat"]))
935        assert result == [tags.Tag("cp3", "none", "plat")]
936
937    @pytest.mark.parametrize("abis", [[], ["abi3"], ["none"]])
938    def test_skip_redundant_abis(self, abis):
939        results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"]))
940        assert results == [tags.Tag("cp30", "none", "any")]
941
942    def test_abi3_python33(self):
943        results = list(tags.cpython_tags((3, 3), abis=["cp33"], platforms=["plat"]))
944        assert results == [
945            tags.Tag("cp33", "cp33", "plat"),
946            tags.Tag("cp33", "abi3", "plat"),
947            tags.Tag("cp33", "none", "plat"),
948            tags.Tag("cp32", "abi3", "plat"),
949        ]
950
951    def test_no_excess_abi3_python32(self):
952        results = list(tags.cpython_tags((3, 2), abis=["cp32"], platforms=["plat"]))
953        assert results == [
954            tags.Tag("cp32", "cp32", "plat"),
955            tags.Tag("cp32", "abi3", "plat"),
956            tags.Tag("cp32", "none", "plat"),
957        ]
958
959    def test_no_abi3_python31(self):
960        results = list(tags.cpython_tags((3, 1), abis=["cp31"], platforms=["plat"]))
961        assert results == [
962            tags.Tag("cp31", "cp31", "plat"),
963            tags.Tag("cp31", "none", "plat"),
964        ]
965
966    def test_no_abi3_python27(self):
967        results = list(tags.cpython_tags((2, 7), abis=["cp27"], platforms=["plat"]))
968        assert results == [
969            tags.Tag("cp27", "cp27", "plat"),
970            tags.Tag("cp27", "none", "plat"),
971        ]
972
973
974class TestGenericTags:
975    @pytest.mark.skipif(
976        not sysconfig.get_config_var("SOABI"), reason="SOABI not defined"
977    )
978    def test__generic_abi_soabi_provided(self):
979        abi = sysconfig.get_config_var("SOABI").replace(".", "_").replace("-", "_")
980        assert [abi] == list(tags._generic_abi())
981
982    def test__generic_abi(self, monkeypatch):
983        monkeypatch.setattr(
984            sysconfig, "get_config_var", lambda key: "cpython-37m-darwin"
985        )
986        assert list(tags._generic_abi()) == ["cpython_37m_darwin"]
987
988    def test__generic_abi_no_soabi(self, monkeypatch):
989        monkeypatch.setattr(sysconfig, "get_config_var", lambda key: None)
990        assert not list(tags._generic_abi())
991
992    def test_generic_platforms(self):
993        platform = distutils.util.get_platform().replace("-", "_")
994        platform = platform.replace(".", "_")
995        assert list(tags._generic_platforms()) == [platform]
996
997    def test_iterator_returned(self):
998        result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"])
999        assert isinstance(result_iterator, collections_abc.Iterator)
1000
1001    def test_all_args(self):
1002        result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"])
1003        result = list(result_iterator)
1004        assert result == [
1005            tags.Tag("sillywalk33", "abi", "plat1"),
1006            tags.Tag("sillywalk33", "abi", "plat2"),
1007            tags.Tag("sillywalk33", "none", "plat1"),
1008            tags.Tag("sillywalk33", "none", "plat2"),
1009        ]
1010
1011    @pytest.mark.parametrize("abi", [[], ["none"]])
1012    def test_abi_unspecified(self, abi):
1013        no_abi = list(tags.generic_tags("sillywalk34", abi, ["plat1", "plat2"]))
1014        assert no_abi == [
1015            tags.Tag("sillywalk34", "none", "plat1"),
1016            tags.Tag("sillywalk34", "none", "plat2"),
1017        ]
1018
1019    def test_interpreter_default(self, monkeypatch):
1020        monkeypatch.setattr(tags, "interpreter_name", lambda: "sillywalk")
1021        monkeypatch.setattr(tags, "interpreter_version", lambda warn: "NN")
1022        result = list(tags.generic_tags(abis=["none"], platforms=["any"]))
1023        assert result == [tags.Tag("sillywalkNN", "none", "any")]
1024
1025    def test_abis_default(self, monkeypatch):
1026        monkeypatch.setattr(tags, "_generic_abi", lambda: iter(["abi"]))
1027        result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"]))
1028        assert result == [
1029            tags.Tag("sillywalk", "abi", "any"),
1030            tags.Tag("sillywalk", "none", "any"),
1031        ]
1032
1033    def test_platforms_default(self, monkeypatch):
1034        monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat"])
1035        result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"]))
1036        assert result == [tags.Tag("sillywalk", "none", "plat")]
1037
1038
1039class TestCompatibleTags:
1040    def test_all_args(self):
1041        result = list(tags.compatible_tags((3, 3), "cp33", ["plat1", "plat2"]))
1042        assert result == [
1043            tags.Tag("py33", "none", "plat1"),
1044            tags.Tag("py33", "none", "plat2"),
1045            tags.Tag("py3", "none", "plat1"),
1046            tags.Tag("py3", "none", "plat2"),
1047            tags.Tag("py32", "none", "plat1"),
1048            tags.Tag("py32", "none", "plat2"),
1049            tags.Tag("py31", "none", "plat1"),
1050            tags.Tag("py31", "none", "plat2"),
1051            tags.Tag("py30", "none", "plat1"),
1052            tags.Tag("py30", "none", "plat2"),
1053            tags.Tag("cp33", "none", "any"),
1054            tags.Tag("py33", "none", "any"),
1055            tags.Tag("py3", "none", "any"),
1056            tags.Tag("py32", "none", "any"),
1057            tags.Tag("py31", "none", "any"),
1058            tags.Tag("py30", "none", "any"),
1059        ]
1060
1061    def test_all_args_needs_underscore(self):
1062        result = list(tags.compatible_tags((3, 11), "cp3_11", ["plat1", "plat2"]))
1063        assert result == [
1064            tags.Tag("py3_11", "none", "plat1"),
1065            tags.Tag("py3_11", "none", "plat2"),
1066            tags.Tag("py3", "none", "plat1"),
1067            tags.Tag("py3", "none", "plat2"),
1068            tags.Tag("py3_10", "none", "plat1"),
1069            tags.Tag("py3_10", "none", "plat2"),
1070            tags.Tag("py39", "none", "plat1"),
1071            tags.Tag("py39", "none", "plat2"),
1072            tags.Tag("py38", "none", "plat1"),
1073            tags.Tag("py38", "none", "plat2"),
1074            tags.Tag("py37", "none", "plat1"),
1075            tags.Tag("py37", "none", "plat2"),
1076            tags.Tag("py36", "none", "plat1"),
1077            tags.Tag("py36", "none", "plat2"),
1078            tags.Tag("py35", "none", "plat1"),
1079            tags.Tag("py35", "none", "plat2"),
1080            tags.Tag("py34", "none", "plat1"),
1081            tags.Tag("py34", "none", "plat2"),
1082            tags.Tag("py33", "none", "plat1"),
1083            tags.Tag("py33", "none", "plat2"),
1084            tags.Tag("py32", "none", "plat1"),
1085            tags.Tag("py32", "none", "plat2"),
1086            tags.Tag("py31", "none", "plat1"),
1087            tags.Tag("py31", "none", "plat2"),
1088            tags.Tag("py30", "none", "plat1"),
1089            tags.Tag("py30", "none", "plat2"),
1090            tags.Tag("cp3_11", "none", "any"),
1091            tags.Tag("py3_11", "none", "any"),
1092            tags.Tag("py3", "none", "any"),
1093            tags.Tag("py3_10", "none", "any"),
1094            tags.Tag("py39", "none", "any"),
1095            tags.Tag("py38", "none", "any"),
1096            tags.Tag("py37", "none", "any"),
1097            tags.Tag("py36", "none", "any"),
1098            tags.Tag("py35", "none", "any"),
1099            tags.Tag("py34", "none", "any"),
1100            tags.Tag("py33", "none", "any"),
1101            tags.Tag("py32", "none", "any"),
1102            tags.Tag("py31", "none", "any"),
1103            tags.Tag("py30", "none", "any"),
1104        ]
1105
1106    def test_major_only_python_version(self):
1107        result = list(tags.compatible_tags((3,), "cp33", ["plat"]))
1108        assert result == [
1109            tags.Tag("py3", "none", "plat"),
1110            tags.Tag("cp33", "none", "any"),
1111            tags.Tag("py3", "none", "any"),
1112        ]
1113
1114    def test_default_python_version(self, monkeypatch):
1115        monkeypatch.setattr(sys, "version_info", (3, 1))
1116        result = list(tags.compatible_tags(interpreter="cp31", platforms=["plat"]))
1117        assert result == [
1118            tags.Tag("py31", "none", "plat"),
1119            tags.Tag("py3", "none", "plat"),
1120            tags.Tag("py30", "none", "plat"),
1121            tags.Tag("cp31", "none", "any"),
1122            tags.Tag("py31", "none", "any"),
1123            tags.Tag("py3", "none", "any"),
1124            tags.Tag("py30", "none", "any"),
1125        ]
1126
1127    def test_default_python_version_needs_underscore(self, monkeypatch):
1128        monkeypatch.setattr(sys, "version_info", (3, 11))
1129        result = list(tags.compatible_tags(interpreter="cp3_11", platforms=["plat"]))
1130        assert result == [
1131            tags.Tag("py3_11", "none", "plat"),
1132            tags.Tag("py3", "none", "plat"),
1133            tags.Tag("py3_10", "none", "plat"),
1134            tags.Tag("py39", "none", "plat"),
1135            tags.Tag("py38", "none", "plat"),
1136            tags.Tag("py37", "none", "plat"),
1137            tags.Tag("py36", "none", "plat"),
1138            tags.Tag("py35", "none", "plat"),
1139            tags.Tag("py34", "none", "plat"),
1140            tags.Tag("py33", "none", "plat"),
1141            tags.Tag("py32", "none", "plat"),
1142            tags.Tag("py31", "none", "plat"),
1143            tags.Tag("py30", "none", "plat"),
1144            tags.Tag("cp3_11", "none", "any"),
1145            tags.Tag("py3_11", "none", "any"),
1146            tags.Tag("py3", "none", "any"),
1147            tags.Tag("py3_10", "none", "any"),
1148            tags.Tag("py39", "none", "any"),
1149            tags.Tag("py38", "none", "any"),
1150            tags.Tag("py37", "none", "any"),
1151            tags.Tag("py36", "none", "any"),
1152            tags.Tag("py35", "none", "any"),
1153            tags.Tag("py34", "none", "any"),
1154            tags.Tag("py33", "none", "any"),
1155            tags.Tag("py32", "none", "any"),
1156            tags.Tag("py31", "none", "any"),
1157            tags.Tag("py30", "none", "any"),
1158        ]
1159
1160    def test_default_interpreter(self):
1161        result = list(tags.compatible_tags((3, 1), platforms=["plat"]))
1162        assert result == [
1163            tags.Tag("py31", "none", "plat"),
1164            tags.Tag("py3", "none", "plat"),
1165            tags.Tag("py30", "none", "plat"),
1166            tags.Tag("py31", "none", "any"),
1167            tags.Tag("py3", "none", "any"),
1168            tags.Tag("py30", "none", "any"),
1169        ]
1170
1171    def test_default_platforms(self, monkeypatch):
1172        monkeypatch.setattr(tags, "_platform_tags", lambda: iter(["plat", "plat2"]))
1173        result = list(tags.compatible_tags((3, 1), "cp31"))
1174        assert result == [
1175            tags.Tag("py31", "none", "plat"),
1176            tags.Tag("py31", "none", "plat2"),
1177            tags.Tag("py3", "none", "plat"),
1178            tags.Tag("py3", "none", "plat2"),
1179            tags.Tag("py30", "none", "plat"),
1180            tags.Tag("py30", "none", "plat2"),
1181            tags.Tag("cp31", "none", "any"),
1182            tags.Tag("py31", "none", "any"),
1183            tags.Tag("py3", "none", "any"),
1184            tags.Tag("py30", "none", "any"),
1185        ]
1186
1187
1188class TestSysTags:
1189    def teardown_method(self):
1190        # Clear the version cache
1191        tags._glibc_version = []
1192
1193    @pytest.mark.parametrize(
1194        "name,expected",
1195        [("CPython", "cp"), ("PyPy", "pp"), ("Jython", "jy"), ("IronPython", "ip")],
1196    )
1197    def test_interpreter_name(self, name, expected, mock_interpreter_name):
1198        mock_interpreter_name(name)
1199        assert tags.interpreter_name() == expected
1200
1201    def test_iterator(self):
1202        assert isinstance(tags.sys_tags(), collections_abc.Iterator)
1203
1204    def test_mac_cpython(self, mock_interpreter_name, monkeypatch):
1205        if mock_interpreter_name("CPython"):
1206            monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"])
1207        if platform.system() != "Darwin":
1208            monkeypatch.setattr(platform, "system", lambda: "Darwin")
1209            monkeypatch.setattr(tags, "mac_platforms", lambda: ["macosx_10_5_x86_64"])
1210        abis = tags._cpython_abis(sys.version_info[:2])
1211        platforms = list(tags.mac_platforms())
1212        result = list(tags.sys_tags())
1213        assert len(abis) == 1
1214        assert result[0] == tags.Tag(
1215            "cp" + tags._version_nodot(sys.version_info[:2]), abis[0], platforms[0]
1216        )
1217        assert result[-1] == tags.Tag(
1218            "py" + tags._version_nodot((sys.version_info[0], 0)), "none", "any"
1219        )
1220
1221    def test_windows_cpython(self, mock_interpreter_name, monkeypatch):
1222        if mock_interpreter_name("CPython"):
1223            monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"])
1224        if platform.system() != "Windows":
1225            monkeypatch.setattr(platform, "system", lambda: "Windows")
1226            monkeypatch.setattr(tags, "_generic_platforms", lambda: ["win_amd64"])
1227        abis = list(tags._cpython_abis(sys.version_info[:2]))
1228        platforms = list(tags._generic_platforms())
1229        result = list(tags.sys_tags())
1230        interpreter = "cp" + tags._version_nodot(sys.version_info[:2])
1231        assert len(abis) == 1
1232        expected = tags.Tag(interpreter, abis[0], platforms[0])
1233        assert result[0] == expected
1234        expected = tags.Tag(
1235            "py" + tags._version_nodot((sys.version_info[0], 0)), "none", "any"
1236        )
1237        assert result[-1] == expected
1238
1239    def test_linux_cpython(self, mock_interpreter_name, monkeypatch):
1240        if mock_interpreter_name("CPython"):
1241            monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"])
1242        if platform.system() != "Linux":
1243            monkeypatch.setattr(platform, "system", lambda: "Linux")
1244            monkeypatch.setattr(tags, "_linux_platforms", lambda: ["linux_x86_64"])
1245        abis = list(tags._cpython_abis(sys.version_info[:2]))
1246        platforms = list(tags._linux_platforms())
1247        result = list(tags.sys_tags())
1248        expected_interpreter = "cp" + tags._version_nodot(sys.version_info[:2])
1249        assert len(abis) == 1
1250        assert result[0] == tags.Tag(expected_interpreter, abis[0], platforms[0])
1251        expected = tags.Tag(
1252            "py" + tags._version_nodot((sys.version_info[0], 0)), "none", "any"
1253        )
1254        assert result[-1] == expected
1255
1256    def test_generic(self, monkeypatch):
1257        monkeypatch.setattr(platform, "system", lambda: "Generic")
1258        monkeypatch.setattr(tags, "interpreter_name", lambda: "generic")
1259
1260        result = list(tags.sys_tags())
1261        expected = tags.Tag(
1262            "py" + tags._version_nodot((sys.version_info[0], 0)), "none", "any"
1263        )
1264        assert result[-1] == expected
1265
1266    def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch, manylinux_module):
1267        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l")
1268        monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
1269        platforms = list(tags._linux_platforms(is_32bit=True))
1270        expected = ["linux_armv6l"]
1271        assert platforms == expected
1272
1273    def test_skip_manylinux_2014(self, monkeypatch, manylinux_module):
1274        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_ppc64")
1275        monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 20))
1276        monkeypatch.setattr(
1277            manylinux_module, "manylinux2014_compatible", False, raising=False
1278        )
1279        expected = [
1280            "manylinux_2_20_ppc64",
1281            "manylinux_2_19_ppc64",
1282            "manylinux_2_18_ppc64",
1283            # "manylinux2014_ppc64",  # this one is skipped
1284            # "manylinux_2_17_ppc64", # this one is also skipped
1285            "linux_ppc64",
1286        ]
1287        platforms = list(tags._linux_platforms())
1288        assert platforms == expected
1289
1290    @pytest.mark.parametrize(
1291        "machine, abi, alt_machine",
1292        [("x86_64", "x32", "i686"), ("armv7l", "armel", "armv7l")],
1293    )
1294    def test_linux_platforms_not_manylinux_abi(
1295        self, monkeypatch, manylinux_module, machine, abi, alt_machine
1296    ):
1297        monkeypatch.setattr(
1298            distutils.util, "get_platform", lambda: "linux_{}".format(machine)
1299        )
1300        monkeypatch.setattr(
1301            sys,
1302            "executable",
1303            os.path.join(
1304                os.path.dirname(__file__), "hello-world-{}-{}".format(machine, abi)
1305            ),
1306        )
1307        platforms = list(tags._linux_platforms(is_32bit=True))
1308        expected = ["linux_{}".format(alt_machine)]
1309        assert platforms == expected
1310
1311    @pytest.mark.parametrize(
1312        "machine, major, minor, tf", [("x86_64", 2, 20, False), ("s390x", 2, 22, True)]
1313    )
1314    def test_linux_use_manylinux_compatible(
1315        self, monkeypatch, manylinux_module, machine, major, minor, tf
1316    ):
1317        def manylinux_compatible(tag_major, tag_minor, tag_arch):
1318            if tag_major == 2 and tag_minor == 22:
1319                return tag_arch == "s390x"
1320            return False
1321
1322        monkeypatch.setattr(tags, "_get_glibc_version", lambda: (major, minor))
1323        monkeypatch.setattr(
1324            distutils.util, "get_platform", lambda: "linux_{}".format(machine)
1325        )
1326        monkeypatch.setattr(
1327            manylinux_module,
1328            "manylinux_compatible",
1329            manylinux_compatible,
1330            raising=False,
1331        )
1332        platforms = list(tags._linux_platforms(is_32bit=False))
1333        if tf:
1334            expected = ["manylinux_2_22_{}".format(machine)]
1335        else:
1336            expected = []
1337        expected.append("linux_{}".format(machine))
1338        assert platforms == expected
1339
1340    def test_linux_use_manylinux_compatible_none(self, monkeypatch, manylinux_module):
1341        def manylinux_compatible(tag_major, tag_minor, tag_arch):
1342            if tag_major == 2 and tag_minor < 25:
1343                return False
1344            return None
1345
1346        monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 30))
1347        monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
1348        monkeypatch.setattr(
1349            manylinux_module,
1350            "manylinux_compatible",
1351            manylinux_compatible,
1352            raising=False,
1353        )
1354        platforms = list(tags._linux_platforms(is_32bit=False))
1355        expected = [
1356            "manylinux_2_30_x86_64",
1357            "manylinux_2_29_x86_64",
1358            "manylinux_2_28_x86_64",
1359            "manylinux_2_27_x86_64",
1360            "manylinux_2_26_x86_64",
1361            "manylinux_2_25_x86_64",
1362            "linux_x86_64",
1363        ]
1364        assert platforms == expected
1365