mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 18:47:20 +01:00
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
file length / file-length (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
palette drift / palette (push) Has been cancelled
repo-contract / repo contract (push) Has been cancelled
shared gate tests / gate unit tests (push) Has been cancelled
toolchain:python's "latest" was platform.python_version(), so the verdict depended on the machine running the gate: setup-python "3.x" gave kuhyx/spellcode 3.15.0 on one runner and 3.14.8 on another, making its transitive:pyodide allowlist entry dead (exit 2) on one and required (exit 1) on the other. Now registries/toolchain.python_latest() reads python.org's release API (is_published, pre_release=false), keeps only exact "Python X.Y.Z" names (the feed also lists "Python install manager 26.4"), and takes the newest stable. The never_cached() carve-out is gone: it existed because a cached *interpreter* version from an older runner image (3.13.15) was served to a 3.14.7 job in screen-locker's CI. A registry answer is safe to cache, so python now goes through the normal TTL cache, prefetch, and offline fallback (expired cache -> DEGRADED; no cache -> unavailable), never the local interpreter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4VrLZn9YjhPuoxTj54WKk
111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
"""`toolchain:python` is a registry answer, never the interpreter running the gate.
|
|
|
|
kuhyx/spellcode went red on one runner and green on another: `setup-python`
|
|
with "3.x" handed one job 3.15.0 and the next 3.14.8, so a `transitive:`
|
|
allowlist entry was dead (exit 2) on one machine and required (exit 1) on the
|
|
other. The target must not depend on where the gate happens to run.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import platform
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from dep_freshness.models import Dep
|
|
from dep_freshness.registries import http
|
|
from dep_freshness.registries import toolchain as tc
|
|
from dep_freshness.resolve import Resolver, _toolchain_latest
|
|
|
|
# Shaped like python.org's /api/v2/downloads/release/ rows, trimmed.
|
|
RELEASES = [
|
|
{"name": "Python 2.7.18", "pre_release": False},
|
|
{"name": "Python 3.14.8", "pre_release": False},
|
|
{"name": "Python 3.15.0", "pre_release": False},
|
|
# The Windows install manager is listed as a "release" with version 100;
|
|
# read naively it is Python 26.4.
|
|
{"name": "Python install manager 26.4", "pre_release": False},
|
|
{"name": "Python 3.16.0a1", "pre_release": True},
|
|
{"name": "Python 3.16.0", "pre_release": True}, # flagged, so not final
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def feed(monkeypatch):
|
|
def install(payload):
|
|
monkeypatch.setattr(tc, "get_json", lambda *_a, **_k: payload)
|
|
|
|
return install
|
|
|
|
|
|
def python_dep() -> Dep:
|
|
return Dep(
|
|
ecosystem="toolchain", name="python", constraint="3.14.8",
|
|
path=Path(".python-version"), line=1,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def no_interpreter(monkeypatch):
|
|
"""Any read of the running interpreter's version is a failure."""
|
|
monkeypatch.setattr(
|
|
platform,
|
|
"python_version",
|
|
lambda: pytest.fail("the target must not come from this interpreter"),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("interpreter", ["3.14.8", "3.15.0"])
|
|
def test_latest_is_the_same_whatever_interpreter_runs_the_gate(
|
|
feed, monkeypatch, interpreter
|
|
):
|
|
feed(RELEASES)
|
|
monkeypatch.setattr(platform, "python_version", lambda: interpreter)
|
|
assert _toolchain_latest("python") == "3.15.0"
|
|
|
|
|
|
def test_the_registry_answer_skips_pre_releases_and_the_install_manager(feed):
|
|
feed(RELEASES)
|
|
assert tc.python_latest() == "3.15.0"
|
|
|
|
|
|
def test_a_missing_feed_is_unknown(feed):
|
|
feed(None)
|
|
assert tc.python_latest() is None
|
|
|
|
|
|
def test_rows_without_a_name_are_ignored(feed):
|
|
feed([{"pre_release": False}, {"name": "Python 3.14.8"}])
|
|
assert tc.python_latest() == "3.14.8"
|
|
|
|
|
|
@pytest.fixture
|
|
def resolver(tmp_path):
|
|
from dep_freshness.cache import Cache
|
|
|
|
return Resolver(Cache(tmp_path / "registry.json"))
|
|
|
|
|
|
def test_the_answer_is_cached_like_any_registry(resolver, feed, no_interpreter):
|
|
feed(RELEASES)
|
|
resolver.prefetch([python_dep()])
|
|
entry = resolver.cache.get("toolchain", "python")
|
|
assert entry is not None and entry.version == "3.15.0"
|
|
|
|
|
|
def test_offline_serves_the_cached_answer_as_degraded(resolver, no_interpreter):
|
|
resolver.cache.put("toolchain", "python", "3.15.0")
|
|
resolver.cache._data["toolchain/python"]["fetched_at"] = 0.0 # long expired
|
|
http.force_offline()
|
|
answer = resolver.latest(python_dep())
|
|
assert answer.version == "3.15.0"
|
|
assert answer.stale_cache_days is not None
|
|
|
|
|
|
def test_offline_with_no_cache_is_unavailable(resolver, no_interpreter):
|
|
http.force_offline()
|
|
answer = resolver.latest(python_dep())
|
|
assert answer.version is None
|
|
assert answer.unavailable
|