Add a consistent digest and tests

This commit is contained in:
Mikhail f. Shiryaev 2023-11-06 16:12:48 +01:00
parent 4fe3d10604
commit 53033d2347
No known key found for this signature in database
GPG Key ID: 4B02ED204C7D93F4
8 changed files with 155 additions and 1 deletions

View File

@ -46,7 +46,7 @@ def digest_path(path: Path, hash_object: Optional[HASH] = None) -> HASH:
def digest_paths(paths: Iterable[Path], hash_object: Optional[HASH] = None) -> HASH:
"""Calculates aggregated md5 (or updates existing hash_object) hash of passed paths.
The order matters"""
The order is processed as given"""
hash_object = hash_object or md5()
for path in paths:
if path.exists():
@ -54,6 +54,15 @@ def digest_paths(paths: Iterable[Path], hash_object: Optional[HASH] = None) -> H
return hash_object
def digest_consistent_paths(
paths: Iterable[Path], hash_object: Optional[HASH] = None
) -> HASH:
"""Calculates aggregated md5 (or updates existing hash_object) hash of passed paths.
The order doesn't matter, paths are converted to `absolute` and ordered before
calculation"""
return digest_paths(sorted(p.absolute() for p in paths), hash_object)
def digest_script(path_str: str) -> HASH:
"""Accepts value of the __file__ executed script and calculates the md5 hash for it"""
path = Path(path_str)

139
tests/ci/test_digest.py Normal file
View File

@ -0,0 +1,139 @@
#!/usr/bin/env python
import unittest
from hashlib import md5
from pathlib import Path
import digest_helper as dh
_12 = b"12\n"
_13 = b"13\n"
# pylint:disable=protected-access
class TestDigests(unittest.TestCase):
tests_dir = Path("tests/digests")
broken_link = tests_dir / "broken-symlink"
empty_digest = "d41d8cd98f00b204e9800998ecf8427e"
def test__digest_file(self):
hash_tested = md5()
with self.assertRaises(
AssertionError, msg="_digest_file shouldn't work with dirs"
):
dh._digest_file(self.tests_dir, hash_tested)
with self.assertRaises(
AssertionError, msg="_digest_file shouldn't work with broken links"
):
dh._digest_file(self.broken_link, hash_tested)
# file with content '12\n'
hash_expected = md5()
hash_expected.update(_12)
dh._digest_file(self.tests_dir / "12", hash_tested)
self.assertEqual(hash_expected.digest(), hash_tested.digest())
# symlink to '12\n'
hash_tested = md5()
dh._digest_file(self.tests_dir / "symlink-12", hash_tested)
self.assertEqual(hash_expected.digest(), hash_tested.digest())
def test__digest_directory(self):
hash_tested = md5()
with self.assertRaises(
AssertionError, msg="_digest_directory shouldn't work with files"
):
dh._digest_directory(self.tests_dir / "12", hash_tested)
with self.assertRaises(
AssertionError, msg="_digest_directory shouldn't work with broken links"
):
dh._digest_file(self.broken_link, hash_tested)
# dir1
hash_expected = md5()
hash_expected.update(_12)
dh._digest_directory(self.tests_dir / "dir1", hash_tested)
self.assertEqual(hash_expected.digest(), hash_tested.digest())
# dir2 contains 12 and 13
hash_expected.update(_13)
hash_tested = md5()
dh._digest_directory(self.tests_dir / "dir2", hash_tested)
self.assertEqual(hash_expected.digest(), hash_tested.digest())
# dir3 is symlink to dir2
hash_tested = md5()
dh._digest_directory(self.tests_dir / "dir3", hash_tested)
self.assertEqual(hash_expected.digest(), hash_tested.digest())
def test_digest_path(self):
# test broken link does nothing
self.assertEqual(
self.empty_digest, dh.digest_path(self.broken_link).hexdigest()
)
# Test file works fine
hash_expected = md5()
hash_expected.update(_12)
self.assertEqual(
hash_expected.digest(), dh.digest_path(self.tests_dir / "12").digest()
)
# Test directory works fine
hash_expected = md5()
hash_expected.update(_12)
self.assertEqual(
hash_expected.digest(), dh.digest_path(self.tests_dir / "dir1").digest()
)
# Test existed hash is updated from symlink dir3
hash_tested = hash_expected.copy()
dh.digest_path(self.tests_dir / "dir3", hash_tested)
hash_expected = md5()
hash_expected.update(_12 + _12 + _13)
self.assertEqual(hash_expected.digest(), hash_tested.digest())
# Test the full content of the following structure
# tests/digests
# ├── 12
# ├── dir1
# │   └── 12
# ├── dir2
# │   ├── 12
# │   └── 13
# ├── dir3 -> dir2
# └── symlink-12 -> 12
hash_expected = md5()
hash_expected.update(_12 * 3 + (_13 + _12) * 2)
self.assertEqual(
hash_expected.digest(), dh.digest_path(self.tests_dir).digest()
)
def test_digest_paths(self):
# test paths order matters
hash_ordered = dh.digest_paths(
(self.tests_dir / d for d in ("dir1", "dir2", "dir3"))
)
hash_reversed = dh.digest_paths(
(self.tests_dir / d for d in ("dir3", "dir2", "dir1"))
)
hash_unordered = dh.digest_paths(
(self.tests_dir / d for d in ("dir3", "dir1", "dir2"))
)
self.assertNotEqual(hash_ordered.digest(), hash_unordered.digest())
self.assertNotEqual(hash_ordered.digest(), hash_reversed.digest())
self.assertNotEqual(hash_unordered.digest(), hash_reversed.digest())
def test_digest_consistent_paths(self):
# test paths order does not matter
hash_ordered = dh.digest_consistent_paths(
(self.tests_dir / d for d in ("dir1", "dir2", "dir3"))
)
hash_reversed = dh.digest_consistent_paths(
(self.tests_dir / d for d in ("dir3", "dir2", "dir1"))
)
self.assertEqual(hash_ordered.digest(), hash_reversed.digest())
@classmethod
def setUpClass(cls):
# create a broken symlink
(TestDigests.broken_link).symlink_to("non-existent-link")
@classmethod
def tearDownClass(cls):
(TestDigests.broken_link).unlink()

View File

@ -0,0 +1 @@
12

View File

@ -0,0 +1 @@
12

View File

@ -0,0 +1 @@
12

View File

@ -0,0 +1 @@
13

1
tests/ci/tests/digests/dir3 Symbolic link
View File

@ -0,0 +1 @@
dir2

View File

@ -0,0 +1 @@
12