Files
serverAppConfigs/tests/autoresearch_measure.bats
T
Frank 55f87fcdce llm: retire the trio service names — fleet services renamed to frank-archon/frank-dragoon/frank-zealot
The 2026-10-09 wire aliases become the service names everywhere:
systemd units, serve scripts, env files and FRANK_* variables,
deploy-plan/deploy-remote service keys, container names, Prometheus
job names + alert rules, Grafana dashboard queries, bats seams,
runbook filenames, homepage/AGENTS lore, and historical prose.
Rollback tags renamed in lockstep. Residue in the tree: zero.
Retired frank-medium/small/npu names are history and stay.

Known casualty: scripts/patches/*.patch context lines were rewritten;
those retired-era vLLM-fork patches no longer apply byte-exact.
2026-10-10 17:03:14 -07:00

151 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bats
# Seam: autoresearch measure client behavior.
#
# The measure client is the whole yardstick of the frank-archon speed
# session, so its METRIC arithmetic gets a behavior test: against a
# deterministic mock endpoint that emits a KNOWN number of completion
# tokens at a KNOWN per-token delay, the reported decode rate must land
# near the nominal rate, the usage accounting must come back exact, and an
# unhealthy endpoint must fail loudly (no half-measured numbers).
#
# The mock implements only what the client calls: POST
# /v1/messages/count_tokens (word count as the token count, which makes the
# client's words/token calibration come out to exactly 1.0) and a streaming
# POST /v1/chat/completions (one content chunk per completion token, then a
# usage chunk, then [DONE]).
load helpers
MEASURE="$REPO_ROOT/scripts/autoresearch/frank-archon-measure.py"
MOCK="$BATS_TEST_TMPDIR/mock.py"
PORT=""
MOCK_PID=""
setup() {
# Deterministic mock; argv: <port> <delay-seconds>; env MOCK_FAIL=1
# makes the chat endpoint answer 500.
cat >"$MOCK" <<'PYEOF'
import json, os, sys, time
from http.server import BaseHTTPRequestHandler, HTTPServer
PORT = int(sys.argv[1])
DELAY = float(sys.argv[2])
FAIL = os.environ.get("MOCK_FAIL", "0") == "1"
class Handler(BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def _send(self, obj, code=200):
b = json.dumps(obj).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(b)))
self.end_headers()
self.wfile.write(b)
def do_POST(self):
n = int(self.headers.get("Content-Length", "0"))
body = json.loads(self.rfile.read(n) or b"{}")
if self.path == "/v1/messages/count_tokens":
text = body["messages"][0]["content"]
self._send({"input_tokens": max(1, len(text.split()))})
elif self.path == "/v1/chat/completions":
if FAIL:
self._send({"error": "engine down"}, 500)
return
max_tokens = int(body.get("max_tokens", 64))
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.end_headers()
for i in range(max_tokens):
ev = {"choices": [{"delta": {"content": "tok%d " % i}}]}
self.wfile.write(("data: %s\n\n" % json.dumps(ev)).encode())
self.wfile.flush()
time.sleep(DELAY)
prompt_words = max(1, len(body["messages"][0]["content"].split()))
ev = {"choices": [{"delta": {}}],
"usage": {"prompt_tokens": prompt_words,
"completion_tokens": max_tokens}}
self.wfile.write(("data: %s\n\n" % json.dumps(ev)).encode())
self.wfile.write(b"data: [DONE]\n\n")
self.wfile.flush()
else:
self._send({"error": "unknown path"}, 404)
HTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
PYEOF
}
free_port() {
python3 -c 'import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()'
}
start_mock() { # $1 = ok|fail
PORT="$(free_port)"
if [ "$1" = "fail" ]; then
MOCK_FAIL=1 python3 "$MOCK" "$PORT" 0.005 &
else
python3 "$MOCK" "$PORT" 0.005 &
fi
MOCK_PID=$!
local i
for i in $(seq 1 100); do
if python3 -c "import socket; socket.create_connection(('127.0.0.1', $PORT), 0.2).close()" 2>/dev/null; then
return 0
fi
sleep 0.1
done
echo "mock did not start" >&2
return 1
}
stop_mock() {
if [ -n "$MOCK_PID" ]; then
kill "$MOCK_PID" 2>/dev/null || true
wait "$MOCK_PID" 2>/dev/null || true
MOCK_PID=""
fi
}
@test "measure exists and compiles" {
[ -f "$MEASURE" ]
python3 -m py_compile "$MEASURE"
}
@test "measure computes decode rate and exact usage from a controlled endpoint" {
start_mock ok || return 1
run env AR_BASE="http://127.0.0.1:$PORT" AR_MODEL=mock python3 "$MEASURE"
stop_mock
[ "$status" -eq 0 ] || { echo "$output"; return 1; }
# run() merges stderr in; the contract is: exactly ONE json result line
# (the window script parses the same way), progress on stderr.
printf '%s\n' "$output" | python3 -c '
import json, sys
lines = [l for l in sys.stdin.read().splitlines() if l.startswith("{")]
assert len(lines) == 1, "expected exactly one JSON line, got %d" % len(lines)
d = json.loads(lines[0])
# nominal 200 t/s (0.005 s/chunk) minus small per-chunk overhead
for k in ("decode_tps", "decode_tps_115", "decode_tps_1056", "decode_tps_4096"):
v = d[k]
assert 60.0 <= v <= 320.0, "%s=%s outside [60, 320]" % (k, v)
# the mock emits exactly max_tokens completion tokens; the accounting must be exact
assert d["completion_tokens_4096"] == 256, d["completion_tokens_4096"]
assert d["completion_tokens_115"] == 256
# prompt sizing: calibration is exactly 1.0 words/token in the mock, so the
# measured prompt lengths equal the targets
assert d["prompt_tokens_115"] == 115
assert d["prompt_tokens_4096"] == 4096
print("measure claims verified")
'
}
@test "measure fails loudly on an unhealthy endpoint instead of half-measuring" {
start_mock fail || return 1
run env AR_BASE="http://127.0.0.1:$PORT" AR_MODEL=mock python3 "$MEASURE"
stop_mock
[ "$status" -ne 0 ]
! printf '%s\n' "$output" | grep -qE '^\{'
}