Merge remote-tracking branch 'origin/main' into studio-security-headers-pure-asgi

This commit is contained in:
Daniel Han 2026-06-18 05:33:21 +00:00
commit 57e888dc7b
11 changed files with 453 additions and 114 deletions

View file

@ -55,6 +55,9 @@ class LlamaUpdateStatusResponse(BaseModel):
source_build: bool = Field(
False, description = "True when there is no marker (source build) but a prebuilt is offered."
)
update_size_bytes: Optional[int] = Field(
None, description = "Download size of the prebuilt Update would fetch, in bytes."
)
job: LlamaUpdateJob = Field(default_factory = LlamaUpdateJob)

View file

@ -520,3 +520,114 @@ def test_in_memory_only_reset_replays_stale_same_base_mix(monkeypatch, tmp_path)
assert info["latest_tag"] == "b9596-mix-aaa"
assert info["behind"] is True
assert info["stale"] is True
# update_download_size_bytes (banner download-size lookup).
def _patch_assets(monkeypatch, mapping):
"""Stub latest_release_assets with a per-repo {asset_name: size} lookup."""
monkeypatch.setattr(
fr,
"latest_release_assets",
lambda repo, *, force_refresh = False: mapping.get(repo),
)
def test_update_size_unsloth_prebuilt_exact_match(monkeypatch):
# The unsloth fork's own bundle (app-<tag>-<platform>): the want= exact match
# on app-<latest>-<suffix> wins.
marker = {
"asset": "app-b9190-linux-x64-cuda13-newer.tar.gz",
"published_repo": "unslothai/llama.cpp",
}
_patch_assets(
monkeypatch,
{
"unslothai/llama.cpp": {
"app-b9300-linux-x64-cuda13-newer.tar.gz": 123_456_789,
"app-b9300-windows-x64-cuda13-newer.zip": 999,
}
},
)
assert fr.update_download_size_bytes(marker, "b9300", "unslothai/llama.cpp") == 123_456_789
def test_update_size_macos_fork_asset_suffix_fallback(monkeypatch):
# macOS bundles use the upstream-style llama-<tag>-bin-macos-*, matched via the
# endswith fallback in the publish repo.
marker = {
"asset": "llama-b9190-bin-macos-arm64.tar.gz",
"published_repo": "unslothai/llama.cpp",
}
_patch_assets(
monkeypatch,
{"unslothai/llama.cpp": {"llama-b9300-bin-macos-arm64.tar.gz": 55_000_000}},
)
assert fr.update_download_size_bytes(marker, "b9300", "unslothai/llama.cpp") == 55_000_000
def test_update_size_upstream_ubuntu_uses_binary_repo(monkeypatch):
# #6338 P2: ggml-org ubuntu-* prebuilt lives in binary_repo, not the fork
# publish repo. The size must still resolve.
marker = {
"asset": "llama-b9190-bin-ubuntu-x64.tar.gz",
"published_repo": "unslothai/llama.cpp",
"binary_repo": "ggml-org/llama.cpp",
}
_patch_assets(
monkeypatch,
{
"unslothai/llama.cpp": {"app-b9300-linux-x64-cuda13-newer.tar.gz": 1},
"ggml-org/llama.cpp": {
"llama-b9673-bin-ubuntu-x64.tar.gz": 42_000_000,
"llama-b9673-bin-ubuntu-vulkan-x64.tar.gz": 7,
},
},
)
assert fr.update_download_size_bytes(marker, "b9300", "unslothai/llama.cpp") == 42_000_000
def test_update_size_upstream_windows_uses_binary_repo(monkeypatch):
# Regression (#6338 P2): the Windows upstream CPU prebuilt uses a win-* token.
marker = {
"asset": "llama-b9190-bin-win-cpu-x64.zip",
"published_repo": "unslothai/llama.cpp",
"binary_repo": "ggml-org/llama.cpp",
}
_patch_assets(
monkeypatch,
{"ggml-org/llama.cpp": {"llama-b9673-bin-win-cpu-x64.zip": 33_000_000}},
)
assert fr.update_download_size_bytes(marker, "b9300", "unslothai/llama.cpp") == 33_000_000
def test_update_size_no_matching_asset_fails_open(monkeypatch):
# A ROCm version drift (installed 6.4 vs latest 7.2) leaves no suffix match;
# the helper fails open to None rather than guessing a wrong artifact.
marker = {
"asset": "llama-b9190-bin-ubuntu-rocm-6.4-x64.tar.gz",
"published_repo": "unslothai/llama.cpp",
"binary_repo": "ggml-org/llama.cpp",
}
_patch_assets(
monkeypatch,
{"ggml-org/llama.cpp": {"llama-b9673-bin-ubuntu-rocm-7.2-x64.tar.gz": 9}},
)
assert fr.update_download_size_bytes(marker, "b9300", "unslothai/llama.cpp") is None
def test_update_size_missing_inputs_fail_open(monkeypatch):
_patch_assets(
monkeypatch,
{"unslothai/llama.cpp": {"app-b9300-linux-x64-cpu.tar.gz": 5}},
)
# No marker, no latest tag, or no asset string -> None (never raise).
assert fr.update_download_size_bytes(None, "b9300", "unslothai/llama.cpp") is None
assert (
fr.update_download_size_bytes(
{"asset": "app-b9190-linux-x64-cpu.tar.gz"}, None, "unslothai/llama.cpp"
)
is None
)
assert fr.update_download_size_bytes({"asset": None}, "b9300", "unslothai/llama.cpp") is None

