"""The package must actually be installable and importable. Before the `src/` -> `affmae/` rename, `pyproject.toml` declared `packages.find include = ["src*"]` with **no `__init__.py` anywhere**, so `src` was a PEP-420 namespace package that `find_packages` could not see: `pip install .` shipped **zero modules**. Only `pip install -e .` worked, and only because `pythonpath = ["."]` put the source tree on the path. Reusing any part of this repo from another project was impossible regardless of coupling. The build itself is exercised by test_wheel_ships_modules, which is marked slow. """ import subprocess import sys from pathlib import Path import pytest REPO = Path(__file__).resolve().parents[1] PACKAGE = "affmae" @pytest.fixture(scope="module") def pyproject(): with open(REPO / "pyproject.toml", "rb") as handle: return _load(handle) def _load(handle): """tomllib is 3.11+; tomli is the 3.10 backport.""" try: import tomllib except ModuleNotFoundError: try: import tomli as tomllib except ModuleNotFoundError: import pytest pytest.skip("needs tomllib (py>=3.11) or tomli", allow_module_level=True) return tomllib.load(handle) class TestDiscoverability: def test_setuptools_finds_the_packages(self, pyproject): """The failure mode that shipped an empty wheel.""" from setuptools import find_packages include = pyproject["tool"]["setuptools"]["packages"]["find"]["include"] found = find_packages(where=str(REPO), include=include) assert found, ( f"find_packages(include={include}) is empty -- pip install . would " f"ship no modules. Every package directory needs an __init__.py.") assert PACKAGE in found def test_every_package_dir_has_an_init(self): """A missing __init__.py silently drops a subpackage from the wheel.""" missing = [] for path in sorted((REPO / PACKAGE).rglob("*")): if not path.is_dir() or "__pycache__" in path.parts: continue # The CUDA extension's own src/ holds .cu/.cpp, not importable code. if path.name == "src" and path.parent.name == "cuda_ext": continue if not (path / "__init__.py").exists(): missing.append(path.relative_to(REPO).as_posix()) assert missing == [], f"package dirs without __init__.py: {missing}" def test_package_is_not_named_src(self, pyproject): """`src` as a distributed package name collides with every other repo.""" assert pyproject["project"]["name"] == PACKAGE assert not (REPO / "src").exists(), "the old src/ tree is still present" class TestDependencyDeclaration: def test_triton_is_optional(self, pyproject): """Triton is CUDA-centric; a hard dep contradicts CPU/MPS support.""" required = " ".join(pyproject["project"]["dependencies"]) assert "triton" not in required, "triton must live in the [cuda] extra" extras = pyproject["project"]["optional-dependencies"] assert any("triton" in dep for dep in extras["cuda"]) def test_renderers_are_optional(self, pyproject): """Someone who wants only the operators should not install matplotlib.""" required = " ".join(pyproject["project"]["dependencies"]) for dep in ("matplotlib", "scikit-learn", "opencv"): assert dep not in required, f"{dep} must be an extra, not required" def test_declared_extras_exist(self, pyproject): extras = pyproject["project"]["optional-dependencies"] for name in ("cuda", "baselines", "viz", "train", "demo"): assert name in extras, f"missing extra: {name}" def test_license_is_not_asserted_prematurely(self, pyproject): """Licensing is deferred pending lab guidance. Drop this test once the LICENSE file lands. """ assert "license" not in pyproject["project"], ( "add the LICENSE file and the pyproject license field together") @pytest.mark.slow def test_wheel_ships_modules(tmp_path): """Build and install into a clean venv, then import from elsewhere. Importing from a different cwd is the point: from inside the repo, `affmae` resolves to the source tree whether or not the install worked. """ venv = tmp_path / "venv" subprocess.run([sys.executable, "-m", "venv", str(venv)], check=True, capture_output=True) python = venv / "bin" / "python" build = subprocess.run( [str(python), "-m", "pip", "install", "--no-deps", "--quiet", str(REPO)], capture_output=True, text=True) assert build.returncode == 0, build.stderr[-3000:] probe = subprocess.run( [str(python), "-c", "import affmae, pathlib;" "root = pathlib.Path(affmae.__file__).parent;" "print('site-packages' in str(root), len(list(root.rglob('*.py'))))"], cwd=tmp_path, capture_output=True, text=True) assert probe.returncode == 0, probe.stderr[-2000:] in_site, count = probe.stdout.split() assert in_site == "True", "import resolved to the source tree, not the install" assert int(count) > 20, f"wheel shipped only {count} modules" class TestKeopsIsDefaultInstalledButCapabilityGated: """pykeops is a default dependency, and that is safe because of the probe. Two requirements that look contradictory and are not. It must be installed by default: the ``unfused`` decoder path calls ``knn_keops``, and without it that silently falls back to a slower implementation, which inflated every fused-vs-unfused ratio measured before it was installed -- the 1024px figure went from 2.66x to 1.82x once it was. A silent fallback that moves a headline number by 46% does not belong behind an optional extra. And it must not break a portable install: KeOps has no ROCm and no MPS backend. That is handled by capability rather than by absence -- ``dispatch.can_use_keops`` refuses those platforms with the package present -- so installing it everywhere is safe. This class pins both halves, because dropping either one reintroduces a real bug: without the dependency, silent slow paths; without the probe, a hard crash on ROCm and Apple silicon. """ def test_declared_as_a_runtime_dependency(self, pyproject): deps = [d.split(">")[0].split("[")[0].strip().lower() for d in pyproject["project"]["dependencies"]] assert "pykeops" in deps, ( f"pykeops must be default-installed, not an extra; got {deps}") def test_the_extra_still_resolves(self, pyproject): """`affmae[keops]` appears in existing scripts and docs; keep it valid.""" extras = pyproject["project"]["optional-dependencies"] assert extras.get("keops") == ["pykeops"] def test_the_probe_refuses_rocm_and_mps(self, monkeypatch): """The flag that makes the default install safe. Verified on an MI300X with pykeops 2.3 actually installed: the refusal is real, not an import failure. `is_cuda` cannot be used here, because a ROCm tensor reports `is_cuda == True` -- measured on that same hardware. """ import torch from affmae.ops import dispatch monkeypatch.setattr(dispatch, "is_rocm_build", lambda: True) assert dispatch.can_use_keops(torch.zeros(1)) is False, ( "a ROCm build must refuse KeOps even for a CPU tensor") monkeypatch.setattr(dispatch, "is_rocm_build", lambda: False) mps = torch.zeros(1).to("meta") # stands in for a non-CUDA accel assert dispatch.can_use_keops(mps) is False class TestSpaceApp: """The HuggingFace Space entry point, kept honest against the library. The Space is a copy of this repository plus app.py, so the two cannot be allowed to drift: a rename in affmae/demo.py that app.py still calls the old way fails only once the Space rebuilds, where nobody is watching. """ APP = REPO / "app.py" def test_it_exists_and_parses(self): import ast ast.parse(self.APP.read_text()) def test_importing_it_has_no_side_effects(self, monkeypatch): """It must not set CHECKPOINT_ROOT at import; other tests read that.""" import importlib import os monkeypatch.delenv("CHECKPOINT_ROOT", raising=False) module = importlib.import_module("app") importlib.reload(module) assert "CHECKPOINT_ROOT" not in os.environ def test_zerogpu_is_gated_on_the_hardware_not_the_package(self, monkeypatch): """`spaces` is in requirements.txt, so it is importable on a CPU Space. Gating on the import alone would decorate handlers with spaces.GPU and force device="cuda" on hardware that has neither. """ import importlib monkeypatch.delenv("SPACES_ZERO_GPU", raising=False) module = importlib.reload(importlib.import_module("app")) assert module.HAS_ZEROGPU is False def test_it_calls_build_interface_with_the_gpu_hook(self): """The whole point of the Space; without gpu= it silently runs on CPU.""" source = self.APP.read_text() assert "build_interface(" in source assert "gpu=gpu" in source def test_requirements_covers_what_the_app_needs(self): required = {"spaces", "torch", "gradio", "huggingface_hub", "triton"} text = (REPO / "requirements.txt").read_text() listed = {line.split(">=")[0].split("==")[0].strip() for line in text.splitlines() if line.strip() and not line.startswith("#")} assert required <= listed, required - listed def test_requirements_omits_pykeops(self): """KeOps compiles CUDA C++ on first use; a Space has no toolchain. dispatch.can_use_keops() gates it by capability, so leaving it out means the PyTorch KNN path, not a failure. """ listed = {line.split(">=")[0].split("==")[0].strip() for line in (REPO / "requirements.txt").read_text().splitlines() if line.strip() and not line.lstrip().startswith("#")} # Not a substring check: the file's own comment explains the omission. assert "pykeops" not in listed