"""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. The 720p clip is # deliberately not a fallback: tests should run the sanctioned sample or skip, # never silently verify against a different resolution. DEFAULT_VIDEO_CANDIDATES = ( Path("tests") / "idle_1min.mp4", Path("data") / "sample" / "s1.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: # noqa: S112 - a failing config is simply not a candidate # Probe: a config that cannot be loaded or lacks the key is simply not # a candidate. Any such failure is reported if no config survives. 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