96 lines
3.7 KiB
Python
96 lines
3.7 KiB
Python
"""Minor-style updates follow advertised artifacts without dropping other pins."""
|
|
from __future__ import annotations
|
|
|
|
from argparse import Namespace
|
|
import hashlib
|
|
import importlib
|
|
import io
|
|
import json
|
|
import zipfile
|
|
|
|
import pytest
|
|
|
|
from pm import cli, paths, registry
|
|
from pm.lock import Facts, Lockfile
|
|
from pm.package import Package
|
|
from pm.store import current_target
|
|
from tests.pm._range_server import RangeHandler, dl_server, url
|
|
|
|
|
|
@pytest.mark.parametrize("next_version", ["1.2.3", "1.2.4"])
|
|
def test_same_minor_refresh_updates_real_bytes_and_preserves_unresolved_targets(
|
|
tmp_path, dl_server, monkeypatch, next_version,
|
|
):
|
|
class RollingPackage(Package):
|
|
name = "rolling-tool"
|
|
version_style = "minor"
|
|
|
|
def latest_versions(self, target, locked=None):
|
|
return [next_version] if target == current_target() else []
|
|
|
|
def fetch_url(self, version, target):
|
|
assert version == next_version
|
|
assert target == current_target()
|
|
return url(dl_server, "/new-build.zip")
|
|
|
|
buffer = io.BytesIO()
|
|
with zipfile.ZipFile(buffer, "w") as archive:
|
|
archive.writestr("tool.txt", b"new rolling build")
|
|
body = buffer.getvalue()
|
|
RangeHandler.payloads["/new-build.zip"] = body
|
|
original_get = RangeHandler.do_GET
|
|
requests = []
|
|
|
|
def record(handler):
|
|
requests.append(handler.path)
|
|
original_get(handler)
|
|
|
|
monkeypatch.setattr(RangeHandler, "do_GET", record)
|
|
target = current_target()
|
|
held_target = "linux-arm64-bionic" if target != "linux-arm64-bionic" else "win32-arm64"
|
|
held = {"url": "https://upstream.invalid/held.deb", "sha256": "b" * 64}
|
|
lock = Lockfile(tmp_path / "lock.json")
|
|
lock.set_pin("rolling-tool", "1.2.3", {
|
|
target: {"url": url(dl_server, "/old-build.zip"), "sha256": "a" * 64},
|
|
held_target: held,
|
|
})
|
|
lock.set_pin("unrelated", "2.0", {"any": held})
|
|
lock.save()
|
|
store = tmp_path / "tools"
|
|
monkeypatch.setitem(registry._packages, "rolling-tool", RollingPackage())
|
|
monkeypatch.setattr(paths, "lockfile_path", lambda: lock.path)
|
|
monkeypatch.setattr(paths, "repo_root", lambda: tmp_path)
|
|
monkeypatch.setattr(paths, "partials_root", lambda: tmp_path / "partials")
|
|
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(store))
|
|
syncs = []
|
|
engine = importlib.import_module("pm.install")
|
|
monkeypatch.setattr(engine, "sync_venv", lambda **kwargs: syncs.append(kwargs))
|
|
before = lock.path.read_bytes()
|
|
args = Namespace(names=["rolling-tool"], target=None, check=True, uv=False, npm=False, termux=False)
|
|
assert cli.cmd_update(args) == 1
|
|
assert lock.path.read_bytes() == before and not requests and not syncs
|
|
assert not store.exists()
|
|
|
|
args.check = False
|
|
assert cli.cmd_update(args) == 0
|
|
after = json.loads(lock.path.read_text(encoding="utf-8"))["packages"]
|
|
original = json.loads(before)["packages"]
|
|
assert after["unrelated"] == original["unrelated"]
|
|
assert after["rolling-tool"]["version"] == original["rolling-tool"]["version"]
|
|
assert after["rolling-tool"]["artifacts"][held_target] == held
|
|
assert Lockfile(lock.path).artifacts("rolling-tool", target) == [{
|
|
"url": url(dl_server, "/new-build.zip"), "sha256": hashlib.sha256(body).hexdigest(),
|
|
}]
|
|
fact = Facts(store / "facts.json").get("rolling-tool")
|
|
assert (store / fact["entry"] / "tool.txt").read_bytes() == b"new rolling build"
|
|
assert syncs == [{"explicit": True}]
|
|
assert requests and all(path == "/new-build.zip" for path in requests)
|
|
|
|
pinned = lock.path.read_bytes()
|
|
requests.clear()
|
|
syncs.clear()
|
|
for check in (True, False):
|
|
args.check = check
|
|
assert cli.cmd_update(args) == 0
|
|
assert lock.path.read_bytes() == pinned and not requests and not syncs
|