Take main's svc recover test with the shared free-port helper
Why: the initiative branch's merge resolution kept a stale test_svc_recover.py that lost main's free_fixture_port helper while its callers (test_svc_superadmin_envfile) reference it, breaking the CI test job on every initiative-branch head.
Evidence: main @33d6ef69 has the helper and a fully green CI (test/test-pg/svc-realcombo success); this branch's callers now match.
Impact: Test helper alignment only; no production code.
Refs: initiative PR #635; merge af2e6b41
This commit is contained in:
+18
-28
@@ -184,12 +184,17 @@ esac
|
||||
"""
|
||||
|
||||
|
||||
def free_fixture_port():
|
||||
with socket.socket() as reservation:
|
||||
reservation.bind(("127.0.0.1", 0))
|
||||
return reservation.getsockname()[1]
|
||||
|
||||
|
||||
class SvcAutoRecoverTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
tmp = tempfile.mkdtemp(prefix="svc-autorecover-")
|
||||
self.addCleanup(shutil.rmtree, tmp, True)
|
||||
self.root = Path(tmp)
|
||||
self._allocated_ports = set()
|
||||
(self.root / "scripts").mkdir(parents=True)
|
||||
shutil.copy2(SVC_SCRIPT, self.root / "scripts" / "svc.sh")
|
||||
self.trace = self.root / "trace"
|
||||
@@ -249,21 +254,6 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout)
|
||||
return proc
|
||||
|
||||
def available_port(self):
|
||||
"""Allocate a distinct service port for this test, including address changes.
|
||||
|
||||
The socket closes before svc.sh binds it, leaving a small allocation-to-start
|
||||
race with unrelated processes. Keep allocated numbers distinct within the
|
||||
test so stopped/restarted services never share another instance's port.
|
||||
"""
|
||||
while True:
|
||||
with socket.socket() as sock:
|
||||
sock.bind(("0.0.0.0", 0))
|
||||
port = sock.getsockname()[1]
|
||||
if port not in self._allocated_ports:
|
||||
self._allocated_ports.add(port)
|
||||
return port
|
||||
|
||||
def runtime(self, name):
|
||||
return self.root / "runtime" / name
|
||||
|
||||
@@ -346,14 +336,14 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
# ---- 1. 期望态:start 成功置位 / 显式 stop 清除(含进程已死的 stop)----
|
||||
|
||||
def test_start_records_autostart_and_stop_clears(self):
|
||||
self.seed_agent("dev", self.available_port())
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
self.assertEqual(self.kv("dev", "autostart"), "1")
|
||||
self.svc("dev", "stop")
|
||||
self.assertIsNone(self.kv("dev", "autostart"))
|
||||
|
||||
def test_stop_on_dead_process_clears_autostart(self):
|
||||
self.seed_agent("dev", self.available_port())
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
self.kill9("dev")
|
||||
# 进程已死但 autostart 保留(等待 recover);显式 stop 表示"保持停止"。
|
||||
@@ -364,7 +354,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
# ---- 2. recover:崩溃拉回 / 运行中不动 / 显式停止不复活 / 未启动不碰 ----
|
||||
|
||||
def test_recover_restarts_sigkilled_service(self):
|
||||
port = self.available_port()
|
||||
port = free_fixture_port()
|
||||
self.seed_agent("dev", port)
|
||||
self.start_ok("dev")
|
||||
old_pid = self.kill9("dev")
|
||||
@@ -381,7 +371,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
self.assertIn("--addr 127.0.0.1:%d" % port, self.kv("dev", "exec"))
|
||||
|
||||
def test_recover_noop_while_running(self):
|
||||
self.seed_agent("dev", self.available_port())
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
pid = self.pid_of("dev")
|
||||
result = self.svc("recover")
|
||||
@@ -390,7 +380,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
self.assertEqual(self.history_events("dev", "recover"), [])
|
||||
|
||||
def test_recover_respects_explicit_stop(self):
|
||||
self.seed_agent("dev", self.available_port())
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
self.svc("dev", "stop")
|
||||
result = self.svc("recover")
|
||||
@@ -399,7 +389,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
self.assertEqual(self.history_events("dev", "recover"), [])
|
||||
|
||||
def test_recover_ignores_never_started_service(self):
|
||||
self.seed_agent("dev", self.available_port()) # 从未 start:无 autostart 键
|
||||
self.seed_agent("dev", free_fixture_port()) # 从未 start:无 autostart 键
|
||||
result = self.svc("recover")
|
||||
self.assertEqual(result.returncode, 0, result.stdout)
|
||||
self.assertFalse(self.pid_alive(self.pid_of("dev")))
|
||||
@@ -408,7 +398,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
# ---- 3. 并发防护:生命周期锁被持有时 recover 跳过 ----
|
||||
|
||||
def test_recover_skips_when_lock_held(self):
|
||||
self.seed_agent("dev", self.available_port())
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
self.kill9("dev")
|
||||
lock = self.runtime("dev") / ".start.lock"
|
||||
@@ -428,7 +418,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
# ---- 4. 恢复失败路径:端口被外部进程占用 → fail 事件 + 非零退出 ----
|
||||
|
||||
def test_recover_fails_when_port_stolen(self):
|
||||
port = self.available_port()
|
||||
port = free_fixture_port()
|
||||
self.seed_agent("dev", port)
|
||||
self.start_ok("dev")
|
||||
self.kill9("dev")
|
||||
@@ -453,7 +443,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
if bound:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
self.assertTrue(bound, f"thief 未能在 5s 内监听 {port}")
|
||||
self.assertTrue(bound, "thief 未能在 5s 内监听 18808")
|
||||
try:
|
||||
result = self.svc("recover", check=False)
|
||||
self.assertEqual(result.returncode, 1, result.stdout)
|
||||
@@ -497,7 +487,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
"""模拟 update 误清 pidfile 后本 bin 仍占口:裸 start 应回收并拉起新进程。"""
|
||||
if not self._ss_can_attribute_pid():
|
||||
self.skipTest("ss -ltnp 无法归因 PID(reclaim_own_port 需此能力)")
|
||||
port = self.available_port()
|
||||
port = free_fixture_port()
|
||||
self.seed_agent("dev", port)
|
||||
self.start_ok("dev")
|
||||
old = self.pid_of("dev")
|
||||
@@ -518,7 +508,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
|
||||
def test_start_rejects_foreign_port_occupant(self):
|
||||
"""外部 python http.server 占口:start 必须失败并提示非本服务。"""
|
||||
port = self.available_port()
|
||||
port = free_fixture_port()
|
||||
self.seed_agent("dev", port)
|
||||
thief = subprocess.Popen(
|
||||
["python3", "-m", "http.server", str(port), "--bind", "127.0.0.1"],
|
||||
@@ -555,7 +545,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
self.assertFalse((unit_dir / "agent-system-svc-recover.service").exists())
|
||||
|
||||
def test_recover_reserved_name_rejected(self):
|
||||
self.seed_agent("recover", self.available_port()) # 保留字不可作服务名
|
||||
self.seed_agent("recover", free_fixture_port()) # 保留字不可作服务名
|
||||
result = self.svc("recover", "recover", check=False)
|
||||
self.assertEqual(result.returncode, 2, result.stdout)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user