fix: a refused standalone copy is named

A copy refused by a read-only emulator folder raised the error count with no failures entry: the summary named no file, gave the BIOS folder as the location and no advice. The failure now carries its path, and the summary names the emulator folder and what to do.
This commit is contained in:
Abdessamad Derraz committed 2026-10-20 04:41:18 +02:00
1 parent 4603b6ec77
commit d087caec40
4 files changed
+58 -4

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ param(
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "26f2b2fc61c828de3eceec4cc6ba90d10801b3c97a88531adf9b8307a3866e2e"
$defaultInstallSha256 = "300fcfd077c970a6807b2414b19cbae87b330664f3f7092d8275f5da2fe6e022"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+19 -2
View File
@@ -2765,11 +2765,21 @@ def _report_outcome(
print("Run the same command again to carry on from here. Nothing is lost.")
sys.exit(130)
refused = any(reason == NOT_WRITABLE for _dest, reason in failures or [])
# A standalone copy fails by absolute path, in an emulator's folder
# rather than the BIOS folder: that folder is where the write went.
elsewhere = sorted({Path(dest).parent for dest, _reason in failures or []
if Path(dest).is_absolute()})
refused = any(reason == NOT_WRITABLE for dest, reason in failures or []
if not Path(dest).is_absolute())
advice = [
_write_advice(path, path in (assumed or set()), (platform_of or {}).get(path, ""))
for path in paths
] if refused else []
advice += [_write_advice(folder, False) for folder in sorted({
Path(dest).parent for dest, reason in failures or []
if Path(dest).is_absolute() and reason == NOT_WRITABLE
})]
paths = paths + [folder for folder in elsewhere if folder not in paths]
if errors and not downloaded:
print(f"\nNothing was installed. {_plural(errors, 'file')} failed.")
@@ -3110,6 +3120,7 @@ def select_data_directories(manifest: dict, files: list[dict], narrowed: bool) -
def do_standalone_copies(
manifest: dict, bios_path: Path, os_type: str,
extra_dirs: dict[str, Path] | None = None,
failures: list[tuple[str, str]] | None = None,
) -> tuple[int, int, int]:
"""Copy BIOS files to standalone emulator directories.
@@ -3126,6 +3137,8 @@ def do_standalone_copies(
Returns (copied, skipped, failed): skipped is a target directory that
does not exist, failed a copy the system refused (permission, disk
full, read-only mount), which used to be counted as "dir not found".
Each failure is also appended to failures, by absolute path, for the
summary to name.
"""
from fnmatch import fnmatch
@@ -3200,6 +3213,10 @@ def do_standalone_copies(
except OSError as exc:
print(f" ERROR: {dest}: {exc.strerror or exc}")
failed += 1
if failures is not None:
reason = NOT_WRITABLE if _write_refused(exc) else str(
exc.strerror or exc)
failures.append((str(dest), reason))
return copied, skipped, failed
@@ -4135,7 +4152,7 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
lb_root = launchbox_root(os_type)
extra_dirs = launchbox_bios_dirs(lb_root) if lb_root else None
copied, skipped, copy_failed = do_standalone_copies(
manifest, bios_path, os_type, extra_dirs
manifest, bios_path, os_type, extra_dirs, failures
)
if copied or skipped or copy_failed:
print(
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="26f2b2fc61c828de3eceec4cc6ba90d10801b3c97a88531adf9b8307a3866e2e"
DEFAULT_INSTALL_SHA256="300fcfd077c970a6807b2414b19cbae87b330664f3f7092d8275f5da2fe6e022"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+37
View File
@@ -2852,6 +2852,43 @@ class ACheckNamesWhatTheCollectionLacks(unittest.TestCase):
class ARefusedStandaloneCopyIsNamed(unittest.TestCase):
"""A standalone copy refused by a read-only emulator folder counted as
an error with no failures entry: the summary named no file, pointed at
the BIOS folder and gave no advice."""
def test_the_file_and_its_folder_reach_the_summary(self):
import contextlib # noqa: PLC0415
if os.name == "nt" or os.geteuid() == 0:
self.skipTest("a read-only folder needs POSIX modes and a non-root user")
bios = Path(_tempdir(self))
(bios / "scph5501.bin").write_bytes(b"x")
target = Path(_tempdir(self)) / "pcsx2-bios"
target.mkdir()
target.chmod(0o500)
self.addCleanup(target.chmod, 0o700)
manifest = {"standalone_copies": [
{"file": "scph5501.bin", "emulator": "pcsx2",
"targets": {"linux": [str(target)]}},
]}
failures: list = []
with contextlib.redirect_stdout(io.StringIO()):
_copied, _skipped, failed = install.do_standalone_copies(
manifest, bios, "linux", None, failures)
self.assertEqual(failed, 1)
self.assertEqual(failures, [(str(target / "scph5501.bin"), install.NOT_WRITABLE)])
out = io.StringIO()
with contextlib.redirect_stdout(out), self.assertRaises(SystemExit):
install._report_outcome([bios], 0, 1, failed, 0, False, failures=failures)
text = out.getvalue()
self.assertIn(str(target / "scph5501.bin"), text)
self.assertIn(str(target), text.split("Location")[1])
self.assertIn("cannot write there", text)
class ThePagesQuoteTheBootstrapsOwnWords(unittest.TestCase):
"""The installer page and the FAQ quoted "install.py SHA-256 mismatch",
a message the bootstraps no longer print."""