test: replace /bin/sleep with portable Python sleep in test_gateway_init
test / integration-firecracker (pull_request) Successful in 17s
test / integration-docker (pull_request) Successful in 18s
test / coverage (pull_request) Failing after 33s
lint / lint (push) Successful in 54s
test / unit (pull_request) Successful in 1m34s
tracker-policy-pr / check-pr (pull_request) Failing after 11m24s
test / integration-firecracker (pull_request) Successful in 17s
test / integration-docker (pull_request) Successful in 18s
test / coverage (pull_request) Failing after 33s
lint / lint (push) Successful in 54s
test / unit (pull_request) Successful in 1m34s
tracker-policy-pr / check-pr (pull_request) Failing after 11m24s
/bin/sleep does not exist at FHS paths on NixOS (the KVM self-hosted runner's OS). All 13 FileNotFoundError failures in TestSupervisor were caused by _DaemonSpec tuples spawning /bin/sleep directly. Replace with a module-level _SLEEP_30/_SLEEP_60 tuple using sys.executable so the supervisor tests run on any platform. Also fix TestMainEndToEnd._run() and remove the now-unnecessary setUpClass guard.
This commit is contained in:
@@ -2,9 +2,8 @@
|
||||
|
||||
Tests both the helper functions in `bot_bottle.gateway_init`
|
||||
and the supervisor's end-to-end signal / exit-code behavior. The
|
||||
end-to-end tests use real subprocesses (`/bin/sleep`,
|
||||
`/bin/sh -c '...'`) — short-lived, no docker required — so they
|
||||
run under `tests/unit/` rather than `tests/integration/`."""
|
||||
end-to-end tests use real subprocesses — short-lived, no docker required
|
||||
— so they run under `tests/unit/` rather than `tests/integration/`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -15,7 +14,6 @@ import sys
|
||||
import time
|
||||
import unittest
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from bot_bottle.gateway_init import (
|
||||
@@ -26,6 +24,11 @@ from bot_bottle.gateway_init import (
|
||||
_selected_daemons,
|
||||
)
|
||||
|
||||
# /bin/sleep does not exist on FHS-free systems (e.g. NixOS). Use a
|
||||
# portable Python one-liner so supervisor tests run on any platform.
|
||||
_SLEEP_30 = (sys.executable, "-c", "import time; time.sleep(30)")
|
||||
_SLEEP_60 = (sys.executable, "-c", "import time; time.sleep(60)")
|
||||
|
||||
|
||||
class TestEnvForDaemon(unittest.TestCase):
|
||||
"""Scope egress-only credential env vars to the egress daemon.
|
||||
@@ -182,7 +185,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
# up and the supervisor never set shutdown_at.
|
||||
specs = [
|
||||
_DaemonSpec("crasher", ("/bin/sh", "-c", "exit 1")),
|
||||
_DaemonSpec("longrun", ("/bin/sleep", "30")),
|
||||
_DaemonSpec("longrun", _SLEEP_30),
|
||||
]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
@@ -214,7 +217,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
# signal-killed longrun's negative returncode.
|
||||
specs = [
|
||||
_DaemonSpec("crasher", ("/bin/sh", "-c", "exit 1")),
|
||||
_DaemonSpec("longrun", ("/bin/sleep", "30")),
|
||||
_DaemonSpec("longrun", _SLEEP_30),
|
||||
]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
@@ -259,7 +262,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
)
|
||||
specs = [
|
||||
_DaemonSpec("egress", sighup_marker),
|
||||
_DaemonSpec("other", ("/bin/sleep", "30")),
|
||||
_DaemonSpec("other", _SLEEP_30),
|
||||
]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
@@ -282,7 +285,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
self._drive(sup)
|
||||
|
||||
def test_forward_signal_unknown_daemon_no_op(self):
|
||||
specs = [_DaemonSpec("a", ("/bin/sleep", "30"))]
|
||||
specs = [_DaemonSpec("a", _SLEEP_30)]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
delivered = sup.forward_signal(signal.SIGHUP, "ghost")
|
||||
@@ -294,8 +297,8 @@ class TestSupervisor(unittest.TestCase):
|
||||
# Restart one daemon; the other (supervise, the MCP server
|
||||
# in production) must remain untouched.
|
||||
specs = [
|
||||
_DaemonSpec("git-gate", ("/bin/sleep", "30")),
|
||||
_DaemonSpec("supervise", ("/bin/sleep", "30")),
|
||||
_DaemonSpec("git-gate", _SLEEP_30),
|
||||
_DaemonSpec("supervise", _SLEEP_30),
|
||||
]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
@@ -319,8 +322,8 @@ class TestSupervisor(unittest.TestCase):
|
||||
|
||||
def test_request_restart_is_drained_by_tick(self):
|
||||
specs = [
|
||||
_DaemonSpec("git-gate", ("/bin/sleep", "30")),
|
||||
_DaemonSpec("supervise", ("/bin/sleep", "30")),
|
||||
_DaemonSpec("git-gate", _SLEEP_30),
|
||||
_DaemonSpec("supervise", _SLEEP_30),
|
||||
]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
@@ -343,7 +346,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
self._drive(sup)
|
||||
|
||||
def test_repeated_restart_requests_coalesce(self):
|
||||
specs = [_DaemonSpec("git-gate", ("/bin/sleep", "30"))]
|
||||
specs = [_DaemonSpec("git-gate", _SLEEP_30)]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
time.sleep(0.1)
|
||||
@@ -366,7 +369,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
self._drive(sup)
|
||||
|
||||
def test_request_restart_unknown_daemon_no_op(self):
|
||||
specs = [_DaemonSpec("a", ("/bin/sleep", "30"))]
|
||||
specs = [_DaemonSpec("a", _SLEEP_30)]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
ok = sup.request_restart("ghost")
|
||||
@@ -376,7 +379,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
self._drive(sup)
|
||||
|
||||
def test_restart_unknown_daemon_no_op(self):
|
||||
specs = [_DaemonSpec("a", ("/bin/sleep", "30"))]
|
||||
specs = [_DaemonSpec("a", _SLEEP_30)]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
ok = sup.restart_daemon("ghost")
|
||||
@@ -385,7 +388,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
self._drive(sup)
|
||||
|
||||
def test_restart_during_shutdown_is_no_op(self):
|
||||
specs = [_DaemonSpec("git-gate", ("/bin/sleep", "30"))]
|
||||
specs = [_DaemonSpec("git-gate", _SLEEP_30)]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
sup.request_shutdown(reason="test")
|
||||
@@ -395,7 +398,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
self._drive(sup)
|
||||
|
||||
def test_pending_restart_dropped_during_shutdown(self):
|
||||
specs = [_DaemonSpec("git-gate", ("/bin/sleep", "30"))]
|
||||
specs = [_DaemonSpec("git-gate", _SLEEP_30)]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
time.sleep(0.1)
|
||||
@@ -413,8 +416,8 @@ class TestSupervisor(unittest.TestCase):
|
||||
# both should receive SIGTERM and exit. Signal-only
|
||||
# shutdown clamps to a zero supervisor exit code.
|
||||
specs = [
|
||||
_DaemonSpec("a", ("/bin/sleep", "60")),
|
||||
_DaemonSpec("b", ("/bin/sleep", "60")),
|
||||
_DaemonSpec("a", _SLEEP_60),
|
||||
_DaemonSpec("b", _SLEEP_60),
|
||||
]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
@@ -449,7 +452,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
self.assertEqual(0, rc)
|
||||
|
||||
def test_idempotent_shutdown_requests(self):
|
||||
specs = [_DaemonSpec("a", ("/bin/sleep", "60"))]
|
||||
specs = [_DaemonSpec("a", _SLEEP_60)]
|
||||
sup = _Supervisor(specs)
|
||||
sup.start_all()
|
||||
time.sleep(0.1)
|
||||
@@ -465,28 +468,22 @@ class TestSupervisor(unittest.TestCase):
|
||||
|
||||
class TestMainEndToEnd(unittest.TestCase):
|
||||
"""Run gateway_init.py as a real subprocess to cover the
|
||||
signal-handler installation path. Skipped on platforms
|
||||
without /bin/sleep + /bin/sh."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
for p in ("/bin/sh", "/bin/sleep"):
|
||||
if not Path(p).exists():
|
||||
raise unittest.SkipTest(f"missing {p}")
|
||||
signal-handler installation path."""
|
||||
|
||||
def _run(self, daemons_csv: str, send_signal: int | None,
|
||||
wait_before_signal: float = 0.4,
|
||||
overall_timeout: float = 6.0) -> tuple[int, str]:
|
||||
"""Spawn gateway_init.main() in a child process with the
|
||||
DAEMONS list patched to harmless `sleep 30` commands.
|
||||
DAEMONS list patched to harmless long-sleep commands.
|
||||
Returns (returncode, captured stdout)."""
|
||||
|
||||
helper = (
|
||||
"import os, runpy, sys\n"
|
||||
"from bot_bottle import gateway_init as si\n"
|
||||
"sleep_cmd = (sys.executable, '-c', 'import time; time.sleep(30)')\n"
|
||||
"si._DAEMONS = (\n"
|
||||
" si._DaemonSpec('alpha', ('/bin/sleep','30')),\n"
|
||||
" si._DaemonSpec('beta', ('/bin/sleep','30')),\n"
|
||||
" si._DaemonSpec('alpha', sleep_cmd),\n"
|
||||
" si._DaemonSpec('beta', sleep_cmd),\n"
|
||||
")\n"
|
||||
"sys.exit(si.main([]))\n"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user