|
18 | 18 | backup, |
19 | 19 | backup_marker_path, |
20 | 20 | check_dest_is_backup_folder, |
| 21 | + deal_with_no_space_left, |
21 | 22 | expire_backups, |
22 | 23 | find, |
23 | 24 | find_backup_marker, |
@@ -199,6 +200,82 @@ def test_find_backup_marker(tmp_path: Path) -> None: |
199 | 200 | assert find_backup_marker(str(tmp_path), None) == marker_path |
200 | 201 |
|
201 | 202 |
|
| 203 | +def test_deal_with_no_space_left_handles_broken_pipe_when_dest_full( |
| 204 | + tmp_path: Path, |
| 205 | + monkeypatch: pytest.MonkeyPatch, |
| 206 | +) -> None: |
| 207 | + """Broken pipe with a full destination should trigger expiration.""" |
| 208 | + log_file = tmp_path / "2025-10-12-212400.log" |
| 209 | + log_file.write_text("rsync: [sender] write error: Broken pipe (32)\n", encoding="utf-8") |
| 210 | + |
| 211 | + backups = [ |
| 212 | + "/dest/2025-10-11-120000", |
| 213 | + "/dest/2025-10-12-120000", |
| 214 | + ] |
| 215 | + |
| 216 | + monkeypatch.setattr(rsync_time_machine, "find_backups", Mock(return_value=backups)) |
| 217 | + |
| 218 | + expired: list[str] = [] |
| 219 | + monkeypatch.setattr( |
| 220 | + rsync_time_machine, |
| 221 | + "expire_backup", |
| 222 | + lambda path, ssh: expired.append(path), |
| 223 | + ) |
| 224 | + |
| 225 | + def fake_run_cmd(cmd: str, ssh: rsync_time_machine.SSH | None = None) -> rsync_time_machine.CmdResult: |
| 226 | + if cmd.startswith("df -Pk"): |
| 227 | + df_output = ( |
| 228 | + "Filesystem 1024-blocks Used Available Capacity Mounted on\n" |
| 229 | + "/dev/sda1 100 100 0 100% /dest\n" |
| 230 | + ) |
| 231 | + return rsync_time_machine.CmdResult(df_output, "", 0) |
| 232 | + return rsync_time_machine.CmdResult("", "", 0) |
| 233 | + |
| 234 | + monkeypatch.setattr(rsync_time_machine, "run_cmd", fake_run_cmd) |
| 235 | + |
| 236 | + should_retry = deal_with_no_space_left( |
| 237 | + str(log_file), |
| 238 | + "/dest", |
| 239 | + ssh=None, |
| 240 | + auto_expire=True, |
| 241 | + ) |
| 242 | + |
| 243 | + assert should_retry is True |
| 244 | + assert expired == [sorted(backups)[-1]] |
| 245 | + |
| 246 | + |
| 247 | +def test_deal_with_no_space_left_ignores_broken_pipe_when_space_available( |
| 248 | + tmp_path: Path, |
| 249 | + monkeypatch: pytest.MonkeyPatch, |
| 250 | +) -> None: |
| 251 | + """Broken pipe without the destination filling up should not expire backups.""" |
| 252 | + log_file = tmp_path / "2025-10-12-212400.log" |
| 253 | + log_file.write_text("rsync: [sender] write error: Broken pipe (32)\n", encoding="utf-8") |
| 254 | + |
| 255 | + monkeypatch.setattr(rsync_time_machine, "find_backups", Mock(return_value=["/dest/2025-10-12-120000", "/dest/2025-10-11-120000"])) |
| 256 | + monkeypatch.setattr(rsync_time_machine, "expire_backup", Mock()) |
| 257 | + |
| 258 | + def fake_run_cmd(cmd: str, ssh: rsync_time_machine.SSH | None = None) -> rsync_time_machine.CmdResult: |
| 259 | + if cmd.startswith("df -Pk"): |
| 260 | + df_output = ( |
| 261 | + "Filesystem 1024-blocks Used Available Capacity Mounted on\n" |
| 262 | + "/dev/sda1 100 58 42 58% /dest\n" |
| 263 | + ) |
| 264 | + return rsync_time_machine.CmdResult(df_output, "", 0) |
| 265 | + return rsync_time_machine.CmdResult("", "", 0) |
| 266 | + |
| 267 | + monkeypatch.setattr(rsync_time_machine, "run_cmd", fake_run_cmd) |
| 268 | + |
| 269 | + should_retry = deal_with_no_space_left( |
| 270 | + str(log_file), |
| 271 | + "/dest", |
| 272 | + ssh=None, |
| 273 | + auto_expire=True, |
| 274 | + ) |
| 275 | + |
| 276 | + assert should_retry is False |
| 277 | + |
| 278 | + |
202 | 279 | def test_run_cmd() -> None: |
203 | 280 | """Test the run_cmd function.""" |
204 | 281 | result = run_cmd("echo 'Hello, World!'") |
|
0 commit comments