Files
orisys-sdk/tests/test_release_publish.py
ykcheungab 9c951e9d3c fix: 将 ARM64 wheel 纳入发布校验和上传流程
补齐全部架构的运行证据、GitHub 发布说明和 PyPI 逐文件下载核验。已通过 161 项测试、代码和文档门禁、九个 wheel 的本地索引下载核验,以及 ARM 三个 ABI 的 40 帧 CPU 检查。
2026-10-15 21:39:31 +08:00

733 lines
29 KiB
Python

"""Tests for the publish gate, the GitHub plan and the PyPI command shape.
Nothing here touches the network, ``gh`` or an index: the mutating paths are
verified through their pure planning functions, which is exactly what the CLI
executes after the gate passes.
"""
from __future__ import annotations
import json
import subprocess
import sys
import urllib.error
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 release import github, publish, pypi # noqa: E402
def _wheel(tmp_path: Path, name: str, payload: bytes = b"wheel") -> Path:
path = tmp_path / name
path.write_bytes(payload)
return path
def _report_payload(version: str, wheels: list[Path], **overrides) -> dict:
payload = {
"schema": 1,
"version": version,
"ok": True,
"aborted": False,
"git": {"commit": "a" * 40},
"options": {"skip_tests": False, "allow_dirty": False},
"wheels": [
{"name": wheel.name, "sha256": publish.sha256_file(wheel), "size": wheel.stat().st_size}
for wheel in wheels
],
"steps": [{"name": "wheel-runtime", "ok": True, "data": {
"records": [{"wheel": wheel.name, "sha256": publish.sha256_file(wheel)} for wheel in wheels]
}}],
}
payload.update(overrides)
return payload
# --- gate -------------------------------------------------------------------
def test_gate_passes_for_matching_wheels(tmp_path):
wheels = [_wheel(tmp_path, "orisys-1.2.3-cp310-cp310-win_amd64.whl")]
problems, warnings = publish.evaluate_gate(
_report_payload("1.2.3", wheels), "1.2.3", {w.name: w for w in wheels}
)
assert problems == []
assert warnings == []
def test_gate_rejects_version_mismatch(tmp_path):
wheels = [_wheel(tmp_path, "orisys-1.2.3-cp310-cp310-win_amd64.whl")]
problems, _ = publish.evaluate_gate(
_report_payload("9.9.9", wheels), "1.2.3", {w.name: w for w in wheels}
)
assert any("report is for version" in problem for problem in problems)
def test_gate_rejects_failed_report(tmp_path):
wheels = [_wheel(tmp_path, "orisys-1.2.3-cp310-cp310-win_amd64.whl")]
problems, _ = publish.evaluate_gate(
_report_payload("1.2.3", wheels, ok=False), "1.2.3", {w.name: w for w in wheels}
)
assert any("result is FAIL" in problem for problem in problems)
def test_gate_rejects_waived_guard_options(tmp_path):
wheels = [_wheel(tmp_path, "orisys-1.2.3-cp310-cp310-win_amd64.whl")]
payload = _report_payload("1.2.3", wheels, options={"skip_tests": True, "no_runtime": True})
problems, _ = publish.evaluate_gate(payload, "1.2.3", {w.name: w for w in wheels})
assert any("--skip-tests" in problem and "--no-runtime" in problem for problem in problems)
def test_gate_rejects_changed_wheel(tmp_path):
wheels = [_wheel(tmp_path, "orisys-1.2.3-cp310-cp310-win_amd64.whl")]
payload = _report_payload("1.2.3", wheels)
wheels[0].write_bytes(b"tampered")
problems, _ = publish.evaluate_gate(payload, "1.2.3", {w.name: w for w in wheels})
assert any("changed after preflight" in problem for problem in problems)
def test_gate_rejects_arm_wheel_without_runtime_evidence(tmp_path):
wheel = _wheel(tmp_path, "orisys-1.2.3-cp311-cp311-manylinux_2_28_aarch64.whl")
payload = _report_payload("1.2.3", [wheel], steps=[])
problems, _ = publish.evaluate_gate(payload, "1.2.3", {wheel.name: wheel})
assert any("runtime" in problem and wheel.name in problem for problem in problems)
def test_gate_rejects_runtime_evidence_for_different_bytes(tmp_path):
wheel = _wheel(tmp_path, "orisys-1.2.3-cp311-cp311-manylinux_2_28_aarch64.whl")
payload = _report_payload("1.2.3", [wheel])
payload["steps"][0]["data"]["records"][0]["sha256"] = "stale"
problems, _ = publish.evaluate_gate(payload, "1.2.3", {wheel.name: wheel})
assert any("runtime" in problem for problem in problems)
def test_gate_rejects_uncertified_and_missing_wheels(tmp_path):
wheels = [_wheel(tmp_path, "orisys-1.2.3-cp310-cp310-win_amd64.whl")]
payload = _report_payload("1.2.3", wheels)
extra = _wheel(tmp_path, "orisys-1.2.3-cp311-cp311-win_amd64.whl")
problems, _ = publish.evaluate_gate(
payload, "1.2.3", {w.name: w for w in [*wheels, extra]}
)
assert any("not covered by the report" in problem for problem in problems)
def test_gate_warns_on_commit_drift(tmp_path):
wheels = [_wheel(tmp_path, "orisys-1.2.3-cp310-cp310-win_amd64.whl")]
payload = _report_payload("1.2.3", wheels)
problems, warnings = publish.evaluate_gate(
payload, "1.2.3", {w.name: w for w in wheels}, head_commit="b" * 40
)
assert problems == []
assert any("HEAD is" in warning for warning in warnings)
def test_load_gate_requires_a_report(tmp_path, monkeypatch):
monkeypatch.setattr(publish, "REPO_ROOT", tmp_path, raising=False)
with pytest.raises(publish.PublishGateError, match="no preflight report"):
publish.load_gate(tmp_path, "1.2.3")
def test_load_gate_explains_waived_report(tmp_path):
report_dir = tmp_path / "build" / "release" / "reports" / "1.2.3"
report_dir.mkdir(parents=True)
wheel_dir = tmp_path / "SDK" / "1.2.3"
wheel_dir.mkdir(parents=True)
wheel = wheel_dir / "orisys-1.2.3-cp310-cp310-win_amd64.whl"
wheel.write_bytes(b"wheel")
(report_dir / "preflight.json").write_text(
json.dumps(_report_payload("1.2.3", [wheel], options={"skip_tests": True})),
encoding="utf-8",
)
with pytest.raises(publish.PublishGateError, match="--skip-tests"):
publish.load_gate(tmp_path, "1.2.3")
def test_load_gate_returns_certified_wheels(tmp_path):
report_dir = tmp_path / "build" / "release" / "reports" / "1.2.3"
report_dir.mkdir(parents=True)
wheel_dir = tmp_path / "SDK" / "1.2.3"
wheel_dir.mkdir(parents=True)
wheel = wheel_dir / "orisys-1.2.3-cp310-cp310-win_amd64.whl"
wheel.write_bytes(b"wheel")
(report_dir / "preflight.json").write_text(
json.dumps(_report_payload("1.2.3", [wheel])), encoding="utf-8"
)
gate = publish.load_gate(tmp_path, "1.2.3")
assert [record.name for record in gate.wheels] == [wheel.name]
assert gate.wheels[0].sha256 == publish.sha256_file(wheel)
assert gate.git_commit == "a" * 40
# --- github -----------------------------------------------------------------
def test_github_steps_shape(tmp_path):
assets = [_wheel(tmp_path, f"orisys-1.2.3-cp3n{n}-cp3n{n}-win_amd64.whl") for n in (0, 1)]
steps = github.build_github_steps(
version="1.2.3",
branch="main",
tag="v1.2.3",
assets=assets,
notes_file=None,
)
by_description = {step.description: step.argv for step in steps}
assert by_description["create tag"] == ["git", "tag", "v1.2.3"]
assert by_description["push branch"] == ["git", "push", "origin", "main"]
assert by_description["push tag"] == ["git", "push", "origin", "v1.2.3"]
release_argv = by_description["create GitHub Release with assets"]
assert release_argv[:2] == ["gh", "release"]
assert "v1.2.3" in release_argv
assert "--verify-tag" in release_argv
assert "--latest" in release_argv
assert release_argv[-2:] == [str(asset) for asset in assets]
def test_github_steps_include_notes_file(tmp_path):
notes = tmp_path / "notes.md"
notes.write_text("hello", encoding="utf-8")
steps = github.build_github_steps(
version="1.2.3", branch="main", tag="v1.2.3", assets=[], notes_file=notes
)
release_argv = next(
step.argv for step in steps if step.description.startswith("create GitHub Release")
)
assert "--notes-file" in release_argv
assert str(notes) in release_argv
def test_release_notes_list_every_wheel():
notes = github.build_release_notes(
"1.2.3", [Path("orisys-1.2.3-cp311-cp311-win_amd64.whl"),
Path("orisys-1.2.3-cp311-cp311-manylinux_2_28_aarch64.whl")], "c" * 40
)
assert "win_amd64.whl" in notes and "aarch64.whl" in notes
assert "Linux ARM64" in notes
assert "Linux x86_64" not in notes
assert "cccccccccccc" in notes
def test_manifest_scope_excludes_internal_docs():
"""The public repo must not receive our internal process documents."""
entry = github.manifest_entry()
docs_entry = next(item for item in entry["sync"] if item["from"] == "docs")
assert set(docs_entry["include"]) == {"get_started.md", "api.md"}
assert "publishing.md" not in docs_entry["include"]
assert "pypi_readme.md" not in docs_entry["include"]
def test_manifest_scope_covers_examples_and_sensor_json_only():
entry = github.manifest_entry()
examples_entry = next(item for item in entry["sync"] if item["from"] == "examples")
# Whole tree (no include list), with caches excluded.
assert "include" not in examples_entry
assert set(examples_entry["exclude"]) >= {"__pycache__", "*.pyc"}
sensors_entry = next(item for item in entry["sync"] if item["from"] == "sensors")
assert sensors_entry["include"]
assert all(
name.endswith(".json") for name in sensors_entry["include"]
), "binary sensor assets belong in the wheel, not the public repo"
def test_github_steps_resume_skips_tag_creation(tmp_path):
"""A tag pushed by an earlier failed run must not block the release upload."""
assets = [_wheel(tmp_path, "orisys-1.2.3-cp310-cp310-win_amd64.whl")]
steps = github.build_github_steps(
version="1.2.3",
branch="main",
tag="v1.2.3",
assets=assets,
notes_file=None,
create_tag=False,
)
descriptions = [step.description for step in steps]
assert "create tag" not in descriptions
assert descriptions[-1] == "create GitHub Release with assets"
# The pushes stay: pushing an up-to-date ref is a safe no-op.
assert "push branch" in descriptions and "push tag" in descriptions
def test_non_github_remote_is_reported(tmp_path):
repo = tmp_path / "release"
repo.mkdir()
subprocess.run(["git", "init", "-q"], cwd=repo, check=True, capture_output=True)
subprocess.run(
["git", "remote", "add", "origin", "ssh://git@internal.example:24928/orisys-tech/orisys-sdk.git"],
cwd=repo,
check=True,
capture_output=True,
)
problem = github.non_github_remote_problem(repo)
assert problem is not None and "not a GitHub host" in problem
subprocess.run(
["git", "remote", "set-url", "origin", "git@github.com:orisys-tech/orisys-sdk.git"],
cwd=repo,
check=True,
capture_output=True,
)
assert github.non_github_remote_problem(repo) is None
def _synthetic_publish_layout(tmp_path: Path, version: str, *, tag_exists: bool):
"""Build a fake SDK checkout + release repo and point the module at them."""
sdk = tmp_path / "sdk"
wheel_dir = sdk / "SDK" / version
wheel_dir.mkdir(parents=True)
wheels = [wheel_dir / f"orisys-{version}-cp310-cp310-win_amd64.whl"]
wheels[0].write_bytes(b"wheel")
report_dir = sdk / "build" / "release" / "reports" / version
report_dir.mkdir(parents=True)
(report_dir / "preflight.json").write_text(
json.dumps(_report_payload(version, wheels)), encoding="utf-8"
)
release = tmp_path / "orisys-sdk-release"
release.mkdir()
for command in (
["git", "init", "-q"],
["git", "config", "user.email", "test@example.invalid"],
["git", "config", "user.name", "test"],
["git", "remote", "add", "origin", "git@github.com:orisys-tech/orisys-sdk.git"],
):
subprocess.run(command, cwd=release, check=True, capture_output=True)
(release / "README.md").write_text("x", encoding="utf-8")
subprocess.run(["git", "add", "-A"], cwd=release, check=True, capture_output=True)
subprocess.run(["git", "commit", "-qm", "init"], cwd=release, check=True, capture_output=True)
if tag_exists:
subprocess.run(["git", "tag", f"v{version}"], cwd=release, check=True, capture_output=True)
manifest = sdk / "tools" / "downstream_sync_manifest.json"
manifest.parent.mkdir(parents=True)
manifest.write_text(
json.dumps(
{
"version": 1,
"downstreams": [
{"name": "orisys-sdk-release", "path": "../orisys-sdk-release", "sync": []}
],
}
),
encoding="utf-8",
)
return sdk, release, wheels[0]
def test_publish_dry_run_resumes_when_tag_already_exists(tmp_path, monkeypatch, capsys):
sdk, release, wheel = _synthetic_publish_layout(tmp_path, "1.2.3", tag_exists=True)
monkeypatch.setattr(github, "REPO_ROOT", sdk)
monkeypatch.setattr(github, "MANIFEST_PATH", sdk / "tools" / "downstream_sync_manifest.json")
monkeypatch.setattr(github, "_check_gh", lambda repo: [])
monkeypatch.setattr(github, "_release_exists", lambda repo, tag: False)
exit_code = github.publish(
github.Options(version="1.2.3", apply=False, skip_fetch=True)
)
output = capsys.readouterr().out
assert exit_code == 0
assert "already exist" in output and "skipping tag creation" in output
assert "git tag v1.2.3" not in output
assert "gh release create v1.2.3" in output
def test_publish_dry_run_creates_tag_when_absent(tmp_path, monkeypatch, capsys):
sdk, release, wheel = _synthetic_publish_layout(tmp_path, "1.2.4", tag_exists=False)
monkeypatch.setattr(github, "REPO_ROOT", sdk)
monkeypatch.setattr(github, "MANIFEST_PATH", sdk / "tools" / "downstream_sync_manifest.json")
monkeypatch.setattr(github, "_check_gh", lambda repo: [])
monkeypatch.setattr(github, "_release_exists", lambda repo, tag: False)
exit_code = github.publish(
github.Options(version="1.2.4", apply=False, skip_fetch=True)
)
output = capsys.readouterr().out
assert exit_code == 0
assert "git tag v1.2.4" in output
def test_publish_refuses_when_release_already_exists(tmp_path, monkeypatch, capsys):
sdk, release, wheel = _synthetic_publish_layout(tmp_path, "1.2.5", tag_exists=True)
monkeypatch.setattr(github, "REPO_ROOT", sdk)
monkeypatch.setattr(github, "MANIFEST_PATH", sdk / "tools" / "downstream_sync_manifest.json")
monkeypatch.setattr(github, "_check_gh", lambda repo: [])
monkeypatch.setattr(github, "_release_exists", lambda repo, tag: True)
exit_code = github.publish(
github.Options(version="1.2.5", apply=False, skip_fetch=True)
)
assert exit_code == 1
assert "already exists; nothing to publish" in capsys.readouterr().out
def _synthetic_report_layout(tmp_path: Path, version: str) -> tuple[Path, Path]:
"""A fake SDK checkout with wheels and a passing preflight report."""
sdk = tmp_path / "sdk"
wheel_dir = sdk / "SDK" / version
wheel_dir.mkdir(parents=True)
wheel = wheel_dir / f"orisys-{version}-cp310-cp310-win_amd64.whl"
wheel.write_bytes(b"wheel")
report_dir = sdk / "build" / "release" / "reports" / version
report_dir.mkdir(parents=True)
(report_dir / "preflight.json").write_text(
json.dumps(_report_payload(version, [wheel])), encoding="utf-8"
)
return sdk, wheel
def test_upload_phases_default_is_testpypi_only():
assert pypi.upload_phases(pypi.Options(version="1.2.3")) == ["testpypi"]
def test_upload_phases_confirm_pypi_adds_the_real_index():
options = pypi.Options(version="1.2.3", confirm_pypi=True)
assert pypi.upload_phases(options) == ["testpypi", "pypi"]
def test_upload_phases_pypi_only_skips_the_rehearsal():
# The relevant case: a production token cannot be used against TestPyPI.
assert pypi.upload_phases(pypi.Options(version="1.2.3", pypi_only=True)) == ["pypi"]
def test_pypi_only_dry_run_targets_pypi_only(tmp_path, monkeypatch, capsys):
sdk, wheel = _synthetic_report_layout(tmp_path, "1.2.3")
monkeypatch.setattr(pypi, "REPO_ROOT", sdk)
monkeypatch.setattr(pypi, "index_state", lambda index, **k: pypi.IndexState(versions=[]))
from release import auth
auth.write_token("pypi", "pypi-" + "A" * 30, path=tmp_path / ".pypirc")
monkeypatch.setattr(auth, "pypirc_path", lambda: tmp_path / ".pypirc")
exit_code = pypi.publish(
pypi.Options(version="1.2.3", apply=False, pypi_only=True, skip_check=True)
)
output = capsys.readouterr().out
assert exit_code == 0
assert "--pypi-only" in output
assert "upload 1 wheel(s) to pypi" in output
assert "testpypi" not in output
def test_pypi_only_and_confirm_pypi_are_mutually_exclusive():
from release.cli import _cmd_pypi
with pytest.raises(SystemExit) as excinfo:
_cmd_pypi(["--version", "1.2.3", "--pypi-only", "--confirm-pypi"])
assert excinfo.value.code == 2
def test_manifest_checks_use_the_python_placeholder():
entry = github.manifest_entry()
for command in entry["checks"]:
assert command.startswith("{python} "), command
# --- pypi -------------------------------------------------------------------
def test_twine_upload_argv_targets_the_repository():
argv = pypi.twine_upload_argv([Path("a.whl")], "testpypi")
assert argv[:3] == [sys.executable, "-m", "twine"]
assert "--repository" in argv and "testpypi" in argv
assert argv[-1] == "a.whl"
def test_twine_upload_argv_defaults_to_pypi():
argv = pypi.twine_upload_argv([Path("a.whl")], None)
assert "--repository" not in argv
def test_credentials_hint_points_at_the_setup_command(tmp_path, monkeypatch):
from release import auth
monkeypatch.setattr(auth, "pypirc_path", lambda: tmp_path / ".pypirc")
monkeypatch.delenv("TWINE_PASSWORD", raising=False)
hint = pypi.credentials_hint("pypi")
assert hint is not None
assert "release auth --set-token pypi" in hint
def test_credentials_hint_for_testpypi_mentions_pypi_only(tmp_path, monkeypatch):
from release import auth
monkeypatch.setattr(auth, "pypirc_path", lambda: tmp_path / ".pypirc")
monkeypatch.delenv("TWINE_PASSWORD", raising=False)
hint = pypi.credentials_hint("testpypi")
assert hint is not None
assert "--set-token testpypi" in hint
assert "--pypi-only" in hint
def test_credentials_hint_silent_when_configured(tmp_path, monkeypatch):
from release import auth
path = tmp_path / ".pypirc"
auth.write_token("pypi", "pypi-" + "A" * 30, path=path)
monkeypatch.setattr(auth, "pypirc_path", lambda: path)
monkeypatch.delenv("TWINE_PASSWORD", raising=False)
assert pypi.credentials_hint("pypi") is None
def test_index_state_parses_versions(monkeypatch):
class Response:
def __init__(self, payload):
self._payload = json.dumps(payload).encode()
def read(self, *args):
return self._payload
def __enter__(self):
return self
def __exit__(self, *exc):
return False
monkeypatch.setattr(
pypi.urllib.request,
"urlopen",
lambda *a, **k: Response({"releases": {"1.2.3": [{"filename": "a"}], "0.0.0": []}}),
)
state = pypi.index_state("pypi")
assert state.known
assert state.versions == ["1.2.3"]
def test_index_state_reports_lookup_failure(monkeypatch):
def _raise(*args, **kwargs):
raise urllib.error.URLError("offline")
monkeypatch.setattr(pypi.urllib.request, "urlopen", _raise)
state = pypi.index_state("pypi")
assert not state.known
assert "offline" in state.error
def test_index_files_returns_none_when_version_absent(monkeypatch):
class Response:
def read(self, *args):
return json.dumps({"releases": {}}).encode()
def __enter__(self):
return self
def __exit__(self, *exc):
return False
monkeypatch.setattr(pypi.urllib.request, "urlopen", lambda *a, **k: Response())
assert pypi.index_files("pypi", "1.2.3") == (None, None)
def test_index_files_distinguishes_lookup_failure_from_absence(monkeypatch):
"""A network hiccup must not be reported as "not published yet"."""
def _raise(*args, **kwargs):
raise urllib.error.URLError("connection reset")
monkeypatch.setattr(pypi.urllib.request, "urlopen", _raise)
filenames, error = pypi.index_files("testpypi", "1.2.3")
assert filenames is None
assert error is not None and "connection reset" in error
def test_index_files_returns_filenames(monkeypatch):
class Response:
def read(self, *args):
return json.dumps(
{"releases": {"1.2.3": [{"filename": "b.whl"}, {"filename": "a.whl"}]}}
).encode()
def __enter__(self):
return self
def __exit__(self, *exc):
return False
monkeypatch.setattr(pypi.urllib.request, "urlopen", lambda *a, **k: Response())
assert pypi.index_files("pypi", "1.2.3") == (["a.whl", "b.whl"], None)
def test_wait_for_release_retries_until_indexing_finishes(monkeypatch):
"""TestPyPI lags the upload response; the check must poll, not give up."""
calls = {"n": 0}
slept: list[float] = []
def _fake(index, version, *, timeout=20.0):
calls["n"] += 1
if calls["n"] < 3:
return None, None
return ["a.whl"], None
monkeypatch.setattr(pypi, "index_files", _fake)
filenames, error = pypi.wait_for_release(
"testpypi", "1.2.3", attempts=5, delay=1.0, sleep=slept.append
)
assert filenames == ["a.whl"]
assert error is None
assert calls["n"] == 3
assert slept == [1.0, 1.0]
def test_wait_for_release_reports_the_last_error(monkeypatch):
def _fake(index, version, *, timeout=20.0):
return None, "HTTP 503"
monkeypatch.setattr(pypi, "index_files", _fake)
filenames, error = pypi.wait_for_release(
"pypi", "1.2.3", attempts=2, delay=0.0, sleep=lambda _: None
)
assert filenames is None
assert error == "HTTP 503"
def test_existing_release_state_satisfied_conflict_absent(monkeypatch, capsys):
expected = {"orisys-1.2.3-cp310-cp310-win_amd64.whl"}
monkeypatch.setattr(pypi, "index_files", lambda i, v, **k: (None, None))
assert pypi.existing_release_state("pypi", "1.2.3", expected) == "absent"
monkeypatch.setattr(pypi, "index_files", lambda i, v, **k: (sorted(expected), None))
assert pypi.existing_release_state("pypi", "1.2.3", expected) == "satisfied"
monkeypatch.setattr(
pypi, "index_files", lambda i, v, **k: (["orisys-1.2.3-cp311-cp311-win_amd64.whl"], None)
)
assert pypi.existing_release_state("pypi", "1.2.3", expected) == "conflict"
output = capsys.readouterr().out
assert "different file set" in output and "missing" in output
def test_publish_rerun_after_success_is_not_a_failure(tmp_path, monkeypatch, capsys):
"""Re-running a finished phase must succeed, not look like a new error."""
sdk, wheel = _synthetic_report_layout(tmp_path, "1.2.3")
monkeypatch.setattr(pypi, "REPO_ROOT", sdk)
from release import auth
auth.write_token("pypi", "pypi-" + "A" * 30, path=tmp_path / ".pypirc")
monkeypatch.setattr(auth, "pypirc_path", lambda: tmp_path / ".pypirc")
monkeypatch.setattr(
pypi, "index_files", lambda i, v, **k: ([wheel.name], None)
)
exit_code = pypi.publish(
pypi.Options(version="1.2.3", apply=True, pypi_only=True, skip_check=True)
)
output = capsys.readouterr().out
assert exit_code == 0
assert "already serves orisys 1.2.3" in output
assert "every requested index already has this release" in output
def test_publish_aborts_on_a_conflicting_existing_release(tmp_path, monkeypatch, capsys):
sdk, wheel = _synthetic_report_layout(tmp_path, "1.2.3")
monkeypatch.setattr(pypi, "REPO_ROOT", sdk)
from release import auth
auth.write_token("pypi", "pypi-" + "A" * 30, path=tmp_path / ".pypirc")
monkeypatch.setattr(auth, "pypirc_path", lambda: tmp_path / ".pypirc")
monkeypatch.setattr(pypi, "index_files", lambda i, v, **k: (["other.whl"], None))
exit_code = pypi.publish(
pypi.Options(version="1.2.3", apply=True, pypi_only=True, skip_check=True)
)
assert exit_code == 1
assert "different file set" in capsys.readouterr().out
def test_index_json_urls_cover_both_indexes():
assert pypi.INDEX_JSON["testpypi"].startswith("https://test.pypi.org/")
assert pypi.INDEX_JSON["pypi"].startswith("https://pypi.org/")
@pytest.mark.parametrize("abi", ["cp310", "cp311", "cp312"])
@pytest.mark.parametrize("platform", ["win_amd64", "manylinux_2_28_x86_64", "manylinux_2_28_aarch64"])
def test_fetch_back_targets_every_abi_and_platform(tmp_path, monkeypatch, abi, platform):
wheel = _wheel(tmp_path, f"orisys-1.2.3-{abi}-{abi}-{platform}.whl")
monkeypatch.setenv("PIP_EXTRA_INDEX_URL", "https://fallback.invalid/simple")
def run(argv, **kwargs):
assert "PIP_EXTRA_INDEX_URL" not in kwargs["env"]
assert kwargs["env"]["PIP_CONFIG_FILE"] == pypi.os.devnull
assert argv[argv.index("--python-version") + 1] == abi[2:]
assert argv[argv.index("--abi") + 1] == abi
assert argv[argv.index("--platform") + 1] == platform
assert argv[argv.index("--index-url") + 1] == pypi.INDEX_SIMPLE["testpypi"]
assert "--extra-index-url" not in argv
target = Path(argv[argv.index("--dest") + 1])
target.mkdir()
(target / wheel.name).write_bytes(wheel.read_bytes())
return subprocess.CompletedProcess(argv, 0)
monkeypatch.setattr(pypi.subprocess, "run", run)
assert pypi._fetch_back("testpypi", "1.2.3", wheel) == 0
def test_fetch_back_rejects_modified_download(tmp_path, monkeypatch):
wheel = _wheel(tmp_path, "orisys-1.2.3-cp311-cp311-manylinux_2_28_aarch64.whl")
def run(argv, **kwargs):
target = Path(argv[argv.index("--dest") + 1])
target.mkdir()
(target / wheel.name).write_bytes(b"tampered")
return subprocess.CompletedProcess(argv, 0)
monkeypatch.setattr(pypi.subprocess, "run", run)
with pytest.raises(publish.PublishGateError, match="different bytes"):
pypi._fetch_back("pypi", "1.2.3", wheel)
def test_pypi_upload_and_fetch_include_arm_wheels(tmp_path, monkeypatch):
sdk, original = _synthetic_report_layout(tmp_path, "1.2.3")
wheels = [original, _wheel(original.parent, "orisys-1.2.3-cp311-cp311-manylinux_2_28_aarch64.whl")]
report = sdk / "build/release/reports/1.2.3/preflight.json"
report.write_text(json.dumps(_report_payload("1.2.3", wheels)), encoding="utf-8")
monkeypatch.setattr(pypi, "REPO_ROOT", sdk)
monkeypatch.setattr(pypi, "_ensure_credentials", lambda *a, **k: True)
monkeypatch.setattr(pypi, "index_files", lambda *a, **k: (None, None))
monkeypatch.setattr(pypi, "index_state", lambda *a, **k: pypi.IndexState(versions=[]))
monkeypatch.setattr(pypi, "wait_for_release", lambda *a, **k: ([w.name for w in wheels], None))
uploaded, fetched = [], []
monkeypatch.setattr(pypi, "_upload", lambda files, index: uploaded.extend(files) or 0)
monkeypatch.setattr(pypi, "_fetch_back", lambda index, version, wheel: fetched.append(wheel) or 0)
assert pypi.publish(pypi.Options(version="1.2.3", apply=True, pypi_only=True, skip_check=True)) == 0
assert set(uploaded) == set(fetched) == set(wheels)
def test_github_dry_run_includes_arm_asset_and_generated_notes(tmp_path, monkeypatch, capsys):
sdk, release, original = _synthetic_publish_layout(tmp_path, "1.2.3", tag_exists=False)
wheels = [original, _wheel(original.parent, "orisys-1.2.3-cp311-cp311-manylinux_2_28_aarch64.whl")]
report = sdk / "build/release/reports/1.2.3/preflight.json"
report.write_text(json.dumps(_report_payload("1.2.3", wheels)), encoding="utf-8")
monkeypatch.setattr(github, "REPO_ROOT", sdk)
monkeypatch.setattr(github, "MANIFEST_PATH", sdk / "tools/downstream_sync_manifest.json")
monkeypatch.setattr(github, "_check_gh", lambda repo: [])
monkeypatch.setattr(github, "_release_exists", lambda repo, tag: False)
assert github.publish(github.Options(version="1.2.3", skip_fetch=True)) == 0
output = capsys.readouterr().out
assert wheels[1].name in output
assert "Linux ARM64" in output
assert "--notes-file" in output
assert not (release / "release").exists()
assert not (report.parent / "github-release-notes.md").exists()
def test_inconclusive_testpypi_download_stops_before_production(tmp_path, monkeypatch, capsys):
sdk, wheel = _synthetic_report_layout(tmp_path, "1.2.3")
monkeypatch.setattr(pypi, "REPO_ROOT", sdk)
monkeypatch.setattr(pypi, "_ensure_credentials", lambda *a, **k: True)
monkeypatch.setattr(pypi, "index_files", lambda *a, **k: (None, None))
monkeypatch.setattr(pypi, "index_state", lambda *a, **k: pypi.IndexState(versions=[]))
monkeypatch.setattr(pypi, "wait_for_release", lambda *a, **k: ([wheel.name], None))
uploaded = []
monkeypatch.setattr(pypi, "_upload", lambda files, index: uploaded.append(index) or 0)
monkeypatch.setattr(pypi, "_fetch_back", lambda *a: 1)
assert pypi.publish(pypi.Options(version="1.2.3", apply=True, confirm_pypi=True, skip_check=True)) == 0
assert uploaded == ["testpypi"]
assert "later indexes were not uploaded" in capsys.readouterr().out