From bfa741a9c9771127e3cd77f8d6f2d5bc2b007de6 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sat, 10 Oct 2026 03:59:23 +0200 Subject: [PATCH] fix: accept the slug omission in the installer --- install.ps1 | 2 +- install.py | 16 +++++++++++----- install.sh | 2 +- tests/test_audit_regressions.py | 23 ++++++++++++++++++----- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/install.ps1 b/install.ps1 index 8f1026f0..a63580a9 100644 --- a/install.ps1 +++ b/install.ps1 @@ -8,7 +8,7 @@ param( $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -$defaultInstallSha256 = "df0adf1fd5088ef9509429fb1d5720e5ca5e7bf46aff9ad0021851dce7349107" +$defaultInstallSha256 = "f23eee534cfbf88187f39e47065165c8cc897efb06206a7daadc461de241f8d4" $maximumInstallerBytes = 2MB $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } $temporary = $null diff --git a/install.py b/install.py index e1322451..644fbdbe 100644 --- a/install.py +++ b/install.py @@ -81,6 +81,15 @@ MAX_TARGETS_BYTES = 4 * 1024 * 1024 MAX_MANIFEST_FILES = 100_000 MAX_DOWNLOAD_SIZE = 1024 * 1024 * 1024 MAX_TOTAL_DOWNLOAD_SIZE = 64 * 1024 * 1024 * 1024 +# Why a manifest leaves a file out, as the installer words it. The manifest +# schema and the generator are held to this list by the tests. +OMISSION_REASONS = { + "hash_mismatch": "hash mismatch", + "not_found": "not found", + "external": "external", + "user_provided": "user provided", + "no_platform_slug": "no platform folder", +} _SHA1_RE = re.compile(r"^[0-9a-fA-F]{40}$") _SHA256_RE = re.compile(r"^[0-9a-fA-F]{64}$") @@ -874,9 +883,6 @@ def _validate_manifest(data: object, plat: str) -> dict: if not isinstance(omitted, list) or len(omitted) > MAX_MANIFEST_FILES: raise ValueError("invalid omitted_files list") seen_omitted: set[str] = set() - allowed_omission_reasons = { - "hash_mismatch", "not_found", "external", "user_provided" - } for index, entry in enumerate(omitted): if not isinstance(entry, dict): raise ValueError(f"omitted_files[{index}] must be an object") @@ -894,7 +900,7 @@ def _validate_manifest(data: object, plat: str) -> dict: raise ValueError(f"invalid omitted system for {dest}") if not isinstance(entry.get("required"), bool): raise ValueError(f"invalid omitted required flag for {dest}") - if entry.get("reason") not in allowed_omission_reasons: + if entry.get("reason") not in OMISSION_REASONS: raise ValueError(f"invalid omission reason for {dest}") cores = entry.get("cores") if cores is not None and ( @@ -1677,7 +1683,7 @@ def main() -> None: reason = entry.get("reason", "unknown") reasons[reason] = reasons.get(reason, 0) + 1 reason_summary = ", ".join( - f"{reason.replace('_', ' ')}: {count}" + f"{OMISSION_REASONS.get(reason, reason)}: {count}" for reason, count in sorted(reasons.items()) ) print( diff --git a/install.sh b/install.sh index 3f574dcd..0c43bd10 100755 --- a/install.sh +++ b/install.sh @@ -18,7 +18,7 @@ esac TEMP_INSTALLER="" TEMP_DIRECTORY="" DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -DEFAULT_INSTALL_SHA256="df0adf1fd5088ef9509429fb1d5720e5ca5e7bf46aff9ad0021851dce7349107" +DEFAULT_INSTALL_SHA256="f23eee534cfbf88187f39e47065165c8cc897efb06206a7daadc461de241f8d4" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index 2f107b9b..84264c08 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -1325,15 +1325,28 @@ class EveryManifestEntryIsFetchable(unittest.TestCase): def test_an_unresolvable_file_is_recorded_as_omitted(self): """The reason must be one install.py knows how to report.""" - allowed = { - "hash_mismatch", "not_found", "external", "user_provided", - "no_platform_slug", - } for path in sorted((ROOT / "install").glob("*.json")): with self.subTest(manifest=path.name): data = json.loads(path.read_text()) for entry in data.get("omitted_files", []): - self.assertIn(entry.get("reason"), allowed) + self.assertIn(entry.get("reason"), install.OMISSION_REASONS) + + def test_the_installer_accepts_every_published_manifest(self): + """A reason the schema allowed and the installer did not made the + installer refuse the whole RomM manifest: no RomM install ran.""" + for path in sorted((ROOT / "install").glob("*.json")): + with self.subTest(manifest=path.name): + data = json.loads(path.read_text()) + install._validate_manifest(data, data["platform"]) + + def test_schema_and_installer_name_the_same_reasons(self): + schema = json.loads( + (ROOT / "schemas" / "install-manifest.schema.json").read_text() + ) + reasons = schema["properties"]["omitted_files"]["items"]["properties"][ + "reason" + ]["enum"] + self.assertEqual(set(reasons), set(install.OMISSION_REASONS)) def _manifests(self) -> list[Path]: paths = sorted((ROOT / "install").glob("*.json"))