diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1de5904 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include requirements.txt +recursive-include csrc *.cpp *.hpp diff --git a/tests/test_sdist.py b/tests/test_sdist.py new file mode 100644 index 0000000..1d562fc --- /dev/null +++ b/tests/test_sdist.py @@ -0,0 +1,79 @@ +"""Check source archive inputs without compiling CUDA or Ascend extensions.""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +import tarfile +from pathlib import Path + +import pytest + +# Keep the real setup.py and setuptools commands. Only accelerator discovery +# and extension construction are substituted, so this test needs no SDK or GPU. +_SETUP_SCRIPT = """ +import os +import runpy +import sys +import types + +from setuptools import Extension +from setuptools.command.build_ext import build_ext + +backend, command = sys.argv[1:] +for name in ( + "torch", "torch.utils", "torch.utils.cpp_extension", + "torch_npu", "torch_npu.utils", "torch_npu.utils.cpp_extension", + "deep_cluster", "deep_cluster.hardware", +): + sys.modules[name] = types.ModuleType(name) +sys.modules["torch"]._C = types.SimpleNamespace(_GLIBCXX_USE_CXX11_ABI=True) +ext = sys.modules["torch.utils.cpp_extension"] +ext.CUDA_HOME = "/unused/cuda" +ext.CUDAExtension = Extension +ext.BuildExtension = build_ext +sys.modules["torch_npu.utils.cpp_extension"].NpuExtension = Extension +sys.modules["deep_cluster.hardware"].accelerator_kind = lambda: backend +os.environ["VIRTUAL_ENV"] = "/unused/venv" +sys.argv = ["setup.py", command] +runpy.run_path("setup.py", run_name="__main__") +""" + + +def _run_setup(source: Path, backend: str, command: str) -> None: + result = subprocess.run( + [sys.executable, "-c", _SETUP_SCRIPT, backend, command], + cwd=source, + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stdout + result.stderr + + +@pytest.mark.parametrize("backend", ["nvidia", "ascend"]) +def test_source_archive_contains_build_inputs(tmp_path: Path, backend: str) -> None: + root = Path(__file__).resolve().parents[1] + source = tmp_path / "source" + shutil.copytree( + root, + source, + ignore=shutil.ignore_patterns(".git", "*.egg-info", "__pycache__", "build", "dist", "vendor"), + ) + _run_setup(source, backend, "sdist") + archives = list((source / "dist").glob("*.tar.gz")) + assert len(archives) == 1 + with tarfile.open(archives[0]) as archive: + members = {name.split("/", 1)[-1] for name in archive.getnames()} + required = {"requirements.txt"} + required.update(path.relative_to(root).as_posix() for path in (root / "csrc").rglob("*") if path.is_file()) + assert required <= members, f"Missing source build inputs: {sorted(required - members)}" + extracted = tmp_path / "extracted" + archive.extractall(extracted, filter="data") + + # A source archive must remain usable without the original checkout, and + # contain the inputs for both supported backends regardless of its producer. + unpacked = next(extracted.iterdir()) + for target in ("nvidia", "ascend"): + _run_setup(unpacked, target, "egg_info")