From 8bd2083e8012f5f50ad87663bdf33cdbb8c1beaa Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:23:15 +0200 Subject: [PATCH] feat: report hash proof in existence packs --- scripts/generate_pack.py | 11 ++++++++++ tests/test_e2e.py | 45 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index ffba4b4e..40481254 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -1353,6 +1353,17 @@ def generate_pack( file_status[dedup_key] = "untested" file_reasons[dedup_key] = "hash mismatch" else: + if status == "hash_mismatch" and local_path: + declared = file_entry.get("md5", "") or file_entry.get( + "sha1", "" + ) + actual = compute_hashes(local_path) + file_reasons.setdefault( + dedup_key, + f"packed per {platform_display} existence check; " + f"declared hash {declared}, file md5 " + f"{actual['md5']} sha1 {actual['sha1']}", + ) file_status.setdefault(dedup_key, "ok") # Emulator-level validation: informational only for platform packs. diff --git a/tests/test_e2e.py b/tests/test_e2e.py index acde81af..bbc1d5f2 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -2089,6 +2089,51 @@ class TestE2E(unittest.TestCase): # Verify _Required tag in filename self.assertIn("_Required_", os.path.basename(zip_path)) + def test_130b_existence_pack_reports_hash_mismatch(self): + """Existence mode packs a mismatched file but prints the proof.""" + import contextlib + import io + + from generate_pack import generate_pack + + output_dir = os.path.join(self.root, "pack_mismatch") + os.makedirs(output_dir, exist_ok=True) + config = { + "platform": "MismatchTest", + "verification_mode": "existence", + "base_destination": "system", + "systems": { + "test-sys": { + "files": [ + { + "name": "wrong_hash.bin", + "destination": "wrong_hash.bin", + "md5": "ffffffffffffffffffffffffffffffff", + "required": True, + }, + ], + }, + }, + } + with open(os.path.join(self.platforms_dir, "test_mismatch.yml"), "w") as fh: + yaml.dump(config, fh) + out = io.StringIO() + with contextlib.redirect_stdout(out): + zip_path = generate_pack( + "test_mismatch", + self.platforms_dir, + self.db, + self.bios_dir, + output_dir, + ) + self.assertIsNotNone(zip_path) + with zipfile.ZipFile(zip_path) as zf: + names = zf.namelist() + self.assertTrue(any("wrong_hash.bin" in n for n in names)) + report = out.getvalue() + self.assertIn("DISCREPANCY", report) + self.assertIn("declared hash ffffffffffffffffffffffffffffffff", report) + def test_131_required_only_keeps_default_required(self): """--required-only keeps files with no required field (default = required).""" from generate_pack import generate_pack