fix: refuse every flag a quick mode does not read

This commit is contained in:
Abdessamad Derraz committed 2026-10-06 04:39:08 +02:00
1 parent a6e321c1d6
commit 7b62f7f702
4 files changed
+136 -22

No files matched your search

+65 -21
View File
@@ -2019,6 +2019,7 @@ def _validate_args(args, parser):
parser.error("--source requires --platform or --all")
if has_from_md5:
for flag, given in (
("--manifest", args.manifest),
("--target", args.target),
("--required-only", args.required_only),
("--source", source_given),
@@ -2431,27 +2432,70 @@ def _refuse_unapplied_flags(args, parser) -> None:
parser.error("--one-per-slot is incompatible with --manifest-targets")
_refuse_for_all_variants(args, parser)
if args.verify_packs and not args.all_variants:
# Checks packs already on disk against the platform's own list,
# narrowed by region and target; it narrows by nothing else.
for flag, given in (
("--one-per-slot", args.one_per_slot),
("--required-only", args.required_only),
("--source", args.source != "full"),
("--system", args.system),
):
if given:
parser.error(f"{flag} is incompatible with --verify-packs")
if args.manifest_targets:
# One manifest per hardware target, from the target files themselves:
# it reads none of the narrowing flags.
for flag, given in (
("--target", args.target),
("--required-only", args.required_only),
("--source", args.source != "full"),
):
if given:
parser.error(f"{flag} is incompatible with --manifest-targets")
mode = next(
(
name for name, on in (
("--manifest-targets", args.manifest_targets),
("--verify-packs", args.verify_packs and not args.all_variants),
("--list", args.list),
("--list-emulators", args.list_emulators),
("--list-systems", args.list_systems),
("--list-targets", args.list_targets),
) if on
),
None,
)
if mode is None:
return
given = _given_flags(args)
for flag in sorted(given - QUICK_MODE_FLAGS[mode] - {mode}):
parser.error(f"{flag} is incompatible with {mode}")
# What each mode that returns early reads. Anything else given beside it is
# refused: the mode would answer about an artifact the caller did not name.
QUICK_MODE_FLAGS: dict[str, set[str]] = {
# Checks packs already on disk against the platform's own list, narrowed
# by region and target and by nothing else.
"--verify-packs": {"--platform", "--all", "--target", "--region", "--include-archived"},
# One manifest per hardware target, from the target files themselves.
"--manifest-targets": set(),
"--list": set(),
"--list-emulators": set(),
"--list-systems": {"--platform"},
"--list-targets": {"--platform"},
}
def _given_flags(args) -> set[str]:
"""Flags that select or narrow what a run produces, as typed."""
flags = {
"--platform": args.platform,
"--all": args.all,
"--emulator": args.emulator,
"--system": args.system,
"--standalone": args.standalone,
"--split": args.split,
"--group-by": args.group_by != "system",
"--manifest": args.manifest,
"--manifest-targets": args.manifest_targets,
"--verify-packs": args.verify_packs,
"--all-variants": args.all_variants,
"--from-md5": args.from_md5,
"--from-md5-file": args.from_md5_file,
"--required-only": args.required_only,
"--source": args.source != "full",
"--target": args.target,
"--region": args.region,
"--one-per-slot": args.one_per_slot,
"--list": args.list,
"--list-emulators": args.list_emulators,
"--list-systems": args.list_systems,
"--list-targets": args.list_targets,
"--include-archived": args.include_archived,
"--refresh-data": args.refresh_data,
}
return {flag for flag, on in flags.items() if on}
def main():
+27 -1
View File
@@ -35,6 +35,7 @@ import slots
sys.path.insert(0, os.path.dirname(__file__))
from common import (
PROFILE_IDENTITY_FIELDS,
list_platform_system_ids,
build_target_cores_cache,
build_zip_contents_index,
check_inside_zip,
@@ -1716,11 +1717,36 @@ def main():
except ValueError as exc:
parser.error(str(exc))
# A listing mode reads --platform at most; anything else that narrows is
# refused rather than printed past (generate_pack applies the same table).
listing = next(
(flag for flag, on in (
("--list-emulators", args.list_emulators),
("--list-systems", args.list_systems),
("--list-targets", args.list_targets),
) if on),
None,
)
if listing:
reads_platform = listing != "--list-emulators"
for flag, on in (
("--platform", args.platform and not reads_platform),
("--all", args.all),
("--emulator", args.emulator),
("--system", args.system),
("--region", getattr(args, "region", None)),
("--target", getattr(args, "target", None)),
):
if on:
parser.error(f"{flag} is incompatible with {listing}")
if args.list_emulators:
list_emulator_profiles(args.emulators_dir)
return
if args.list_systems:
list_system_ids(args.emulators_dir)
if args.platform:
list_platform_system_ids(args.platform, args.platforms_dir)
else:
list_system_ids(args.emulators_dir)
return
if args.list_targets:
+12
View File
@@ -5992,6 +5992,18 @@ struct BurnDriver BurnDrvneogeo = {
"refuse"),
(["--all", "--all-variants", "--verify-packs"], ["--split"],
"refuse"),
# Every flag a quick mode does not read, from one table.
(["--platform", "retroarch", "--verify-packs"], ["--split"], "refuse"),
(["--platform", "retroarch", "--verify-packs"], ["--manifest"], "refuse"),
(["--platform", "retroarch", "--verify-packs"], ["--emulator", "handy"],
"refuse"),
(["--manifest-targets"], ["--platform", "batocera"], "refuse"),
(["--manifest-targets"], ["--system", "sony-playstation"], "refuse"),
(["--manifest-targets"], ["--all"], "refuse"),
(["--platform", "retroarch", "--from-md5", "d8f1"], ["--manifest"],
"refuse"),
(["--list-systems"], ["--region", "us"], "refuse"),
(["--list-emulators"], ["--target", "switch"], "refuse"),
]
for mode, flag, expected in matrix:
with self.subTest(mode=mode, flag=flag):
+32
View File
@@ -0,0 +1,32 @@
"""verify.py lists what generate_pack.py lists for the same request."""
from __future__ import annotations
import subprocess
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
def _run(*argv: str) -> subprocess.CompletedProcess:
return subprocess.run(
[sys.executable, *argv], capture_output=True, text=True, cwd=REPO_ROOT, timeout=300
)
class ListingsAgree(unittest.TestCase):
def test_list_systems_honours_platform(self):
verify = _run("scripts/verify.py", "--list-systems", "--platform", "bizhawk")
pack = _run("scripts/generate_pack.py", "--list-systems", "--platform", "bizhawk")
self.assertEqual(verify.returncode, 0)
self.assertEqual(verify.stdout, pack.stdout)
def test_listing_refuses_a_narrowing_flag(self):
result = _run("scripts/verify.py", "--list-emulators", "--region", "us")
self.assertNotEqual(result.returncode, 0)
if __name__ == "__main__":
unittest.main()