import hashlib import json import os import re import shutil import signal import subprocess import sys import tempfile import time import unittest from _common import SETTINGS, SRC from fake_hub import FakeHub def wait_for(pred, timeout=30): end = time.monotonic() + timeout while time.monotonic() < end: if pred(): return True time.sleep(0.1) return False class SelfUpdateE2E(unittest.TestCase): """run.py + настоящий agent.py: v видит публикацию v, ставит её, v подтверждает; Ctrl-C группе — выход 0.""" def setUp(self): self.hub = FakeHub(SETTINGS) self.tmp = tempfile.TemporaryDirectory() d = self.tmp.name shutil.copy(os.path.join(SRC, "run.py"), d) shutil.copy(os.path.join(SRC, "agent.py"), d) with open(os.path.join(d, "agent.json"), "w", encoding="utf-8") as f: json.dump({"hub_url": self.hub.base_url, "hub_token": self.hub.token, "timeout": 5, "poll_seconds": 0.2, "eva": {"base_url": "http://127.0.0.1:1", "token": "eva-secret"}}, f) with open(os.path.join(SRC, "agent.py"), encoding="utf-8") as f: src = f.read() self.cur = int(re.search(r"^AGENT_VERSION = (\d+)", src, re.M).group(1)) cur = "AGENT_VERSION = %d\n" % self.cur content = src.replace(cur, "AGENT_VERSION = %d\n" % (self.cur + 1), 1) self.assertNotEqual(content, src) self.hub.release = {"version": self.cur + 1, "sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(), "content": content} self.content = content self.log = open(os.path.join(d, "out.log"), "wb") self.proc = subprocess.Popen([sys.executable, os.path.join(d, "run.py")], stdout=self.log, stderr=subprocess.STDOUT, start_new_session=True) def tearDown(self): if self.proc.poll() is None: os.killpg(self.proc.pid, signal.SIGKILL) self.proc.wait() self.log.close() self.hub.stop() self.tmp.cleanup() def state(self): try: with open(os.path.join(self.tmp.name, "update.json"), encoding="utf-8") as f: return json.load(f) except (OSError, ValueError): return None def test_update_to_next_and_stop_by_ctrl_c(self): ok = wait_for(lambda: (self.state() or {}).get("state") == "ok", 60) with open(os.path.join(self.tmp.name, "out.log"), encoding="utf-8", errors="replace") as f: out = f.read() self.assertTrue(ok, out) with open(os.path.join(self.tmp.name, "agent.py"), encoding="utf-8") as f: self.assertEqual(f.read(), self.content) self.assertTrue(os.path.exists(os.path.join(self.tmp.name, "agent.py.prev"))) self.assertTrue(wait_for(lambda: any(b.get("agent_version") == self.cur + 1 for b in self.hub.claim_bodies))) last = [b for b in self.hub.claim_bodies if b.get("agent_version") == self.cur + 1][-1] self.assertEqual(last["launcher_version"], 2) os.killpg(self.proc.pid, signal.SIGINT) # Ctrl-C в терминале — всей группе self.assertEqual(self.proc.wait(30), 0)