otko/tests/unit/test_packaging.py

72 lines
2.4 KiB
Python

"""Packaging invariants — version/solver single-sourcing and dependency hygiene.
Pure static checks over the repository files. No imports of :mod:`otko`,
Qt or the solver stack, so this test runs anywhere (including Windows 3.12).
"""
from __future__ import annotations
import re
from pathlib import Path
try: # Python 3.11+
import tomllib
except ModuleNotFoundError: # pragma: no cover - Python 3.10 backport
import tomli as tomllib
REPO_ROOT = Path(__file__).resolve().parents[2]
SRC_ROOT = REPO_ROOT / "src" / "otko"
PYPROJECT = REPO_ROOT / "pyproject.toml"
_VERSION_RE = re.compile(r"^__version__\s*=", re.MULTILINE)
def _pyproject() -> dict:
with PYPROJECT.open("rb") as handle:
return tomllib.load(handle)
def _src_files() -> list[Path]:
return sorted(SRC_ROOT.rglob("*.py"))
def _dep_name(requirement: str) -> str:
"""Return the lowercased distribution name from a PEP 508 requirement."""
match = re.match(r"^\s*([A-Za-z0-9][A-Za-z0-9._-]*)", requirement)
return match.group(1).lower().replace("_", "-") if match else ""
def test_console_script_entrypoint() -> None:
assert _pyproject()["project"]["scripts"]["otko"] == "otko.__main__:main"
def test_project_version_is_dynamic() -> None:
project = _pyproject()["project"]
assert "version" not in project
assert project["dynamic"] == ["version"]
def test_single_version_assignment() -> None:
hits = [path for path in _src_files() if _VERSION_RE.search(path.read_text(encoding="utf-8"))]
assert len(hits) == 1, f"expected exactly one __version__ assignment, got {hits}"
assert hits[0] == SRC_ROOT / "_const.py"
def test_solver_version_literal_only_in_const() -> None:
hits = [path for path in _src_files() if "3.8.0.0" in path.read_text(encoding="utf-8")]
assert hits == [SRC_ROOT / "_const.py"], f"unexpected 3.8.0.0 occurrences: {hits}"
def test_ruff_line_length_and_ignore() -> None:
ruff = _pyproject()["tool"]["ruff"]
assert ruff["line-length"] == 100
assert "E501" in ruff["lint"]["ignore"]
def test_no_phantom_scipy_pandas_dependencies() -> None:
project = _pyproject()["project"]
groups = [project.get("dependencies", [])]
groups.extend(project.get("optional-dependencies", {}).values())
declared = {_dep_name(req) for group in groups for req in group}
assert "scipy" not in declared
assert "pandas" not in declared