diff --git a/scripts/check_freshness.py b/scripts/check_freshness.py index fac97bc71..55fa8a67e 100644 --- a/scripts/check_freshness.py +++ b/scripts/check_freshness.py @@ -58,6 +58,7 @@ from common import ( # noqa: E402 list_registered_platforms, load_emulator_profiles, load_platform_registry, + require_directory, upstream_profile_index, write_text_atomic, yaml_load, @@ -1789,6 +1790,7 @@ def main() -> int: if unknown: parser.error(unknown_areas_message(unknown)) + require_directory(args.platforms_dir, "--platforms-dir") platforms_dir = Path(args.platforms_dir) profiles = load_emulator_profiles(args.emulators_dir, skip_aliases=False) findings: list[Finding] = [] diff --git a/scripts/check_release.py b/scripts/check_release.py index e53e5b560..4fc8c1932 100644 --- a/scripts/check_release.py +++ b/scripts/check_release.py @@ -33,7 +33,7 @@ from pathlib import Path sys.path.insert(0, os.path.dirname(__file__)) from build_port import ARCHIVES from check_release_assets import digests_of -from common import list_release_pack_platforms +from common import list_release_pack_platforms, require_directory from largefiles import LARGE_FILES_REPO from release_record import RECORD, load_record @@ -208,6 +208,7 @@ def main() -> int: ) parser.add_argument("--json", action="store_true", help="JSON output") args = parser.parse_args() + require_directory(args.platforms_dir, "--platforms-dir") record = load_record(args.record) tag = args.tag or record.get("tag") diff --git a/scripts/common.py b/scripts/common.py index 7e78ead96..a8833cecf 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -508,7 +508,7 @@ def list_registered_platforms( """ registry_path = os.path.join(platforms_dir, "_registry.yml") if not os.path.exists(registry_path): - raise FileNotFoundError(f"no platform registry at {registry_path}") + raise InputMissing(f"no platform registry at {registry_path}") with open(registry_path) as f: registry = yaml_load(f) or {} platforms = [] diff --git a/scripts/generate_truth.py b/scripts/generate_truth.py index 37e2728fc..ac5856667 100644 --- a/scripts/generate_truth.py +++ b/scripts/generate_truth.py @@ -21,6 +21,7 @@ from common import ( load_emulator_profiles, load_platform_config, load_target_config, + require_directory, require_platform, require_yaml, truth_target_dir, @@ -72,6 +73,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> None: args = parse_args(argv) + require_directory(args.platforms_dir, "--platforms-dir") registry_path = os.path.join(args.platforms_dir, "_registry.yml") with open(registry_path) as f: diff --git a/scripts/release_notes.py b/scripts/release_notes.py index 3ff155eee..cf3903cae 100644 --- a/scripts/release_notes.py +++ b/scripts/release_notes.py @@ -26,6 +26,7 @@ from common import ( list_release_pack_platforms, load_platform_config, load_platform_registry, + require_directory, write_text_atomic, ) from siteguides import install_command @@ -162,6 +163,7 @@ def main() -> int: parser.add_argument("--output", required=True, type=Path, help="notes file to write, passed to gh release create --notes-file") args = parser.parse_args() + require_directory(args.platforms_dir, "--platforms-dir") record = release_record.load_record(args.record) if not record.get("packs"): diff --git a/scripts/validate_pr.py b/scripts/validate_pr.py index cbc6fa1bd..cae324e65 100644 --- a/scripts/validate_pr.py +++ b/scripts/validate_pr.py @@ -34,6 +34,7 @@ from common import ( load_emulator_profiles, load_platform_config, parse_md5_list, + require_directory, ) from hashing import parse_hash_list from nativemode import digest_algorithm @@ -798,6 +799,7 @@ def main() -> None: if os.path.realpath(os.getcwd()) != os.path.realpath(REPO_ROOT): args.files = [os.path.abspath(f) for f in args.files] work_from_repo_root(args, parser, ["db", "platforms_dir", "emulators_dir"]) + require_directory(args.platforms_dir, "--platforms-dir") files = _files_to_validate(args, parser) if not files: diff --git a/scripts/validate_site.py b/scripts/validate_site.py index c922ad414..6db66bc09 100644 --- a/scripts/validate_site.py +++ b/scripts/validate_site.py @@ -499,6 +499,9 @@ def main() -> None: help="stop listing after this many errors (default: %(default)s)", ) args = parser.parse_args() + from common import require_directory # noqa: PLC0415 + + require_directory(str(args.platforms_dir), "--platforms-dir") try: issues = validate_site(Path(args.site_dir), args.base, args.platforms_dir, args.record) diff --git a/tests/test_system_selection.py b/tests/test_system_selection.py index 56c16cba1..3d554abf3 100644 --- a/tests/test_system_selection.py +++ b/tests/test_system_selection.py @@ -331,6 +331,50 @@ class AMissingProfileDirIsRefused(unittest.TestCase): self.assertEqual(after, before) +class AMissingPlatformsDirIsRefused(unittest.TestCase): + """`verify.py --platforms-dir noplat` ended on a FileNotFoundError at the + bottom of a stack trace, and validate_pr read it as no platform at all + and exited 0.""" + + EXTRA = { + "auto_fetch": ["--all", "--dry-run"], + "check_release": ["--tag", "v1"], + "diff_truth": ["--all"], + "export_native": ["--all", "--output-dir", "{out}"], + "generate_pack": ["--platform", "retroarch", "--offline", "--output-dir", "{out}"], + "generate_site": ["--docs-dir", "{out}"], + "generate_truth": ["--all", "--output-dir", "{out}"], + "release_notes": ["--whats-new", "{notes}", "--output", "{out}/notes.md"], + "release_record": ["{out}", "--tag", "v1"], + "romset_dat_importer": ["--source", "mame", "--pack", "{dat}", "--output", "{out}/r.json"], + "validate_pr": ["bios/Sony/PlayStation/scph5501.bin"], + "validate_site": ["--site-dir", "{out}"], + "verify": ["--platform", "retroarch"], + } + + def test_every_script_taking_a_platforms_dir_refuses_a_missing_one(self): + scratch = tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") + self.addCleanup(scratch.cleanup) + root = Path(scratch.name) + (root / "empty.dat").write_bytes(b"") + (root / "notes.md").write_text("Notes.\n") + values = {"out": str(root / "out"), "dat": str(root / "empty.dat"), + "notes": str(root / "notes.md")} + taking = _scripts_taking("--platforms-dir") + self.assertGreater(len(taking), 15) + for path in taking: + extra = [arg.format(**values) for arg in self.EXTRA.get(path.stem, [])] + with self.subTest(script=path.stem): + proc = subprocess.run( + [*_command(path), "--platforms-dir", "nosuch", *extra], + capture_output=True, text=True, cwd=str(REPO_ROOT), timeout=600, + check=False, env={**os.environ, "RETROBIOS_OFFLINE": "1"}, + ) + self.assertNotEqual(proc.returncode, 0, proc.stdout[-500:]) + self.assertNotIn("Traceback", proc.stderr, proc.stderr[-1500:]) + self.assertRegex(proc.stderr, r"ERROR: .*nosuch") + + class StrictVerifyFailsOnACriticalMiss(unittest.TestCase): """verify exited 0 with a required file missing on an md5 platform, so the pipeline printed "verify OK" over a pack the frontend rejects."""