Compare commits
3 commits
main
...
studio/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2534372537 | ||
|
|
963cec8268 | ||
|
|
98984c761b |
4 changed files with 116 additions and 12 deletions
|
|
@ -303,3 +303,17 @@ def test_format_stale_warning_singular_day():
|
|||
msg = fr.format_stale_warning({"installed_tag": "b9190", "latest_tag": "b9300", "age_days": 1})
|
||||
assert "1 day" in msg
|
||||
assert "1 days" not in msg
|
||||
|
||||
|
||||
def test_reset_caches_drop_disk_removes_cache_file(monkeypatch, tmp_path):
|
||||
# Default reset keeps the on-disk cache; drop_disk=True removes it so the next
|
||||
# read refetches the latest tag (used after an in-app update).
|
||||
cache_dir = tmp_path / ".freshness"
|
||||
cache_dir.mkdir()
|
||||
cache_file = cache_dir / "unslothai__llama.cpp.json"
|
||||
cache_file.write_text("{}")
|
||||
monkeypatch.setattr(fr, "_cache_dir", lambda: cache_dir)
|
||||
fr.reset_caches()
|
||||
assert cache_file.exists()
|
||||
fr.reset_caches(drop_disk = True)
|
||||
assert not cache_file.exists()
|
||||
|
|
|
|||
|
|
@ -795,3 +795,66 @@ def test_start_update_source_build_refuses_when_newer(monkeypatch, tmp_path):
|
|||
res = upd.start_update()
|
||||
assert res["started"] is False
|
||||
assert res["reason"] == "up_to_date"
|
||||
|
||||
|
||||
def test_status_marker_no_downgrade_on_stale_cache(monkeypatch, tmp_path):
|
||||
# Regression: marker is freshly newer (b9585) while the cached GitHub "latest"
|
||||
# still holds the previous release (b9518). Must never offer a downgrade.
|
||||
binary = _write_install(tmp_path, "b9585")
|
||||
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
|
||||
monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518")
|
||||
st = upd.get_update_status(force_refresh = True)
|
||||
assert st["installed_tag"] == "b9585"
|
||||
assert st["latest_tag"] == "b9518"
|
||||
assert st["update_available"] is False
|
||||
|
||||
|
||||
def test_status_source_build_no_downgrade(monkeypatch, tmp_path):
|
||||
# Markerless install whose --version is a known newer build (9585); the resolver
|
||||
# walked back to an older partial release (b9518). Must never downgrade.
|
||||
binary = tmp_path / "llama.cpp" / "build" / "bin" / "llama-server"
|
||||
binary.parent.mkdir(parents = True)
|
||||
binary.write_text("stub")
|
||||
monkeypatch.setattr(upd, "_find_binary", lambda: str(binary))
|
||||
_prebuilt(monkeypatch, release_tag = "b9518")
|
||||
monkeypatch.setattr(upd, "_installed_build_number", lambda b: 9585)
|
||||
st = upd.get_update_status()
|
||||
assert st["update_available"] is False
|
||||
|
||||
|
||||
def test_status_source_build_unparseable_latest_fails_open(monkeypatch, tmp_path):
|
||||
# Resolver returned a tag with no build number; with an unknown installed version
|
||||
# this must fail open to "no update" rather than offer blindly.
|
||||
binary = tmp_path / "llama.cpp" / "build" / "bin" / "llama-server"
|
||||
binary.parent.mkdir(parents = True)
|
||||
binary.write_text("stub")
|
||||
monkeypatch.setattr(upd, "_find_binary", lambda: str(binary))
|
||||
_prebuilt(monkeypatch, release_tag = "master", llama_tag = "master")
|
||||
monkeypatch.setattr(upd, "_installed_build_number", lambda b: None)
|
||||
st = upd.get_update_status()
|
||||
assert st["update_available"] is False
|
||||
|
||||
|
||||
def test_apply_clears_resolve_memo(monkeypatch, tmp_path):
|
||||
# After a successful update the markerless resolver memo must be cleared so a
|
||||
# stale older "latest" cannot make the banner reappear.
|
||||
install_dir = tmp_path / "llama.cpp"
|
||||
binary = _write_install(install_dir, "b9493")
|
||||
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
|
||||
monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
|
||||
monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518")
|
||||
upd._resolve_memo.update(at = time.time(), value = {"prebuilt_available": True})
|
||||
_patch_installer_popen(
|
||||
monkeypatch,
|
||||
lines = ["installed\n"],
|
||||
on_start = lambda cmd: _write_install(install_dir, "b9518"),
|
||||
)
|
||||
assert upd.start_update()["started"] is True
|
||||
deadline = time.time() + 10
|
||||
while time.time() < deadline:
|
||||
job = upd.get_update_status()["job"]
|
||||
if job["state"] in ("success", "error"):
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert job["state"] == "success", job
|
||||
assert upd._resolve_memo == {}
|
||||
|
|
|
|||
|
|
@ -232,7 +232,15 @@ def format_stale_warning(info: dict) -> str:
|
|||
)
|
||||
|
||||
|
||||
def reset_caches() -> None:
|
||||
"""Test-only: drop all in-memory caches."""
|
||||
def reset_caches(*, drop_disk: bool = False) -> None:
|
||||
"""Drop in-memory caches. drop_disk=True also removes the on-disk release cache
|
||||
so the next read refetches the latest tag (used after an in-app update so the
|
||||
banner cannot reappear off a stale 'latest')."""
|
||||
_marker_cache.clear()
|
||||
_release_memo.clear()
|
||||
if drop_disk:
|
||||
try:
|
||||
for path in _cache_dir().glob("*.json"):
|
||||
path.unlink(missing_ok = True)
|
||||
except OSError as exc:
|
||||
logger.debug("freshness cache clear failed", error = str(exc))
|
||||
|
|
|
|||
|
|
@ -179,6 +179,16 @@ def _installed_build_number(binary: Optional[str]) -> Optional[int]:
|
|||
return n if n > 1 else None
|
||||
|
||||
|
||||
def _tag_build_number(tag: Optional[str]) -> Optional[int]:
|
||||
"""Integer build from a ``bNNNN`` llama.cpp release tag ('b9585' -> 9585).
|
||||
Anchored on the ``b`` prefix so wrapper/date tags do not match a stray number;
|
||||
None when absent, so callers fail open (offer nothing)."""
|
||||
if not tag:
|
||||
return None
|
||||
m = re.search(r"b(\d+)", tag)
|
||||
return int(m.group(1)) if m else None
|
||||
|
||||
|
||||
def _is_under(path: Path, root: Path) -> bool:
|
||||
try:
|
||||
p, r = path.resolve(), root.resolve()
|
||||
|
|
@ -230,12 +240,12 @@ def _source_build_status(binary: str, *, force_refresh: bool) -> Optional[dict]:
|
|||
if _llama_install_root(binary) is None:
|
||||
return None
|
||||
installed_build = _installed_build_number(binary)
|
||||
m = re.search(r"(\d+)", latest)
|
||||
latest_build = int(m.group(1)) if m else None
|
||||
# Suppress only when the source build is reliably newer/equal; unknown
|
||||
# version (the involuntary source-build case) is treated as behind.
|
||||
update_available = (
|
||||
installed_build is None or latest_build is None or installed_build < latest_build
|
||||
latest_build = _tag_build_number(latest)
|
||||
# Offer only a STRICTLY newer build. Unknown installed version (version: 1 or
|
||||
# unparseable) stays "behind" so the involuntary source build still gets the
|
||||
# prebuilt, but a known build is never downgraded to an older walked-back release.
|
||||
update_available = latest_build is not None and (
|
||||
installed_build is None or installed_build < latest_build
|
||||
)
|
||||
with _job_lock:
|
||||
job = dict(_job)
|
||||
|
|
@ -286,8 +296,15 @@ def get_update_status(*, force_refresh: bool = False) -> dict:
|
|||
freshness = check_prebuilt_freshness(binary)
|
||||
installed = freshness.get("installed_tag")
|
||||
latest = freshness.get("latest_tag")
|
||||
installed_build = _tag_build_number(installed)
|
||||
latest_build = _tag_build_number(latest)
|
||||
# Offer only a STRICTLY newer build. A stale release cache (latest behind the
|
||||
# freshly installed marker) or an older partial release must never downgrade.
|
||||
update_available = bool(
|
||||
freshness.get("has_marker") and installed and latest and installed != latest
|
||||
freshness.get("has_marker")
|
||||
and installed_build is not None
|
||||
and latest_build is not None
|
||||
and latest_build > installed_build
|
||||
)
|
||||
|
||||
with _job_lock:
|
||||
|
|
@ -405,9 +422,11 @@ def _run_update(install_dir: Path, repo: str, asset: Optional[str], script: Path
|
|||
tail = "".join(tail_lines).strip()[-1500:]
|
||||
raise RuntimeError(f"installer exited {returncode}: {tail or 'no output'}")
|
||||
|
||||
# New UNSLOTH_PREBUILT_INFO.json is on disk; drop caches so the next
|
||||
# status read reflects the freshly installed tag.
|
||||
reset_caches()
|
||||
# New UNSLOTH_PREBUILT_INFO.json is on disk; drop caches (incl. the disk
|
||||
# release cache and the markerless resolver memo) so the next status read
|
||||
# reflects the freshly installed tag and cannot reappear off stale data.
|
||||
reset_caches(drop_disk = True)
|
||||
_resolve_memo.clear()
|
||||
new_marker = read_install_marker(_find_binary())
|
||||
new_tag = (new_marker or {}).get("tag") or (new_marker or {}).get("release_tag")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue