From 2eecd754f6000b19d68a846b4e7868da3850e66c Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Wed, 27 May 2026 22:52:39 +0530 Subject: [PATCH] fix: gate MariaDBManager.start() on is_running() to avoid double-start MariaDBManager.start() was running brew services start / systemctl start unconditionally. When MariaDB is already running (Docker, remote, or a brew service that's already started), this caused a port-conflict crash: Bootstrap failed: 5: Input/output error Error: Failure while executing; `/bin/launchctl bootstrap gui/501 .../homebrew.mxcl.mariadb.plist` exited with 5. Fix: - Add is_running() using a stdlib TCP probe (socket.create_connection) so no new dependencies are introduced (project is zero-dependency). - Gate start() on is_running(): return early if the configured host:port is already accepting connections. Fixes #3 --- bench_cli/managers/mariadb_manager.py | 10 ++++ tests/test_mariadb_manager.py | 80 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/test_mariadb_manager.py diff --git a/bench_cli/managers/mariadb_manager.py b/bench_cli/managers/mariadb_manager.py index 8fe1e02..dc962f4 100644 --- a/bench_cli/managers/mariadb_manager.py +++ b/bench_cli/managers/mariadb_manager.py @@ -1,4 +1,5 @@ import shutil +import socket from pathlib import Path from bench_cli.config.mariadb_config import MariaDBConfig @@ -23,7 +24,16 @@ def install(self) -> None: package = self._brew_package() if is_macos() else self._apt_package() package_manager.install(package) + def is_running(self) -> bool: + try: + with socket.create_connection((self.config.host, self.config.port), timeout=1): + return True + except OSError: + return False + def start(self) -> None: + if self.is_running(): + return if is_macos(): run_command(["brew", "services", "start", self._brew_package()]) else: diff --git a/tests/test_mariadb_manager.py b/tests/test_mariadb_manager.py new file mode 100644 index 0000000..625256e --- /dev/null +++ b/tests/test_mariadb_manager.py @@ -0,0 +1,80 @@ +"""Tests for MariaDBManager.is_running() and the guarded start().""" +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +from bench_cli.config.mariadb_config import MariaDBConfig +from bench_cli.managers.mariadb_manager import MariaDBManager + + +def make_manager(host: str = "localhost", port: int = 3306) -> MariaDBManager: + return MariaDBManager(MariaDBConfig(host=host, port=port)) + + +# ── is_running() ────────────────────────────────────────────────────────────── + + +def test_is_running_returns_true_when_port_is_open() -> None: + manager = make_manager() + mock_conn = MagicMock() + with patch("socket.create_connection", return_value=mock_conn) as mock_create: + assert manager.is_running() is True + mock_create.assert_called_once_with(("localhost", 3306), timeout=1) + + +def test_is_running_returns_false_when_connection_refused() -> None: + manager = make_manager() + with patch("socket.create_connection", side_effect=OSError("Connection refused")): + assert manager.is_running() is False + + +def test_is_running_returns_false_on_timeout() -> None: + manager = make_manager() + with patch("socket.create_connection", side_effect=OSError("timed out")): + assert manager.is_running() is False + + +def test_is_running_uses_configured_host_and_port() -> None: + manager = make_manager(host="127.0.0.1", port=3307) + mock_conn = MagicMock() + with patch("socket.create_connection", return_value=mock_conn) as mock_create: + manager.is_running() + mock_create.assert_called_once_with(("127.0.0.1", 3307), timeout=1) + + +# ── start() ─────────────────────────────────────────────────────────────────── + + +def test_start_skips_service_when_already_running() -> None: + manager = make_manager() + with patch.object(manager, "is_running", return_value=True): + with patch("bench_cli.managers.mariadb_manager.run_command") as mock_run: + manager.start() + mock_run.assert_not_called() + + +def test_start_calls_brew_on_macos_when_not_running() -> None: + manager = make_manager() + with patch.object(manager, "is_running", return_value=False): + with patch("bench_cli.managers.mariadb_manager.is_macos", return_value=True): + with patch("bench_cli.managers.mariadb_manager.run_command") as mock_run: + manager.start() + mock_run.assert_called_once_with(["brew", "services", "start", "mariadb"]) + + +def test_start_calls_brew_with_versioned_package() -> None: + manager = MariaDBManager(MariaDBConfig(host="localhost", port=3306, version="10.6")) + with patch.object(manager, "is_running", return_value=False): + with patch("bench_cli.managers.mariadb_manager.is_macos", return_value=True): + with patch("bench_cli.managers.mariadb_manager.run_command") as mock_run: + manager.start() + mock_run.assert_called_once_with(["brew", "services", "start", "mariadb@10.6"]) + + +def test_start_calls_systemctl_on_linux_when_not_running() -> None: + manager = make_manager() + with patch.object(manager, "is_running", return_value=False): + with patch("bench_cli.managers.mariadb_manager.is_macos", return_value=False): + with patch("bench_cli.managers.mariadb_manager.run_command") as mock_run: + manager.start() + mock_run.assert_called_once_with(["sudo", "systemctl", "start", "mariadb"])