cmd_check проверяет хаб и Eva (код 0 или 3), флаг run --once, scripts/e2e-hub.sh поднимает тестовый хаб во временном JIRA_HUB_HOME. Решения 33–35 в design.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
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_eva import FakeEva
|
|
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<N> видит публикацию v<N+1>, ставит её, v<N+1> подтверждает; Ctrl-C группе — выход 0."""
|
|
|
|
def setUp(self):
|
|
self.hub = FakeHub(SETTINGS)
|
|
self.eva = FakeEva() # selftest = check: нужна доступная Eva
|
|
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": self.eva.base_url, "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.eva.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)
|