Files
jundot--omlx/tests/test_status_endpoint.py
wehub-resource-sync e9a2f726c9
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:29:51 +08:00

263 lines
9.7 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Tests for GET /api/status endpoint."""
from unittest.mock import MagicMock, patch
import pytest
from fastapi.testclient import TestClient
from omlx.server import ServerState, app
@pytest.fixture
def client():
return TestClient(app)
class TestStatusEndpoint:
"""Tests for /api/status lightweight status endpoint."""
@pytest.fixture(autouse=True)
def setup_server_state(self):
"""Set up a clean server state for each test."""
state = ServerState()
with patch("omlx.server._server_state", state):
self._state = state
yield
def test_returns_ok_when_pool_is_none(self, client):
"""When engine pool is not initialized, return basic status."""
resp = client.get("/api/status")
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "ok"
assert data["models_discovered"] == 0
assert data["models_loaded"] == 0
assert data["models_loading"] == 0
assert data["loaded_models"] == []
assert data["active_requests"] == 0
assert data["waiting_requests"] == 0
assert "version" in data
assert "uptime_seconds" in data
def test_returns_pool_info(self, client):
"""When engine pool exists, return model and memory stats."""
pool = MagicMock(spec=[
"model_count", "loaded_model_count", "get_loaded_model_ids",
"current_model_memory", "_entries",
])
pool.model_count = 5
pool.loaded_model_count = 2
pool.get_loaded_model_ids.return_value = ["model-a", "model-b"]
pool.current_model_memory = 16 * 1024**3
enforcer = MagicMock(spec=["get_final_ceiling"])
enforcer.get_final_ceiling.return_value = 32 * 1024**3
self._state.process_memory_enforcer = enforcer
entry_a = MagicMock(spec=["is_loading", "engine"])
entry_a.is_loading = False
entry_a.engine = None
entry_b = MagicMock(spec=["is_loading", "engine"])
entry_b.is_loading = True
entry_b.engine = None
pool._entries = {"model-a": entry_a, "model-b": entry_b}
self._state.engine_pool = pool
resp = client.get("/api/status")
assert resp.status_code == 200
data = resp.json()
assert data["models_discovered"] == 5
assert data["models_loaded"] == 2
assert data["models_loading"] == 1
assert data["loaded_models"] == ["model-a", "model-b"]
assert data["model_memory_used"] == 16 * 1024**3
assert data["model_memory_max"] == 32 * 1024**3
assert "GB" in data["model_memory_used_formatted"]
assert "GB" in data["model_memory_max_formatted"]
def test_status_ignores_memory_ceiling_error(self, client):
"""Memory telemetry failures should not break status polling."""
pool = MagicMock(spec=[
"model_count", "loaded_model_count", "get_loaded_model_ids",
"current_model_memory", "_entries",
])
pool.model_count = 1
pool.loaded_model_count = 1
pool.get_loaded_model_ids.return_value = ["model-a"]
pool.current_model_memory = 16 * 1024**3
pool._entries = {}
enforcer = MagicMock(spec=["get_final_ceiling"])
enforcer.get_final_ceiling.side_effect = RuntimeError(
"host_statistics64 failed"
)
self._state.engine_pool = pool
self._state.process_memory_enforcer = enforcer
resp = client.get("/api/status")
assert resp.status_code == 200
data = resp.json()
assert data["model_memory_max"] is None
assert data["model_memory_max_formatted"] == "unlimited"
def test_health_ignores_memory_ceiling_error(self, client):
"""Health should stay healthy when optional memory telemetry fails."""
pool = MagicMock(spec=[
"model_count", "loaded_model_count", "current_model_memory",
])
pool.model_count = 1
pool.loaded_model_count = 1
pool.current_model_memory = 16 * 1024**3
enforcer = MagicMock(spec=["get_final_ceiling"])
enforcer.get_final_ceiling.side_effect = RuntimeError(
"host_statistics64 failed"
)
self._state.engine_pool = pool
self._state.process_memory_enforcer = enforcer
resp = client.get("/health")
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "healthy"
assert data["engine_pool"]["final_ceiling"] == 0
def test_aggregates_active_waiting_requests(self, client):
"""Active/waiting request counts are summed across loaded engines."""
# Build a mock engine with scheduler
scheduler = MagicMock(spec=["waiting"])
scheduler.waiting = [1, 2] # 2 waiting
core = MagicMock(spec=["_output_collectors", "scheduler"])
core._output_collectors = {"req-1": None, "req-2": None, "req-3": None}
core.scheduler = scheduler
async_core = MagicMock(spec=["engine"])
async_core.engine = core
engine = MagicMock(spec=["_engine"])
engine._engine = async_core
entry = MagicMock(spec=["is_loading", "engine"])
entry.is_loading = False
entry.engine = engine
pool = MagicMock(spec=[
"model_count", "loaded_model_count", "get_loaded_model_ids",
"current_model_memory", "_entries",
])
pool.model_count = 1
pool.loaded_model_count = 1
pool.get_loaded_model_ids.return_value = ["model-a"]
pool.current_model_memory = 0
pool._entries = {"model-a": entry}
self._state.engine_pool = pool
resp = client.get("/api/status")
assert resp.status_code == 200
data = resp.json()
assert data["active_requests"] == 3
assert data["waiting_requests"] == 2
def test_requires_auth_when_api_key_set(self, client):
"""The endpoint should require an API key when one is configured."""
self._state.api_key = "test-secret-key"
resp = client.get("/api/status")
assert resp.status_code == 401
resp = client.get(
"/api/status",
headers={"Authorization": "Bearer test-secret-key"},
)
assert resp.status_code == 200
def test_serving_metrics_included(self, client):
"""Check that serving metrics from ServerMetrics are present."""
resp = client.get("/api/status")
data = resp.json()
expected_keys = [
"total_requests", "total_prompt_tokens", "total_completion_tokens",
"total_cached_tokens", "cache_efficiency",
"avg_prefill_tps", "avg_generation_tps",
]
for key in expected_keys:
assert key in data, f"Missing key: {key}"
def test_unlimited_memory_max(self, client):
"""When no enforcer is present, formatted shows 'unlimited'."""
pool = MagicMock(spec=[
"model_count", "loaded_model_count", "get_loaded_model_ids",
"current_model_memory", "_entries",
])
pool.model_count = 0
pool.loaded_model_count = 0
pool.get_loaded_model_ids.return_value = []
pool.current_model_memory = 0
pool._entries = {}
self._state.process_memory_enforcer = None
self._state.engine_pool = pool
resp = client.get("/api/status")
data = resp.json()
assert data["model_memory_max"] is None
assert data["model_memory_max_formatted"] == "unlimited"
class TestStatusCustomKernels:
"""/api/status reports native custom kernel availability.
Diagnosability for silently-degraded source installs: without the
native extensions the affected model families fall back to much slower
generic paths (issue #2137), and this block makes that detectable by
external polling instead of only a log line.
"""
@pytest.fixture(autouse=True)
def setup_server_state(self):
state = ServerState()
with patch("omlx.server._server_state", state):
yield
def test_custom_kernels_block_lists_every_package(self, client):
from omlx.custom_kernels import NATIVE_KERNEL_PACKAGES
resp = client.get("/api/status")
assert resp.status_code == 200
kernels = resp.json()["custom_kernels"]
assert set(kernels) == set(NATIVE_KERNEL_PACKAGES)
for report in kernels.values():
assert set(report) == {"available", "import_error"}
assert isinstance(report["available"], bool)
assert report["import_error"] is None or isinstance(
report["import_error"], str
)
def test_available_packages_report_no_import_error(self, client):
resp = client.get("/api/status")
for name, report in resp.json()["custom_kernels"].items():
if report["available"]:
assert report["import_error"] is None, name
else:
assert report["import_error"], name
def test_native_kernel_status_never_raises_on_broken_package(self):
from omlx import custom_kernels
real_import = custom_kernels.importlib.import_module
def broken_import(name, *args, **kwargs):
if name.endswith(".fast"):
raise RuntimeError("simulated native import explosion")
return real_import(name, *args, **kwargs)
with patch.object(
custom_kernels.importlib, "import_module", side_effect=broken_import
):
status = custom_kernels.native_kernel_status()
for name, report in status.items():
assert report["available"] is False, name
assert "simulated native import explosion" in report["import_error"]