1try:
2    import ctypes
3except ImportError:
4    ctypes = None
5import os
6import platform
7import sys
8import types
9import warnings
10
11import pretend
12import pytest
13
14from packaging import _manylinux
15from packaging._manylinux import (
16    _ELFFileHeader,
17    _get_elf_header,
18    _get_glibc_version,
19    _glibc_version_string,
20    _glibc_version_string_confstr,
21    _glibc_version_string_ctypes,
22    _is_compatible,
23    _is_linux_armhf,
24    _is_linux_i686,
25    _parse_glibc_version,
26)
27
28
29@pytest.fixture(autouse=True)
30def clear_lru_cache():
31    yield
32    _get_glibc_version.cache_clear()
33
34
35@pytest.fixture
36def manylinux_module(monkeypatch):
37    monkeypatch.setattr(_manylinux, "_get_glibc_version", lambda *args: (2, 20))
38    module_name = "_manylinux"
39    module = types.ModuleType(module_name)
40    monkeypatch.setitem(sys.modules, module_name, module)
41    return module
42
43
44@pytest.mark.parametrize("tf", (True, False))
45@pytest.mark.parametrize(
46    "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17)))
47)
48def test_module_declaration(monkeypatch, manylinux_module, attribute, glibc, tf):
49    manylinux = f"manylinux{attribute}_compatible"
50    monkeypatch.setattr(manylinux_module, manylinux, tf, raising=False)
51    res = _is_compatible(manylinux, "x86_64", glibc)
52    assert tf is res
53
54
55@pytest.mark.parametrize(
56    "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17)))
57)
58def test_module_declaration_missing_attribute(
59    monkeypatch, manylinux_module, attribute, glibc
60):
61    manylinux = f"manylinux{attribute}_compatible"
62    monkeypatch.delattr(manylinux_module, manylinux, raising=False)
63    assert _is_compatible(manylinux, "x86_64", glibc)
64
65
66@pytest.mark.parametrize(
67    "version,compatible", (((2, 0), True), ((2, 5), True), ((2, 10), False))
68)
69def test_is_manylinux_compatible_glibc_support(version, compatible, monkeypatch):
70    monkeypatch.setitem(sys.modules, "_manylinux", None)
71    monkeypatch.setattr(_manylinux, "_get_glibc_version", lambda: (2, 5))
72    assert bool(_is_compatible("manylinux1", "any", version)) == compatible
73
74
75@pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"])
76def test_check_glibc_version_warning(version_str):
77    with warnings.catch_warnings(record=True) as w:
78        _parse_glibc_version(version_str)
79        assert len(w) == 1
80        assert issubclass(w[0].category, RuntimeWarning)
81
82
83@pytest.mark.skipif(not ctypes, reason="requires ctypes")
84@pytest.mark.parametrize(
85    "version_str,expected",
86    [
87        # Be very explicit about bytes and Unicode for Python 2 testing.
88        (b"2.4", "2.4"),
89        ("2.4", "2.4"),
90    ],
91)
92def test_glibc_version_string(version_str, expected, monkeypatch):
93    class LibcVersion:
94        def __init__(self, version_str):
95            self.version_str = version_str
96
97        def __call__(self):
98            return version_str
99
100    class ProcessNamespace:
101        def __init__(self, libc_version):
102            self.gnu_get_libc_version = libc_version
103
104    process_namespace = ProcessNamespace(LibcVersion(version_str))
105    monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace)
106    monkeypatch.setattr(_manylinux, "_glibc_version_string_confstr", lambda: False)
107
108    assert _glibc_version_string() == expected
109
110    del process_namespace.gnu_get_libc_version
111    assert _glibc_version_string() is None
112
113
114def test_glibc_version_string_confstr(monkeypatch):
115    monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
116    assert _glibc_version_string_confstr() == "2.20"
117
118
119def test_glibc_version_string_fail(monkeypatch):
120    monkeypatch.setattr(os, "confstr", lambda x: None, raising=False)
121    monkeypatch.setitem(sys.modules, "ctypes", None)
122    assert _glibc_version_string() is None
123    assert _get_glibc_version() == (-1, -1)
124
125
126@pytest.mark.parametrize(
127    "failure",
128    [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"],
129)
130def test_glibc_version_string_confstr_fail(monkeypatch, failure):
131    monkeypatch.setattr(os, "confstr", failure, raising=False)
132    assert _glibc_version_string_confstr() is None
133
134
135def test_glibc_version_string_confstr_missing(monkeypatch):
136    monkeypatch.delattr(os, "confstr", raising=False)
137    assert _glibc_version_string_confstr() is None
138
139
140def test_glibc_version_string_ctypes_missing(monkeypatch):
141    monkeypatch.setitem(sys.modules, "ctypes", None)
142    assert _glibc_version_string_ctypes() is None
143
144
145def test_glibc_version_string_ctypes_raise_oserror(monkeypatch):
146    def patched_cdll(name):
147        raise OSError("Dynamic loading not supported")
148
149    monkeypatch.setattr(ctypes, "CDLL", patched_cdll)
150    assert _glibc_version_string_ctypes() is None
151
152
153@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux")
154def test_is_manylinux_compatible_old():
155    # Assuming no one is running this test with a version of glibc released in
156    # 1997.
157    assert _is_compatible("any", "any", (2, 0))
158
159
160def test_is_manylinux_compatible(monkeypatch):
161    monkeypatch.setattr(_manylinux, "_glibc_version_string", lambda: "2.4")
162    assert _is_compatible("", "any", (2, 4))
163
164
165def test_glibc_version_string_none(monkeypatch):
166    monkeypatch.setattr(_manylinux, "_glibc_version_string", lambda: None)
167    assert not _is_compatible("any", "any", (2, 4))
168
169
170def test_is_linux_armhf_not_elf(monkeypatch):
171    monkeypatch.setattr(_manylinux, "_get_elf_header", lambda: None)
172    assert not _is_linux_armhf()
173
174
175def test_is_linux_i686_not_elf(monkeypatch):
176    monkeypatch.setattr(_manylinux, "_get_elf_header", lambda: None)
177    assert not _is_linux_i686()
178
179
180@pytest.mark.parametrize(
181    "machine, abi, elf_class, elf_data, elf_machine",
182    [
183        (
184            "x86_64",
185            "x32",
186            _ELFFileHeader.ELFCLASS32,
187            _ELFFileHeader.ELFDATA2LSB,
188            _ELFFileHeader.EM_X86_64,
189        ),
190        (
191            "x86_64",
192            "i386",
193            _ELFFileHeader.ELFCLASS32,
194            _ELFFileHeader.ELFDATA2LSB,
195            _ELFFileHeader.EM_386,
196        ),
197        (
198            "x86_64",
199            "amd64",
200            _ELFFileHeader.ELFCLASS64,
201            _ELFFileHeader.ELFDATA2LSB,
202            _ELFFileHeader.EM_X86_64,
203        ),
204        (
205            "armv7l",
206            "armel",
207            _ELFFileHeader.ELFCLASS32,
208            _ELFFileHeader.ELFDATA2LSB,
209            _ELFFileHeader.EM_ARM,
210        ),
211        (
212            "armv7l",
213            "armhf",
214            _ELFFileHeader.ELFCLASS32,
215            _ELFFileHeader.ELFDATA2LSB,
216            _ELFFileHeader.EM_ARM,
217        ),
218        (
219            "s390x",
220            "s390x",
221            _ELFFileHeader.ELFCLASS64,
222            _ELFFileHeader.ELFDATA2MSB,
223            _ELFFileHeader.EM_S390,
224        ),
225    ],
226)
227def test_get_elf_header(monkeypatch, machine, abi, elf_class, elf_data, elf_machine):
228    path = os.path.join(
229        os.path.dirname(__file__),
230        "manylinux",
231        f"hello-world-{machine}-{abi}",
232    )
233    monkeypatch.setattr(sys, "executable", path)
234    elf_header = _get_elf_header()
235    assert elf_header.e_ident_class == elf_class
236    assert elf_header.e_ident_data == elf_data
237    assert elf_header.e_machine == elf_machine
238
239
240@pytest.mark.parametrize(
241    "content", [None, "invalid-magic", "invalid-class", "invalid-data", "too-short"]
242)
243def test_get_elf_header_bad_executable(monkeypatch, content):
244    if content:
245        path = os.path.join(
246            os.path.dirname(__file__),
247            "manylinux",
248            f"hello-world-{content}",
249        )
250    else:
251        path = None
252    monkeypatch.setattr(sys, "executable", path)
253    assert _get_elf_header() is None
254