verify_host_channel_worker.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import asyncio
  2. import json
  3. import signal
  4. import subprocess
  5. import sys
  6. import time
  7. from verify_channel_worker import MAX_SECONDS, ROOT, inspect_environment
  8. def main():
  9. enabled, pending, initial = asyncio.run(inspect_environment())
  10. if enabled or pending:
  11. raise RuntimeError("生命周期验收要求当前没有启用渠道和等待投递记录")
  12. windows = sys.platform == "win32"
  13. started = time.monotonic()
  14. # 独立进程组仅接收本次测试的停止信号;Windows 需要从终端运行以共享控制台。
  15. process = subprocess.Popen(
  16. [sys.executable, "-m", "zhifu.modules.platform.delivery"],
  17. cwd=ROOT,
  18. stdout=subprocess.PIPE,
  19. stderr=subprocess.PIPE,
  20. text=True,
  21. creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if windows else 0,
  22. )
  23. try:
  24. while time.monotonic() - started < MAX_SECONDS:
  25. if process.poll() is not None:
  26. stdout, stderr = process.communicate(timeout=5)
  27. raise RuntimeError(f"渠道工作进程提前退出:{stdout}\n{stderr}")
  28. _, _, current = asyncio.run(inspect_environment())
  29. if current - initial:
  30. break
  31. time.sleep(0.25)
  32. else:
  33. raise RuntimeError("渠道工作进程未在 25 秒期限内登记心跳")
  34. assert len(current - initial) == 1, "渠道工作进程应只登记一份心跳"
  35. # 完成一次空队列轮询后发送真实控制台事件或 POSIX 信号,验证完整清理过程。
  36. time.sleep(1)
  37. process.send_signal(signal.CTRL_BREAK_EVENT if windows else signal.SIGTERM)
  38. stdout, stderr = process.communicate(timeout=10)
  39. assert process.returncode == 0, (process.returncode, stdout, stderr)
  40. assert "worker=channel-delivery stopped" in stdout, stdout
  41. _, _, after = asyncio.run(inspect_environment())
  42. assert after == initial, "正常停止后应立即清除本进程心跳"
  43. report = {
  44. "role": "channel-delivery",
  45. "platform": sys.platform,
  46. "heartbeat_count": 1,
  47. "signal": "CTRL_BREAK_EVENT" if windows else "SIGTERM",
  48. "exit_code": process.returncode,
  49. "heartbeat_removed": True,
  50. "seconds": round(time.monotonic() - started, 2),
  51. "generated_model_requests": 0,
  52. "external_delivery_requests": 0,
  53. }
  54. target = ROOT / ".work/channel-worker-host-lifecycle.json"
  55. target.parent.mkdir(exist_ok=True)
  56. target.write_text(json.dumps(report, indent=2), encoding="utf-8")
  57. print(json.dumps(report))
  58. finally:
  59. if process.poll() is None:
  60. process.kill()
  61. process.communicate(timeout=5)
  62. if __name__ == "__main__":
  63. main()