443 lines
14 KiB
Python
443 lines
14 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
import pytest
|
|
from dateutil.tz import tzutc
|
|
from pytest import fail
|
|
|
|
from backup.model import GenConfig, GenerationalScheme, DummyBackup, Backup
|
|
from backup.time import Time
|
|
|
|
|
|
def test_timezone(time) -> None:
|
|
assert time.local_tz is not None
|
|
|
|
|
|
def test_trivial(time) -> None:
|
|
config = GenConfig(days=1)
|
|
|
|
scheme = GenerationalScheme(time, config, count=0)
|
|
|
|
backups = [
|
|
makeBackup("single", time.local(1928, 12, 6))
|
|
]
|
|
|
|
assert scheme.getOldest(backups)[1].date() == time.local(1928, 12, 6)
|
|
|
|
|
|
def test_trivial_empty(time):
|
|
config = GenConfig(days=1)
|
|
scheme = GenerationalScheme(time, config, count=0)
|
|
assert scheme.getOldest([])[1] is None
|
|
|
|
|
|
def test_trivial_oldest(time: Time) -> None:
|
|
config = GenConfig(days=1)
|
|
scheme = GenerationalScheme(time, config, count=0)
|
|
|
|
backups = [
|
|
makeBackup("test", time.local(1985, 12, 6, 10)),
|
|
makeBackup("test", time.local(1985, 12, 6, 12)),
|
|
makeBackup("test", time.local(1985, 12, 6, 13))
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(1985, 12, 6, 10),
|
|
time.local(1985, 12, 6, 12),
|
|
time.local(1985, 12, 6, 13)
|
|
])
|
|
|
|
|
|
def test_duplicate_weeks(time):
|
|
config = GenConfig(weeks=1, day_of_week='wed')
|
|
|
|
scheme = GenerationalScheme(time, config, count=0)
|
|
|
|
backups = [
|
|
makeBackup("test", time.local(1985, 12, 5)),
|
|
makeBackup("test", time.local(1985, 12, 4)),
|
|
makeBackup("test", time.local(1985, 12, 1)),
|
|
makeBackup("test", time.local(1985, 12, 2))
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(1985, 12, 1),
|
|
time.local(1985, 12, 2),
|
|
time.local(1985, 12, 5),
|
|
time.local(1985, 12, 4)
|
|
])
|
|
|
|
|
|
def test_duplicate_months(time) -> None:
|
|
config = GenConfig(months=2, day_of_month=15)
|
|
|
|
scheme = GenerationalScheme(time, config, count=0)
|
|
|
|
backups = [
|
|
makeBackup("test", time.local(1985, 12, 6)),
|
|
makeBackup("test", time.local(1985, 12, 15)),
|
|
makeBackup("test", time.local(1985, 11, 20)),
|
|
makeBackup("test", time.local(1985, 11, 15))
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(1985, 11, 20),
|
|
time.local(1985, 12, 6),
|
|
time.local(1985, 11, 15),
|
|
time.local(1985, 12, 15)
|
|
])
|
|
|
|
|
|
def test_duplicate_years(time):
|
|
config = GenConfig(years=2, day_of_year=1)
|
|
|
|
scheme = GenerationalScheme(time, config, count=0)
|
|
|
|
backups = [
|
|
makeBackup("test", time.local(1985, 12, 31)),
|
|
makeBackup("test", time.local(1985, 1, 1)),
|
|
makeBackup("test", time.local(1984, 12, 31)),
|
|
makeBackup("test", time.local(1984, 1, 1))
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(1984, 12, 31),
|
|
time.local(1985, 12, 31),
|
|
time.local(1984, 1, 1),
|
|
time.local(1985, 1, 1)
|
|
])
|
|
|
|
|
|
def test_removal_order(time) -> None:
|
|
config = GenConfig(days=5, weeks=2, months=2, years=2,
|
|
day_of_week='mon', day_of_month=15, day_of_year=1)
|
|
|
|
scheme = GenerationalScheme(time, config, count=0)
|
|
|
|
backups = [
|
|
# 5 days, week 1
|
|
makeBackup("test", time.local(1985, 12, 7)), # day 1
|
|
makeBackup("test", time.local(1985, 12, 6)), # day 2
|
|
makeBackup("test", time.local(1985, 12, 5)), # day 3
|
|
makeBackup("test", time.local(1985, 12, 4)), # day 4
|
|
makeBackup("test", time.local(1985, 12, 3)), # day 5
|
|
|
|
makeBackup("test", time.local(1985, 12, 1)), # 1st week pref
|
|
|
|
# week 2
|
|
makeBackup("test", time.local(1985, 11, 25)), # 1st month pref
|
|
|
|
# month2
|
|
makeBackup("test", time.local(1985, 11, 15)), # 2nd month pref
|
|
|
|
# year 1
|
|
makeBackup("test", time.local(1985, 1, 1)), # 1st year preference
|
|
makeBackup("test", time.local(1985, 1, 2)),
|
|
|
|
# year 2
|
|
makeBackup("test", time.local(1984, 6, 1)), # 2nd year pref
|
|
makeBackup("test", time.local(1984, 7, 1)),
|
|
|
|
# year 3
|
|
makeBackup("test", time.local(1983, 1, 1)),
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(1983, 1, 1),
|
|
time.local(1984, 7, 1),
|
|
time.local(1985, 1, 2),
|
|
|
|
time.local(1984, 6, 1),
|
|
time.local(1985, 1, 1),
|
|
time.local(1985, 11, 15),
|
|
time.local(1985, 11, 25),
|
|
time.local(1985, 12, 1),
|
|
time.local(1985, 12, 3),
|
|
time.local(1985, 12, 4),
|
|
time.local(1985, 12, 5),
|
|
time.local(1985, 12, 6),
|
|
time.local(1985, 12, 7)
|
|
])
|
|
|
|
|
|
@pytest.mark.timeout(60)
|
|
def test_simulate_daily_backup_for_4_years(time):
|
|
config = GenConfig(days=4, weeks=4, months=4, years=4,
|
|
day_of_week='mon', day_of_month=1, day_of_year=1)
|
|
scheme = GenerationalScheme(time, config, count=16)
|
|
backups = simulate(time.local(2019, 1, 1),
|
|
time.local(2022, 12, 31),
|
|
scheme)
|
|
assertRemovalOrder(GenerationalScheme(time, config, count=0), backups, [
|
|
# 4 years
|
|
time.local(2019, 1, 1),
|
|
time.local(2020, 1, 1),
|
|
time.local(2021, 1, 1),
|
|
time.local(2022, 1, 1),
|
|
|
|
# 4 months
|
|
time.local(2022, 9, 1),
|
|
time.local(2022, 10, 1),
|
|
time.local(2022, 11, 1),
|
|
time.local(2022, 12, 1),
|
|
|
|
# 4 weeks
|
|
time.local(2022, 12, 5),
|
|
time.local(2022, 12, 12),
|
|
time.local(2022, 12, 19),
|
|
time.local(2022, 12, 26),
|
|
|
|
# 4 days
|
|
time.local(2022, 12, 28),
|
|
time.local(2022, 12, 29),
|
|
time.local(2022, 12, 30),
|
|
time.local(2022, 12, 31)
|
|
])
|
|
|
|
|
|
@pytest.mark.timeout(60)
|
|
def test_simulate_agressive_daily_backup_for_4_years(time):
|
|
config = GenConfig(days=4, weeks=4, months=4, years=4,
|
|
day_of_week='mon', day_of_month=1, day_of_year=1, aggressive=True)
|
|
scheme = GenerationalScheme(time, config, count=16)
|
|
backups = simulate(time.local(2019, 1, 1),
|
|
time.local(2022, 12, 31),
|
|
scheme)
|
|
|
|
assertRemovalOrder(GenerationalScheme(time, config, count=0), backups, [
|
|
# 4 years
|
|
time.local(2019, 1, 1),
|
|
time.local(2020, 1, 1),
|
|
time.local(2021, 1, 1),
|
|
time.local(2022, 1, 1),
|
|
|
|
# 4 months
|
|
time.local(2022, 9, 1),
|
|
time.local(2022, 10, 1),
|
|
time.local(2022, 11, 1),
|
|
time.local(2022, 12, 1),
|
|
|
|
# 4 weeks
|
|
time.local(2022, 12, 5),
|
|
time.local(2022, 12, 12),
|
|
time.local(2022, 12, 19),
|
|
time.local(2022, 12, 26),
|
|
|
|
# 4 days
|
|
time.local(2022, 12, 28),
|
|
time.local(2022, 12, 29),
|
|
time.local(2022, 12, 30),
|
|
time.local(2022, 12, 31),
|
|
])
|
|
|
|
|
|
def test_count_limit(time):
|
|
config = GenConfig(years=2, day_of_year=1)
|
|
scheme = GenerationalScheme(time, config, count=1)
|
|
backups = [
|
|
makeBackup("test", time.local(1985, 1, 1)),
|
|
makeBackup("test", time.local(1984, 1, 1))
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(1984, 1, 1)
|
|
])
|
|
|
|
|
|
def test_aggressive_removal_below_limit(time):
|
|
config = GenConfig(years=2, day_of_year=1, aggressive=True)
|
|
scheme = GenerationalScheme(time, config, count=5)
|
|
backups = [
|
|
makeBackup("test", time.local(1985, 1, 1)),
|
|
makeBackup("test", time.local(1985, 1, 2))
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(1985, 1, 2)
|
|
])
|
|
|
|
|
|
def test_aggressive_removal_at_limit_ok(time):
|
|
config = GenConfig(years=2, day_of_year=1, aggressive=True)
|
|
scheme = GenerationalScheme(time, config, count=2)
|
|
backups = [
|
|
makeBackup("test", time.local(1985, 1, 1)),
|
|
makeBackup("test", time.local(1984, 1, 1))
|
|
]
|
|
assertRemovalOrder(scheme, backups, [])
|
|
|
|
|
|
def test_aggressive_removal_over_limit(time):
|
|
config = GenConfig(years=2, day_of_year=1, aggressive=True)
|
|
scheme = GenerationalScheme(time, config, count=2)
|
|
backups = [
|
|
makeBackup("test", time.local(1985, 1, 1)),
|
|
makeBackup("test", time.local(1984, 1, 1)),
|
|
makeBackup("test", time.local(1983, 1, 1)),
|
|
makeBackup("test", time.local(1983, 1, 2))
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(1983, 1, 1),
|
|
time.local(1983, 1, 2)
|
|
])
|
|
|
|
|
|
def test_removal_order_week(time: Time):
|
|
config = GenConfig(weeks=1, day_of_week='wed', aggressive=True)
|
|
scheme = GenerationalScheme(time, config, count=1)
|
|
backups = [
|
|
makeBackup("test", time.local(2019, 10, 28)),
|
|
makeBackup("test", time.local(2019, 10, 29)),
|
|
makeBackup("test", time.local(2019, 10, 30, 1)),
|
|
makeBackup("test", time.local(2019, 10, 30, 2)),
|
|
makeBackup("test", time.local(2019, 10, 31)),
|
|
makeBackup("test", time.local(2019, 11, 1)),
|
|
makeBackup("test", time.local(2019, 11, 2)),
|
|
makeBackup("test", time.local(2019, 11, 3)),
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(2019, 10, 28),
|
|
time.local(2019, 10, 29),
|
|
time.local(2019, 10, 30, 1),
|
|
time.local(2019, 10, 31),
|
|
time.local(2019, 11, 1),
|
|
time.local(2019, 11, 2),
|
|
time.local(2019, 11, 3)
|
|
])
|
|
|
|
|
|
def test_removal_order_month(time):
|
|
config = GenConfig(months=1, day_of_month=20, aggressive=True)
|
|
|
|
scheme = GenerationalScheme(time, config, count=1)
|
|
|
|
backups = [
|
|
makeBackup("test", time.local(2019, 1, 1)),
|
|
makeBackup("test", time.local(2019, 1, 2)),
|
|
makeBackup("test", time.local(2019, 1, 20, 1)),
|
|
makeBackup("test", time.local(2019, 1, 20, 2)),
|
|
makeBackup("test", time.local(2019, 1, 21)),
|
|
makeBackup("test", time.local(2019, 1, 25)),
|
|
makeBackup("test", time.local(2019, 1, 26)),
|
|
makeBackup("test", time.local(2019, 1, 27)),
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(2019, 1, 1),
|
|
time.local(2019, 1, 2),
|
|
time.local(2019, 1, 20, 1),
|
|
time.local(2019, 1, 21),
|
|
time.local(2019, 1, 25),
|
|
time.local(2019, 1, 26),
|
|
time.local(2019, 1, 27)
|
|
])
|
|
|
|
|
|
def test_removal_order_many_months(time):
|
|
config = GenConfig(months=70, day_of_month=20, aggressive=True)
|
|
|
|
scheme = GenerationalScheme(time, config, count=10)
|
|
|
|
backups = [
|
|
makeBackup("test", time.local(2019, 7, 20)), # preferred
|
|
makeBackup("test", time.local(2018, 7, 18)), # preferred
|
|
makeBackup("test", time.local(2018, 7, 21)),
|
|
makeBackup("test", time.local(2017, 1, 19)),
|
|
makeBackup("test", time.local(2017, 1, 20)), # preferred
|
|
makeBackup("test", time.local(2017, 1, 31)),
|
|
makeBackup("test", time.local(2016, 12, 1)), # preferred
|
|
makeBackup("test", time.local(2014, 1, 31)),
|
|
makeBackup("test", time.local(2014, 1, 1)), # preferred
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(2014, 1, 31),
|
|
time.local(2017, 1, 19),
|
|
time.local(2017, 1, 31),
|
|
time.local(2018, 7, 21),
|
|
])
|
|
|
|
|
|
def test_removal_order_years(time):
|
|
config = GenConfig(years=2, day_of_year=15, aggressive=True)
|
|
|
|
scheme = GenerationalScheme(time, config, count=10)
|
|
|
|
backups = [
|
|
makeBackup("test", time.local(2019, 2, 15)),
|
|
makeBackup("test", time.local(2019, 1, 15)), # keep
|
|
makeBackup("test", time.local(2018, 1, 14)),
|
|
makeBackup("test", time.local(2018, 1, 15)), # keep
|
|
makeBackup("test", time.local(2018, 1, 16)),
|
|
makeBackup("test", time.local(2017, 1, 15)),
|
|
]
|
|
assertRemovalOrder(scheme, backups, [
|
|
time.local(2017, 1, 15),
|
|
time.local(2018, 1, 14),
|
|
time.local(2018, 1, 16),
|
|
time.local(2019, 2, 15),
|
|
])
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ignored_generational_labels(time):
|
|
config = GenConfig(days=2)
|
|
|
|
scheme = GenerationalScheme(time, config, count=10)
|
|
backup1 = makeBackup("test", time.local(2019, 2, 15))
|
|
backup2 = makeBackup("test", time.local(2019, 2, 14))
|
|
backup3 = makeBackup("test", time.local(2019, 2, 13), ignore=True)
|
|
backups = [backup1, backup2, backup3]
|
|
scheme.handleNaming(backups)
|
|
assert backup1.getStatusDetail() == ['Day 1 of 2']
|
|
assert backup2.getStatusDetail() == ['Day 2 of 2']
|
|
assert backup3.getStatusDetail() is None
|
|
|
|
|
|
def getRemovalOrder(scheme, toCheck):
|
|
backups = list(toCheck)
|
|
removed = []
|
|
while True:
|
|
oldest = scheme.getOldest(backups)
|
|
if not oldest:
|
|
break
|
|
removed.append(oldest.date())
|
|
backups.remove(oldest)
|
|
return removed
|
|
|
|
|
|
def assertRemovalOrder(scheme, toCheck, expected):
|
|
backups = list(toCheck)
|
|
removed = []
|
|
index = 0
|
|
time = scheme.time
|
|
while True:
|
|
reason, oldest = scheme.getOldest(backups)
|
|
if index >= len(expected):
|
|
if oldest is not None:
|
|
fail("at index {0}, expected 'None' but got {1}".format(
|
|
index, time.toLocal(oldest.date())))
|
|
break
|
|
if oldest.date() != expected[index]:
|
|
fail("at index {0}, expected {1} but got {2}".format(
|
|
index, time.toLocal(expected[index]), time.toLocal(oldest.date())))
|
|
removed.append(oldest.date())
|
|
backups.remove(oldest)
|
|
index += 1
|
|
return removed
|
|
|
|
|
|
def makeBackup(slug, date, name=None, ignore=False) -> Backup:
|
|
if not name:
|
|
name = slug
|
|
return DummyBackup(name, date.astimezone(tzutc()), "src", slug, ignore=ignore)
|
|
|
|
|
|
def simulate(start: datetime, end: datetime, scheme: GenerationalScheme, backups=[]):
|
|
today = start
|
|
while today <= end:
|
|
backups.append(makeBackup("test", today))
|
|
test = scheme.getOldest(backups)
|
|
if test is None:
|
|
pass
|
|
reason, oldest = test
|
|
while oldest is not None:
|
|
backups.remove(oldest)
|
|
test = scheme.getOldest(backups)
|
|
if test is None:
|
|
pass
|
|
reason, oldest = test
|
|
today = today + timedelta(hours=27)
|
|
today = scheme.time.local(today.year, today.month, today.day)
|
|
return backups
|