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