diff --git a/tests/docker/__init__.py b/tests/docker/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/docker/conftest.py b/tests/docker/conftest.py new file mode 100644 index 00000000000..ce821797c76 --- /dev/null +++ b/tests/docker/conftest.py @@ -0,0 +1,79 @@ +"""Shared fixtures for docker-image integration tests. + +Tests in this directory build the image with the current ``Dockerfile`` +and exercise it via ``docker run``. They skip when Docker is unavailable +(e.g. on developer laptops without a daemon). + +Override the image with ``HERMES_TEST_IMAGE`` env var to point at a pre-built +image (faster local iteration); otherwise the ``built_image`` fixture builds +the repo's Dockerfile once per session. +""" +from __future__ import annotations + +import os +import shutil +import subprocess +from collections.abc import Iterator + +import pytest + +IMAGE_TAG = os.environ.get("HERMES_TEST_IMAGE", "hermes-agent-harness:latest") + + +def _docker_available() -> bool: + """Return True iff a docker CLI is on PATH and the daemon answers.""" + if shutil.which("docker") is None: + return False + try: + r = subprocess.run( + ["docker", "info"], capture_output=True, timeout=5, + ) + return r.returncode == 0 + except (subprocess.TimeoutExpired, OSError): + return False + + +def pytest_collection_modifyitems(config, items): # noqa: D401 - pytest hook + """Skip every test under tests/docker/ when docker is unavailable.""" + if _docker_available(): + return + skip_docker = pytest.mark.skip( + reason="Docker not available or daemon not running", + ) + for item in items: + if "tests/docker/" in str(item.fspath).replace(os.sep, "/"): + item.add_marker(skip_docker) + + +@pytest.fixture(scope="session") +def built_image() -> str: + """Build the image once per test session. + + Override with ``HERMES_TEST_IMAGE`` env var to point at a pre-built + image (faster local iteration). + """ + if os.environ.get("HERMES_TEST_IMAGE"): + return IMAGE_TAG + repo_root = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", ".."), + ) + result = subprocess.run( + ["docker", "build", "-t", IMAGE_TAG, repo_root], + capture_output=True, text=True, timeout=1200, + ) + assert result.returncode == 0, ( + f"docker build failed:\n{result.stderr[-2000:]}" + ) + return IMAGE_TAG + + +@pytest.fixture +def container_name(request) -> Iterator[str]: + """Generate a unique container name and ensure cleanup on test exit.""" + safe = request.node.name.replace("[", "_").replace("]", "_") + name = f"hermes-test-{safe}" + yield name + subprocess.run( + ["docker", "rm", "-f", name], + capture_output=True, timeout=10, + )