From 08302135b65f75c74462fd3dcd2dd2b70940454b Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 21 May 2026 12:52:51 +1000 Subject: [PATCH] test(docker): add conftest fixtures for docker harness Task 0.1 of the s6-overlay supervision plan. Establishes the test infrastructure for tests/docker/: skip-on-missing-Docker collection hook, session-scoped image-build fixture (overridable via the HERMES_TEST_IMAGE env var for faster local iteration), and a container_name fixture that ensures cleanup on test exit. Refs: docs/plans/2026-05-07-s6-overlay-dynamic-subagent-gateways.md --- tests/docker/__init__.py | 0 tests/docker/conftest.py | 79 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 tests/docker/__init__.py create mode 100644 tests/docker/conftest.py 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, + )