CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
173 lines
4.9 KiB
Python
173 lines
4.9 KiB
Python
"""Shared pytest fixtures and helpers for RDC CI tests."""
|
|
|
|
import os
|
|
import signal
|
|
import shutil
|
|
import subprocess
|
|
import time
|
|
|
|
import pytest
|
|
|
|
|
|
def _find_binary(name):
|
|
"""Locate an RDC binary, checking ROCM_DIR first, then PATH."""
|
|
rocm = os.environ.get("ROCM_DIR", "/opt/rocm")
|
|
candidate = os.path.join(rocm, "bin", name)
|
|
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
return candidate
|
|
found = shutil.which(name)
|
|
if found:
|
|
return found
|
|
return None
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def rocm_dir():
|
|
return os.environ.get("ROCM_DIR", "/opt/rocm")
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def rdcd_path(rocm_dir):
|
|
path = _find_binary("rdcd")
|
|
if path is None:
|
|
pytest.skip("rdcd binary not found")
|
|
return path
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def rdci_path(rocm_dir):
|
|
path = _find_binary("rdci")
|
|
if path is None:
|
|
pytest.skip("rdci binary not found")
|
|
return path
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def rdctst_path(rocm_dir):
|
|
"""Locate the rdctst GoogleTest binary."""
|
|
candidates = [
|
|
os.path.join(rocm_dir, "share", "rdc", "rdctst"),
|
|
os.path.join(rocm_dir, "share", "rdc", "rdctst_tests", "rdctst"),
|
|
]
|
|
for c in candidates:
|
|
if os.path.isfile(c) and os.access(c, os.X_OK):
|
|
return c
|
|
path = _find_binary("rdctst")
|
|
if path is None:
|
|
pytest.skip("rdctst binary not found")
|
|
return path
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def has_gpu():
|
|
"""Return True if at least one AMD GPU is accessible."""
|
|
try:
|
|
if not os.access("/dev/kfd", os.R_OK | os.W_OK):
|
|
return False
|
|
|
|
drm_dir = "/sys/class/drm"
|
|
device_dir = "/dev/dri"
|
|
if not any(
|
|
entry.startswith("renderD") # codespell:ignore renderd
|
|
and os.access(os.path.join(device_dir, entry), os.R_OK | os.W_OK)
|
|
for entry in os.listdir(device_dir)
|
|
):
|
|
return False
|
|
|
|
for entry in os.listdir(drm_dir):
|
|
suffix = entry.removeprefix("card")
|
|
if not entry.startswith("card") or not suffix.isdigit():
|
|
continue
|
|
vendor_path = os.path.join(drm_dir, entry, "device", "vendor")
|
|
if os.path.isfile(vendor_path):
|
|
with open(vendor_path) as f:
|
|
if f.read().strip().lower() == "0x1002":
|
|
return True
|
|
except Exception:
|
|
pass
|
|
return False
|
|
|
|
|
|
def _wait_for_port(port, timeout=10):
|
|
"""Block until something is listening on *port* or timeout expires."""
|
|
import socket
|
|
|
|
deadline = time.monotonic() + timeout
|
|
while time.monotonic() < deadline:
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.settimeout(1)
|
|
if s.connect_ex(("localhost", port)) == 0:
|
|
return True
|
|
time.sleep(0.3)
|
|
return False
|
|
|
|
|
|
def terminate_process(proc, timeout=10):
|
|
"""Terminate a child process without masking an earlier test failure."""
|
|
if proc is None or proc.poll() is not None:
|
|
return
|
|
try:
|
|
proc.send_signal(signal.SIGTERM)
|
|
except ProcessLookupError:
|
|
return
|
|
try:
|
|
proc.wait(timeout=timeout)
|
|
except subprocess.TimeoutExpired:
|
|
if proc.poll() is None:
|
|
try:
|
|
proc.kill()
|
|
except ProcessLookupError:
|
|
return
|
|
proc.wait()
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def rdcd_server(rdcd_path, has_gpu):
|
|
"""Start rdcd in unauthenticated mode for the test session.
|
|
|
|
Yields the subprocess.Popen handle. Cleans up on teardown.
|
|
Skips if no GPU is present (daemon won't be useful).
|
|
"""
|
|
if not has_gpu:
|
|
pytest.skip("No AMD GPU detected -- skipping daemon tests")
|
|
|
|
env = os.environ.copy()
|
|
proc = subprocess.Popen(
|
|
[rdcd_path, "-u"], # unauthenticated mode
|
|
env=env,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
|
|
if not _wait_for_port(50051, timeout=15):
|
|
terminate_process(proc)
|
|
pytest.fail("rdcd did not start within 15 s")
|
|
|
|
yield proc
|
|
|
|
terminate_process(proc)
|
|
|
|
|
|
def run_rdci(rdci_path, *args, timeout=30):
|
|
"""Run rdci with the given subsystem arguments and return CompletedProcess."""
|
|
if not args:
|
|
raise ValueError("an RDC subsystem is required")
|
|
subsystem, *subsystem_args = args
|
|
cmd = [rdci_path, subsystem]
|
|
if "-u" not in subsystem_args and "--unauth" not in subsystem_args:
|
|
cmd.append("-u")
|
|
cmd.extend(subsystem_args)
|
|
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def python_binding_dir():
|
|
"""Return path to python_binding/ directory."""
|
|
base = os.environ.get(
|
|
"RDC_SRC_DIR", os.path.join(os.path.dirname(__file__), "..", "..", "python_binding")
|
|
)
|
|
base = os.path.realpath(base)
|
|
if not os.path.isdir(base):
|
|
pytest.skip("python_binding directory not found")
|
|
return base
|