diff --git a/src/flext_tests/_constants/docker.py b/src/flext_tests/_constants/docker.py index 124d088..b6d0a0e 100644 --- a/src/flext_tests/_constants/docker.py +++ b/src/flext_tests/_constants/docker.py @@ -23,6 +23,10 @@ class FlextTestsConstantsDocker: ENV_CI: Final[str] = "CI" CI_MAKE_VALUE: Final[str] = "Y" DOCKER_CI_SKIP_REASON: Final[str] = "docker disabled under CI=Y" + DOCKER_CONNECTIVITY_MARKER: Final[str] = "docker" + DOCKER_UNREACHABLE_SKIP_REASON: Final[str] = ( + "Docker daemon unreachable; start it to run Docker integration tests" + ) # Default probe ceiling for callers that omit max_wait. Under CI=Y the # Docker lifecycle skips before probing. Outside CI, shared-container # startup_timeout remains the SSOT for long boots (Oracle/kind). @@ -38,6 +42,10 @@ class FlextTestsConstantsDocker: "ldap": "flext-openldap-test", "kubernetes": "flext-kind-test", } + CONNECTIVITY_MARKERS: Final[tuple[str, ...]] = ( + DOCKER_CONNECTIVITY_MARKER, + *CONNECTIVITY_MARKER_CONTAINERS, + ) UNREACHABLE_SKIP_REASON: Final[str] = ( "{marker} service unreachable at {host}:{port}; start it to run these tests" ) diff --git a/src/flext_tests/_fixtures/connectivity.py b/src/flext_tests/_fixtures/connectivity.py index 60b7e98..d22a696 100644 --- a/src/flext_tests/_fixtures/connectivity.py +++ b/src/flext_tests/_fixtures/connectivity.py @@ -45,6 +45,19 @@ def _unreachable_reason(marker: str) -> str | None: """Return a skip reason when the marker's service cannot be reached.""" if marker in _probe_cache: return _probe_cache[marker] + if marker == c.Tests.DOCKER_CONNECTIVITY_MARKER: + from flext_tests.docker import FlextTestsDocker + + manager = FlextTestsDocker() + client = manager.client + docker_reason: str | None + if client is None: + docker_reason = c.Tests.DOCKER_UNREACHABLE_SKIP_REASON + else: + client.close() + docker_reason = None + _probe_cache[marker] = docker_reason + return docker_reason reason: str | None = None container = c.Tests.CONNECTIVITY_MARKER_CONTAINERS.get(marker) endpoint = None if container is None else _endpoint(container) @@ -69,7 +82,7 @@ def pytest_collection_modifyitems( """Mark connectivity-bound tests as skipped when their service is down.""" del config for item in items: - for marker in c.Tests.CONNECTIVITY_MARKER_CONTAINERS: + for marker in c.Tests.CONNECTIVITY_MARKERS: if item.get_closest_marker(marker) is None: continue reason = _unreachable_reason(marker) diff --git a/src/flext_tests/docker.py b/src/flext_tests/docker.py index 9179261..ec3c999 100644 --- a/src/flext_tests/docker.py +++ b/src/flext_tests/docker.py @@ -12,7 +12,9 @@ import pytest from docker import DockerClient as DockerSDKClient, from_env as docker_from_env +from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import DockerException, NotFound +from docker.transport import UnixHTTPAdapter from python_on_whales import DockerClient as WhalesDockerClient from python_on_whales.exceptions import DockerException as WhalesDockerException @@ -130,15 +132,27 @@ def model_post_init(self, __context: t.JsonValue | None, /) -> None: @property def client(self) -> DockerSDKClient | None: """Docker client with lazy initialization.""" - if self.docker_client is None: + if self.docker_client is None and self.client_error is None: + client: DockerSDKClient | None = None try: - self.docker_client = docker_from_env() - self.client_error = None + client = docker_from_env(version=DEFAULT_DOCKER_API_VERSION) + adapter = client.api.get_adapter(client.api.base_url) + if ( + isinstance(adapter, UnixHTTPAdapter) + and not Path(adapter.socket_path).exists() + ): + raise FileNotFoundError(adapter.socket_path) + _ = client.ping() except (DockerException, OSError, TypeError, ValueError) as error: + if client is not None: + client.close() self.logger.exception( "Failed to initialize Docker client", error=str(error) ) self.client_error = str(error) + else: + self.docker_client = client + self.client_error = None return self.docker_client @property