175 lines
6.6 KiB
Python
175 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 early 0.5.x releases 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
|
|
|
|
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 ( # noqa: E402
|
|
metadata_kwargs,
|
|
read_long_description,
|
|
write_wheel_setup,
|
|
)
|
|
|
|
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="1.2.3",
|
|
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: 1.2.3" 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."
|
|
)
|