1- from kernels_data import Digest
1+ import pytest
2+
3+ from kernels_data import Digest , DigestValidationError , DigestViolation
24
35
46def test_hash_variant_hashes_file_with_sha256 (tmp_path ):
@@ -70,10 +72,11 @@ def test_validate_returns_no_violations_for_identical_digests(tmp_path):
7072 expected = Digest .hash_variant ("sha256" , tmp_path )
7173 actual = Digest .hash_variant ("sha256" , tmp_path )
7274
73- assert expected .validate (actual ) == []
75+ expected .validate (actual )
7476
7577
76- def test_validate_reports_missing_unknown_and_mismatched_files (tmp_path ):
78+ @pytest .fixture
79+ def mismatched_digests (tmp_path ):
7780 expected_dir = tmp_path / "expected"
7881 actual_dir = tmp_path / "actual"
7982 expected_dir .mkdir ()
@@ -91,12 +94,61 @@ def test_validate_reports_missing_unknown_and_mismatched_files(tmp_path):
9194
9295 expected = Digest .hash_variant ("sha256" , expected_dir )
9396 actual = Digest .hash_variant ("sha256" , actual_dir )
97+ return expected , actual
9498
95- messages = expected .validate (actual )
9699
97- assert "Missing file: gone.py" in messages
98- assert "Unknown file: extra.bin" in messages
99- assert any (
100- message .startswith ("File hash mismatch for shared.so" ) for message in messages
101- )
102- assert len (messages ) == 3
100+ def test_validate_raises_with_structured_violations (mismatched_digests ):
101+ expected , actual = mismatched_digests
102+
103+ with pytest .raises (DigestValidationError ) as exc_info :
104+ expected .validate (actual )
105+
106+ violations = exc_info .value .violations
107+ assert len (violations ) == 3
108+
109+ by_path = {v .path : v for v in violations }
110+
111+ assert isinstance (by_path ["gone.py" ], DigestViolation .MissingFile )
112+ assert isinstance (by_path ["extra.bin" ], DigestViolation .UnknownFile )
113+
114+ mismatch = by_path ["shared.so" ]
115+ assert isinstance (mismatch , DigestViolation .HashMismatch )
116+ assert mismatch .expected != mismatch .got
117+
118+
119+ def test_validate_violations_support_pattern_matching (mismatched_digests ):
120+ expected , actual = mismatched_digests
121+
122+ with pytest .raises (DigestValidationError ) as exc_info :
123+ expected .validate (actual )
124+
125+ violations = exc_info .value .violations
126+ assert len (violations ) == 3
127+
128+ for violation in violations :
129+ match violation :
130+ case DigestViolation .MissingFile (path = path ):
131+ assert path == "gone.py"
132+ case DigestViolation .UnknownFile (path = path ):
133+ assert path == "extra.bin"
134+ case DigestViolation .HashMismatch (path = path ):
135+ assert path == "shared.so"
136+ case _:
137+ pytest .fail (f"unexpected violation: { violation !r} " )
138+
139+
140+ def test_validate_error_str_lists_every_violation (mismatched_digests ):
141+ expected , actual = mismatched_digests
142+
143+ with pytest .raises (DigestValidationError ) as exc_info :
144+ expected .validate (actual )
145+
146+ message = str (exc_info .value )
147+ assert "Missing file: gone.py" in message
148+ assert "Unknown file: extra.bin" in message
149+ assert "File hash mismatch for shared.so" in message
150+
151+ # `str(violation)` reuses the same human-readable rendering.
152+ rendered = {str (v ) for v in exc_info .value .violations }
153+ assert "Missing file: gone.py" in rendered
154+ assert "Unknown file: extra.bin" in rendered
0 commit comments