@wendellresearch-ai/benchwarmers 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -398,7 +398,7 @@ def main() -> int:
398
398
  stopped = {i["served"]: i["state"] for i in serve.onboard_queue()["items"]}
399
399
  order.remove("quiet")
400
400
  serve.queue_onboard([{"served": s, "endpoint": "e", "mode": m, "vision": s == "quiet"} for s, m in
401
- (("ok", "full"), ("quiet", "quick"), ("bad", "quick-adhoc"))])
401
+ (("ok", "full-adhoc"), ("quiet", "quick"), ("bad", "quick-adhoc"))])
402
402
  settle()
403
403
  batch = {i["served"]: (i["state"], i["alias"], bool(i["note"])) for i in serve.onboard_queue()["items"]}
404
404
  cleared = serve.clear_onboard_queue()["items"]
@@ -412,10 +412,55 @@ def main() -> int:
412
412
  and batch == {"ok": ("done", "ok-x", False), "quiet": ("no answer", "quiet-x", True),
413
413
  "bad": ("failed", "bad-x", True)}
414
414
  and cleared == [] and bad_input == (False, False, False)
415
- and modes == {"a": "full", "ok": "full", "quiet": "quick vision", "bad": "quick-adhoc"})
415
+ and modes == {"a": "full", "ok": "full-adhoc", "quiet": "quick vision", "bad": "quick-adhoc"}
416
+ and serve.ONBOARD_MODES["full-adhoc"] == ["--ad-hoc"]) # the suite now, then ad hoc
416
417
  print(("ok " if ok else "FAIL ") + "onboarding queue" + ("" if ok else f" -> {stopped} {order} {batch} {modes}"))
417
418
  failures += 0 if ok else 1
418
419
 
420
+ # Restart: refused unless serve.py's parent started it (nothing would start it
421
+ # again); refused while a job runs, unless forced; then the server shuts down.
422
+ import scheduler
423
+ kept = os.environ.get("BENCH_SERVED"), serve.SERVER
424
+ shut = []
425
+ try:
426
+ os.environ.pop("BENCH_SERVED", None)
427
+ alone = serve.restart()
428
+ os.environ["BENCH_SERVED"] = "1"
429
+ serve.SERVER = type("Server", (), {"shutdown": lambda self: shut.append(1)})()
430
+ scheduler.RUNNING["suite"] = True
431
+ busy, forced = serve.restart(), serve.restart(force=True)
432
+ time.sleep(0.8) # the shutdown waits half a second
433
+ finally:
434
+ scheduler.RUNNING.pop("suite", None)
435
+ serve.SERVER = kept[1]
436
+ os.environ.pop("BENCH_SERVED", None) if kept[0] is None else os.environ.update(BENCH_SERVED=kept[0])
437
+ ok = (not alone["ok"] and busy.get("busy") and not busy["ok"] and "suite" in busy["errors"][0]
438
+ and forced["ok"] and forced["cut"] and shut == [1])
439
+ print(("ok " if ok else "FAIL ") + "restart" + ("" if ok else f" -> {alone} {busy} {forced} {shut}"))
440
+ failures += 0 if ok else 1
441
+
442
+ # Action needed: an alert whose models have all left the bench is not shown,
443
+ # before the daily check rewrites the file (2026-10-28: a kilo connection
444
+ # alert stayed after Retire all 4).
445
+ kept_data, kept_reg = serve.DATA, serve.registered
446
+ with tempfile.TemporaryDirectory() as tmp:
447
+ (pathlib.Path(tmp) / "metrics").mkdir()
448
+ (pathlib.Path(tmp) / "metrics" / "alerts.json").write_text(json.dumps({
449
+ "connection:held:gone.example": {"kind": "connection", "message": "every model on gone.example is failing",
450
+ "aliases": ["g1", "g2"], "first_seen": "2026-10-28"},
451
+ "connection:held:here.example": {"kind": "connection", "message": "every model on here.example is failing",
452
+ "aliases": ["h1", "g1"], "first_seen": "2026-10-28"},
453
+ "retiring:x:old": {"kind": "retiring", "alias": "old", "message": "old retires", "first_seen": "2026-10-28"}}),
454
+ encoding="utf-8")
455
+ serve.DATA, serve.registered = pathlib.Path(tmp), lambda: {"h1-served": "h1"}
456
+ try:
457
+ shown = serve.problems()
458
+ finally:
459
+ serve.DATA, serve.registered = kept_data, kept_reg
460
+ ok = "here.example" in shown and "gone.example" not in shown and "old retires" not in shown
461
+ print(("ok " if ok else "FAIL ") + "action needed drops alerts on retired models")
462
+ failures += 0 if ok else 1
463
+
419
464
  # Dismiss: one picker key or many in one write, a change-log line each;
420
465
  # undo brings back only the ones named; nothing named is refused.
421
466
  serve.DISMISSED_PATH = cfg / "dismissed.json"
@@ -433,6 +478,14 @@ def main() -> int:
433
478
  print(("ok " if ok else "FAIL ") + "dismiss in bulk, and undo" + ("" if ok else f" -> {gone} {left} {log} {refused}"))
434
479
  failures += 0 if ok else 1
435
480
 
481
+ # The member picker's provider chips: a proxy's vendor, else the bare host.
482
+ names = {"nvidia-api": "nvidia", "api.unorouter.com": "unorouter", "router.requesty.ai": "requesty",
483
+ "cli-proxy-api/codex": "codex", "openrouter": "openrouter", "apihub.agnes-ai.com": "agnes-ai"}
484
+ got = {t: serve.provider_label(t) for t in names}
485
+ ok = got == names
486
+ print(("ok " if ok else "FAIL ") + "provider chip names" + ("" if ok else f" -> {got}"))
487
+ failures += 0 if ok else 1
488
+
436
489
  # The owner's files are as they were. alerts.json can change under a
437
490
  # running suite, so only what this test could do counts: the victim's
438
491
  # alerts cleared from the real file.