DEVX-27: fix: add Docker socket diagnostics to start_docker

This commit is contained in:
emil
2026-06-24 01:59:55 +00:00
parent b9c3b55680
commit 05aa2ffe76
3 changed files with 145 additions and 17 deletions
+72 -9
View File
@@ -1,10 +1,16 @@
"""Unit tests for devx.molecule.start_docker."""
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock, mock_open, patch
from click.testing import CliRunner
from devx.molecule.start_docker import DOCKER_SOCK, is_docker_ready, main, start_docker_daemon
from devx.molecule.start_docker import (
DOCKER_SOCK,
_diagnose_socket,
is_docker_ready,
main,
start_docker_daemon,
)
class TestIsDockerReady:
@@ -22,13 +28,46 @@ class TestIsDockerReady:
assert is_docker_ready() is False
class TestDiagnoseSocket:
@patch("devx.molecule.start_docker.os.stat")
@patch("devx.molecule.start_docker.os.path.exists", return_value=True)
@patch("devx.molecule.start_docker.subprocess.run")
def test_socket_exists(self, mock_run: MagicMock, mock_exists: MagicMock, mock_stat: MagicMock) -> None:
mock_stat.return_value = MagicMock(st_mode=0o660, st_uid=0, st_gid=0)
mock_run.side_effect = [
MagicMock(stdout="/dev/sda1 /var/lib/docker ext4\n", returncode=0, text=""),
MagicMock(stdout="default\n", returncode=0, text=""),
MagicMock(
stdout="Server Version: 29.5.2\nStorage Driver: overlay2\nDocker Root Dir: /var/lib/docker\n",
returncode=0,
text="",
),
]
_diagnose_socket()
mock_exists.assert_called_with(DOCKER_SOCK)
@patch("devx.molecule.start_docker.os.path.exists", return_value=False)
@patch("devx.molecule.start_docker.subprocess.run")
def test_socket_missing(self, mock_run: MagicMock, mock_exists: MagicMock) -> None:
mock_run.side_effect = [
MagicMock(stdout="proc on /proc type proc\n", returncode=0, text=""),
MagicMock(stdout="default\n", returncode=0, text=""),
MagicMock(stdout="", stderr="Cannot connect", returncode=1, text=""),
]
_diagnose_socket()
mock_exists.assert_called_with(DOCKER_SOCK)
class TestStartDockerDaemon:
@patch("devx.molecule.start_docker._diagnose_socket")
@patch("devx.molecule.start_docker.is_docker_ready", return_value=True)
def test_host_socket_available(self, mock_ready: MagicMock) -> None:
def test_host_socket_available(self, mock_ready: MagicMock, mock_diag: MagicMock) -> None:
"""Should return immediately if host Docker is available."""
assert start_docker_daemon(timeout=5) is True
mock_ready.assert_called_once()
mock_diag.assert_called_once()
@patch("devx.molecule.start_docker._diagnose_socket")
@patch("devx.molecule.start_docker.time.sleep")
@patch("devx.molecule.start_docker.is_docker_ready")
@patch("devx.molecule.start_docker.subprocess.Popen")
@@ -39,9 +78,9 @@ class TestStartDockerDaemon:
mock_popen: MagicMock,
mock_ready: MagicMock,
mock_sleep: MagicMock,
mock_diag: MagicMock,
) -> None:
mock_ntf.return_value = MagicMock()
# Host socket not available, then local daemon starts on third check
mock_ntf.return_value = MagicMock(name="/tmp/dockerd.log")
mock_ready.side_effect = [False, False, False, True]
assert start_docker_daemon(timeout=5) is True
mock_popen.assert_called_once()
@@ -53,6 +92,7 @@ class TestStartDockerDaemon:
assert f"unix://{DOCKER_SOCK}" in popen_args
assert mock_sleep.call_count == 2
@patch("devx.molecule.start_docker._diagnose_socket")
@patch("devx.molecule.start_docker.time.sleep")
@patch("devx.molecule.start_docker.is_docker_ready", return_value=False)
@patch("devx.molecule.start_docker.subprocess.Popen")
@@ -63,12 +103,33 @@ class TestStartDockerDaemon:
mock_popen: MagicMock,
mock_ready: MagicMock,
mock_sleep: MagicMock,
mock_diag: MagicMock,
) -> None:
mock_ntf.return_value = MagicMock()
assert start_docker_daemon(timeout=3) is False
mock_ntf.return_value = MagicMock(name="/tmp/dockerd.log")
with patch("builtins.open", mock_open(read_data="dockerd error log")):
assert start_docker_daemon(timeout=3) is False
mock_popen.assert_called_once()
assert mock_sleep.call_count == 3
@patch("devx.molecule.start_docker._diagnose_socket")
@patch("devx.molecule.start_docker.time.sleep")
@patch("devx.molecule.start_docker.is_docker_ready", return_value=False)
@patch("devx.molecule.start_docker.subprocess.Popen")
@patch("devx.molecule.start_docker.tempfile.NamedTemporaryFile")
def test_fails_log_read_error(
self,
mock_ntf: MagicMock,
mock_popen: MagicMock,
mock_ready: MagicMock,
mock_sleep: MagicMock,
mock_diag: MagicMock,
) -> None:
"""Should handle log read errors gracefully."""
mock_ntf.return_value = MagicMock(name="/tmp/dockerd.log")
with patch("builtins.open", side_effect=OSError("permission denied")):
assert start_docker_daemon(timeout=2) is False
@patch("devx.molecule.start_docker._diagnose_socket")
@patch("devx.molecule.start_docker.time.sleep")
@patch("devx.molecule.start_docker.is_docker_ready")
@patch("devx.molecule.start_docker.subprocess.Popen")
@@ -79,20 +140,22 @@ class TestStartDockerDaemon:
mock_popen: MagicMock,
mock_ready: MagicMock,
mock_sleep: MagicMock,
mock_diag: MagicMock,
) -> None:
mock_ntf.return_value = MagicMock()
# Host not available, local daemon ready on first loop check
mock_ntf.return_value = MagicMock(name="/tmp/dockerd.log")
mock_ready.side_effect = [False, False, True]
assert start_docker_daemon(timeout=5) is True
mock_popen.assert_called_once()
mock_sleep.assert_called_once_with(1)
@patch("devx.molecule.start_docker._diagnose_socket")
@patch("devx.molecule.start_docker.os.environ")
@patch("devx.molecule.start_docker.is_docker_ready", return_value=True)
def test_sets_docker_host(
self,
mock_ready: MagicMock,
mock_environ: MagicMock,
mock_diag: MagicMock,
) -> None:
"""DOCKER_HOST must be set so molecule connects to correct socket."""
start_docker_daemon(timeout=5)