Files
hao-ai-lab-FastVideo/apps/dreamverse/server/tests/test_gpu_pool.py
T
SolitaryThinker aef113a861 [fix] dreamverse-monorepo: carry generic-merged files product-local
Source: Dreamverse repo at will/integrate-public-fastvideo @ ec8ef92.\n\nCarry the generic-merged backend modules as Dreamverse product-local code because the public streaming APIs are not drop-in compatible with the product runtime yet. Revert the Phase 2 public compatibility shims so FastVideo's public streaming surface returns to the pre-migration state.\n\nCo-authored-by: Junda (David) Su <90978028+Davids048@users.noreply.github.com>\nCo-authored-by: Matthew Noto <99706358+RandNMR73@users.noreply.github.com>\nCo-authored-by: XOR-op <17672363+XOR-op@users.noreply.github.com>\nCo-authored-by: Zhang Peiyuan <42993249+jzhang38@users.noreply.github.com>
2026-05-07 13:37:25 -07:00

128 lines
3.9 KiB
Python

# pyright: reportAttributeAccessIssue=false
from __future__ import annotations
import asyncio
import multiprocessing as mp
import sys
from pathlib import Path
from types import SimpleNamespace
import pytest
SERVER_DIR = Path(__file__).resolve().parent
if str(SERVER_DIR) not in sys.path:
sys.path.insert(0, str(SERVER_DIR))
import gpu_pool
def _child_consume_and_exit(cmd_q, resp_q):
"""Top-level so the spawn context can pickle it.
Signals startup by putting "READY" on resp_q (so the parent can
wait out spawn-import latency separately from the actual test
assertion), then consumes one command from cmd_q and exits without
putting anything else on resp_q. Simulates a worker that dies
mid-command — e.g. SIGQUIT'd after a fatal pipeline error.
"""
resp_q.put("READY")
cmd_q.get()
def test_get_available_gpus_defaults_to_single_detected_gpu(monkeypatch):
monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False)
monkeypatch.delenv("FASTVIDEO_GPU_COUNT", raising=False)
def fake_run(*args, **kwargs):
del args, kwargs
return SimpleNamespace(returncode=0, stdout="0\n1\n2\n")
monkeypatch.setattr(gpu_pool.subprocess, "run", fake_run)
assert gpu_pool.get_available_gpus() == [0]
def test_get_available_gpus_respects_explicit_gpu_count(monkeypatch):
monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False)
monkeypatch.setenv("FASTVIDEO_GPU_COUNT", "2")
def fake_run(*args, **kwargs):
del args, kwargs
return SimpleNamespace(returncode=0, stdout="0\n1\n2\n")
monkeypatch.setattr(gpu_pool.subprocess, "run", fake_run)
assert gpu_pool.get_available_gpus() == [0, 1]
def test_get_available_gpus_can_use_all_visible_devices(monkeypatch):
monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "3,5,7")
monkeypatch.setenv("FASTVIDEO_GPU_COUNT", "all")
assert gpu_pool.get_available_gpus() == [3, 5, 7]
def test_get_available_gpus_defaults_to_first_visible_device(monkeypatch):
monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "3,5,7")
monkeypatch.delenv("FASTVIDEO_GPU_COUNT", raising=False)
assert gpu_pool.get_available_gpus() == [3]
def test_get_available_gpus_rejects_invalid_gpu_count(monkeypatch):
monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False)
monkeypatch.setenv("FASTVIDEO_GPU_COUNT", "zero")
with pytest.raises(RuntimeError, match="Invalid FASTVIDEO_GPU_COUNT"):
gpu_pool.get_available_gpus()
def test_send_command_raises_on_worker_death():
"""A worker that consumes a command and exits without replying must
surface as RuntimeError via sentinel detection, not after the long
queue timeout.
The child signals startup with a "READY" message; we drain that
first so the wait_for budget bounds only the sentinel-detection
time, not spawn-import latency.
"""
ctx = mp.get_context("spawn")
cmd_q = ctx.Queue()
resp_q = ctx.Queue()
proc = ctx.Process(
target=_child_consume_and_exit, args=(cmd_q, resp_q)
)
proc.start()
# Wait for the spawn child to fully boot. Allow generous time —
# this isn't what we're measuring.
ready = resp_q.get(timeout=30.0)
assert ready == "READY"
async def runner():
slot = gpu_pool.GPUSlot(gpu_id=0, cuda_device="0")
slot.process = proc
slot.command_queue = cmd_q
slot.response_queue = resp_q
# Now the child is blocked in cmd_q.get(). Sending the cmd
# makes it exit ~immediately; sentinel must fire well before
# the queue timeout.
await asyncio.wait_for(
slot._send_command(
gpu_pool.Command(gpu_pool.CommandType.SHUTDOWN),
timeout=10.0,
),
timeout=5.0,
)
try:
with pytest.raises(RuntimeError, match="worker died"):
asyncio.run(runner())
finally:
proc.join(timeout=5)
cmd_q.close()
resp_q.close()