From c54a68c6ca79182a0d512e3a6bb3622b0c463d2f Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:25:33 +0100 Subject: [PATCH] fix: end the bootstrap with 130 on ctrl+c --- install.sh | 8 +++++++- tests/test_install.py | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index ade706434..d5303ddf2 100755 --- a/install.sh +++ b/install.sh @@ -34,7 +34,13 @@ cleanup() { rmdir -- "$TEMP_DIRECTORY" 2>/dev/null || true fi } -trap cleanup EXIT HUP INT TERM +# A signal ends the run with the code a shell gives it, 130 for Ctrl+C: +# trapped by cleanup alone, the run went on, and Ctrl+C during the download +# was told as a network failure, with code 1. +trap cleanup EXIT +trap 'exit 129' HUP +trap 'exit 130' INT +trap 'exit 143' TERM if [ -z "$INSTALLER" ] || [ ! -f "$INSTALLER" ]; then install_url=${RETROBIOS_INSTALL_URL:-$DEFAULT_INSTALL_URL} diff --git a/tests/test_install.py b/tests/test_install.py index f928fb395..c1f5e6b99 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -14,6 +14,7 @@ import select import shlex import re import shutil +import signal import socket import subprocess import sys @@ -2288,6 +2289,51 @@ class TestAvailablePlatforms(unittest.TestCase): self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) self.assertIn("retroarch", proc.stdout) + @unittest.skipIf(os.name == "nt", "the sh bootstrap is POSIX") + def test_ctrl_c_during_the_download_ends_with_130(self): + """Ctrl+C while install.py downloaded: the trap cleaned up and the + script went on to blame the network, with code 1.""" + scratch = REPO_ROOT / "tmp" / "tests" + scratch.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(dir=scratch) as directory: + root = Path(directory) + fake_bin = root / "bin" + fake_bin.mkdir() + started = root / "started" + (fake_bin / "curl").write_text( + f"#!/bin/sh\n: > '{started}'\nexec sleep 60\n", encoding="utf-8") + (fake_bin / "curl").chmod(0o755) + env = {**os.environ, "PATH": f"{fake_bin}{os.pathsep}{os.environ['PATH']}", + "TMPDIR": str(root), + "RETROBIOS_INSTALL_URL": "https://example.invalid/install.py"} + for shell in ("sh", "bash"): + if not shutil.which(shell): + continue + with self.subTest(shell=shell): + started.unlink(missing_ok=True) + proc = subprocess.Popen( + [shell, "-s", "--", "--check"], cwd=root, env=env, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, text=True, start_new_session=True) + proc.stdin.write((REPO_ROOT / "install.sh").read_text(encoding="utf-8")) + proc.stdin.close() + proc.stdin = None + try: + deadline = time.monotonic() + 30 + while not started.exists() and time.monotonic() < deadline: + time.sleep(0.05) + self.assertTrue(started.exists(), "the download never started") + os.killpg(proc.pid, signal.SIGINT) + _out, err = proc.communicate(timeout=30) + finally: + if proc.poll() is None: + os.killpg(proc.pid, signal.SIGKILL) + proc.communicate() + self.assertEqual(proc.returncode, 130, err) + self.assertNotIn("could not be downloaded", err) + self.assertEqual(sorted(p.name for p in root.iterdir()), + ["bin", "started"]) + def test_a_failed_download_says_so_in_one_message(self): """Offline, the one-liner printed curl's progress table and "curl: (6) ...", then exited 6, a code the table does not list."""