diff --git a/install.ps1 b/install.ps1 index b1ee9018..37a6b587 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 = "3ea3a3e21012cc2dbf24c08a0ad314233ade04e62c0d2164b76146f1b361d714" +$defaultInstallSha256 = "42b1747ae867d41da39b11daf783c0106daa6ed18861a2214c196f3a5c9a3c60" $maximumInstallerBytes = 2MB $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } $temporary = $null diff --git a/install.py b/install.py index d91be472..a906bb68 100644 --- a/install.py +++ b/install.py @@ -954,8 +954,13 @@ def fetch_manifest(plat: str) -> dict: sys.exit(1) -def fetch_targets(plat: str) -> dict: - """Download target core list. Returns empty dict on 404.""" +def fetch_targets(plat: str) -> "dict | None": + """Download the target core list. + + Empty on 404, where the platform publishes no targets. None when the list + could not be read: a timeout says nothing about the platform, and + answering "no targets" for it sent users to the full pack. + """ url = TARGETS_URL.format(platform=plat) try: with urllib.request.urlopen(url, timeout=30) as resp: @@ -965,10 +970,11 @@ def fetch_targets(plat: str) -> dict: except urllib.error.HTTPError as exc: if exc.code == 404: return {} - print(f" Warning: failed to fetch targets for {plat}: {exc}", file=sys.stderr) - return {} - except (urllib.error.URLError, OSError, ValueError): - return {} + print(f" Error: failed to fetch targets for {plat}: {exc}", file=sys.stderr) + return None + except (urllib.error.URLError, OSError, ValueError) as exc: + print(f" Error: failed to fetch targets for {plat}: {exc}", file=sys.stderr) + return None def _filter_by_target( @@ -1580,6 +1586,8 @@ def main() -> None: if args.list_targets: targets = fetch_targets(plat_name) + if targets is None: + sys.exit(1) if not targets: print(f" No targets available for {plat_name}") else: @@ -1592,6 +1600,8 @@ def main() -> None: # Target filtering if args.target: targets = fetch_targets(plat_name) + if targets is None: + sys.exit(1) target_info = targets.get(args.target) if target_info is None: # Carrying on would install every file, which is the opposite diff --git a/install.sh b/install.sh index 8a26dd5d..1d6d7bdb 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="3ea3a3e21012cc2dbf24c08a0ad314233ade04e62c0d2164b76146f1b361d714" +DEFAULT_INSTALL_SHA256="42b1747ae867d41da39b11daf783c0106daa6ed18861a2214c196f3a5c9a3c60" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install.py b/tests/test_install.py index 2722d366..6e48468f 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1357,5 +1357,29 @@ class TestAvailablePlatforms(unittest.TestCase): self.assertIn("retroarch", proc.stdout) +class TargetListFailureIsNotAnAnswer(unittest.TestCase): + """A timeout is not 'no targets': it sent users to the full pack.""" + + def test_unreachable_list_is_none(self): + import urllib.error + from unittest import mock + + def boom(*args, **kwargs): + raise urllib.error.URLError("timed out") + + with mock.patch.object(install.urllib.request, "urlopen", boom), \ + contextlib.redirect_stderr(io.StringIO()): + self.assertIsNone(install.fetch_targets("retroarch")) + + def test_absent_list_is_empty(self): + import urllib.error + from unittest import mock + + def missing(url, *args, **kwargs): + raise urllib.error.HTTPError(url, 404, "Not Found", {}, None) + + with mock.patch.object(install.urllib.request, "urlopen", missing): + self.assertEqual(install.fetch_targets("retroarch"), {}) + if __name__ == "__main__": unittest.main()