feat(agent): версия 9; тесты без жёстких номеров версий (#23)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ebbd5d74dc
commit
47a7424eca
@ -33,7 +33,7 @@ import uuid
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
AGENT_VERSION = 5
|
||||
AGENT_VERSION = 9
|
||||
EXIT_UPDATE = 75 # «обновление готово»: run.py подменит agent.py на agent.py.new
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
API = "/rest/api/2"
|
||||
|
||||
@ -214,7 +214,7 @@ class ClientsTest(unittest.TestCase):
|
||||
with contextlib.redirect_stdout(out):
|
||||
# хаб недоступен (порт 1), Jira доступен — selftest печатает версию и возвращает 3, а не 1
|
||||
self.assertEqual(agent.main(["--config", path, "selftest"]), 3)
|
||||
self.assertIn("jira-agent v5", out.getvalue())
|
||||
self.assertIn("jira-agent v%d" % agent.AGENT_VERSION, out.getvalue())
|
||||
self.assertIn("Jira: доступен", out.getvalue())
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -157,8 +157,8 @@ class FolderIntegrationTest(unittest.TestCase):
|
||||
def cfg(self):
|
||||
return jira_cfg(self.fake, hub_url="folder:" + self.root, hub_token=self.hub.token)
|
||||
|
||||
def test_version_is_5(self):
|
||||
self.assertEqual(agent.AGENT_VERSION, 5)
|
||||
def test_version_is_9(self):
|
||||
self.assertEqual(agent.AGENT_VERSION, 9)
|
||||
|
||||
def test_runner_through_folder(self):
|
||||
self.fake.add("AURORA-1", duedate="2026-10-20")
|
||||
|
||||
@ -59,7 +59,7 @@ class RunnerTest(unittest.TestCase):
|
||||
self.assertEqual(body["launcher_version"], 1)
|
||||
self.assertEqual(body["update_state"]["state"], "trial")
|
||||
self.assertEqual(agent.read_update_state(self.runner.updater.state_path)["state"], "ok") # первый успешный claim
|
||||
self.assertEqual(self.hub.claim_bodies[0]["agent_version"], 5)
|
||||
self.assertEqual(self.hub.claim_bodies[0]["agent_version"], agent.AGENT_VERSION)
|
||||
|
||||
def test_claim_without_updater_sends_nulls(self):
|
||||
self.runner.loop()
|
||||
@ -67,17 +67,17 @@ class RunnerTest(unittest.TestCase):
|
||||
self.assertIsNone(self.hub.claim_bodies[0]["launcher_version"])
|
||||
|
||||
def test_idle_with_release_exits_for_update(self):
|
||||
rel = {"version": 6, "sha256": "s", "content": "x"}
|
||||
rel = {"version": agent.AGENT_VERSION + 1, "sha256": "s", "content": "x"}
|
||||
self.hub.release = rel
|
||||
self.runner.updater = agent.Updater(self.runner.hub, self.dir.name, 1)
|
||||
self.runner.updater.check = lambda r: r == {"version": 6, "sha256": "s"}
|
||||
self.runner.updater.check = lambda r: r == {"version": agent.AGENT_VERSION + 1, "sha256": "s"}
|
||||
self.runner.idle_limit = 5
|
||||
self.runner.loop()
|
||||
self.assertEqual(self.runner.exit_code, agent.EXIT_UPDATE)
|
||||
self.assertEqual(self.hub.claims, 1)
|
||||
|
||||
def test_stop_during_check_exits_0(self):
|
||||
self.hub.release = {"version": 6, "sha256": "s", "content": "x"}
|
||||
self.hub.release = {"version": agent.AGENT_VERSION + 1, "sha256": "s", "content": "x"}
|
||||
self.runner.updater = agent.Updater(self.runner.hub, self.dir.name, 1)
|
||||
def check(r):
|
||||
self.runner.stop = True # Ctrl-C пришёл, когда версия уже staged
|
||||
@ -88,7 +88,7 @@ class RunnerTest(unittest.TestCase):
|
||||
self.assertEqual(self.runner.exit_code, 0) # остановка человеком; staged доделает run.py при старте
|
||||
|
||||
def test_command_first_then_update(self):
|
||||
self.hub.release = {"version": 6, "sha256": "s", "content": "x"}
|
||||
self.hub.release = {"version": agent.AGENT_VERSION + 1, "sha256": "s", "content": "x"}
|
||||
self.hub.enqueue("ensure_fields", {"key": "AURORA-1", "duedate": "2026-10-23"})
|
||||
self.runner.updater = agent.Updater(self.runner.hub, self.dir.name, 1)
|
||||
seen = []
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
@ -24,7 +25,7 @@ def wait_for(pred, timeout=30):
|
||||
|
||||
|
||||
class SelfUpdateE2E(unittest.TestCase):
|
||||
"""run.py + настоящий agent.py: v5 видит публикацию v6, ставит её, v6 подтверждает; Ctrl-C группе — выход 0."""
|
||||
"""run.py + настоящий agent.py: v<N> видит публикацию v<N+1>, ставит её, v<N+1> подтверждает; Ctrl-C группе — выход 0."""
|
||||
|
||||
def setUp(self):
|
||||
self.jira = FakeJira()
|
||||
@ -39,9 +40,12 @@ class SelfUpdateE2E(unittest.TestCase):
|
||||
"auth": {"type": "basic", "user": "me", "password": "secret"}}}, f)
|
||||
with open(os.path.join(SRC, "agent.py"), encoding="utf-8") as f:
|
||||
src = f.read()
|
||||
content = src.replace("AGENT_VERSION = 5\n", "AGENT_VERSION = 6\n", 1)
|
||||
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": 6, "sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(), "content": content}
|
||||
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,
|
||||
@ -63,7 +67,7 @@ class SelfUpdateE2E(unittest.TestCase):
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
|
||||
def test_update_to_v6_and_stop_by_ctrl_c(self):
|
||||
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()
|
||||
@ -71,8 +75,8 @@ class SelfUpdateE2E(unittest.TestCase):
|
||||
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") == 6 for b in self.hub.claim_bodies)))
|
||||
last = [b for b in self.hub.claim_bodies if b.get("agent_version") == 6][-1]
|
||||
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"], 1)
|
||||
os.killpg(self.proc.pid, signal.SIGINT) # Ctrl-C в терминале — всей группе
|
||||
self.assertEqual(self.proc.wait(30), 0)
|
||||
|
||||
@ -7,6 +7,9 @@ import unittest
|
||||
from _common import SETTINGS, agent
|
||||
from fake_hub import FakeHub
|
||||
|
||||
CUR = agent.AGENT_VERSION
|
||||
NEXT = CUR + 1
|
||||
|
||||
|
||||
class UpdateStateTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -76,50 +79,50 @@ class UpdaterCheckTest(unittest.TestCase):
|
||||
return {"version": rel["version"], "sha256": rel["sha256"]}
|
||||
|
||||
def test_good_version_is_staged(self):
|
||||
rel = make_release(6)
|
||||
rel = make_release(NEXT)
|
||||
self.hub.release = rel
|
||||
self.assertTrue(self.u.check(self.ref(rel)))
|
||||
with open(self.u.new_path, encoding="utf-8") as f:
|
||||
self.assertEqual(f.read(), rel["content"])
|
||||
st = self.u.state()
|
||||
self.assertEqual((st["state"], st["from_version"], st["to_version"], st["sha256"]), ("staged", 5, 6, rel["sha256"]))
|
||||
self.assertEqual((st["state"], st["from_version"], st["to_version"], st["sha256"]), ("staged", CUR, NEXT, rel["sha256"]))
|
||||
self.assertEqual(self.selftests, [self.u.new_path])
|
||||
|
||||
def test_not_newer_or_disabled_or_no_release(self):
|
||||
self.hub.release = make_release(5)
|
||||
self.hub.release = make_release(CUR)
|
||||
self.assertFalse(self.u.check(self.ref(self.hub.release)))
|
||||
self.assertFalse(self.u.check(None))
|
||||
self.hub.release = make_release(6)
|
||||
self.hub.release = make_release(NEXT)
|
||||
self.u.enabled = False
|
||||
self.assertFalse(self.u.check(self.ref(self.hub.release)))
|
||||
self.assertEqual(self.hub.release_requests, 0)
|
||||
|
||||
def test_sha_mismatch_rejected_once(self):
|
||||
rel = make_release(6)
|
||||
rel = make_release(NEXT)
|
||||
self.hub.release = dict(rel, content=rel["content"] + "# подмена\n")
|
||||
ref = self.ref(rel)
|
||||
self.hub.release["sha256"] = rel["sha256"]
|
||||
self.assertFalse(self.u.check(ref))
|
||||
st = self.u.state()
|
||||
self.assertEqual((st["state"], st["to_version"], st["error"]), ("rejected", 6, "sha256 не сходится"))
|
||||
self.assertEqual((st["state"], st["to_version"], st["error"]), ("rejected", NEXT, "sha256 не сходится"))
|
||||
self.assertFalse(os.path.exists(self.u.new_path))
|
||||
self.assertFalse(self.u.check(ref))
|
||||
self.assertEqual(self.hub.release_requests, 1) # отвергнутую версию больше не качает
|
||||
|
||||
def test_syntax_error_and_wrong_version_rejected(self):
|
||||
bad = make_release(6, body="def (:\n")
|
||||
bad = make_release(NEXT, body="def (:\n")
|
||||
self.hub.release = bad
|
||||
self.assertFalse(self.u.check(self.ref(bad)))
|
||||
self.assertIn("не компилируется", self.u.state()["error"])
|
||||
wrong = make_release(7)
|
||||
wrong = dict(wrong, version=8)
|
||||
wrong = make_release(NEXT + 1)
|
||||
wrong = dict(wrong, version=NEXT + 2)
|
||||
self.hub.release = wrong
|
||||
self.assertFalse(self.u.check(self.ref(wrong)))
|
||||
self.assertIn("AGENT_VERSION", self.u.state()["error"])
|
||||
self.assertEqual(self.selftests, [])
|
||||
|
||||
def test_selftest_bad_code_rejected(self):
|
||||
self.hub.release = make_release(6)
|
||||
self.hub.release = make_release(NEXT)
|
||||
self.code = 1
|
||||
self.assertFalse(self.u.check(self.ref(self.hub.release)))
|
||||
st = self.u.state()
|
||||
@ -129,7 +132,7 @@ class UpdaterCheckTest(unittest.TestCase):
|
||||
self.assertFalse(os.path.exists(self.u.new_path))
|
||||
|
||||
def test_selftest_unavailable_is_transient(self):
|
||||
self.hub.release = make_release(6)
|
||||
self.hub.release = make_release(NEXT)
|
||||
self.code = 3
|
||||
ref = self.ref(self.hub.release)
|
||||
self.assertFalse(self.u.check(ref))
|
||||
@ -143,35 +146,35 @@ class UpdaterCheckTest(unittest.TestCase):
|
||||
self.assertTrue(self.u.check(ref))
|
||||
|
||||
def test_selftest_timeout_is_transient(self):
|
||||
self.hub.release = make_release(6)
|
||||
self.hub.release = make_release(NEXT)
|
||||
self.code = None
|
||||
self.assertFalse(self.u.check(self.ref(self.hub.release)))
|
||||
self.assertIsNone(self.u.state())
|
||||
|
||||
def test_download_failure_is_transient(self):
|
||||
rel = make_release(6) # в claim есть, а /agent/release — 404
|
||||
rel = make_release(NEXT) # в claim есть, а /agent/release — 404
|
||||
self.assertFalse(self.u.check(self.ref(rel)))
|
||||
self.assertIsNone(self.u.state())
|
||||
|
||||
def test_release_changed_between_requests_is_transient(self):
|
||||
old, new = make_release(6), make_release(7)
|
||||
old, new = make_release(NEXT), make_release(NEXT + 1)
|
||||
self.hub.release = new
|
||||
self.assertFalse(self.u.check(self.ref(old)))
|
||||
self.assertIsNone(self.u.state())
|
||||
self.assertEqual(self.selftests, [])
|
||||
|
||||
def test_rolled_back_same_sha_not_retried(self):
|
||||
rel = make_release(6)
|
||||
rel = make_release(NEXT)
|
||||
self.hub.release = rel
|
||||
agent.write_update_state(self.u.state_path, "rolled_back", 5, 6, rel["sha256"], "код 1 через 3 с")
|
||||
agent.write_update_state(self.u.state_path, "rolled_back", CUR, NEXT, rel["sha256"], "код 1 через 3 с")
|
||||
self.assertFalse(self.u.check(self.ref(rel)))
|
||||
fixed = make_release(6, body="print('fixed')\n")
|
||||
fixed = make_release(NEXT, body="print('fixed')\n")
|
||||
self.hub.release = fixed
|
||||
self.assertTrue(self.u.check(self.ref(fixed))) # тот же номер, другой sha — пробуем
|
||||
|
||||
def test_without_launcher_only_hint(self):
|
||||
self.u.launcher_version = None
|
||||
self.hub.release = make_release(6)
|
||||
self.hub.release = make_release(NEXT)
|
||||
logs = []
|
||||
orig = agent.log
|
||||
agent.log = logs.append
|
||||
@ -182,11 +185,11 @@ class UpdaterCheckTest(unittest.TestCase):
|
||||
self.assertFalse(self.u.check(self.ref(self.hub.release)))
|
||||
finally:
|
||||
agent.log = orig
|
||||
self.assertEqual(logs.count("доступна v6; для самообновления запускайте через run.py"), 2)
|
||||
self.assertEqual(logs.count("доступна v%d; для самообновления запускайте через run.py" % NEXT), 2)
|
||||
self.assertEqual(self.hub.release_requests, 0)
|
||||
|
||||
def test_stop_during_selftest_is_not_reject(self):
|
||||
self.hub.release = make_release(6)
|
||||
self.hub.release = make_release(NEXT)
|
||||
stop = [False]
|
||||
self.u.should_stop = lambda: stop[0]
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user