diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index a6988e89..2b81570b 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -355,6 +355,7 @@ def _data_directory_members( print( f" WARNING: data directory '{ref_key}' not cached at {local_path} -run refresh_data_dirs.py" ) + _MISSING_DATA_DIRS.add(ref_key) continue dd_dest = dd.get("destination", "") if base_dest and dd_dest: @@ -1640,6 +1641,7 @@ def generate_emulator_pack( print( f" Note: data directory '{ref}' required but not included (use refresh_data_dirs.py)" ) + _MISSING_DATA_DIRS.update(data_dir_notices) return ( zip_path @@ -2777,9 +2779,22 @@ def _given_flags(args) -> set[str]: return {flag for flag, on in flags.items() if on} +# Data directories a pack needed and the cache did not hold. The pack is +# written without them, so the run cannot exit as if it were complete: a +# refresh refused by a quota left the PPSSPP pack with 2 files, exit 0. +_MISSING_DATA_DIRS: set[str] = set() + + def main(): with contextlib.ExitStack() as held: _main(held) + if _MISSING_DATA_DIRS: + print( + "ERROR: packs written without data directories that are not cached: " + f"{', '.join(sorted(_MISSING_DATA_DIRS))} (run refresh_data_dirs.py)", + file=sys.stderr, + ) + sys.exit(1) def _main(held: contextlib.ExitStack) -> None: diff --git a/tests/test_core_file_fills_declaration.py b/tests/test_core_file_fills_declaration.py index 32899d8b..67e1f2de 100644 --- a/tests/test_core_file_fills_declaration.py +++ b/tests/test_core_file_fills_declaration.py @@ -9,6 +9,7 @@ frontend sees a file at the path. verify kept it MISSING, so the pack said from __future__ import annotations import hashlib +import contextlib import io import os import shutil @@ -18,6 +19,7 @@ import unittest import zipfile from contextlib import redirect_stdout from pathlib import Path +from unittest import mock import yaml @@ -141,5 +143,44 @@ class AFileDeclaredUnderAnotherName(unittest.TestCase): self.assertIn(("ROM", True), reported) + +class AMissingDataDirectoryFailsTheRun(unittest.TestCase): + """A refresh refused by a quota left the PPSSPP pack with 2 files and + the run exited 0; nothing downstream checks data directories.""" + + def test_the_pack_records_it_and_main_exits_non_zero(self): + import generate_pack as gp + + root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp")) + self.addCleanup(shutil.rmtree, root, True) + (root / "platforms").mkdir() + (root / "emulators").mkdir() + (root / "bios").mkdir() + (root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({ + "platform": "FakePlat", "verification_mode": "existence", + "base_destination": "system", "cores": [], + "systems": {"console-a": {"files": [], "data_directories": [ + {"ref": "assets", "destination": "assets"}, + ]}}, + })) + db = {"files": {}, "indexes": {"by_md5": {}, "by_name": {}, "by_crc32": {}, + "by_path_suffix": {}}} + gp._MISSING_DATA_DIRS.clear() + self.addCleanup(gp._MISSING_DATA_DIRS.clear) + with redirect_stdout(io.StringIO()): + gp.generate_pack( + "fakeplat", str(root / "platforms"), db, str(root / "bios"), + str(root / "out"), emulators_dir=str(root / "emulators"), + data_registry={"assets": {"local_cache": str(root / "absent")}}, + offline=True, + ) + self.assertEqual(gp._MISSING_DATA_DIRS, {"assets"}) + with mock.patch.object(gp, "_main", lambda held: None), \ + contextlib.redirect_stderr(io.StringIO()), \ + self.assertRaises(SystemExit) as stop: + gp.main() + self.assertEqual(stop.exception.code, 1) + + if __name__ == "__main__": unittest.main()