2019-04-10 19:23:17 -07:00
|
|
|
"""
|
2019-04-14 14:56:09 -07:00
|
|
|
.. moduleauthor:: Eric Torres
|
2019-04-10 19:23:17 -07:00
|
|
|
|
|
|
|
Tests for the rbackup.struct.repository module.
|
|
|
|
"""
|
2019-04-11 22:12:08 -07:00
|
|
|
import re
|
2019-03-16 07:46:03 -07:00
|
|
|
import unittest
|
2019-04-16 22:49:01 -07:00
|
|
|
from pathlib import Path
|
2019-04-18 21:44:03 -07:00
|
|
|
from unittest.mock import DEFAULT, PropertyMock, patch
|
2019-03-16 07:46:03 -07:00
|
|
|
|
2019-03-28 12:11:52 -07:00
|
|
|
from hypothesis import given
|
2019-04-11 22:12:08 -07:00
|
|
|
from hypothesis.strategies import from_regex, lists, text
|
2019-04-10 18:03:58 -07:00
|
|
|
|
2019-04-09 17:45:38 -07:00
|
|
|
from rbackup.struct.repository import Repository
|
|
|
|
from rbackup.struct.snapshot import Snapshot
|
2019-03-16 07:46:03 -07:00
|
|
|
|
|
|
|
# ========== Constants ==========
|
2019-04-09 17:45:38 -07:00
|
|
|
TESTING_PACKAGE = "rbackup.struct"
|
2019-04-16 17:14:27 -07:00
|
|
|
TESTING_MODULE = f"{TESTING_PACKAGE}.repository"
|
2019-03-28 12:11:52 -07:00
|
|
|
SS_MODULE = f"{TESTING_PACKAGE}.snapshot"
|
2019-03-16 07:46:03 -07:00
|
|
|
|
2019-04-18 22:04:16 -07:00
|
|
|
VALID_SNAPSHOT_NAME = r"[\w._+-]+[^/]*"
|
2019-03-16 07:46:03 -07:00
|
|
|
|
|
|
|
|
|
|
|
# ========== Integration Tests ==========
|
2019-05-01 12:32:02 -07:00
|
|
|
class TestDunderMethods(unittest.TestCase):
|
|
|
|
"""Test dunder methods of the Repository.
|
2019-04-10 18:13:13 -07:00
|
|
|
|
|
|
|
Mocked Modules/Classes
|
|
|
|
----------------------
|
|
|
|
rbackup.struct.repository.Snapshot
|
|
|
|
|
|
|
|
Mocked Attributes
|
|
|
|
-----------------
|
|
|
|
* Repository.read_metadata
|
|
|
|
* Repository.write_metadata
|
|
|
|
"""
|
2019-03-29 15:38:37 -07:00
|
|
|
|
2019-03-20 00:23:25 -07:00
|
|
|
def setUp(self):
|
2019-04-16 22:49:01 -07:00
|
|
|
self.patched_path = patch.multiple(
|
|
|
|
Path, exists=DEFAULT, mkdir=DEFAULT, symlink_to=DEFAULT, touch=DEFAULT
|
2019-03-29 15:38:37 -07:00
|
|
|
)
|
2019-04-16 22:49:01 -07:00
|
|
|
self.patched_metadata = patch.multiple(
|
|
|
|
Repository, read_metadata=DEFAULT, write_metadata=DEFAULT
|
2019-04-10 18:17:21 -07:00
|
|
|
)
|
2019-03-28 12:11:52 -07:00
|
|
|
self.patched_snapshot = patch(
|
|
|
|
f"{TESTING_PACKAGE}.repository.Snapshot", spec_set=Snapshot
|
|
|
|
)
|
|
|
|
|
2019-03-29 15:38:37 -07:00
|
|
|
self.mocked_path = self.patched_path.start()
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_metadata = self.patched_metadata.start()
|
2019-03-28 12:11:52 -07:00
|
|
|
self.mocked_snapshot = self.patched_snapshot.start()
|
2019-03-30 14:14:41 -07:00
|
|
|
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_path["exists"].return_value = True
|
2019-03-30 14:14:41 -07:00
|
|
|
|
2019-04-11 22:18:42 -07:00
|
|
|
@given(lists(from_regex(VALID_SNAPSHOT_NAME, fullmatch=True), unique=True))
|
|
|
|
def test_dunder_len(self, snapshots):
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_metadata["read_metadata"].return_value = snapshots.copy()
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-04-10 18:17:21 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
self.assertEqual(len(repo), len(snapshots))
|
2019-04-10 18:17:21 -07:00
|
|
|
self.assertEqual(len(repo.snapshots), len(snapshots))
|
2019-03-29 15:38:37 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
repo.create_snapshot()
|
|
|
|
|
|
|
|
self.assertEqual(len(repo), len(snapshots) + 1)
|
|
|
|
self.assertEqual(len(repo.snapshots), len(snapshots) + 1)
|
|
|
|
|
|
|
|
@given(from_regex(VALID_SNAPSHOT_NAME, fullmatch=True))
|
2019-04-11 22:18:42 -07:00
|
|
|
def test_dunder_contains(self, name):
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_metadata["read_metadata"].return_value = []
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-03-28 12:11:52 -07:00
|
|
|
|
2019-04-10 18:17:21 -07:00
|
|
|
self.assertFalse(name in repo)
|
2019-05-01 12:32:02 -07:00
|
|
|
repo.create_snapshot(name)
|
|
|
|
self.assertTrue(name in repo)
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
patch.stopall()
|
|
|
|
|
|
|
|
|
|
|
|
class TestProperties(unittest.TestCase):
|
|
|
|
"""Test properties of the Repository before running create_snapshot().
|
|
|
|
|
|
|
|
Mocked Modules/Classes
|
|
|
|
----------------------
|
|
|
|
rbackup.struct.repository.Snapshot
|
|
|
|
|
|
|
|
Mocked Attributes
|
|
|
|
-----------------
|
|
|
|
* Repository.read_metadata
|
|
|
|
* Repository.write_metadata
|
|
|
|
"""
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.patched_path = patch.multiple(
|
|
|
|
Path, exists=DEFAULT, mkdir=DEFAULT, symlink_to=DEFAULT, touch=DEFAULT
|
|
|
|
)
|
|
|
|
self.patched_metadata = patch.multiple(
|
|
|
|
Repository, read_metadata=DEFAULT, write_metadata=DEFAULT
|
|
|
|
)
|
|
|
|
self.patched_snapshot = patch(
|
|
|
|
f"{TESTING_PACKAGE}.repository.Snapshot", spec_set=Snapshot
|
|
|
|
)
|
|
|
|
|
|
|
|
self.mocked_path = self.patched_path.start()
|
|
|
|
self.mocked_metadata = self.patched_metadata.start()
|
|
|
|
self.mocked_snapshot = self.patched_snapshot.start()
|
|
|
|
|
|
|
|
self.mocked_path["exists"].return_value = True
|
|
|
|
|
|
|
|
@given(lists(from_regex(VALID_SNAPSHOT_NAME, fullmatch=True), unique=True))
|
|
|
|
def test_empty(self, snapshots):
|
|
|
|
self.mocked_metadata["read_metadata"].return_value = snapshots.copy()
|
|
|
|
repo = Repository("/tmp/backup")
|
|
|
|
|
|
|
|
if not snapshots:
|
|
|
|
self.assertTrue(repo.empty)
|
|
|
|
else:
|
|
|
|
self.assertFalse(repo.empty)
|
2019-03-28 12:11:52 -07:00
|
|
|
|
2019-04-10 18:17:21 -07:00
|
|
|
@given(text())
|
|
|
|
def test_valid_name(self, name):
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_metadata["read_metadata"].return_value = []
|
2019-03-29 15:38:37 -07:00
|
|
|
|
2019-04-11 22:12:08 -07:00
|
|
|
if not re.match(VALID_SNAPSHOT_NAME, name):
|
2019-04-10 18:17:21 -07:00
|
|
|
self.assertFalse(Repository.is_valid_snapshot_name(name))
|
2019-03-29 15:38:37 -07:00
|
|
|
else:
|
2019-04-10 18:17:21 -07:00
|
|
|
self.assertTrue(Repository.is_valid_snapshot_name(name))
|
2019-03-28 12:11:52 -07:00
|
|
|
|
2019-04-10 18:17:21 -07:00
|
|
|
def test_snapshots_returns_empty_list(self):
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-04-11 22:18:42 -07:00
|
|
|
self.assertListEqual(repo.snapshots, [])
|
|
|
|
|
|
|
|
@given(
|
|
|
|
lists(from_regex(VALID_SNAPSHOT_NAME, fullmatch=True), min_size=1, unique=True)
|
|
|
|
)
|
|
|
|
def snapshots_property_contains_snapshot_objects(self, snapshots):
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_metadata["read_metadata"].return_value = snapshots
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-04-11 22:18:42 -07:00
|
|
|
|
|
|
|
self.assertTrue(all(isinstance(p, Snapshot) for p in repo))
|
2019-03-28 12:11:52 -07:00
|
|
|
|
|
|
|
def tearDown(self):
|
2019-04-17 11:53:05 -07:00
|
|
|
patch.stopall()
|
2019-03-28 12:11:52 -07:00
|
|
|
|
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
class TestRepositoryCreateSnapshotNormalCases(unittest.TestCase):
|
2019-04-10 18:13:13 -07:00
|
|
|
"""Test properties of the Repository after running create_snapshot().
|
|
|
|
|
|
|
|
Mocked Modules/Classes
|
|
|
|
----------------------
|
|
|
|
rbackup.struct.repository.Snapshot
|
|
|
|
|
|
|
|
Mocked Attributes
|
|
|
|
-----------------
|
|
|
|
* Repository.read_metadata
|
|
|
|
* Repository.write_metadata
|
|
|
|
"""
|
2019-03-29 15:38:37 -07:00
|
|
|
|
2019-03-17 18:21:04 -07:00
|
|
|
def setUp(self):
|
2019-04-16 22:49:01 -07:00
|
|
|
self.patched_path = patch.multiple(
|
2019-05-01 12:32:02 -07:00
|
|
|
Path, exists=DEFAULT, mkdir=DEFAULT, symlink_to=DEFAULT, touch=DEFAULT
|
2019-04-10 18:17:21 -07:00
|
|
|
)
|
2019-04-16 22:49:01 -07:00
|
|
|
self.patched_metadata = patch.multiple(
|
2019-05-01 12:32:02 -07:00
|
|
|
Repository, read_metadata=DEFAULT, write_metadata=DEFAULT
|
2019-04-10 18:17:21 -07:00
|
|
|
)
|
2019-03-28 12:11:52 -07:00
|
|
|
self.patched_snapshot = patch(
|
2019-05-01 12:32:02 -07:00
|
|
|
f"{TESTING_PACKAGE}.repository.Snapshot", spec_set=Snapshot
|
2019-03-28 12:11:52 -07:00
|
|
|
)
|
2019-03-18 10:36:30 -07:00
|
|
|
|
2019-03-29 15:38:37 -07:00
|
|
|
self.mocked_path = self.patched_path.start()
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_metadata = self.patched_metadata.start()
|
2019-03-28 12:11:52 -07:00
|
|
|
self.mocked_snapshot = self.patched_snapshot.start()
|
2019-04-16 22:49:01 -07:00
|
|
|
|
|
|
|
self.mocked_path["exists"].return_value = True
|
2019-03-18 10:36:30 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
def test_returns_snapshot_object(self):
|
|
|
|
self.mocked_metadata["read_metadata"].return_value = []
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-03-17 18:21:04 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
self.assertIsInstance(repo.create_snapshot(), Snapshot)
|
2019-03-17 18:21:04 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
def tearDown(self):
|
|
|
|
patch.stopall()
|
2019-03-17 18:21:04 -07:00
|
|
|
|
2019-04-11 22:12:08 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
class TestRepositoryCreateSnapshotSpecialCases(unittest.TestCase):
|
|
|
|
"""Test properties of the Repository after running create_snapshot().
|
2019-04-11 22:12:08 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
Mocked Modules/Classes
|
|
|
|
----------------------
|
|
|
|
rbackup.struct.repository.Snapshot
|
2019-03-20 00:23:25 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
Mocked Attributes
|
|
|
|
-----------------
|
|
|
|
* Repository.read_metadata
|
|
|
|
* Repository.write_metadata
|
|
|
|
"""
|
2019-03-20 00:23:25 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
def setUp(self):
|
|
|
|
self.patched_path = patch.multiple(
|
|
|
|
Path, exists=DEFAULT, mkdir=DEFAULT, symlink_to=DEFAULT, touch=DEFAULT
|
|
|
|
)
|
|
|
|
self.patched_metadata = patch.multiple(
|
|
|
|
Repository, read_metadata=DEFAULT, write_metadata=DEFAULT
|
|
|
|
)
|
|
|
|
self.patched_snapshot = patch(
|
|
|
|
f"{TESTING_PACKAGE}.repository.Snapshot", spec_set=Snapshot
|
|
|
|
)
|
2019-03-20 00:23:25 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
self.mocked_path = self.patched_path.start()
|
|
|
|
self.mocked_metadata = self.patched_metadata.start()
|
|
|
|
self.mocked_snapshot = self.patched_snapshot.start()
|
2019-03-18 10:36:30 -07:00
|
|
|
|
2019-05-01 12:32:02 -07:00
|
|
|
self.mocked_path["exists"].return_value = True
|
2019-04-11 22:18:42 -07:00
|
|
|
|
|
|
|
def test_create_duplicate_snapshot(self):
|
|
|
|
# Test that if a snapshot is a duplicate, then return that duplicate snapshot
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_metadata["read_metadata"].return_value = []
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-04-11 22:18:42 -07:00
|
|
|
name = "new-snapshot"
|
|
|
|
|
|
|
|
first = repo.create_snapshot(name)
|
|
|
|
second = repo.create_snapshot(name)
|
|
|
|
|
|
|
|
self.assertIs(first, second)
|
2019-04-10 18:17:21 -07:00
|
|
|
self.assertTrue(name in repo)
|
2019-04-11 22:13:14 -07:00
|
|
|
self.assertEqual(len(repo), 1)
|
2019-03-18 10:36:30 -07:00
|
|
|
|
2019-03-28 12:11:52 -07:00
|
|
|
def tearDown(self):
|
2019-04-17 11:53:05 -07:00
|
|
|
patch.stopall()
|
2019-04-11 22:13:14 -07:00
|
|
|
|
|
|
|
|
|
|
|
class TestRepositoryCleanup(unittest.TestCase):
|
|
|
|
"""Test that repository cleanup works properly.
|
|
|
|
|
|
|
|
Test cases
|
|
|
|
----------
|
|
|
|
* Function stops if system is not symlink attack-resistant
|
|
|
|
* If symlink attack-resistant, then only delete metadata when all others false
|
|
|
|
* Function only deletes snapshots when told to
|
|
|
|
* Function only deletes repository directory when told to
|
|
|
|
"""
|
|
|
|
|
|
|
|
def setUp(self):
|
2019-04-16 22:49:01 -07:00
|
|
|
self.patched_path = patch.multiple(
|
|
|
|
Path,
|
|
|
|
exists=DEFAULT,
|
|
|
|
mkdir=DEFAULT,
|
|
|
|
symlink_to=DEFAULT,
|
|
|
|
touch=DEFAULT,
|
|
|
|
unlink=DEFAULT,
|
|
|
|
)
|
|
|
|
self.patched_metadata = patch.multiple(
|
|
|
|
Repository, read_metadata=DEFAULT, write_metadata=DEFAULT
|
|
|
|
)
|
2019-04-11 22:13:14 -07:00
|
|
|
self.patched_snapshot = patch(
|
|
|
|
f"{TESTING_PACKAGE}.repository.Snapshot", spec_set=Snapshot
|
|
|
|
)
|
2019-04-16 22:49:01 -07:00
|
|
|
self.patched_shutil = patch.multiple(f"{TESTING_MODULE}.shutil", rmtree=DEFAULT)
|
2019-04-11 22:13:14 -07:00
|
|
|
|
|
|
|
self.mocked_path = self.patched_path.start()
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_metadata = self.patched_metadata.start()
|
2019-04-11 22:13:14 -07:00
|
|
|
self.mocked_shutil = self.patched_shutil.start()
|
|
|
|
self.mocked_snapshot = self.patched_snapshot.start()
|
|
|
|
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_shutil["rmtree"].avoids_symlink_attacks = True
|
2019-04-12 07:51:08 -07:00
|
|
|
|
2019-04-16 22:49:01 -07:00
|
|
|
def test_stops_on_non_symlink_resistant(self):
|
2019-04-18 21:44:03 -07:00
|
|
|
self.mocked_shutil["rmtree"].avoids_symlink_attacks = False
|
2019-04-16 17:14:27 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-04-11 22:13:14 -07:00
|
|
|
|
|
|
|
repo.cleanup(remove_snapshots=True)
|
|
|
|
|
2019-04-16 22:49:01 -07:00
|
|
|
self.mocked_path["unlink"].assert_not_called()
|
|
|
|
self.mocked_shutil["rmtree"].assert_not_called()
|
2019-04-11 22:13:14 -07:00
|
|
|
|
2019-04-18 21:44:03 -07:00
|
|
|
@patch.object(Repository, "snapshot_symlink", new_callable=PropertyMock)
|
|
|
|
@patch.object(Repository, "metadata_path", new_callable=PropertyMock)
|
|
|
|
def test_removes_metadata_by_default(
|
|
|
|
self, mocked_metadata_path, mocked_snapshot_symlink
|
|
|
|
):
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-04-12 07:51:08 -07:00
|
|
|
|
|
|
|
repo.cleanup()
|
|
|
|
|
2019-04-18 21:44:03 -07:00
|
|
|
mocked_metadata_path.return_value.unlink.assert_called_once()
|
|
|
|
mocked_snapshot_symlink.return_value.unlink.assert_called_once()
|
2019-04-12 07:51:08 -07:00
|
|
|
|
|
|
|
def test_removes_snapshots(self):
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-04-12 07:51:08 -07:00
|
|
|
|
|
|
|
repo.cleanup(remove_snapshots=True)
|
|
|
|
|
2019-04-18 21:44:03 -07:00
|
|
|
self.mocked_shutil["rmtree"].assert_called_once()
|
2019-04-12 07:51:08 -07:00
|
|
|
|
|
|
|
def test_removes_repo_dir(self):
|
2019-04-16 17:14:46 -07:00
|
|
|
repo = Repository("/tmp/backup")
|
2019-04-12 07:51:08 -07:00
|
|
|
|
|
|
|
repo.cleanup(remove_repo_dir=True)
|
|
|
|
|
2019-04-18 21:44:03 -07:00
|
|
|
self.mocked_shutil["rmtree"].assert_called_once()
|
2019-04-12 07:51:08 -07:00
|
|
|
|
2019-04-11 22:13:14 -07:00
|
|
|
def tearDown(self):
|
2019-04-17 11:53:05 -07:00
|
|
|
patch.stopall()
|