From e87244e39784068b4a429e00eca44b1c94253a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Thu, 23 Jan 2025 22:10:41 +0100 Subject: [PATCH] NO-JIRA: chore(tests/containers): improve docker socket detection, reducing the need for `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` --- tests/containers/conftest.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/containers/conftest.py b/tests/containers/conftest.py index 8f7efcb5b..02e8989eb 100644 --- a/tests/containers/conftest.py +++ b/tests/containers/conftest.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import os +from typing import Iterable, TYPE_CHECKING import testcontainers.core.config import testcontainers.core.container @@ -11,6 +12,9 @@ if TYPE_CHECKING: from pytest import ExitCode, Session, Parser, Metafunc +SECURITY_OPTION_ROOTLESS = "name=rootless" +TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE = "TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE" + SHUTDOWN_RYUK = False # NOTE: Configure Testcontainers through `testcontainers.core.config` and not through env variables. @@ -26,11 +30,13 @@ testcontainers.core.config.testcontainers_config.ryuk_privileged = True +# https://docs.pytest.org/en/latest/reference/reference.html#pytest.hookspec.pytest_addoption def pytest_addoption(parser: Parser) -> None: parser.addoption("--image", action="append", default=[], help="Image to use, can be specified multiple times") +# https://docs.pytest.org/en/latest/reference/reference.html#pytest.hookspec.pytest_generate_tests def pytest_generate_tests(metafunc: Metafunc) -> None: if image.__name__ in metafunc.fixturenames: metafunc.parametrize(image.__name__, metafunc.config.getoption("--image")) @@ -43,13 +49,23 @@ def image(request): yield request.param +# https://docs.pytest.org/en/latest/reference/reference.html#pytest.hookspec.pytest_sessionstart def pytest_sessionstart(session: Session) -> None: # first preflight check: ping the Docker API client = testcontainers.core.docker_client.DockerClient() assert client.client.ping(), "Failed to connect to Docker" + # determine the local socket path + # NOTE: this will not work for remote docker, but we will cross the bridge when we come to it + socket_path = the_one(adapter.socket_path for adapter in client.client.api.adapters.values()) + + # set that socket path for ryuk's use, unless user overrode that + if TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE not in os.environ: + testcontainers.core.config.testcontainers_config.ryuk_docker_socket = socket_path + # second preflight check: start the Reaper container - assert testcontainers.core.container.Reaper.get_instance() is not None, "Failed to start Reaper container" + if not testcontainers.core.config.testcontainers_config.ryuk_disabled: + assert testcontainers.core.container.Reaper.get_instance() is not None, "Failed to start Reaper container" # https://docs.pytest.org/en/latest/reference/reference.html#pytest.hookspec.pytest_sessionfinish @@ -57,3 +73,18 @@ def pytest_sessionfinish(session: Session, exitstatus: int | ExitCode) -> None: # resolves a shutdown resource leak warning that would be otherwise reported if SHUTDOWN_RYUK: testcontainers.core.container.Reaper.delete_instance() + + +# https://docs.python.org/3/library/functions.html#iter +def the_one[T](iterable: Iterable[T]) -> T: + """Checks that there is exactly one element in the iterable, and returns it.""" + it = iter(iterable) + try: + v = next(it) + except StopIteration: + raise ValueError("No elements in iterable") + try: + next(it) + except StopIteration: + return v + raise ValueError("More than one element in iterable")