Files
orisys-sdk/tests/conftest.py
T
2026-09-20 11:39:02 +08:00

163 lines
5.3 KiB
Python

"""Shared pytest fixtures for the orisys test suite.
This module was accidentally deleted in commit edc6e42 while four test modules
(``test_backends.py``, ``test_smoke_api.py``, ``test_errors_and_cleanup.py``,
``test_longrun_stability.py``) still depend on its fixtures, which made the
whole suite unrunnable. It is restored here with two fixes:
* the default test video now points at clips that are actually tracked in the
repository (``data/sample/``), instead of the never-committed
``tests/idle_1min.mp4``;
* the stale ``"prototype"`` default config in :func:`make_sensor` is gone, so
callers cannot silently bypass the ``stitcher.box_size`` validation.
``tests/test_harness_guards.py`` guards against a repeat deletion.
"""
import os
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
SRC_ROOT = REPO_ROOT / "src"
BUILD_ROOT = REPO_ROOT / "build"
EXAMPLES_ROOT = REPO_ROOT / "examples"
# Establish import precedence once, here, before any test module is imported. The
# test modules insert the same directories, but all of those inserts are guarded
# by `if path not in sys.path`, so once the paths are present the guards turn
# them into no-ops and this order sticks.
#
# Order matters and is not incidental:
# * examples/ must win over build/ because `build/gui` is also a package named
# `gui`; with build/ first, `import gui.viewer2d` silently resolves to the
# build tooling and fails.
# * build/ must precede site-packages so `wheel`, `common` and `release` mean
# the repository's build tooling, not the unrelated PyPI `wheel` package.
# * src/ provides the checkout's `orisys`.
# Insert in reverse priority (each insert goes to the front).
for _path in (SRC_ROOT, BUILD_ROOT, EXAMPLES_ROOT):
if str(_path) not in sys.path:
sys.path.insert(0, str(_path))
from orisys import Sensor
from orisys import configs as orisys_configs
from orisys.backend_availability import check_cupy_available, check_opencl_available
# Video clips that are tracked in git, in preference order.
DEFAULT_VIDEO_CANDIDATES = (
Path("tests") / "idle_1min.mp4",
Path("data") / "sample" / "s1.mp4",
Path("data") / "sample" / "s1_720p.mp4",
)
def _resolve_test_video() -> Path:
env_video = os.getenv("ORISYS_TEST_VIDEO")
if env_video:
raw = Path(env_video)
candidates = [
raw, # as provided (absolute or cwd-relative)
REPO_ROOT / raw, # repo-relative
]
# Common typo fallback: "test/..." -> "tests/..."
if env_video.startswith("test/") or env_video.startswith("test\\"):
fixed = Path("tests", *Path(env_video).parts[1:])
candidates.append(REPO_ROOT / fixed)
for candidate in candidates:
if candidate.exists():
return candidate
return raw
for relative in DEFAULT_VIDEO_CANDIDATES:
candidate = REPO_ROOT / relative
if candidate.is_file():
return candidate
# Keep the reported path informative when nothing is available; the
# ``test_video_path`` fixture turns this into a skip.
return REPO_ROOT / DEFAULT_VIDEO_CANDIDATES[-1]
@pytest.fixture(scope="session")
def test_video_path() -> Path:
path = _resolve_test_video()
if not path.exists():
pytest.skip(
"Test video not found. Set ORISYS_TEST_VIDEO to a valid video path."
)
return path
@pytest.fixture(scope="session")
def test_config_name() -> str:
requested = os.getenv("ORISYS_TEST_CONFIG")
def _is_usable_config(name: str) -> bool:
cfg = orisys_configs.load_config(name)
return "box_size" in cfg.get("stitcher", {})
if requested:
if not _is_usable_config(requested):
raise RuntimeError(
f"ORISYS_TEST_CONFIG='{requested}' is missing required key "
"'stitcher.box_size'. Use a config like 'ddjx01' or provide a full path."
)
return requested
# Auto-pick a stable config for tests (avoid prototype variants missing keys).
for name in orisys_configs.list_config():
try:
if _is_usable_config(name):
return name
except Exception:
continue
raise RuntimeError(
"No usable config found for tests (missing 'stitcher.box_size'). "
"Set ORISYS_TEST_CONFIG to a valid config JSON/path."
)
@pytest.fixture(scope="session")
def available_backends() -> list[str]:
backends = ["cpu"]
if check_cupy_available():
backends.append("cupy")
if check_opencl_available():
backends.append("opencl")
return backends
def make_sensor(
video_path: Path,
config_name: str,
backend: str = "cpu",
verbose: bool = False,
) -> Sensor:
use_cuda = backend in ("cupy", "opencl")
return Sensor(
vid_src=str(video_path),
cuda=use_cuda,
isstitch=True,
verbose=verbose,
config_name=config_name,
backend=backend,
)
@pytest.fixture
def sensor_factory(test_video_path, test_config_name):
def _factory(backend: str = "cpu", verbose: bool = False):
return make_sensor(
test_video_path,
config_name=test_config_name,
backend=backend,
verbose=verbose,
)
return _factory