diff --git a/.github/scripts/interrupted_install_probe.py b/.github/scripts/interrupted_install_probe.py index 1d9a04890b..95050cc920 100644 --- a/.github/scripts/interrupted_install_probe.py +++ b/.github/scripts/interrupted_install_probe.py @@ -44,7 +44,7 @@ from pathlib import Path def run(cmd: list[str], timeout: int = 120) -> tuple[int, str]: try: - p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + p = subprocess.run(cmd, capture_output = True, text = True, timeout = timeout) return p.returncode, (p.stdout or "") + (p.stderr or "") except (subprocess.TimeoutExpired, OSError) as e: return 127, f"{type(e).__name__}: {e}" @@ -53,7 +53,7 @@ def run(cmd: list[str], timeout: int = 120) -> tuple[int, str]: def has_subcommand(bin_path: str, args: list[str]) -> bool: """Whether the CLI understands a subcommand at all. Older builds do not have the newer verify commands, and 'absent' must not be confused with 'reported failure'.""" - rc, _ = run([bin_path, *args, "--help"], timeout=60) + rc, _ = run([bin_path, *args, "--help"], timeout = 60) return rc == 0 @@ -64,11 +64,11 @@ def free_port() -> int: def main(argv: list[str]) -> int: - ap = argparse.ArgumentParser(description=__doc__) - ap.add_argument("bin", help="path to the unsloth CLI") - ap.add_argument("--port", type=int, default=0, help="0 picks a free port") - ap.add_argument("--out", default="probe", help="directory for probe artefacts") - ap.add_argument("--boot-timeout", type=int, default=120) + ap = argparse.ArgumentParser(description = __doc__) + ap.add_argument("bin", help = "path to the unsloth CLI") + ap.add_argument("--port", type = int, default = 0, help = "0 picks a free port") + ap.add_argument("--out", default = "probe", help = "directory for probe artefacts") + ap.add_argument("--boot-timeout", type = int, default = 120) a = ap.parse_args(argv) binp = a.bin @@ -76,7 +76,7 @@ def main(argv: list[str]) -> int: print(f"::error::unsloth bin not found: {binp}") return 2 out = Path(a.out) - out.mkdir(parents=True, exist_ok=True) + out.mkdir(parents = True, exist_ok = True) port = a.port or free_port() facts: dict[str, object] = {} @@ -85,12 +85,12 @@ def main(argv: list[str]) -> int: print(f"[probe] {k:28} = {v}") # ── the two probes Tauri preflight actually runs ───────────────────────── - rc, log = run([binp, "-h"], timeout=180) - (out / "cli-h.log").write_text(log, encoding="utf-8", errors="replace") + rc, log = run([binp, "-h"], timeout = 180) + (out / "cli-h.log").write_text(log, encoding = "utf-8", errors = "replace") say("cli_h_ok", rc == 0) - rc, caps_raw = run([binp, "studio", "desktop-capabilities", "--json"], timeout=180) - (out / "desktop-capabilities.json").write_text(caps_raw, encoding="utf-8", errors="replace") + rc, caps_raw = run([binp, "studio", "desktop-capabilities", "--json"], timeout = 180) + (out / "desktop-capabilities.json").write_text(caps_raw, encoding = "utf-8", errors = "replace") say("capabilities_ok", rc == 0) # studio_install_ok is added by the install-manifest work; absent on older trees, @@ -107,13 +107,15 @@ def main(argv: list[str]) -> int: say("capabilities.studio_install_ok", install_ok) # ── the deeper probes the fix PRs add ──────────────────────────────────── - for label, args in (("verify_install", ["studio", "verify-install"]), - ("desktop_runtime_check", ["studio", "desktop-runtime-check"])): + for label, args in ( + ("verify_install", ["studio", "verify-install"]), + ("desktop_runtime_check", ["studio", "desktop-runtime-check"]), + ): if not has_subcommand(binp, args): say(label, "absent") continue - rc, log = run([binp, *args], timeout=300) - (out / f"{label}.log").write_text(log, encoding="utf-8", errors="replace") + rc, log = run([binp, *args], timeout = 300) + (out / f"{label}.log").write_text(log, encoding = "utf-8", errors = "replace") say(label, "ok" if rc == 0 else "failed") # The in-progress marker #7490 writes before spawning the installer. @@ -131,7 +133,10 @@ def main(argv: list[str]) -> int: popen_kw["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0) proc = subprocess.Popen( [binp, "studio", "--api-only", "-H", "127.0.0.1", "-p", str(port)], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, **popen_kw, + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + text = True, + **popen_kw, ) backend_ok = False deadline = time.time() + a.boot_timeout @@ -140,7 +145,7 @@ def main(argv: list[str]) -> int: break for path in ("/api/health", "/healthz"): try: - with urllib.request.urlopen(f"http://127.0.0.1:{port}{path}", timeout=2) as r: + with urllib.request.urlopen(f"http://127.0.0.1:{port}{path}", timeout = 2) as r: if r.status == 200: backend_ok = True break @@ -149,6 +154,7 @@ def main(argv: list[str]) -> int: if backend_ok: break time.sleep(1) + def reap() -> None: if os.name == "posix": import signal @@ -158,24 +164,24 @@ def main(argv: list[str]) -> int: except (ProcessLookupError, PermissionError, OSError): pass try: - proc.wait(timeout=10) + proc.wait(timeout = 10) return except subprocess.TimeoutExpired: continue else: proc.terminate() try: - proc.wait(timeout=10) + proc.wait(timeout = 10) except subprocess.TimeoutExpired: proc.kill() reap() try: - blog = proc.communicate(timeout=30)[0] or "" + blog = proc.communicate(timeout = 30)[0] or "" except subprocess.TimeoutExpired: proc.kill() blog = proc.communicate()[0] or "" - (out / "backend.log").write_text(blog, encoding="utf-8", errors="replace") + (out / "backend.log").write_text(blog, encoding = "utf-8", errors = "replace") say("backend_ok", backend_ok) missing = "" @@ -188,25 +194,29 @@ def main(argv: list[str]) -> int: # ── verdict ────────────────────────────────────────────────────────────── if backend_ok: verdict = "HEALTHY" - elif (facts.get("verify_install") == "failed" - or facts.get("desktop_runtime_check") == "failed" - or facts.get("capabilities.studio_install_ok") is False - or facts.get("install_in_progress_marker") is True - or not facts.get("cli_h_ok") - or not facts.get("capabilities_ok")): + elif ( + facts.get("verify_install") == "failed" + or facts.get("desktop_runtime_check") == "failed" + or facts.get("capabilities.studio_install_ok") is False + or facts.get("install_in_progress_marker") is True + or not facts.get("cli_h_ok") + or not facts.get("capabilities_ok") + ): verdict = "REPAIRABLE" else: verdict = "FALSE_READY" facts["verdict"] = verdict - (out / "verdict.json").write_text(json.dumps(facts, indent=2), encoding="utf-8") + (out / "verdict.json").write_text(json.dumps(facts, indent = 2), encoding = "utf-8") print(f"[probe] VERDICT = {verdict}") if verdict == "FALSE_READY": - print("::error::Interrupted install reports READY but the backend cannot boot" - f" ({missing or 'import failure'}). Preflight sees -h ok + desktop-capabilities" - " ok, so the app shows ManagedReady with can_auto_repair=false and the user" - " is stuck.") + print( + "::error::Interrupted install reports READY but the backend cannot boot" + f" ({missing or 'import failure'}). Preflight sees -h ok + desktop-capabilities" + " ok, so the app shows ManagedReady with can_auto_repair=false and the user" + " is stuck." + ) return 1 if verdict == "REPAIRABLE": print("[probe] broken install is detectable -> the desktop app can auto-repair")