Files
orisys-sdk/tests/test_wheel_setup_metadata.py
T
2026-09-12 15:49:28 +08:00

173 lines
6.6 KiB
Python

"""Tests for build/wheel/package.py metadata plumbing.
The Nuitka wheel is built from a generated ``setup.py``, which is exactly where
the 0.5.2 release silently lost ``Requires-Python``, classifiers, project URLs
and the license. These tests run setuptools for real (in a temp dir, on a stub
package tree) and assert on the METADATA it produces.
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
BUILD_ROOT = REPO_ROOT / "build"
if str(BUILD_ROOT) not in sys.path:
sys.path.insert(0, str(BUILD_ROOT))
from wheel.package import metadata_kwargs, read_long_description, write_wheel_setup # noqa: E402
PROJECT = {
"name": "orisys",
"description": "SDK for Orisys Technology Visuotactile Sensors",
"requires-python": ">=3.10",
"license": {"text": "Proprietary"},
"authors": [{"name": "Orisys-Tech"}],
"maintainers": [{"name": "Yijian Zhang", "email": "zhangyijian@orisys-tech.cn"}],
"dependencies": ["numpy>=1.24.4"],
"optional-dependencies": {"gui": ["open3d>=0.17.0"]},
"classifiers": [
"Development Status :: 4 - Beta",
"License :: Other/Proprietary License",
],
"urls": {
"Homepage": "https://www.orisys-tech.cn",
"Documentation": "https://github.com/Orisys-Tech/orisys-sdk",
},
}
LONG_DESCRIPTION = "# Orisys SDK\n\nlong description body.\n"
def _stub_wheel_root(wheel_dir: Path) -> None:
"""Minimal package tree so the generated setup.py can build metadata."""
(wheel_dir / "wheel_root" / "orisys").mkdir(parents=True)
(wheel_dir / "wheel_root" / "orisys" / "__init__.cp312-win_amd64.pyd").write_bytes(b"x")
(wheel_dir / "wheel_root" / "orisys" / "examples").mkdir()
(wheel_dir / "wheel_root" / "orisys" / "examples" / "__init__.py").write_text("", encoding="utf-8")
(wheel_dir / "wheel_root" / "orisys_blend").mkdir()
(wheel_dir / "wheel_root" / "orisys_blend" / "__init__.py").write_text("", encoding="utf-8")
def _generate(wheel_dir: Path, tmp_path: Path) -> Path:
_stub_wheel_root(wheel_dir)
write_wheel_setup(
wheel_dir,
version="0.5.2",
package_data={"orisys": ["*.pyd"]},
install_requires=["numpy>=1.24.4"],
extras_require={"gui": ["open3d>=0.17.0"]},
project=PROJECT,
long_description=LONG_DESCRIPTION,
)
completed = subprocess.run(
[sys.executable, "setup.py", "-q", "dist_info"],
cwd=wheel_dir,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode == 0, (
f"generated setup.py failed:\nstdout:\n{completed.stdout}\nstderr:\n{completed.stderr}"
)
candidates = sorted(wheel_dir.glob("**/*.dist-info/METADATA"))
assert candidates, f"no METADATA produced under {wheel_dir}"
return candidates[0]
def test_metadata_kwargs_translates_pyproject():
kwargs = metadata_kwargs(PROJECT)
assert kwargs["description"] == PROJECT["description"]
assert kwargs["python_requires"] == ">=3.10"
assert kwargs["license"] == "Proprietary"
assert kwargs["classifiers"] == PROJECT["classifiers"]
assert kwargs["project_urls"] == PROJECT["urls"]
assert kwargs["author"] == "Orisys-Tech"
assert kwargs["maintainer"] == "Yijian Zhang <zhangyijian@orisys-tech.cn>"
def test_metadata_kwargs_accepts_string_license_and_drops_empties():
kwargs = metadata_kwargs({"name": "orisys", "license": "Proprietary"})
assert kwargs["license"] == "Proprietary"
assert "python_requires" not in kwargs
assert "classifiers" not in kwargs
def test_read_long_description_uses_pypi_readme(tmp_path):
(tmp_path / "docs").mkdir()
(tmp_path / "docs" / "pypi_readme.md").write_text(LONG_DESCRIPTION, encoding="utf-8")
assert read_long_description(tmp_path) == LONG_DESCRIPTION
def test_read_long_description_missing_returns_none(tmp_path, capsys):
assert read_long_description(tmp_path) is None
assert "not found" in capsys.readouterr().out
def test_generated_wheel_metadata_is_complete(tmp_path):
wheel_dir = tmp_path / "wheel"
wheel_dir.mkdir()
metadata_path = _generate(wheel_dir, tmp_path)
text = metadata_path.read_text(encoding="utf-8")
assert "Name: orisys" in text
assert "Version: 0.5.2" in text
assert f"Summary: {PROJECT['description']}" in text
assert "Requires-Python: >=3.10" in text
assert "License: Proprietary" in text
assert "Classifier: Development Status :: 4 - Beta" in text
assert "Classifier: License :: Other/Proprietary License" in text
assert "Project-URL: Homepage, https://www.orisys-tech.cn" in text
assert "Project-URL: Documentation, https://github.com/Orisys-Tech/orisys-sdk" in text
assert "Requires-Dist: numpy>=1.24.4" in text
assert 'Provides-Extra: gui' in text
assert "Description-Content-Type: text/markdown" in text
assert "long description body." in text
def test_generated_wheel_metadata_keeps_version_static(tmp_path):
"""``Version`` (and ``Name``) must never be annotated as dynamic.
setuptools marks every field supplied through the legacy ``setup()`` path as
``Dynamic:`` even when the value is present and correct (verified by
``test_generated_wheel_metadata_is_complete``). PEP 643 ``Dynamic`` is
sdist-only and pip/PyPI ignore it in wheels, so only the identity fields are
asserted here.
"""
wheel_dir = tmp_path / "wheel"
wheel_dir.mkdir()
text = _generate(wheel_dir, tmp_path).read_text(encoding="utf-8")
dynamic = {line[len("Dynamic: ") :].strip() for line in text.splitlines() if line.startswith("Dynamic:")}
assert "version" not in dynamic, f"wheel METADATA marks Version as dynamic: {sorted(dynamic)}"
assert "name" not in dynamic, f"wheel METADATA marks Name as dynamic: {sorted(dynamic)}"
def test_write_wheel_setup_without_project_keeps_legacy_shape(tmp_path):
"""Calling without metadata must not crash (backwards compatible)."""
wheel_dir = tmp_path / "wheel"
wheel_dir.mkdir()
setup_py = write_wheel_setup(
wheel_dir,
version="0.0.1",
package_data={"orisys": ["*.pyd"]},
install_requires=["numpy"],
extras_require={},
)
text = setup_py.read_text(encoding="utf-8")
assert 'name="orisys"' in text
assert 'version="0.0.1"' in text
assert "long_description" not in text
def test_pypi_readme_exists_in_repo():
assert (REPO_ROOT / "docs" / "pypi_readme.md").is_file(), (
"docs/pypi_readme.md is the PyPI long description source; without it "
"wheels are published with no project description."
)