Skip to content

Commit b86537c

Browse files
committed
test: Add test that reproduces a crash observed in the wild
This commit just adds a test for a bug that was very tricky to reproduce. The test occured on existing backups after recent changes of the single files handling, which caused these backups to break. Such crashes are unacceptable! Old backups with valid configurations must succeed! This commit just adds the test and marks it as expected failure. Later commits will refactor the test a bit and then add a fix.
1 parent 066c78d commit b86537c

1 file changed

Lines changed: 46 additions & 0 deletions

File tree

tests/test_backup_backends.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime as dt
2+
import itertools
23
import os
34
from collections import Counter
45
from pathlib import Path
@@ -230,6 +231,51 @@ def test_do_backup_removes_existing_files_in_exclude_list(
230231
assert result_content == expected_content
231232

232233

234+
@pytest.mark.xfail(reason="Reproduces a bug for which no fix is yet available.")
235+
@pytest.mark.parametrize(
236+
"first_source, second_source",
237+
[(FIRST_BACKUP, SECOND_BACKUP)],
238+
)
239+
def test_btrfs_backend_gracefully_handles_existing_snapshots_owned_by_root(
240+
first_source, second_source, mounted_device
241+
) -> None:
242+
# THIS IS A REGRESSION TEST!
243+
#
244+
# After the improvements on the handling of single files backups a weird error
245+
# ocurred. Sometimes backups would fail due to "PermissionError"s. This happened
246+
# when the existing snapshot was owned by root and was missing the single files
247+
# target folder. This test reproduces this scenario and ensures that the backup
248+
# works correctly, even in this case.
249+
empty_config, device = mounted_device
250+
if not isinstance(empty_config, cp.BtrFSRsyncConfig):
251+
# This test works for BtrfsConfig only. However, encrypted_device on
252+
# which mounted_device depends on, is parameterised over all backends.
253+
# Since this simplifies many other tests it seemed to be an acceptable
254+
# tradeoff to short-circuit the test here.
255+
return
256+
257+
first_config = complement_configuration(empty_config, first_source)
258+
first_backend = bb.BackupBackend.from_config(first_config)
259+
first_backend.do_backup(device)
260+
261+
snapshot_root = device / first_config.BackupRepositoryFolder
262+
latest_snapshot = sorted(snapshot_root.iterdir())[-1]
263+
for cur in itertools.chain(snapshot_root.glob("*"), snapshot_root.glob("*/*")):
264+
print(f"Changing ownership of {cur} to root:root")
265+
sh.run_cmd(cmd=["sudo", "chown", "root:root", cur])
266+
sh.run_cmd(cmd=["sudo", "rm", "-rf", latest_snapshot / first_config.FilesDest])
267+
268+
second_config = complement_configuration(empty_config, second_source).model_copy(
269+
update={"ExcludePatternsFile": EXCLUDE_FILE}
270+
)
271+
second_backend = bb.BackupBackend.from_config(second_config)
272+
second_backend.do_backup(device)
273+
274+
result_content = get_result_content(second_config, device)
275+
expected_content = get_expected_content(second_config, exclude_to_ignore_file=True)
276+
assert result_content == expected_content
277+
278+
233279
def test_do_backup_for_btrfs_creates_snapshots_with_timestamp_names(
234280
mounted_device,
235281
) -> None:

0 commit comments

Comments
 (0)