@wendellresearch-ai/benchwarmers 0.2.0 → 0.2.2
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.
- package/AGENTS.md +152 -147
- package/BENCHMARKS.md +262 -262
- package/GATEWAY.md +415 -272
- package/NOTICE +28 -28
- package/PRIVACY.md +46 -46
- package/README.md +408 -314
- package/SECURITY.md +83 -83
- package/TERMS.md +22 -22
- package/USAGE.md +186 -163
- package/VERSION +1 -1
- package/bin/benchwarmers.js +16 -74
- package/cli.py +674 -0
- package/examples/config/gateway.json +5 -5
- package/examples/config/model-roles.json +11 -11
- package/gateway.py +939 -755
- package/launcher.py +120 -0
- package/mcp_server.py +266 -266
- package/metrics/alerts.py +116 -116
- package/metrics/aliases.py +41 -41
- package/metrics/bench_collections.py +141 -141
- package/metrics/catalog.py +304 -295
- package/metrics/client.py +804 -394
- package/metrics/cliproxy.py +191 -191
- package/metrics/connect.py +691 -691
- package/metrics/cost.py +171 -152
- package/metrics/decide.py +227 -227
- package/metrics/discover.py +318 -318
- package/metrics/endpoints.py +436 -436
- package/metrics/formats.py +1021 -0
- package/metrics/guard.py +647 -592
- package/metrics/learn.py +234 -221
- package/metrics/ledger.py +148 -0
- package/metrics/litellm_hub.py +206 -206
- package/metrics/model_roles.py +94 -84
- package/metrics/offload.py +166 -166
- package/metrics/owui.py +205 -205
- package/metrics/pacing.py +248 -0
- package/metrics/paths.py +60 -60
- package/metrics/quota.py +383 -0
- package/metrics/route.py +581 -579
- package/metrics/scheduler.py +275 -275
- package/metrics/sources.py +167 -167
- package/metrics/wrong.py +144 -144
- package/package.json +69 -65
- package/perf/README.md +89 -89
- package/perf/run.py +284 -261
- package/scripts/auto_retire.py +367 -367
- package/scripts/check.py +113 -106
- package/scripts/check_deprecations.py +378 -378
- package/scripts/check_themes.py +103 -103
- package/scripts/init.py +124 -124
- package/scripts/list_served.py +78 -78
- package/scripts/notice.py +78 -78
- package/scripts/onboard.py +410 -331
- package/scripts/provider_tags.py +129 -129
- package/scripts/reprefix.py +136 -136
- package/scripts/sync_config.py +105 -105
- package/scripts/test_auth.py +255 -255
- package/scripts/test_cli.py +189 -0
- package/scripts/test_client.py +277 -233
- package/scripts/test_connect.py +222 -222
- package/scripts/test_first_run.py +180 -180
- package/scripts/test_formats.py +549 -0
- package/scripts/test_hubs.py +328 -328
- package/scripts/test_launcher.py +67 -0
- package/scripts/test_llm_log.py +85 -81
- package/scripts/test_mcp.py +178 -178
- package/scripts/test_merged.py +215 -214
- package/scripts/test_no_owui.py +291 -291
- package/scripts/test_owui.py +90 -90
- package/scripts/test_quota.py +334 -0
- package/scripts/test_serve_edit.py +453 -438
- package/scripts/test_server_json.py +79 -0
- package/scripts/test_verdict.py +102 -98
- package/scripts/test_vision.py +231 -228
- package/serve.py +3972 -3764
- package/suite/mine.py +154 -154
- package/suite/pace.py +145 -145
- package/suite/run.py +1046 -1042
- package/suite/seed.py +421 -421
- package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
- package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
- package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
- package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
- package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
- package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
- package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
- package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
- package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
- package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
- package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
- package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
- package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
- package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
- package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
- package/ui/bench.css +430 -425
- package/ui/bench.js +1757 -1576
- package/ui/themes/dark.css +35 -35
- package/ui/themes/hardwood.css +52 -52
- package/ui/themes/rainforest.css +32 -32
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
"""A stranger's first run, on an empty data folder. No real files.
|
|
2
|
-
|
|
3
|
-
python scripts/test_first_run.py
|
|
4
|
-
|
|
5
|
-
RELEASE-PLAN Phases 1 and 4. The dashboard renders with no endpoint and leads
|
|
6
|
-
into Add, through the real handler; a job that needs an endpoint still
|
|
7
|
-
refuses, saying how to add one; the readers of seed files fall back to empty
|
|
8
|
-
when a file is missing; the shipped roles and shapes merge under the
|
|
9
|
-
install's own; init copies examples/, points a checkout at the folder (a
|
|
10
|
-
temp pointer here) and adds a first endpoint by save_endpoint's rules
|
|
11
|
-
against a fake server. BENCHWARMERS_HOME and LLM_USER_PATH name temp folders
|
|
12
|
-
before anything imports metrics/paths.py, so no install's data and not the
|
|
13
|
-
real llm key store are touched.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
import json
|
|
19
|
-
import os
|
|
20
|
-
import pathlib
|
|
21
|
-
import shutil
|
|
22
|
-
import subprocess
|
|
23
|
-
import sys
|
|
24
|
-
import tempfile
|
|
25
|
-
import threading
|
|
26
|
-
import urllib.error
|
|
27
|
-
import urllib.request
|
|
28
|
-
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
29
|
-
|
|
30
|
-
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
31
|
-
TMP = pathlib.Path(tempfile.mkdtemp())
|
|
32
|
-
DATA, LLM = TMP / "data", TMP / "llm"
|
|
33
|
-
DATA.mkdir()
|
|
34
|
-
os.environ.update(BENCHWARMERS_HOME=str(DATA), LLM_USER_PATH=str(LLM))
|
|
35
|
-
sys.path[:0] = [str(ROOT), str(ROOT / "scripts")]
|
|
36
|
-
import serve # noqa: E402
|
|
37
|
-
import init # noqa: E402
|
|
38
|
-
import auto_retire # noqa: E402
|
|
39
|
-
import onboard # noqa: E402
|
|
40
|
-
import model_roles # noqa: E402
|
|
41
|
-
import paths # noqa: E402
|
|
42
|
-
import route # noqa: E402
|
|
43
|
-
import run as suite # noqa: E402
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class Provider(BaseHTTPRequestHandler):
|
|
47
|
-
def do_GET(self): # noqa: N802
|
|
48
|
-
body = json.dumps({"data": [{"id": "m1"}, {"id": "m2"}]} if self.path == "/v1/models" else {}).encode()
|
|
49
|
-
self.send_response(200 if self.path == "/v1/models" else 404)
|
|
50
|
-
self.send_header("Content-Type", "application/json")
|
|
51
|
-
self.send_header("Content-Length", str(len(body)))
|
|
52
|
-
self.end_headers()
|
|
53
|
-
self.wfile.write(body)
|
|
54
|
-
|
|
55
|
-
def log_message(self, *_a):
|
|
56
|
-
pass
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def serve_on(handler) -> ThreadingHTTPServer:
|
|
60
|
-
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
|
|
61
|
-
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
62
|
-
return server
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def main() -> int:
|
|
66
|
-
assert serve.DATA == DATA and serve.endpoints.LLM_DIR == LLM, "not on the temp folders"
|
|
67
|
-
# The npm package (bin/benchwarmers.js) carries the app at the app's version.
|
|
68
|
-
pkg = json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
|
|
69
|
-
assert pkg["version"] == (ROOT / "VERSION").read_text(encoding="utf-8").strip(), "package.json and VERSION differ"
|
|
70
|
-
provider, dash = serve_on(Provider), serve_on(serve.Handler)
|
|
71
|
-
serve.PORT = dash.server_port
|
|
72
|
-
home = f"http://127.0.0.1:{dash.server_port}"
|
|
73
|
-
|
|
74
|
-
def get(path: str):
|
|
75
|
-
with urllib.request.urlopen(home + path, timeout=10) as r:
|
|
76
|
-
body = r.read().decode("utf-8")
|
|
77
|
-
return json.loads(body) if path.startswith("/api/") else body
|
|
78
|
-
|
|
79
|
-
def post(path: str, data: dict) -> tuple[int, dict]:
|
|
80
|
-
req = urllib.request.Request(home + path, json.dumps(data).encode(), method="POST",
|
|
81
|
-
headers={"Content-Type": "application/json"})
|
|
82
|
-
try:
|
|
83
|
-
with urllib.request.urlopen(req, timeout=10) as r:
|
|
84
|
-
return r.status, json.loads(r.read())
|
|
85
|
-
except urllib.error.HTTPError as exc:
|
|
86
|
-
return exc.code, json.loads(exc.read())
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
# 0. An endpoints file that cannot be read is not a fresh install: the
|
|
90
|
-
# page says which file and why (load() refuses, so no writer saves over it).
|
|
91
|
-
broken = DATA / "config" / "endpoints.json"
|
|
92
|
-
broken.parent.mkdir()
|
|
93
|
-
broken.write_text("{", encoding="utf-8")
|
|
94
|
-
assert "cannot be read" in get("/"), "a broken endpoints file read as none"
|
|
95
|
-
broken.unlink()
|
|
96
|
-
|
|
97
|
-
# 1. No endpoint: the page renders and leads into Add; the APIs answer.
|
|
98
|
-
page = get("/")
|
|
99
|
-
assert "id='first-run' data-step='endpoint'" in page and "id='e-add'" in page, "no first-run step"
|
|
100
|
-
assert get("/api/endpoints")["endpoints"] == []
|
|
101
|
-
assert get("/api/connect")["providers"], "no providers to sign in with"
|
|
102
|
-
cand = get("/api/candidates")
|
|
103
|
-
assert not cand["ok"] and "add one first" in cand["errors"][0], cand
|
|
104
|
-
assert get("/api/collections")["ok"] and get("/api/connections")["ok"]
|
|
105
|
-
status, res = post("/api/onboard", {"served": "m1"})
|
|
106
|
-
assert status == 400 and "no endpoints configured" in res["errors"][0], res # a reply, not a dropped request
|
|
107
|
-
# ...while a job that needs an endpoint refuses, saying how to add one.
|
|
108
|
-
empty = TMP / "job"
|
|
109
|
-
empty.mkdir()
|
|
110
|
-
job = subprocess.run([sys.executable, "scripts/check_deprecations.py", "--dry-run"], cwd=ROOT,
|
|
111
|
-
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=120,
|
|
112
|
-
env={**os.environ, "BENCHWARMERS_HOME": str(empty)})
|
|
113
|
-
assert job.returncode and "no endpoints configured" in job.stderr, job.stderr[-500:]
|
|
114
|
-
assert not any(empty.iterdir()), "a refused job wrote something"
|
|
115
|
-
|
|
116
|
-
# 2. Every seed file missing: the readers say "nothing yet".
|
|
117
|
-
assert suite.registered_models() == [] and suite._registered_names() == {}
|
|
118
|
-
assert suite.available_models() == []
|
|
119
|
-
assert auto_retire.evidence() == ([], {}, {})
|
|
120
|
-
assert auto_retire.run({}, set(), {}, dry_run=True) == ([], set())
|
|
121
|
-
assert onboard.existing_aliases() == {}
|
|
122
|
-
assert onboard.alias_for("v/m1", {"m1": "other/m1"}) == "m1-alt" # a collision, no provider tags
|
|
123
|
-
assert model_roles.load() == {"roles": model_roles.ROLES, "shapes": model_roles.SHAPES,
|
|
124
|
-
"models": {}, "ad_hoc": []}
|
|
125
|
-
assert route.roles_for_shape("review") == ["general", "code"] and route.role_of("x") == "general"
|
|
126
|
-
|
|
127
|
-
# 3. A file from before the split (all four keys): its own wording wins,
|
|
128
|
-
# name by name, and what it does not define still comes from the product.
|
|
129
|
-
roles_file = DATA / "config" / "model-roles.json"
|
|
130
|
-
roles_file.parent.mkdir(parents=True, exist_ok=True)
|
|
131
|
-
roles_file.write_text(json.dumps({"roles": {"code": "mine"}, "shapes": {"review": ["general"],
|
|
132
|
-
"mine": ["light"]}, "models": {"a": "code"}, "ad_hoc": ["b"]}), encoding="utf-8")
|
|
133
|
-
merged = model_roles.load()
|
|
134
|
-
assert merged["roles"]["code"] == "mine" and merged["roles"]["light"] == model_roles.ROLES["light"]
|
|
135
|
-
assert merged["shapes"]["review"] == ["general"] and merged["shapes"]["mine"] == ["light"]
|
|
136
|
-
assert merged["shapes"]["quick"] == model_roles.SHAPES["quick"]
|
|
137
|
-
assert (merged["models"], merged["ad_hoc"]) == ({"a": "code"}, ["b"])
|
|
138
|
-
assert route.roles_for_shape("mine") == ["light"] and "mine" in serve.collections_view()["shapes"]
|
|
139
|
-
roles_file.unlink()
|
|
140
|
-
|
|
141
|
-
# 4. init: examples copied, the layout made, the checkout pointed, a first endpoint added.
|
|
142
|
-
init.POINTER = TMP / "pointer"
|
|
143
|
-
os.environ.pop("BENCHWARMERS_HOME")
|
|
144
|
-
ok, said = init.run(DATA, ("fake", f"http://127.0.0.1:{provider.server_port}/v1"), "sk-test")
|
|
145
|
-
assert ok, said
|
|
146
|
-
assert json.loads(roles_file.read_text(encoding="utf-8"))["models"] == {}
|
|
147
|
-
assert (DATA / "config" / "gateway.json").exists() and all((DATA / d).is_dir() for d in paths.LAYOUT)
|
|
148
|
-
assert init.POINTER.read_text(encoding="utf-8").strip() == str(DATA.resolve())
|
|
149
|
-
assert list(serve.endpoints.load()) == ["fake"] and serve.endpoints.default() == "fake"
|
|
150
|
-
assert json.loads((LLM / "keys.json").read_text(encoding="utf-8")) == {"fake": "sk-test"}
|
|
151
|
-
assert "data-step='onboard'" in get("/"), "the next step is not onboarding"
|
|
152
|
-
serve.endpoints.YAML.write_text(serve.endpoints.yaml_block("m1", "m1", "fake"), encoding="utf-8")
|
|
153
|
-
assert "data-step='collection'" in serve.first_run(), "a model on the bench, and no collection"
|
|
154
|
-
(DATA / "config" / "collections.json").write_text('{"c": {"members": ["m1"]}}', encoding="utf-8")
|
|
155
|
-
assert serve.first_run() == "", "set up, yet still a first-run step"
|
|
156
|
-
# Again, on a folder in use: nothing overwritten, the pointer kept.
|
|
157
|
-
(DATA / "config" / "gateway.json").write_text('{"max_calls": 3}', encoding="utf-8")
|
|
158
|
-
os.environ.pop("BENCHWARMERS_HOME")
|
|
159
|
-
ok, said = init.run(DATA)
|
|
160
|
-
assert ok and "0 starter file(s)" in said[0] and "already points" in said[1], said
|
|
161
|
-
assert json.loads((DATA / "config" / "gateway.json").read_text(encoding="utf-8")) == {"max_calls": 3}
|
|
162
|
-
# A checkout already pointing elsewhere, or BENCHWARMERS_HOME set: left alone, and said so.
|
|
163
|
-
init.POINTER.write_text("elsewhere\n", encoding="utf-8")
|
|
164
|
-
os.environ.pop("BENCHWARMERS_HOME")
|
|
165
|
-
ok, said = init.run(TMP / "other")
|
|
166
|
-
assert ok and "left so" in said[1] and init.POINTER.read_text(encoding="utf-8") == "elsewhere\n", said
|
|
167
|
-
assert (TMP / "other" / "config" / "model-roles.json").exists()
|
|
168
|
-
os.environ["BENCHWARMERS_HOME"] = str(TMP / "mine")
|
|
169
|
-
ok, said = init.run(TMP / "third")
|
|
170
|
-
assert ok and "which wins" in said[1] and init.POINTER.read_text(encoding="utf-8") == "elsewhere\n", said
|
|
171
|
-
finally:
|
|
172
|
-
provider.shutdown()
|
|
173
|
-
dash.shutdown()
|
|
174
|
-
shutil.rmtree(TMP, ignore_errors=True)
|
|
175
|
-
print("ok")
|
|
176
|
-
return 0
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if __name__ == "__main__":
|
|
180
|
-
sys.exit(main())
|
|
1
|
+
"""A stranger's first run, on an empty data folder. No real files.
|
|
2
|
+
|
|
3
|
+
python scripts/test_first_run.py
|
|
4
|
+
|
|
5
|
+
RELEASE-PLAN Phases 1 and 4. The dashboard renders with no endpoint and leads
|
|
6
|
+
into Add, through the real handler; a job that needs an endpoint still
|
|
7
|
+
refuses, saying how to add one; the readers of seed files fall back to empty
|
|
8
|
+
when a file is missing; the shipped roles and shapes merge under the
|
|
9
|
+
install's own; init copies examples/, points a checkout at the folder (a
|
|
10
|
+
temp pointer here) and adds a first endpoint by save_endpoint's rules
|
|
11
|
+
against a fake server. BENCHWARMERS_HOME and LLM_USER_PATH name temp folders
|
|
12
|
+
before anything imports metrics/paths.py, so no install's data and not the
|
|
13
|
+
real llm key store are touched.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import pathlib
|
|
21
|
+
import shutil
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
import tempfile
|
|
25
|
+
import threading
|
|
26
|
+
import urllib.error
|
|
27
|
+
import urllib.request
|
|
28
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
29
|
+
|
|
30
|
+
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
31
|
+
TMP = pathlib.Path(tempfile.mkdtemp())
|
|
32
|
+
DATA, LLM = TMP / "data", TMP / "llm"
|
|
33
|
+
DATA.mkdir()
|
|
34
|
+
os.environ.update(BENCHWARMERS_HOME=str(DATA), LLM_USER_PATH=str(LLM))
|
|
35
|
+
sys.path[:0] = [str(ROOT), str(ROOT / "scripts")]
|
|
36
|
+
import serve # noqa: E402
|
|
37
|
+
import init # noqa: E402
|
|
38
|
+
import auto_retire # noqa: E402
|
|
39
|
+
import onboard # noqa: E402
|
|
40
|
+
import model_roles # noqa: E402
|
|
41
|
+
import paths # noqa: E402
|
|
42
|
+
import route # noqa: E402
|
|
43
|
+
import run as suite # noqa: E402
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Provider(BaseHTTPRequestHandler):
|
|
47
|
+
def do_GET(self): # noqa: N802
|
|
48
|
+
body = json.dumps({"data": [{"id": "m1"}, {"id": "m2"}]} if self.path == "/v1/models" else {}).encode()
|
|
49
|
+
self.send_response(200 if self.path == "/v1/models" else 404)
|
|
50
|
+
self.send_header("Content-Type", "application/json")
|
|
51
|
+
self.send_header("Content-Length", str(len(body)))
|
|
52
|
+
self.end_headers()
|
|
53
|
+
self.wfile.write(body)
|
|
54
|
+
|
|
55
|
+
def log_message(self, *_a):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def serve_on(handler) -> ThreadingHTTPServer:
|
|
60
|
+
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
|
|
61
|
+
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
62
|
+
return server
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main() -> int:
|
|
66
|
+
assert serve.DATA == DATA and serve.endpoints.LLM_DIR == LLM, "not on the temp folders"
|
|
67
|
+
# The npm package (bin/benchwarmers.js) carries the app at the app's version.
|
|
68
|
+
pkg = json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
|
|
69
|
+
assert pkg["version"] == (ROOT / "VERSION").read_text(encoding="utf-8").strip(), "package.json and VERSION differ"
|
|
70
|
+
provider, dash = serve_on(Provider), serve_on(serve.Handler)
|
|
71
|
+
serve.PORT = dash.server_port
|
|
72
|
+
home = f"http://127.0.0.1:{dash.server_port}"
|
|
73
|
+
|
|
74
|
+
def get(path: str):
|
|
75
|
+
with urllib.request.urlopen(home + path, timeout=10) as r:
|
|
76
|
+
body = r.read().decode("utf-8")
|
|
77
|
+
return json.loads(body) if path.startswith("/api/") else body
|
|
78
|
+
|
|
79
|
+
def post(path: str, data: dict) -> tuple[int, dict]:
|
|
80
|
+
req = urllib.request.Request(home + path, json.dumps(data).encode(), method="POST",
|
|
81
|
+
headers={"Content-Type": "application/json"})
|
|
82
|
+
try:
|
|
83
|
+
with urllib.request.urlopen(req, timeout=10) as r:
|
|
84
|
+
return r.status, json.loads(r.read())
|
|
85
|
+
except urllib.error.HTTPError as exc:
|
|
86
|
+
return exc.code, json.loads(exc.read())
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
# 0. An endpoints file that cannot be read is not a fresh install: the
|
|
90
|
+
# page says which file and why (load() refuses, so no writer saves over it).
|
|
91
|
+
broken = DATA / "config" / "endpoints.json"
|
|
92
|
+
broken.parent.mkdir()
|
|
93
|
+
broken.write_text("{", encoding="utf-8")
|
|
94
|
+
assert "cannot be read" in get("/"), "a broken endpoints file read as none"
|
|
95
|
+
broken.unlink()
|
|
96
|
+
|
|
97
|
+
# 1. No endpoint: the page renders and leads into Add; the APIs answer.
|
|
98
|
+
page = get("/")
|
|
99
|
+
assert "id='first-run' data-step='endpoint'" in page and "id='e-add'" in page, "no first-run step"
|
|
100
|
+
assert get("/api/endpoints")["endpoints"] == []
|
|
101
|
+
assert get("/api/connect")["providers"], "no providers to sign in with"
|
|
102
|
+
cand = get("/api/candidates")
|
|
103
|
+
assert not cand["ok"] and "add one first" in cand["errors"][0], cand
|
|
104
|
+
assert get("/api/collections")["ok"] and get("/api/connections")["ok"]
|
|
105
|
+
status, res = post("/api/onboard", {"served": "m1"})
|
|
106
|
+
assert status == 400 and "no endpoints configured" in res["errors"][0], res # a reply, not a dropped request
|
|
107
|
+
# ...while a job that needs an endpoint refuses, saying how to add one.
|
|
108
|
+
empty = TMP / "job"
|
|
109
|
+
empty.mkdir()
|
|
110
|
+
job = subprocess.run([sys.executable, "scripts/check_deprecations.py", "--dry-run"], cwd=ROOT,
|
|
111
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=120,
|
|
112
|
+
env={**os.environ, "BENCHWARMERS_HOME": str(empty)})
|
|
113
|
+
assert job.returncode and "no endpoints configured" in job.stderr, job.stderr[-500:]
|
|
114
|
+
assert not any(empty.iterdir()), "a refused job wrote something"
|
|
115
|
+
|
|
116
|
+
# 2. Every seed file missing: the readers say "nothing yet".
|
|
117
|
+
assert suite.registered_models() == [] and suite._registered_names() == {}
|
|
118
|
+
assert suite.available_models() == []
|
|
119
|
+
assert auto_retire.evidence() == ([], {}, {})
|
|
120
|
+
assert auto_retire.run({}, set(), {}, dry_run=True) == ([], set())
|
|
121
|
+
assert onboard.existing_aliases() == {}
|
|
122
|
+
assert onboard.alias_for("v/m1", {"m1": "other/m1"}) == "m1-alt" # a collision, no provider tags
|
|
123
|
+
assert model_roles.load() == {"roles": model_roles.ROLES, "shapes": model_roles.SHAPES,
|
|
124
|
+
"models": {}, "ad_hoc": []}
|
|
125
|
+
assert route.roles_for_shape("review") == ["general", "code"] and route.role_of("x") == "general"
|
|
126
|
+
|
|
127
|
+
# 3. A file from before the split (all four keys): its own wording wins,
|
|
128
|
+
# name by name, and what it does not define still comes from the product.
|
|
129
|
+
roles_file = DATA / "config" / "model-roles.json"
|
|
130
|
+
roles_file.parent.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
roles_file.write_text(json.dumps({"roles": {"code": "mine"}, "shapes": {"review": ["general"],
|
|
132
|
+
"mine": ["light"]}, "models": {"a": "code"}, "ad_hoc": ["b"]}), encoding="utf-8")
|
|
133
|
+
merged = model_roles.load()
|
|
134
|
+
assert merged["roles"]["code"] == "mine" and merged["roles"]["light"] == model_roles.ROLES["light"]
|
|
135
|
+
assert merged["shapes"]["review"] == ["general"] and merged["shapes"]["mine"] == ["light"]
|
|
136
|
+
assert merged["shapes"]["quick"] == model_roles.SHAPES["quick"]
|
|
137
|
+
assert (merged["models"], merged["ad_hoc"]) == ({"a": "code"}, ["b"])
|
|
138
|
+
assert route.roles_for_shape("mine") == ["light"] and "mine" in serve.collections_view()["shapes"]
|
|
139
|
+
roles_file.unlink()
|
|
140
|
+
|
|
141
|
+
# 4. init: examples copied, the layout made, the checkout pointed, a first endpoint added.
|
|
142
|
+
init.POINTER = TMP / "pointer"
|
|
143
|
+
os.environ.pop("BENCHWARMERS_HOME")
|
|
144
|
+
ok, said = init.run(DATA, ("fake", f"http://127.0.0.1:{provider.server_port}/v1"), "sk-test")
|
|
145
|
+
assert ok, said
|
|
146
|
+
assert json.loads(roles_file.read_text(encoding="utf-8"))["models"] == {}
|
|
147
|
+
assert (DATA / "config" / "gateway.json").exists() and all((DATA / d).is_dir() for d in paths.LAYOUT)
|
|
148
|
+
assert init.POINTER.read_text(encoding="utf-8").strip() == str(DATA.resolve())
|
|
149
|
+
assert list(serve.endpoints.load()) == ["fake"] and serve.endpoints.default() == "fake"
|
|
150
|
+
assert json.loads((LLM / "keys.json").read_text(encoding="utf-8")) == {"fake": "sk-test"}
|
|
151
|
+
assert "data-step='onboard'" in get("/"), "the next step is not onboarding"
|
|
152
|
+
serve.endpoints.YAML.write_text(serve.endpoints.yaml_block("m1", "m1", "fake"), encoding="utf-8")
|
|
153
|
+
assert "data-step='collection'" in serve.first_run(), "a model on the bench, and no collection"
|
|
154
|
+
(DATA / "config" / "collections.json").write_text('{"c": {"members": ["m1"]}}', encoding="utf-8")
|
|
155
|
+
assert serve.first_run() == "", "set up, yet still a first-run step"
|
|
156
|
+
# Again, on a folder in use: nothing overwritten, the pointer kept.
|
|
157
|
+
(DATA / "config" / "gateway.json").write_text('{"max_calls": 3}', encoding="utf-8")
|
|
158
|
+
os.environ.pop("BENCHWARMERS_HOME")
|
|
159
|
+
ok, said = init.run(DATA)
|
|
160
|
+
assert ok and "0 starter file(s)" in said[0] and "already points" in said[1], said
|
|
161
|
+
assert json.loads((DATA / "config" / "gateway.json").read_text(encoding="utf-8")) == {"max_calls": 3}
|
|
162
|
+
# A checkout already pointing elsewhere, or BENCHWARMERS_HOME set: left alone, and said so.
|
|
163
|
+
init.POINTER.write_text("elsewhere\n", encoding="utf-8")
|
|
164
|
+
os.environ.pop("BENCHWARMERS_HOME")
|
|
165
|
+
ok, said = init.run(TMP / "other")
|
|
166
|
+
assert ok and "left so" in said[1] and init.POINTER.read_text(encoding="utf-8") == "elsewhere\n", said
|
|
167
|
+
assert (TMP / "other" / "config" / "model-roles.json").exists()
|
|
168
|
+
os.environ["BENCHWARMERS_HOME"] = str(TMP / "mine")
|
|
169
|
+
ok, said = init.run(TMP / "third")
|
|
170
|
+
assert ok and "which wins" in said[1] and init.POINTER.read_text(encoding="utf-8") == "elsewhere\n", said
|
|
171
|
+
finally:
|
|
172
|
+
provider.shutdown()
|
|
173
|
+
dash.shutdown()
|
|
174
|
+
shutil.rmtree(TMP, ignore_errors=True)
|
|
175
|
+
print("ok")
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
sys.exit(main())
|