Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ repos:
rev: v1.18.1
hooks:
- id: mypy
exclude: '^(docs|tasks|tests)|setup\.py'
exclude: '^(docs|tasks)'
args: []
additional_dependencies: [pyparsing, nox, orjson]
additional_dependencies: [pyparsing, nox, orjson, 'pytest<9', tomli, tomli_w]

- repo: https://github.com/codespell-project/codespell
rev: "v2.4.1"
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ strict = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
warn_unused_ignores = true
python_version = "3.8"
files = ["src", "tests", "noxfile.py"]

[[tool.mypy.overrides]]
module = ["_manylinux"]
module = ["_manylinux", "pretend"]
ignore_missing_imports = true

[tool.ruff]
Expand Down
4 changes: 2 additions & 2 deletions src/packaging/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import os
import platform
import sys
from typing import AbstractSet, Any, Callable, Literal, TypedDict, Union, cast
from typing import AbstractSet, Any, Callable, Literal, Mapping, TypedDict, Union, cast

from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
from ._parser import parse_marker as _parse_marker
Expand Down Expand Up @@ -314,7 +314,7 @@ def __eq__(self, other: object) -> bool:

def evaluate(
self,
environment: dict[str, str] | None = None,
environment: Mapping[str, str | AbstractSet[str]] | None = None,
context: EvaluateContext = "metadata",
) -> bool:
"""Evaluate a marker.
Expand Down
16 changes: 10 additions & 6 deletions tests/test_elffile.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
("s390x-s390x", EIClass.C64, EIData.Msb, EMachine.S390),
],
)
def test_elffile_glibc(name, capacity, encoding, machine):
def test_elffile_glibc(
name: str, capacity: EIClass, encoding: EIData, machine: EMachine
) -> None:
path = DIR_MANYLINUX.joinpath(f"hello-world-{name}")
with path.open("rb") as f:
ef = ELFFile(f)
Expand All @@ -46,7 +48,9 @@ def test_elffile_glibc(name, capacity, encoding, machine):
("x86_64", EIClass.C64, EIData.Lsb, EMachine.X8664, "x86_64"),
],
)
def test_elffile_musl(name, capacity, encoding, machine, interpreter):
def test_elffile_musl(
name: str, capacity: EIClass, encoding: EIData, machine: EMachine, interpreter: str
) -> None:
path = DIR_MUSLLINUX.joinpath(f"musl-{name}")
with path.open("rb") as f:
ef = ELFFile(f)
Expand All @@ -68,25 +72,25 @@ def test_elffile_musl(name, capacity, encoding, machine, interpreter):
],
ids=["no-magic", "wrong-magic", "unknown-format"],
)
def test_elffile_bad_ident(data):
def test_elffile_bad_ident(data: bytes) -> None:
with pytest.raises(ELFInvalid):
ELFFile(io.BytesIO(data))


def test_elffile_no_section():
def test_elffile_no_section() -> None:
"""Enough for magic, but not the section definitions."""
data = BIN_MUSL_X86_64[:25]
with pytest.raises(ELFInvalid):
ELFFile(io.BytesIO(data))


def test_elffile_invalid_section():
def test_elffile_invalid_section() -> None:
"""Enough for section definitions, but not the actual sections."""
data = BIN_MUSL_X86_64[:58]
assert ELFFile(io.BytesIO(data)).interpreter is None


def test_elffle_no_interpreter_section():
def test_elffle_no_interpreter_section() -> None:
ef = ELFFile(io.BytesIO(BIN_MUSL_X86_64))

# Change all sections to *not* PT_INTERP.
Expand Down
4 changes: 2 additions & 2 deletions tests/test_licenses.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from packaging.licenses._spdx import EXCEPTIONS, LICENSES


def test_licenses():
def test_licenses() -> None:
for license_id in LICENSES:
assert license_id == license_id.lower()


def test_exceptions():
def test_exceptions() -> None:
for exception_id in EXCEPTIONS:
assert exception_id == exception_id.lower()
97 changes: 63 additions & 34 deletions tests/test_manylinux.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from __future__ import annotations

try:
import ctypes
except ImportError:
ctypes = None
ctypes = None # type: ignore[assignment]
import os
import pathlib
import platform
import re
import sys
import types
import typing

if typing.TYPE_CHECKING:
from collections.abc import Generator

import pretend
import pytest
Expand All @@ -18,20 +24,21 @@
_glibc_version_string,
_glibc_version_string_confstr,
_glibc_version_string_ctypes,
_GLibCVersion,
_is_compatible,
_parse_elf,
_parse_glibc_version,
)


@pytest.fixture(autouse=True)
def clear_lru_cache():
def clear_lru_cache() -> Generator[None, None, None]:
yield
_get_glibc_version.cache_clear()


@pytest.fixture
def manylinux_module(monkeypatch):
def manylinux_module(monkeypatch: pytest.MonkeyPatch) -> types.ModuleType:
monkeypatch.setattr(_manylinux, "_get_glibc_version", lambda *args: (2, 20))
module_name = "_manylinux"
module = types.ModuleType(module_name)
Expand All @@ -43,41 +50,55 @@ def manylinux_module(monkeypatch):
@pytest.mark.parametrize(
("attribute", "glibc"), [("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17))]
)
def test_module_declaration(monkeypatch, manylinux_module, attribute, glibc, tf):
def test_module_declaration(
monkeypatch: pytest.MonkeyPatch,
manylinux_module: types.ModuleType,
attribute: str,
glibc: tuple[int, int],
tf: bool,
) -> None:
manylinux = f"manylinux{attribute}_compatible"
monkeypatch.setattr(manylinux_module, manylinux, tf, raising=False)
res = _is_compatible("x86_64", glibc)
glibc_version = _GLibCVersion(glibc[0], glibc[1])
res = _is_compatible("x86_64", glibc_version)
assert tf is res


@pytest.mark.parametrize(
("attribute", "glibc"), [("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17))]
)
def test_module_declaration_missing_attribute(
monkeypatch, manylinux_module, attribute, glibc
):
monkeypatch: pytest.MonkeyPatch,
manylinux_module: types.ModuleType,
attribute: str,
glibc: tuple[int, int],
) -> None:
manylinux = f"manylinux{attribute}_compatible"
monkeypatch.delattr(manylinux_module, manylinux, raising=False)
assert _is_compatible("x86_64", glibc)
glibc_version = _GLibCVersion(glibc[0], glibc[1])
assert _is_compatible("x86_64", glibc_version)


@pytest.mark.parametrize(
("version", "compatible"), [((2, 0), True), ((2, 5), True), ((2, 10), False)]
)
def test_is_manylinux_compatible_glibc_support(version, compatible, monkeypatch):
def test_is_manylinux_compatible_glibc_support(
version: tuple[int, int], compatible: bool, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setitem(sys.modules, "_manylinux", None)
monkeypatch.setattr(_manylinux, "_get_glibc_version", lambda: (2, 5))
assert bool(_is_compatible("any", version)) == compatible
glibc_version = _GLibCVersion(version[0], version[1])
assert bool(_is_compatible("any", glibc_version)) == compatible


@pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"])
def test_check_glibc_version_warning(version_str):
def test_check_glibc_version_warning(version_str: str) -> None:
msg = f"Expected glibc version with 2 components major.minor, got: {version_str}"
with pytest.warns(RuntimeWarning, match=re.escape(msg)):
_parse_glibc_version(version_str)


@pytest.mark.skipif(not ctypes, reason="requires ctypes")
@pytest.mark.skipif(not ctypes, reason="requires ctypes") # type: ignore[truthy-bool]
@pytest.mark.parametrize(
("version_str", "expected"),
[
Expand All @@ -86,16 +107,18 @@ def test_check_glibc_version_warning(version_str):
("2.4", "2.4"),
],
)
def test_glibc_version_string(version_str, expected, monkeypatch):
def test_glibc_version_string(
version_str: str | bytes, expected: str, monkeypatch: pytest.MonkeyPatch
) -> None:
class LibcVersion:
def __init__(self, version_str):
def __init__(self, version_str: str | bytes) -> None:
self.version_str = version_str

def __call__(self):
return version_str
def __call__(self) -> str | bytes:
return self.version_str

class ProcessNamespace:
def __init__(self, libc_version):
def __init__(self, libc_version: LibcVersion) -> None:
self.gnu_get_libc_version = libc_version

process_namespace = ProcessNamespace(LibcVersion(version_str))
Expand All @@ -108,12 +131,12 @@ def __init__(self, libc_version):
assert _glibc_version_string() is None


def test_glibc_version_string_confstr(monkeypatch):
def test_glibc_version_string_confstr(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(os, "confstr", lambda _: "glibc 2.20", raising=False)
assert _glibc_version_string_confstr() == "2.20"


def test_glibc_version_string_fail(monkeypatch):
def test_glibc_version_string_fail(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(os, "confstr", lambda _: None, raising=False)
monkeypatch.setitem(sys.modules, "ctypes", None)
assert _glibc_version_string() is None
Expand All @@ -124,55 +147,61 @@ def test_glibc_version_string_fail(monkeypatch):
"failure",
[pretend.raiser(ValueError), pretend.raiser(OSError), lambda _: "XXX"],
)
def test_glibc_version_string_confstr_fail(monkeypatch, failure):
def test_glibc_version_string_confstr_fail(
monkeypatch: pytest.MonkeyPatch, failure: typing.Callable[[int], str | None]
) -> None:
monkeypatch.setattr(os, "confstr", failure, raising=False)
assert _glibc_version_string_confstr() is None


def test_glibc_version_string_confstr_missing(monkeypatch):
def test_glibc_version_string_confstr_missing(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delattr(os, "confstr", raising=False)
assert _glibc_version_string_confstr() is None


def test_glibc_version_string_ctypes_missing(monkeypatch):
def test_glibc_version_string_ctypes_missing(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setitem(sys.modules, "ctypes", None)
assert _glibc_version_string_ctypes() is None


@pytest.mark.xfail(ctypes is None, reason="ctypes not available")
def test_glibc_version_string_ctypes_raise_oserror(monkeypatch):
def patched_cdll(_name):
def test_glibc_version_string_ctypes_raise_oserror(
monkeypatch: pytest.MonkeyPatch,
) -> None:
def patched_cdll(_name: str) -> None:
raise OSError("Dynamic loading not supported")

monkeypatch.setattr(ctypes, "CDLL", patched_cdll)
assert _glibc_version_string_ctypes() is None


@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux")
def test_is_manylinux_compatible_old():
def test_is_manylinux_compatible_old() -> None:
# Assuming no one is running this test with a version of glibc released in
# 1997.
assert _is_compatible("any", (2, 0))
assert _is_compatible("any", _GLibCVersion(2, 0))


def test_is_manylinux_compatible(monkeypatch):
def test_is_manylinux_compatible(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(_manylinux, "_glibc_version_string", lambda: "2.4")
assert _is_compatible("any", (2, 4))
assert _is_compatible("any", _GLibCVersion(2, 4))


def test_glibc_version_string_none(monkeypatch):
def test_glibc_version_string_none(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(_manylinux, "_glibc_version_string", lambda: None)
assert not _is_compatible("any", (2, 4))
assert not _is_compatible("any", _GLibCVersion(2, 4))


@pytest.mark.parametrize(
"content", [None, "invalid-magic", "invalid-class", "invalid-data", "too-short"]
)
def test_parse_elf_bad_executable(content):
def test_parse_elf_bad_executable(content: str | None) -> None:
path_str: str | None
if content:
path = pathlib.Path(__file__).parent / "manylinux" / f"hello-world-{content}"
path = os.fsdecode(path)
path_str = os.fsdecode(path)
else:
path = None
with _parse_elf(path) as ef:
path_str = None
# None is not supported in the type annotation, but it was tested before.
with _parse_elf(path_str) as ef: # type: ignore[arg-type]
assert ef is None
Loading
Loading