Переименование jira-hub → sdlc-hub (#97) #99

Merged
aep merged 21 commits from feat/97-sdlc-rename into main 2026-10-24 14:35:47 +03:00
2 changed files with 47 additions and 9 deletions
Showing only changes of commit 1394f39763 - Show all commits

View File

@ -204,6 +204,19 @@ def _port_open(port: int) -> bool:
return False
_sleep = time.sleep
_clock = time.monotonic
def _wait_port_closed(port: int, timeout: float = 10.0, interval: float = 0.5) -> bool:
deadline = _clock() + timeout
while _port_open(port):
if _clock() >= deadline:
return False
_sleep(interval)
return True
def _domain() -> str:
return "gui/%d" % os.getuid()
@ -233,7 +246,7 @@ def steps(cfg: Config, leftovers: List[str] = None, backups: List[Path] = None)
_run("launchctl", "bootout", "%s/%s" % (_domain(), OLD_LABEL), check=False) # не загружен - не ошибка
if old_plist.exists():
old_plist.unlink()
if _port_open(cfg.port):
if not _wait_port_closed(cfg.port):
raise RuntimeError("после остановки %s порт %d всё ещё занят: хаб не остановлен" % (OLD_LABEL, cfg.port))
def move_data() -> None:
@ -332,13 +345,14 @@ def main(argv: List[str]) -> int:
print("остановлено на шаге %s: %s" % (desc.split(".")[0], e))
print("Выполнено: " + ("; ".join(done) if done else "ничего"))
print("Промежуточное состояние (по выполненным шагам; упавший шаг мог выполниться частично, проверьте):")
for line in _state_lines(done):
for line in _state_lines(done, desc):
print(" " + line)
print("Откат: остановить ru.aepif.sdlc-hub (launchctl bootout %s/%s), перенести каталоги обратно "
"(данные %s -> %s, checkout %s -> %s, origin вернуть на aep/jira-hub), вернуть plist %s и "
"launchctl bootstrap %s; раздел «Переключение с jira-hub» в README." % (
"launchctl bootstrap %s %s; раздел «Переключение с jira-hub» в README." % (
_domain(), NEW_LABEL, cfg.data_root / "sdlc-hub", cfg.data_root / "jira-hub",
cfg.new_checkout, cfg.old_checkout, cfg.launch_agents / (OLD_LABEL + ".plist"), _domain()))
cfg.new_checkout, cfg.old_checkout, cfg.launch_agents / (OLD_LABEL + ".plist"), _domain(),
cfg.launch_agents / (OLD_LABEL + ".plist")))
_print_tail(backups, leftovers)
return 1
done.append(desc)
@ -350,7 +364,7 @@ def main(argv: List[str]) -> int:
return 0
def _state_lines(done: List[str]) -> List[str]:
def _state_lines(done: List[str], failed: str = "") -> List[str]:
n = {d.split(".")[0] for d in done}
out = []
if "2" in n:
@ -363,6 +377,11 @@ def _state_lines(done: List[str]) -> List[str]:
out.append("ru.aepif.sdlc-hub установлен и запущен")
if "6" in n:
out.append("файлы проектов изменены (копии *%s)" % SUFFIX)
if failed:
if failed.startswith("2."):
out.append("упавший шаг 2: хаб ru.aepif.jira-hub мог быть выгружен, его plist удалён")
else:
out.append("упавший шаг %s мог выполниться частично, проверьте" % failed.split(".")[0])
return out or ["ничего не изменено"]

View File

@ -185,12 +185,31 @@ def test_step7_add_before_remove_and_remove_not_fatal(env, monkeypatch, capsys):
assert "ПРЕДУПРЕЖДЕНИЕ" in capsys.readouterr().out
def test_step2_fails_if_port_still_open(env, monkeypatch):
def test_step2_fails_if_port_still_open(env, monkeypatch, capsys):
monkeypatch.setattr(cutover.subprocess, "run", _Fake())
monkeypatch.setattr(cutover, "_port_open", lambda port: True)
cfg = cutover.Config.from_env()
with pytest.raises(RuntimeError, match="порт"):
cutover.steps(cfg)[0][1]()
t = [0.0]
monkeypatch.setattr(cutover, "_clock", lambda: t[0])
monkeypatch.setattr(cutover, "_sleep", lambda d: t.__setitem__(0, t[0] + d))
monkeypatch.setattr(cutover, "preflight", lambda cfg: [])
assert cutover.main(["--apply"]) == 1
out = capsys.readouterr().out
assert "порт" in out and "остановлено на шаге 2" in out
assert "мог быть выгружен" in out and "plist удалён" in out
assert "ничего не изменено" not in out
assert "launchctl bootstrap gui/%d %s" % (os.getuid(), env["tmp"] / "la" / "ru.aepif.jira-hub.plist") in out
assert t[0] >= 10
def test_step2_waits_for_port_to_close(env, monkeypatch):
monkeypatch.setattr(cutover.subprocess, "run", _Fake())
answers = iter([True, True, True, False])
monkeypatch.setattr(cutover, "_port_open", lambda port: next(answers))
t = [0.0]
monkeypatch.setattr(cutover, "_clock", lambda: t[0])
monkeypatch.setattr(cutover, "_sleep", lambda d: t.__setitem__(0, t[0] + d))
cutover.steps(cutover.Config.from_env())[0][1]()
assert 0 < t[0] < 10
def test_failure_prints_state_and_rollback(env, monkeypatch, capsys):