From fed8fc66dd517c9cd18db2e7800db47691d709c4 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 15 Aug 2026 09:37:17 -0700 Subject: [PATCH] test(unraid): add in-host validation runner --- .../app/tail2/validate_runtime.py | 41 +++++++++++++++++++ servers/unraid/tail2-pose-server/compose.yaml | 25 +++++++++++ .../tail2-pose-server/validation/.gitignore | 2 + 3 files changed, 68 insertions(+) create mode 100644 servers/unraid/tail2-pose-server/app/tail2/validate_runtime.py create mode 100644 servers/unraid/tail2-pose-server/validation/.gitignore diff --git a/servers/unraid/tail2-pose-server/app/tail2/validate_runtime.py b/servers/unraid/tail2-pose-server/app/tail2/validate_runtime.py new file mode 100644 index 0000000..a700176 --- /dev/null +++ b/servers/unraid/tail2-pose-server/app/tail2/validate_runtime.py @@ -0,0 +1,41 @@ +import json +import pathlib +import time +import urllib.error +import urllib.request + +from websockets.sync.client import connect + +token = pathlib.Path("/run/secrets/api-token").read_text().strip() +headers = {"Authorization": "Bearer " + token} + + +def get(path): + req = urllib.request.Request("http://127.0.0.1:18120" + path, headers=headers) + with urllib.request.urlopen(req, timeout=10) as response: + return json.load(response) + + +health = get("/v1/health") +capabilities = get("/v1/capabilities") +with connect("ws://127.0.0.1:18120/v1/poses", additional_headers=headers, open_timeout=10) as ws: + messages = [json.loads(ws.recv(timeout=20)) for _ in range(10)] + +frame_ids = [m["frame_id"] for m in messages] +summary = { + "tested_at_unix_ns": time.time_ns(), + "health": health, + "capabilities": capabilities, + "websocket": { + "messages": len(messages), + "frame_ids": frame_ids, + "strictly_increasing": all(b > a for a, b in zip(frame_ids, frame_ids[1:])), + "source_alive_all": all(m["source_alive"] for m in messages), + "people_counts": [len(m["people"]) for m in messages], + "coordinate_space_all": all(m["frame"]["coordinate_space"] == "ndi_buffer_unmodified" for m in messages), + "horizontal_flip_all_false": all(not m["frame"]["horizontal_flip_applied"] for m in messages), + "source_to_sent_ms": [round(m["timing"]["source_to_sent_ms"], 3) for m in messages], + }, +} +print(json.dumps(summary, ensure_ascii=False, indent=2)) + diff --git a/servers/unraid/tail2-pose-server/compose.yaml b/servers/unraid/tail2-pose-server/compose.yaml index 3171511..6a21bf4 100644 --- a/servers/unraid/tail2-pose-server/compose.yaml +++ b/servers/unraid/tail2-pose-server/compose.yaml @@ -96,3 +96,28 @@ services: retries: 6 start_period: 180s + validate: + image: tail2-pose-server:0.1.0 + container_name: tail2-pose-validate + network_mode: host + gpus: all + restart: "no" + entrypoint: ["/bin/bash", "-lc"] + depends_on: + tail2-pose-server: + condition: service_healthy + volumes: + - /mnt/user/appdata/tail2-pose-server/app:/app:ro + - /mnt/user/appdata/tail2-pose-server/models:/models:ro + - /mnt/user/appdata/tail2-pose-server/secrets/api-token:/run/secrets/api-token:ro + - ./validation:/validation + command: + - | + set -euo pipefail + python -m pytest -q /opt/tail2-tests > /validation/unit-tests.txt 2>&1 + python /app/tail2/validate_runtime.py > /validation/runtime.json + python -m pip freeze | sort > /validation/pip-freeze.txt + find / -xdev -type f -name 'libndi.so*' -print > /validation/ndi-runtime-files.txt 2>/dev/null || true + nvidia-smi --query-gpu=name,driver_version,memory.total,memory.used,temperature.gpu --format=csv,noheader > /validation/gpu.csv + sha256sum /models/*.pth > /validation/model-sha256.txt + chmod 0666 /validation/*.txt /validation/*.json /validation/*.csv diff --git a/servers/unraid/tail2-pose-server/validation/.gitignore b/servers/unraid/tail2-pose-server/validation/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/servers/unraid/tail2-pose-server/validation/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore