Files
zfs-backup/test_zfs_backup.py
2022-12-06 14:59:55 +01:00

309 lines
10 KiB
Python

import pytest
from unittest import mock
from zfs_backup.snapshot import Snapshot
from zfs_backup.dataset import Dataset
from zfs_backup.zfs_path import ZFSPath
class TestZFSPath:
@pytest.mark.parametrize(
"elements",
[
["pool"],
["pool", "dataset1", "dataset2"],
],
)
def test__sanity_check_valid(self, elements):
ZFSPath._sanity_check(elements)
@pytest.mark.parametrize(
"elements",
[
[""],
["", "dataset1"],
["pool", "dataset1", ""],
["pool", "", "dataset2"],
],
)
def test__sanity_check_invalid(self, elements):
with pytest.raises(ValueError):
ZFSPath._sanity_check(elements)
@pytest.mark.parametrize(
"elements, snapshot_name, result",
[
[["pool"], None, ""],
[["pool", "dataset"], None, "dataset"],
[["pool", "dataset"], "snapshot", "dataset@snapshot"],
[
["pool", "dataset1", "dataset2"],
"snapshot",
"dataset1/dataset2@snapshot",
],
],
ids=[
"only pool",
"pool with one dataset",
"pool with dataset and snapshot",
"pool with two nested datasets and snapshot",
],
)
def test_name_without_pool(self, elements, snapshot_name, result):
instance = ZFSPath(elements, snapshot_name)
assert instance.name_without_pool == result
@pytest.mark.parametrize(
"elements, snapshot_name, result",
[
[["pool"], None, "pool"],
[["pool", "dataset"], None, "pool/dataset"],
[["pool", "dataset"], "snapshot", "pool/dataset@snapshot"],
[
["pool", "dataset1", "dataset2"],
"snapshot",
"pool/dataset1/dataset2@snapshot",
],
],
ids=[
"only pool",
"pool with one dataset",
"pool with dataset and snapshot",
"pool with two nested datasets and snapshot",
],
)
def test___repr__(self, elements, snapshot_name, result):
assert str(ZFSPath(elements, snapshot_name)) == result
@pytest.mark.parametrize(
"instance1, instance2, result",
[
[ZFSPath(["pool"]), ZFSPath(["pool"]), True],
[ZFSPath(["pool"]), ZFSPath(["tank"]), False],
[ZFSPath(["pool", "dataset"]), ZFSPath(["pool", "dataset"]), True],
[ZFSPath(["pool", "dataset"]), ZFSPath(["pool", "bla"]), False],
[
ZFSPath(["pool", "dataset"], "snapshot"),
ZFSPath(["pool", "dataset"], "snapshot"),
True,
],
[
ZFSPath(["pool", "dataset"], "snapshot"),
ZFSPath(["pool", "dataset"], "bla"),
False,
],
],
ids=[
"only pool equal",
"only pool not equal",
"dataset equal",
"dataset not equal",
"snapshot equal",
"snapshot not equal",
],
)
def test___eq__(self, instance1, instance2, result):
assert (instance1 == instance2) == result
@pytest.mark.parametrize(
"string, elements, snapshot_name",
[
["pool/dataset", ["pool", "dataset"], None],
["pool/dataset@snapshot", ["pool", "dataset"], "snapshot"],
[
"pool/dataset1/dataset2@snapshot",
["pool", "dataset1", "dataset2"],
"snapshot",
],
],
ids=[
"pool/dataset",
"pool/dataset@snapshot",
"pool/dataset1/dataset2@snapshot",
],
)
def test_from_string(self, string, elements, snapshot_name):
instance = ZFSPath.from_string(string)
assert instance._elements == elements
assert instance._snapshot_name == instance.snapshot_name == snapshot_name
@pytest.mark.parametrize(
"instance1, instance2",
[
[ZFSPath(["pool", "dataset"]), ZFSPath(["pool", "dataset"])],
[ZFSPath(["pool", "dataset"], "snapshot"), ZFSPath(["pool", "dataset"])],
],
ids=[
"pool with dataset",
"pool with dataset and snapshot",
],
)
def test_dataset(self, instance1, instance2):
assert instance1.dataset == instance2
@pytest.mark.parametrize(
"instance1, instance2",
[
[ZFSPath(["pool"]), ZFSPath(["tank"])],
[ZFSPath(["pool", "dataset"]), ZFSPath(["tank", "dataset"])],
[
ZFSPath(["pool", "dataset"], "snapshot"),
ZFSPath(["tank", "dataset"], "snapshot"),
],
],
ids=[
"pool",
"pool with dataset",
"pool with dataset and snapshot",
],
)
def test_replace_pool(self, instance1, instance2):
assert instance1.replace_pool(instance2.pool_name) == instance2
class TestZFS:
@pytest.mark.parametrize("string", ["tank", "tank/ROOT", "tank/ROOT/default"])
def test_get_snapshots(self, string):
path = ZFSPath.from_string(string)
ds = Dataset(path)
for snapshot in ds.snapshots:
assert snapshot.dataset == ds
class TestDataset:
@pytest.mark.parametrize("zfspath", ["tank", "tank/ROOT", "tank/ROOT/default"])
def test__clean_old_snapshots_for_interval(self, zfspath):
"""Check that only snapshots that are direct children of the dataset are found during cleanup."""
def validate_snapshot_selection(snapshot: Snapshot):
print(snapshot) # show output with "pytest -s"
str(snapshot).startswith(f"{zfspath}@")
dataset = Dataset(ZFSPath.from_string(zfspath))
with mock.patch.object(
Snapshot, "destroy", lambda snapshot: validate_snapshot_selection(snapshot)
):
dataset._clean_old_snapshots_for_interval("daily", 999)
def test__clean_old_snapshots_for_interval_2(self):
def validate_snapshot_selection(snapshot: Snapshot):
nonlocal counter
print(snapshot)
str(snapshot).startswith("tank/ROOT@")
counter = counter + 1
snapshots = gen_snapshots_for_tank_ROOT()
MAX_COUNT = 30
with mock.patch.object(Dataset, "snapshots", snapshots):
with mock.patch.object(
Snapshot,
"destroy",
lambda snapshot: validate_snapshot_selection(snapshot),
):
ds = Dataset.from_string("tank/ROOT")
counter = 0
ds._clean_old_snapshots_for_interval("monthly", MAX_COUNT)
assert counter <= MAX_COUNT # not enough snapshots to reach MAX_COUNT
counter = 0
ds._clean_old_snapshots_for_interval("daily", MAX_COUNT)
assert counter <= MAX_COUNT
@pytest.mark.parametrize("max_count", [10, 40])
def test__get_oldest_snapshots_exceeding_max_count(self, max_count):
snapshots = [
snap for snap in gen_snapshots_for_tank_ROOT() if "daily" in str(snap)
]
ORIGINAL_COUNT = 31
assert len(snapshots) == ORIGINAL_COUNT
expected_result_len = ORIGINAL_COUNT - max_count
if expected_result_len < 0:
expected_result_len = 0
result = Dataset._get_oldest_snapshots_exceeding_max_count(snapshots, max_count)
assert len(result) == expected_result_len
def gen_snapshots_for_tank_ROOT() -> list[Snapshot]:
return [
Snapshot.from_string(line) for line in SNAPSHOTS.split("\n") if line.strip()
]
SNAPSHOTS = """
tank/ROOT@znap_2021-11-30-2300_monthly
tank/ROOT@znap_2021-12-31-2300_monthly
tank/ROOT@znap_2022-01-31-2300_monthly
tank/ROOT@znap_2022-02-28-2300_monthly
tank/ROOT@znap_2022-03-13-2300_weekly
tank/ROOT@znap_2022-03-20-2300_weekly
tank/ROOT@znap_2022-03-27-2200_weekly
tank/ROOT@znap_2022-03-31-2200_monthly
tank/ROOT@znap_2022-04-03-2200_weekly
tank/ROOT@znap_2022-04-03-2200_daily
tank/ROOT@znap_2022-04-04-2200_daily
tank/ROOT@znap_2022-04-05-2200_daily
tank/ROOT@znap_2022-04-06-2200_daily
tank/ROOT@znap_2022-04-07-2200_daily
tank/ROOT@znap_2022-04-08-2200_daily
tank/ROOT@znap_2022-04-09-2200_daily
tank/ROOT@znap_2022-04-10-2200_weekly
tank/ROOT@znap_2022-04-10-2200_daily
tank/ROOT@znap_2022-04-11-2200_daily
tank/ROOT@znap_2022-04-12-2200_daily
tank/ROOT@znap_2022-04-13-2200_daily
tank/ROOT@znap_2022-04-14-2200_daily
tank/ROOT@znap_2022-04-15-2200_daily
tank/ROOT@znap_2022-04-16-2200_daily
tank/ROOT@znap_2022-04-17-2200_daily
tank/ROOT@znap_2022-04-17-2200_weekly
tank/ROOT@znap_2022-04-18-2200_daily
tank/ROOT@znap_2022-04-19-2200_daily
tank/ROOT@znap_2022-04-20-2200_daily
tank/ROOT@znap_2022-04-21-2200_daily
tank/ROOT@znap_2022-04-22-2200_daily
tank/ROOT@znap_2022-04-23-2200_daily
tank/ROOT@znap_2022-04-24-2200_daily
tank/ROOT@znap_2022-04-24-2200_weekly
tank/ROOT@znap_2022-04-25-2200_daily
tank/ROOT@znap_2022-04-26-2200_daily
tank/ROOT@znap_2022-04-27-2200_daily
tank/ROOT@znap_2022-04-28-2200_daily
tank/ROOT@znap_2022-04-29-2200_daily
tank/ROOT@znap_2022-04-30-2200_daily
tank/ROOT@znap_2022-04-30-2200_monthly
tank/ROOT@znap_2022-05-01-2200_daily
tank/ROOT@znap_2022-05-01-2200_weekly
tank/ROOT@znap_2022-05-02-2200_daily
tank/ROOT@znap_2022-05-03-0900_hourly
tank/ROOT@znap_2022-05-03-1000_hourly
tank/ROOT@znap_2022-05-03-1100_hourly
tank/ROOT@znap_2022-05-03-1200_hourly
tank/ROOT@znap_2022-05-03-1300_hourly
tank/ROOT@znap_2022-05-03-1400_hourly
tank/ROOT@znap_2022-05-03-1500_hourly
tank/ROOT@znap_2022-05-03-1600_hourly
tank/ROOT@znap_2022-05-03-1700_hourly
tank/ROOT@znap_2022-05-03-1800_hourly
tank/ROOT@znap_2022-05-03-1900_hourly
tank/ROOT@znap_2022-05-03-2000_hourly
tank/ROOT@znap_2022-05-03-2100_hourly
tank/ROOT@znap_2022-05-03-2200_hourly
tank/ROOT@znap_2022-05-03-2200_daily
tank/ROOT@znap_2022-05-03-2300_hourly
tank/ROOT@znap_2022-05-04-0000_hourly
tank/ROOT@znap_2022-05-04-0100_hourly
tank/ROOT@znap_2022-05-04-0200_hourly
tank/ROOT@znap_2022-05-04-0300_hourly
tank/ROOT@znap_2022-05-04-0400_hourly
tank/ROOT@znap_2022-05-04-0500_hourly
tank/ROOT@znap_2022-05-04-0600_hourly
tank/ROOT@znap_2022-05-04-0700_hourly
tank/ROOT@znap_2022-05-04-0715_frequent
tank/ROOT@znap_2022-05-04-0730_frequent
tank/ROOT@znap_2022-05-04-0745_frequent
tank/ROOT@znap_2022-05-04-0800_hourly
tank/ROOT@znap_2022-05-04-0800_frequent
"""