From 5d738f70a365eaae6422ac57c984a085e305ea0a Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Mon, 19 Aug 2024 17:29:55 -0400 Subject: [PATCH] Add tests for quarterly pruning strategy. --- src/borg/testsuite/archiver.py | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 33d72819eb..cdc93f2d46 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -2314,6 +2314,76 @@ def test_prune_repository_example(self): for i in range(22, 25): self.assert_not_in('test%02d' % i, output) + def test_prune_quarterly(self): + # Example worked through by hand when developing quarterly + # strategy, based upon existing backups where quarterly strategy + # is desired. Weekly/monthly backups that don't affect results were + # trimmed to speed up the test. + # + # Week number is shown in comment for every row in the below list. + # Year is also shown when it doesn't match the year given in the + # date tuple. + test_dates = [ + (2020, 12, 6), (2021, 1, 3), # 49, 2020-53 + (2021, 3, 28), (2021, 4, 25), # 12, 16 + (2021, 6, 27), (2021, 7, 4), # 25, 26 + (2021, 9, 26), (2021, 10, 3), # 38, 39 + (2021, 12, 26), (2022, 1, 2) # 51, 2021-52 + ] + + def mk_name(tup): + (y, m, d) = tup + suff = datetime(y, m, d).strftime("%Y-%m-%d") + return f"test-{suff}" + + # The kept repos are based on working on an example by hand, + # archives made on the following dates should be kept: + EXPECTED_KEPT = { + "13weekly": [ + (2020, 12, 6), (2021, 1, 3), (2021, 3, 28), (2021, 7, 4), + (2021, 10, 3), (2022, 1, 2) + ], + "3monthly": [ + (2020, 12, 6), (2021, 3, 28), (2021, 6, 27), (2021, 9, 26), + (2021, 12, 26), (2022, 1, 2) + ] + } + + for (strat, to_keep) in EXPECTED_KEPT.items(): + # Initialize our repo. + self.cmd('init', '--encryption=repokey', self.repository_location) + for a, (y, m, d) in zip(map(mk_name, test_dates), test_dates): + self._create_archive_ts(a, y, m, d) + + to_prune = list(set(test_dates) - set(to_keep)) + + # Use 99 instead of -1 to test that oldest backup is kept. + output = self.cmd('prune', '--list', '--dry-run', self.repository_location, f"--keep-{strat}=99") + for a in map(mk_name, to_prune): + assert re.search(fr"Would prune:\s+{a}", output) + + oldest = r"\[oldest\]" if strat in ("13weekly") else "" + assert re.search(fr"Keeping archive \(rule: quarterly_{strat}{oldest} #\d+\):\s+test-2020-12-06", output) + for a in map(mk_name, to_keep[1:]): + assert re.search(fr"Keeping archive \(rule: quarterly_{strat} #\d+\):\s+{a}", output) + + output = self.cmd('list', self.repository_location) + # Nothing pruned after dry run + for a in map(mk_name, test_dates): + self.assert_in(a, output) + + self.cmd('prune', self.repository_location, f"--keep-{strat}=99") + output = self.cmd('list', self.repository_location) + # All matching backups plus oldest kept + for a in map(mk_name, to_keep): + self.assert_in(a, output) + # Other backups have been pruned + for a in map(mk_name, to_prune): + self.assert_not_in(a, output) + + # Delete repo and begin anew + self.cmd('delete', self.repository_location) + # With an initial and daily backup, prune daily until oldest is replaced by a monthly backup def test_prune_retain_and_expire_oldest(self): self.cmd('init', '--encryption=repokey', self.repository_location)