Add test for combined retention policy interaction

Add test_combined_retention_policies to verify all 5 retention policies
(keep_all, keep_daily, keep_weekly, keep_monthly, keep_yearly) work
correctly together. Test validates precedence rules where later retention
tiers can override earlier ones (e.g., monthly overrides weekly, yearly
overrides monthly) and documents threshold inclusive behavior.

Test uses 36 backups spanning 2017-2021 with expected 21 backups kept
based on policy interaction.
This commit is contained in:
2026-02-04 20:33:07 -08:00
parent fb9dcbd392
commit 06583f7e1a

View File

@@ -1,4 +1,5 @@
import os
import pytest
from unittest import mock
from datetime import datetime
@@ -193,6 +194,119 @@ class TestBackupCleanup:
run_cleanup(keep_all=2, dry_run=True)
check_backups(backups)
@pytest.mark.xfail(reason="Bug #32: coarser retention tiers override finer "
"tiers, violating retention guarantees", strict=True)
@mock.patch(f"{bk.__name__}.datetime", wraps=datetime)
def test_combined_retention_policies(self, mock_datetime, add_backup,
run_cleanup, check_backups):
"""Test all retention policies working together
Tests EXPECTED behavior where retention policies honor their
documented guarantees. Currently fails due to bug where coarser
retention tiers (weekly/monthly/yearly) can override finer tiers
(all/daily/weekly/monthly), violating retention guarantees.
Bug affects multiple tier interactions (see issue #32):
- Weekly overrides daily: Nov 4 removed by weekly
- Monthly overrides weekly: Oct 11 removed by monthly
- Yearly overrides monthly: May 15 removed by yearly
Once fixed, this test will pass and validate that each tier protects
its threshold range from coarser tiers.
"""
# Current: 2021-11-11 (Thursday)
# Policies: keep_all=2, keep_daily=7, keep_weekly=4,
# keep_monthly=6, keep_yearly=3
# Thresholds: all=Nov 9, daily=Nov 4, weekly=Oct 11,
# monthly=May 1, yearly=Jan 1 2018
mock_datetime.now.return_value = datetime(2021, 11, 11)
# Use dict for readability
b = {}
# Keep-all range (all backups after Nov 9)
b["nov_11"] = add_backup("20211111_0300")
b["nov_10_23h"] = add_backup("20211110_2300")
b["nov_10_12h"] = add_backup("20211110_1200")
b["nov_10_01h"] = add_backup("20211110_0100")
b["nov_09"] = add_backup("20211109_0300")
# Daily range (one per day: Nov 4-8)
b["nov_08_dupe"] = add_backup("20211108_2200") # removed: not oldest
b["nov_08"] = add_backup("20211108_0100")
b["nov_07"] = add_backup("20211107_0100")
b["nov_06_dupe"] = add_backup("20211106_1800") # removed: not oldest
b["nov_06"] = add_backup("20211106_0100")
b["nov_05"] = add_backup("20211105_0100")
b["nov_04"] = add_backup("20211104_0100") # BUG: removed by weekly!
# Weekly range (one per week: Oct 11-Nov 7)
b["nov_03"] = add_backup("20211103_0300") # removed: same week
b["nov_01_dupe"] = add_backup("20211101_1500") # removed: not oldest
b["nov_01"] = add_backup("20211101_0100")
b["oct_31"] = add_backup("20211031_0100") # removed: same week
b["oct_25_dupe"] = add_backup("20211025_2000") # removed: not oldest
b["oct_25"] = add_backup("20211025_0100")
b["oct_18"] = add_backup("20211018_0100")
b["oct_11"] = add_backup("20211011_0100") # BUG: removed by monthly!
# Monthly range (one per month: May-Oct)
b["oct_04"] = add_backup("20211004_0100") # removed: same month
b["oct_01"] = add_backup("20211001_0100")
b["sep_15_dupe"] = add_backup("20210915_2000") # removed: not oldest
b["sep_15"] = add_backup("20210915_0100")
b["aug_15"] = add_backup("20210815_0100")
b["jul_15_dupe"] = add_backup("20210715_1200") # removed: not oldest
b["jul_15"] = add_backup("20210715_0100")
b["jun_15"] = add_backup("20210615_0100")
b["may_15"] = add_backup("20210515_0100") # BUG: removed by yearly!
# Yearly range (one per year: 2018-2021)
b["apr_15"] = add_backup("20210415_0100") # removed: same year
b["jan_15_2021"] = add_backup("20210115_0100")
b["dec_15_2020"] = add_backup("20201215_0100") # removed: same year
b["jul_15_2020"] = add_backup("20200715_0100") # removed: same year
b["jan_15_2020"] = add_backup("20200115_0100")
b["dec_15_2019_dupe"] = add_backup("20191215_2300") # removed: not oldest
b["dec_15_2019"] = add_backup("20191215_0100") # removed: same year
b["jul_15_2019"] = add_backup("20190715_0100") # removed: same year
b["jan_15_2019"] = add_backup("20190115_0100")
b["nov_15_2018"] = add_backup("20181115_0100") # removed: same year
b["jan_15_2018"] = add_backup("20180115_0100")
# Beyond all thresholds
b["jan_15_2017"] = add_backup("20170115_0100") # removed: too old
# Expected: each tier protects its range
expected = [
# keep_all: all 5 backups
b["nov_11"], b["nov_10_23h"], b["nov_10_12h"],
b["nov_10_01h"], b["nov_09"],
# daily: oldest per day (Nov 4-8) = 5 backups
# BUG: nov_04 missing - removed by weekly (same week as nov_03)
b["nov_08"], b["nov_07"], b["nov_06"], b["nov_05"],
b["nov_04"],
# weekly: oldest per week (Oct 11 - Nov 7) = 4 backups
# BUG: oct_11 missing - removed by monthly (same month as oct_01)
b["nov_01"], b["oct_25"], b["oct_18"], b["oct_11"],
# monthly: oldest per month (May-Oct) = 6 backups
# BUG: may_15 missing - removed by yearly (same year as jan_15_2021)
b["oct_01"], b["sep_15"], b["aug_15"],
b["jul_15"], b["jun_15"],
b["may_15"],
# yearly: oldest per year (2018-2021) = 4 backups
b["jan_15_2021"], b["jan_15_2020"],
b["jan_15_2019"], b["jan_15_2018"],
]
run_cleanup(keep_all=2, keep_daily=7, keep_weekly=4,
keep_monthly=6, keep_yearly=3)
check_backups(expected)
class TestBackupLock:
"""Test suite for backup lock file functionality."""