View file

@ -901,3 +901,49 @@ def test_start_update_marked_refuses_when_not_behind(monkeypatch, tmp_path):
res = upd.start_update()
assert res["started"] is False
assert res["reason"] == "up_to_date"
def test_status_update_available_includes_size(monkeypatch, tmp_path):
# Marker (prebuilt) update path attaches the download size of the asset the
# banner would fetch.
binary = _write_install(tmp_path, "b9493", asset = "app-b9493-linux-x64-cuda13-newer.tar.gz")
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518")
monkeypatch.setattr(
freshness,
"latest_release_assets",
lambda repo, *, force_refresh = False: {
"app-b9518-linux-x64-cuda13-newer.tar.gz": 88_000_000
},
)
st = upd.get_update_status(force_refresh = True)
assert st["update_available"] is True
assert st["update_size_bytes"] == 88_000_000
def test_status_source_build_includes_update_size(monkeypatch, tmp_path):
# #6338 P3: a source build offered a prebuilt must carry the asset size too.
binary = tmp_path / "llama.cpp" / "build" / "bin" / "llama-server"
binary.parent.mkdir(parents = True)
binary.write_text("stub") # no marker -> source build
monkeypatch.setattr(upd, "_find_binary", lambda: str(binary))
_prebuilt(
monkeypatch,
repo = "unslothai/llama.cpp",
release_tag = "b9585",
asset = "app-b9585-linux-x64-cpu.tar.gz",
)
monkeypatch.setattr(upd, "_installed_build_number", lambda b: None)
monkeypatch.setattr(
upd,
"latest_release_assets",
lambda repo, *, force_refresh = False: (
{"app-b9585-linux-x64-cpu.tar.gz": 77_000_000}
if repo == "unslothai/llama.cpp"
else None
),
)
st = upd.get_update_status()
assert st["source_build"] is True
assert st["update_available"] is True
assert st["update_size_bytes"] == 77_000_000

View file

@ -281,6 +281,7 @@ def test_kill_process_records_timestamp_on_actual_kill():
backend = LlamaCppBackend.__new__(LlamaCppBackend)
backend._process = None
backend._healthy = False
backend._stats_logger = None # _kill_process stops it in finally
backend._stdout_thread = None
backend._llama_log_fh = None
backend._last_kill_monotonic = 0.0

View file

@ -78,6 +78,27 @@ def test_status_response_exposes_source_build():
rl.LlamaUpdateStatusResponse(**{**payload, "unexpected": 1})
def test_status_response_exposes_update_size_bytes():
payload = {
"supported": True,
"update_available": True,
"stale": False,
"installed_tag": "b9493",
"latest_tag": "b9518",
"published_repo": "unslothai/llama.cpp",
"installed_at_utc": None,
"age_days": None,
"source_build": False,
"update_size_bytes": 123_456_789,
"job": {"state": "idle"},
}
model = rl.LlamaUpdateStatusResponse(**payload)
assert model.model_dump()["update_size_bytes"] == 123_456_789
# Omitted -> defaults to None (the offline / no-matching-asset case).
without = {k: v for k, v in payload.items() if k != "update_size_bytes"}
assert rl.LlamaUpdateStatusResponse(**without).model_dump()["update_size_bytes"] is None
def test_status_handler_runs_off_event_loop(monkeypatch):
seen = {}

