| 1 | from __future__ import annotations |
| 2 | |
| 3 | import stat |
| 4 | import sys |
| 5 | from pathlib import Path |
| 6 | |
| 7 | |
| 8 | PROJECT_ROOT = Path(__file__).resolve().parents[1] |
| 9 | if str(PROJECT_ROOT) not in sys.path: |
| 10 | sys.path.insert(0, str(PROJECT_ROOT)) |
| 11 | |
| 12 | from extensions.python.startup_migration import _10_self_update_manager as migration |
| 13 | |
| 14 | |
| 15 | SAFE_SOURCE = """#!/usr/bin/env python3 |
| 16 | from pathlib import Path |
| 17 | |
| 18 | |
| 19 | REPO_DIR = Path("/a0") |
| 20 | |
| 21 | |
| 22 | def should_include_usr_backup_entry(source_file, logger): |
| 23 | logger.log("Skipping non-regular usr backup entry") |
| 24 | return False |
| 25 | |
| 26 | |
| 27 | def clean_transient_desktop_agent_state(repo_dir, logger): |
| 28 | return None |
| 29 | |
| 30 | |
| 31 | def refresh_codex_cli(logger): |
| 32 | return None |
| 33 | |
| 34 | |
| 35 | def docker_run_ui(): |
| 36 | clean_transient_desktop_agent_state(REPO_DIR, logger) |
| 37 | refresh_codex_cli(logger) |
| 38 | """ |
| 39 | |
| 40 | |
| 41 | def test_self_update_runtime_sync_replaces_stale_manager(tmp_path): |
| 42 | source = tmp_path / "source_self_update_manager.py" |
| 43 | target = tmp_path / "self_update_manager.py" |
| 44 | stale = "# old updater without non-regular usr backup guards\n" |
| 45 | source.write_text(SAFE_SOURCE, encoding="utf-8") |
| 46 | target.write_text(stale, encoding="utf-8") |
| 47 | target.chmod(0o600) |
| 48 | |
| 49 | result = migration.ensure_self_update_manager_runtime_current( |
| 50 | target_path=target, |
| 51 | source_path=source, |
| 52 | ) |
| 53 | |
| 54 | assert result["ok"] is True |
| 55 | assert result["updated"] is True |
| 56 | assert target.read_text(encoding="utf-8") == SAFE_SOURCE |
| 57 | assert (target.stat().st_mode & 0o777) == 0o600 |
| 58 | backup = target.with_name(f"{target.name}{migration.BACKUP_SUFFIX}") |
| 59 | assert backup.read_text(encoding="utf-8") == stale |
| 60 | |
| 61 | |
| 62 | def test_self_update_runtime_sync_accepts_repository_manager_source(tmp_path): |
| 63 | source = PROJECT_ROOT / "docker" / "run" / "fs" / "exe" / "self_update_manager.py" |
| 64 | target = tmp_path / "self_update_manager.py" |
| 65 | stale = "# old updater without non-regular usr backup guards\n" |
| 66 | target.write_text(stale, encoding="utf-8") |
| 67 | |
| 68 | result = migration.ensure_self_update_manager_runtime_current( |
| 69 | target_path=target, |
| 70 | source_path=source, |
| 71 | ) |
| 72 | |
| 73 | assert result["ok"] is True |
| 74 | assert result["updated"] is True |
| 75 | assert target.read_text(encoding="utf-8") == source.read_text(encoding="utf-8") |
| 76 | |
| 77 | |
| 78 | def test_self_update_runtime_sync_starts_codex_refresh(monkeypatch, tmp_path): |
| 79 | calls = [] |
| 80 | manager_path = tmp_path / "self_update_manager.py" |
| 81 | monkeypatch.setattr( |
| 82 | migration.subprocess, |
| 83 | "Popen", |
| 84 | lambda *args, **kwargs: calls.append((args, kwargs)), |
| 85 | ) |
| 86 | |
| 87 | assert migration.start_codex_cli_refresh(manager_path) == "" |
| 88 | assert calls[0][0][0] == [sys.executable, str(manager_path), "refresh-codex"] |
| 89 | assert calls[0][1]["start_new_session"] is True |
| 90 | |
| 91 | |
| 92 | def test_self_update_runtime_sync_skips_current_manager(tmp_path): |
| 93 | source = tmp_path / "source_self_update_manager.py" |
| 94 | target = tmp_path / "self_update_manager.py" |
| 95 | source.write_text(SAFE_SOURCE, encoding="utf-8") |
| 96 | target.write_text(SAFE_SOURCE, encoding="utf-8") |
| 97 | |
| 98 | result = migration.ensure_self_update_manager_runtime_current( |
| 99 | target_path=target, |
| 100 | source_path=source, |
| 101 | ) |
| 102 | |
| 103 | assert result == {"ok": True, "updated": False, "reason": "already-current"} |
| 104 | backup = target.with_name(f"{target.name}{migration.BACKUP_SUFFIX}") |
| 105 | assert not backup.exists() |
| 106 | |
| 107 | |
| 108 | def test_self_update_runtime_sync_refuses_source_without_required_markers(tmp_path): |
| 109 | source = tmp_path / "source_self_update_manager.py" |
| 110 | target = tmp_path / "self_update_manager.py" |
| 111 | stale = "# old updater without non-regular usr backup guards\n" |
| 112 | source.write_text("def create_usr_backup():\n pass\n", encoding="utf-8") |
| 113 | target.write_text(stale, encoding="utf-8") |
| 114 | |
| 115 | result = migration.ensure_self_update_manager_runtime_current( |
| 116 | target_path=target, |
| 117 | source_path=source, |
| 118 | ) |
| 119 | |
| 120 | assert result["ok"] is False |
| 121 | assert result["updated"] is False |
| 122 | assert "missing required safety markers" in result["warning"] |
| 123 | assert target.read_text(encoding="utf-8") == stale |
| 124 | backup = target.with_name(f"{target.name}{migration.BACKUP_SUFFIX}") |
| 125 | assert not backup.exists() |
| 126 | |
| 127 | |
| 128 | def test_self_update_runtime_sync_missing_target_is_quiet(tmp_path): |
| 129 | source = tmp_path / "source_self_update_manager.py" |
| 130 | target = tmp_path / "missing_self_update_manager.py" |
| 131 | source.write_text(SAFE_SOURCE, encoding="utf-8") |
| 132 | |
| 133 | result = migration.ensure_self_update_manager_runtime_current( |
| 134 | target_path=target, |
| 135 | source_path=source, |
| 136 | ) |
| 137 | |
| 138 | assert result["ok"] is True |
| 139 | assert result["updated"] is False |
| 140 | assert "not found" in result["reason"] |
| 141 | |
| 142 | |
| 143 | def test_self_update_runtime_sync_skips_non_regular_target(tmp_path): |
| 144 | source = tmp_path / "source_self_update_manager.py" |
| 145 | target = tmp_path / "self_update_manager.py" |
| 146 | link_target = tmp_path / "linked_self_update_manager.py" |
| 147 | source.write_text(SAFE_SOURCE, encoding="utf-8") |
| 148 | link_target.write_text("# linked updater\n", encoding="utf-8") |
| 149 | target.symlink_to(link_target) |
| 150 | |
| 151 | result = migration.ensure_self_update_manager_runtime_current( |
| 152 | target_path=target, |
| 153 | source_path=source, |
| 154 | ) |
| 155 | |
| 156 | assert result["ok"] is True |
| 157 | assert result["updated"] is False |
| 158 | assert "not a regular file" in result["reason"] |
| 159 | assert stat.S_ISLNK(target.lstat().st_mode) |
| 160 | assert link_target.read_text(encoding="utf-8") == "# linked updater\n" |