diff --git a/backend/app/services/status_checker.py b/backend/app/services/status_checker.py index 7f150e8..4f6ba75 100644 --- a/backend/app/services/status_checker.py +++ b/backend/app/services/status_checker.py @@ -60,8 +60,14 @@ async def check_node(check_method: str, target: str | None, ip: str | None) -> d async def _ping(host: str) -> bool: + # ping(8) -W flag units differ by OS: + # Linux: seconds (-W 1 = 1s) + # macOS: milliseconds (-W 1 = 1ms — fails for any RTT >1ms) + # Windows: -w in ms (-w 1000 = 1s) if sys.platform == "win32": args = ["ping", "-n", "1", "-w", "1000", host] + elif sys.platform == "darwin": + args = ["ping", "-c", "1", "-W", "1000", host] else: args = ["ping", "-c", "1", "-W", "1", host] proc = await asyncio.create_subprocess_exec( diff --git a/backend/tests/test_status_checker.py b/backend/tests/test_status_checker.py index 3033127..3d23cd8 100644 --- a/backend/tests/test_status_checker.py +++ b/backend/tests/test_status_checker.py @@ -169,6 +169,31 @@ async def test_ping_uses_unix_args_on_non_windows(): assert "-c" in captured["args"] assert "-W" in captured["args"] assert "-n" not in captured["args"] + # Linux: -W is in seconds; 1s is the intended timeout + w_idx = captured["args"].index("-W") + assert captured["args"][w_idx + 1] == "1" + + +@pytest.mark.asyncio +async def test_ping_uses_macos_millisecond_timeout(): + """macOS ping(8) -W is milliseconds, not seconds. 1ms would fail any RTT >1ms.""" + captured = {} + + async def fake_exec(*args, **kwargs): + captured["args"] = args + proc = MagicMock() + proc.returncode = 0 + proc.wait = AsyncMock() + return proc + + with patch("app.services.status_checker.sys.platform", "darwin"), \ + patch("asyncio.create_subprocess_exec", side_effect=fake_exec): + await _ping("192.168.1.1") + + assert "-c" in captured["args"] + assert "-W" in captured["args"] + w_idx = captured["args"].index("-W") + assert captured["args"][w_idx + 1] == "1000" @pytest.mark.asyncio