View file

@ -33,6 +33,8 @@ _INSTALL_MARKER_NAME = "UNSLOTH_PREBUILT_INFO.json"
_marker_cache: dict[str, Optional[dict]] = {}
_release_memo: dict[str, tuple[float, Optional[str]]] = {}
# Newest-release asset sizes (name -> bytes), memoized like the tag (24h TTL).
_assets_memo: dict[str, tuple[float, dict[str, int]]] = {}
def _cache_dir() -> Path:
@ -180,6 +182,113 @@ def latest_published_release(repo: str, *, force_refresh: bool = False) -> Optio
return latest
def _fetch_latest_release_assets(repo: str, timeout: float = 5.0) -> Optional[dict[str, int]]:
"""Asset name -> size (bytes) for the newest published release of `repo`,
selected exactly like _fetch_latest_release_tag. None on any failure."""
import urllib.error
import urllib.request
url = f"https://api.github.com/repos/{repo}/releases?per_page=30"
headers = {
"Accept": "application/vnd.github+json",
"User-Agent": "unsloth-studio-freshness-check",
}
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"
req = urllib.request.Request(url, headers = headers)
try:
with urllib.request.urlopen(req, timeout = timeout) as resp:
data = json.loads(resp.read().decode("utf-8"))
except (
urllib.error.URLError,
urllib.error.HTTPError,
OSError,
json.JSONDecodeError,
) as exc:
logger.debug("freshness asset fetch failed", repo = repo, error = str(exc))
return None
if not isinstance(data, list):
return None
published = [
r
for r in data
if isinstance(r, dict)
and not r.get("draft")
and not r.get("prerelease")
and isinstance(r.get("tag_name"), str)
and r.get("tag_name")
]
if not published:
return None
newest = max(published, key = lambda r: r.get("published_at") or "")
assets: dict[str, int] = {}
for a in newest.get("assets") or []:
name, size = a.get("name"), a.get("size")
if isinstance(name, str) and isinstance(size, int):
assets[name] = size
return assets
def latest_release_assets(repo: str, *, force_refresh: bool = False) -> Optional[dict[str, int]]:
"""Newest-release asset sizes for `repo`, memoized (24h TTL). None when
offline and never fetched. In-memory only -- a restart simply re-fetches."""
if not repo:
return None
now = time.time()
if not force_refresh:
memo = _assets_memo.get(repo)
if memo and now - memo[0] < _RELEASE_CACHE_TTL_SECONDS:
return memo[1]
assets = _fetch_latest_release_assets(repo)
if assets is None:
memo = _assets_memo.get(repo)
return memo[1] if memo else None
_assets_memo[repo] = (now, assets)
return assets
def update_download_size_bytes(
marker: Optional[dict],
latest_tag: Optional[str],
repo: Optional[str],
*,
force_refresh: bool = False,
) -> Optional[int]:
"""Download size of the latest-release asset matching this host's installed
bundle (same platform/arch/runtime suffix as the installed asset). None when
there is no marker asset, the latest assets can't be read, or no match."""
if not marker or not latest_tag or not repo:
return None
installed_asset = marker.get("asset")
if not isinstance(installed_asset, str):
return None
# Tag-independent platform suffix: accept the fork's "app-*" bundles and the
# upstream ggml-org "ubuntu-*"/"win-*" prebuilts ("windows" before "win").
m = re.search(r"-((?:linux|ubuntu|windows|win|macos|darwin)-.*)$", installed_asset)
if not m:
return None
suffix = m.group(1)
# Upstream ubuntu/win assets live in the marker's binary_repo, not the fork
# publish repo; try the publish repo first, then it.
repos = [repo]
binary_repo = marker.get("binary_repo")
if isinstance(binary_repo, str) and binary_repo and binary_repo != repo:
repos.append(binary_repo)
want = f"app-{latest_tag}-{suffix}"
for r in repos:
assets = latest_release_assets(r, force_refresh = force_refresh)
if not assets:
continue
if want in assets:
return assets[want]
# Tag formatting can vary (mix suffixes); fall back to the platform suffix.
for name, size in assets.items():
if name.endswith(suffix):
return size
return None
def _parse_installed_at(value: object) -> Optional[datetime]:
if not isinstance(value, str) or not value:
return None
@ -313,6 +422,7 @@ def reset_caches(*, drop_disk: bool = False) -> None:
open (off) instead of pointing at the just-replaced build."""
_marker_cache.clear()
_release_memo.clear()
_assets_memo.clear()
if drop_disk:
import shutil

View file

@ -37,9 +37,11 @@ from utils.llama_cpp_freshness import (
_INSTALL_MARKER_NAME,
check_prebuilt_freshness,
latest_published_release,
latest_release_assets,
parse_base_build,
read_install_marker,
reset_caches,
update_download_size_bytes,
)
logger = structlog.get_logger(__name__)
@ -291,6 +293,18 @@ def _source_build_status(binary: str, *, force_refresh: bool) -> Optional[dict]:
update_available = False
# Display the mix tag when that's what makes it newer; otherwise the base.
latest = release_tag if latest_is_mix else base_tag
# Size of the resolved prebuilt, so source builds show it like the marker
# path. Fails open to None (offline / asset absent from the release).
update_size_bytes = None
if update_available:
asset_name = res.get("asset")
if isinstance(asset_name, str) and asset_name:
try:
assets = latest_release_assets(res.get("repo"), force_refresh = force_refresh)
if assets:
update_size_bytes = assets.get(asset_name)
except Exception as exc: # pragma: no cover - network defensive
logger.debug("llama update: source-build size lookup failed", error = str(exc))
with _job_lock:
job = dict(_job)
return {
@ -303,6 +317,7 @@ def _source_build_status(binary: str, *, force_refresh: bool) -> Optional[dict]:
"installed_at_utc": None,
"age_days": None,
"source_build": True,
"update_size_bytes": update_size_bytes,
"job": job,
}
@ -345,6 +360,20 @@ def get_update_status(*, force_refresh: bool = False) -> dict:
# (see llama_cpp_freshness.is_behind).
update_available = bool(freshness.get("has_marker") and freshness.get("behind"))
# Size of the prebuilt that Update would download, for the banner. Only when
# an update is offered; fails open to None (offline / no matching asset).
update_size_bytes = None
if update_available:
try:
update_size_bytes = update_download_size_bytes(
marker,
latest,
freshness.get("published_repo") or repo,
force_refresh = force_refresh,
)
except Exception as exc: # pragma: no cover - network defensive
logger.debug("llama update: size lookup failed", error = str(exc))
with _job_lock:
job = dict(_job)
@ -358,6 +387,7 @@ def get_update_status(*, force_refresh: bool = False) -> dict:
"installed_at_utc": freshness.get("installed_at_utc"),
"age_days": freshness.get("age_days"),
"source_build": False,
"update_size_bytes": update_size_bytes,
"job": job,
}

View file

@ -7,10 +7,7 @@ import { useShowLlamaUpdateBanner } from "@/hooks/use-llama-update-pref";
import { toast } from "@/lib/toast";
import { cn } from "@/lib/utils";
import { Download } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { type ReactElement, useEffect, useRef, useState } from "react";
const EASE_OUT_QUART: [number, number, number, number] = [0.165, 0.84, 0.44, 1];
// Backend progress is coarse (5% steps, ~0.9 max) and the extract tail emits no
// signal. Creep toward this cap so the bar keeps moving rather than freezing.
const RUNNING_CAP = 0.95;
@ -114,6 +111,12 @@ export function LlamaUpdateBanner({
const show =
visible && status != null && (status.update_available || applying);
const sizeBytes = status?.update_size_bytes ?? null;
// Round to whole MB; these prebuilts are hundreds of MB.
const sizeLabel =
sizeBytes && sizeBytes > 0
? `${Math.round(sizeBytes / (1024 * 1024))} MB`
: null;
const updateProgress = status?.job.progress ?? null;
const jobSucceeded = status?.job.state === "success";
// Drives the bar so it animates continuously; aria reports the real value.
@ -123,113 +126,110 @@ export function LlamaUpdateBanner({
jobSucceeded,
);
return (
<AnimatePresence>
{show ? (
<motion.div
initial={{ opacity: 0, y: 12, scale: 0.96 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 8, scale: 0.97 }}
transition={{ duration: 0.35, ease: EASE_OUT_QUART }}
className={cn(
positioned
? "fixed bottom-4 right-4 z-[9998] w-[calc(100vw-2rem)] max-w-[400px]"
: "pointer-events-auto w-full",
)}
data-testid="llama-update-banner"
>
<div className="relative overflow-hidden rounded-[24px] bg-white px-5 pb-4 pt-5 shadow-[0_2px_8px_-2px_rgba(0,0,0,0.16)] dark:bg-card dark:shadow-[0_8px_28px_-6px_rgba(0,0,0,0.28)]">
{applying ? null : (
<button
type="button"
onClick={dismiss}
className="absolute top-2.5 right-3 flex size-6 items-center justify-center rounded-full text-muted-foreground/60 transition-colors hover:bg-muted hover:text-foreground"
aria-label="Dismiss llama.cpp update notification"
>
<svg
aria-hidden="true"
width="12"
height="12"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 3L3 11M3 3l8 8"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
</button>
)}
<div className="flex min-w-0 items-start gap-4 pr-6">
<Download
aria-hidden="true"
className="mt-1 size-5 shrink-0 text-foreground"
strokeWidth={1.75}
// Render with no enter/exit animation. An opacity/transform transition (in or
// out) promotes a GPU compositing layer whose creation or teardown can flash
// for a frame on real displays, which reads as a flicker on appear and on
// dismiss. A plain conditional mount appears and leaves cleanly.
return show ? (
<div
className={cn(
positioned
? "fixed bottom-4 right-4 z-[9998] w-[calc(100vw-2rem)] max-w-[400px]"
: "pointer-events-auto w-full",
)}
data-testid="llama-update-banner"
>
<div className="relative overflow-hidden rounded-[24px] bg-white px-5 pb-4 pt-5 shadow-[0_2px_8px_-2px_rgba(0,0,0,0.16)] dark:bg-card dark:shadow-[0_8px_28px_-6px_rgba(0,0,0,0.28)]">
{applying ? null : (
<button
type="button"
onClick={dismiss}
className="absolute top-2.5 right-3 flex size-6 items-center justify-center rounded-full text-muted-foreground/60 transition-colors hover:bg-muted hover:text-foreground"
aria-label="Dismiss llama.cpp update notification"
>
<svg
aria-hidden="true"
width="12"
height="12"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 3L3 11M3 3l8 8"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<div className="min-w-0">
<p className="font-heading text-base font-medium text-foreground">
{applying ? "Updating llama.cpp..." : "New llama.cpp update"}
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{status?.installed_tag ?? "unknown"} &rarr;{" "}
<span className="font-medium text-foreground">
{status?.latest_tag ?? ""}
</span>
</p>
<p className="mt-1 text-[11px] text-muted-foreground/70">
No restart needed after update
</p>
</div>
</div>
</svg>
</button>
)}
{applying ? (
<div
className="mb-1.5 mt-4 h-1 overflow-hidden rounded-full bg-muted"
role="progressbar"
aria-label="Updating llama.cpp"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={
updateProgress != null
? Math.round(updateProgress * 100)
: Math.round(displayProgress * 100)
}
data-testid="llama-update-progress"
>
<div
className="h-full rounded-full bg-primary"
style={{ width: `${Math.max(displayProgress * 100, 2)}%` }}
/>
</div>
) : (
<div className="mt-2 flex flex-wrap items-center justify-end gap-x-1 gap-y-2">
<Button
size="sm"
variant="ghost"
className="h-auto rounded-full px-3 py-2 text-[13px] font-medium text-foreground"
onClick={snooze}
data-testid="llama-update-snooze-button"
>
Remind me later
</Button>
<Button
size="sm"
// -mr optically aligns the filled pill's edge with the card padding
className="-mr-1 h-auto rounded-full px-3.5 py-2 text-[13px]"
onClick={handleUpdate}
data-testid="llama-update-button"
>
Update
</Button>
</div>
)}
<div className="flex min-w-0 items-start gap-4 pr-6">
<Download
aria-hidden="true"
className="mt-1 size-5 shrink-0 text-foreground"
strokeWidth={1.75}
/>
<div className="min-w-0">
<p className="font-heading text-base font-medium text-foreground">
{applying ? "Updating llama.cpp..." : "New llama.cpp update"}
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{status?.installed_tag ?? "unknown"} &rarr;{" "}
<span className="font-medium text-foreground">
{status?.latest_tag ?? ""}
</span>
</p>
<p className="mt-1 text-[11px] text-muted-foreground/70">
{sizeLabel ? `${sizeLabel} download · ` : ""}No restart needed
after update
</p>
</div>
</motion.div>
) : null}
</AnimatePresence>
);
</div>
{applying ? (
<div
className="mb-1.5 mt-4 h-1 overflow-hidden rounded-full bg-muted"
role="progressbar"
aria-label="Updating llama.cpp"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={
updateProgress != null
? Math.round(updateProgress * 100)
: Math.round(displayProgress * 100)
}
data-testid="llama-update-progress"
>
<div
className="h-full rounded-full bg-primary"
style={{ width: `${Math.max(displayProgress * 100, 2)}%` }}
/>
</div>
) : (
<div className="mt-2 flex flex-wrap items-center justify-end gap-x-1 gap-y-2">
<Button
size="sm"
variant="ghost"
className="h-auto rounded-full px-3 py-2 text-[13px] font-medium text-foreground"
onClick={snooze}
data-testid="llama-update-snooze-button"
>
Remind me later
</Button>
<Button
size="sm"
// -mr optically aligns the filled pill's edge with the card padding
className="-mr-1 h-auto rounded-full px-3.5 py-2 text-[13px]"
onClick={handleUpdate}
data-testid="llama-update-button"
>
Update
</Button>
</div>
)}
</div>
</div>
) : null;
}

View file

@ -31,6 +31,8 @@ export interface LlamaUpdateStatus {
update_available: boolean;
installed_tag: string | null;
latest_tag: string | null;
// Download size of the prebuilt Update would fetch, in bytes (null if unknown).
update_size_bytes: number | null;
job: LlamaUpdateJob;
}
@ -43,6 +45,8 @@ function parseStatus(value: unknown): LlamaUpdateStatus | null {
update_available: s.update_available === true,
installed_tag: typeof s.installed_tag === "string" ? s.installed_tag : null,
latest_tag: typeof s.latest_tag === "string" ? s.latest_tag : null,
update_size_bytes:
typeof s.update_size_bytes === "number" ? s.update_size_bytes : null,
job: {
state: (job.state as LlamaUpdateJob["state"]) ?? "idle",
message: typeof job.message === "string" ? job.message : "",

View file

@ -471,11 +471,15 @@ class TestInstallPythonStackSubprocessMock:
# -- Normal Linux path (NO_TORCH=False, IS_MACOS=False, IS_WINDOWS=False) --
def test_normal_linux_includes_overrides(self):
"""Normal Linux: overrides.txt IS called."""
"""Normal Linux: the torchao override step IS called.
The override step installs a torch-matched torchao spec via
--force-reinstall (uv: --reinstall), not overrides.txt directly.
"""
cmds = self._capture_install(no_torch = False, is_macos = False, is_windows = False)
assert self._cmds_contain_file(
cmds, "overrides.txt"
), "overrides.txt should be called on normal Linux"
assert any(
"--reinstall" in cmd for cmd in cmds
), "torchao override step (--reinstall) should be called on normal Linux"
def test_normal_linux_includes_triton(self):
"""Normal Linux: triton-kernels.txt IS called."""

View file

@ -1430,7 +1430,16 @@ with sync_playwright() as p:
# Re-login through the UI with NEW2 so the browser has a valid
# access token for the /api/shutdown call (the previous one
# was invalidated by the CLI rotation above).
page.goto(f"{BASE}/login")
# The CLI rotation left a stale token, so the SPA auth guard can
# client-side-redirect mid-navigation and abort this goto with
# net::ERR_ABORTED. Resolve on domcontentloaded and tolerate the
# abort; the password-field wait below confirms we reached /login.
try:
page.goto(f"{BASE}/login", wait_until = "domcontentloaded", timeout = 60_000)
except Exception as exc:
if "ERR_ABORTED" not in str(exc):
raise
info(f"goto /login aborted ({exc!r}); password-field wait will confirm /login")
pw_field = page.locator("#password")
pw_field.wait_for(state = "visible", timeout = 60_000)
pw_field.fill(NEW2)