mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 20:59:03 -05:00
fix: say why detection cannot look, then go on
HOME kept across su without - made every detection probe raise PermissionError, and the run and the interface ended on a traceback. Detection now finds nothing there, says which folder this user cannot open and how to go on, on the first screen too.
This commit is contained in:
1 parent
670f9a614e
commit
bd1c58b6f0
7 files changed
+90
-5
No files matched your search
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "bf5fd291aeffd7759153f2cfc168ef153a8806e20d708b00e5287fdd63036b20"
|
||||
$defaultInstallSha256 = "04070f071adf988291854cf4ad8fb6e7a34f8044cfbcb710ba660863c2c6b19e"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+37
-1
@@ -1922,6 +1922,36 @@ def launchbox_bios_dirs(root: Path) -> dict[str, Path]:
|
||||
return dirs
|
||||
|
||||
|
||||
_DETECTION_REFUSED: list[str] = []
|
||||
|
||||
|
||||
def _detection_guarded(detect: Callable[..., list]) -> Callable[..., list]:
|
||||
"""Detection that finds nothing, and says why once, where this user
|
||||
cannot look.
|
||||
|
||||
HOME kept across `su` without `-`: every probe of a config under it
|
||||
raised PermissionError, and the run ended on a traceback before its
|
||||
first line. A named --dest still installs.
|
||||
"""
|
||||
def guarded(*args: object, **kwargs: object) -> list:
|
||||
try:
|
||||
return detect(*args, **kwargs)
|
||||
except PermissionError as exc:
|
||||
closed = closed_folder(Path(exc.filename)) if exc.filename else None
|
||||
folder, why = closed or (Path(str(exc.filename or "")), exc.strerror or str(exc))
|
||||
words = (f"Could not look for setups: this user cannot open {folder} ({why}). "
|
||||
"Run as its owner (su - or sudo -H), or name the BIOS folder "
|
||||
"with --dest.")
|
||||
if words not in _DETECTION_REFUSED:
|
||||
_DETECTION_REFUSED.append(words)
|
||||
print(f" {screen_text(words)}", file=sys.stderr)
|
||||
return []
|
||||
guarded.__doc__ = detect.__doc__
|
||||
guarded.__name__ = detect.__name__
|
||||
return guarded
|
||||
|
||||
|
||||
@_detection_guarded
|
||||
def detect_frontends(os_type: str, home: Path | None = None) -> list[str]:
|
||||
"""Detect frontends that reference emulators without owning a BIOS dir.
|
||||
|
||||
@@ -2072,6 +2102,7 @@ def _detect_by_path(os_type: str, found: list[tuple[str, Path]]) -> None:
|
||||
found.append((platform_name, _rule_path(bios or path)))
|
||||
|
||||
|
||||
@_detection_guarded
|
||||
def detect_platforms(os_type: str) -> list[tuple[str, Path]]:
|
||||
"""Detect installed emulator platforms and their BIOS directories."""
|
||||
found: list[tuple[str, Path]] = []
|
||||
@@ -11838,8 +11869,9 @@ def _view_setup(state: TuiState, width: int, height: int) -> tuple:
|
||||
after = ([f"BIOS folder: {state.setups[focus.value][1]}"]
|
||||
if focus.action == "setup" else [])
|
||||
before = ["Found on this machine:" if state.setups else "Nothing was found here."]
|
||||
status = [state.status] if state.status else []
|
||||
return ("Where do you play?",
|
||||
_compose(state, width, height, before, rows, after + state.notes),
|
||||
_compose(state, width, height, before, rows, after + status + state.notes),
|
||||
("A/Enter Continue", "B/Esc Quit", "? Help"))
|
||||
|
||||
|
||||
@@ -11853,6 +11885,7 @@ def _view_platforms(state: TuiState, width: int, height: int) -> tuple:
|
||||
before = ["Choose the setup that will use the BIOS files."]
|
||||
needle = state.filters.get("platforms", "")
|
||||
after = [f"No setup matches '{needle}'."] if len(rows) == 1 and needle else []
|
||||
after += [state.status] if state.status else []
|
||||
return (title, _compose(state, width, height, before, rows, after),
|
||||
("A/Enter Choose", "B/Esc Back", "/ Filter", "? Help"))
|
||||
|
||||
@@ -12576,6 +12609,9 @@ def tui_start(
|
||||
found = detect_platforms(os_type)
|
||||
frontends = detect_frontends(os_type)
|
||||
cards = [(card_system(mount), mount) for mount in _mounted_cards()]
|
||||
if _DETECTION_REFUSED:
|
||||
# Said on the normal screen before this one covered it.
|
||||
state.status = _DETECTION_REFUSED[0]
|
||||
state.setups = [(name, path, _setup_label(name, path)) for name, path in found]
|
||||
# On the device itself its own card is the system found, by its
|
||||
# own path (muOS reads /run/muos/storage/bios, a bind of MUOS/bios).
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="bf5fd291aeffd7759153f2cfc168ef153a8806e20d708b00e5287fdd63036b20"
|
||||
DEFAULT_INSTALL_SHA256="04070f071adf988291854cf4ad8fb6e7a34f8044cfbcb710ba660863c2c6b19e"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -1218,7 +1218,9 @@ class ThePortMasterPort(unittest.TestCase):
|
||||
"""The launcher keyed on the words without install.py's prefix, and
|
||||
its fake printed them the same way: the test passed, the device
|
||||
still waited 300 s."""
|
||||
line = f"RetroBIOS: {install.TUI_IDLE_WORDS}"
|
||||
state = install.TuiState(os_type="linux", home=Path.home())
|
||||
state.idle_closed = True
|
||||
line = install._farewell_lines(state)[0]
|
||||
tui = (REPO_ROOT / "ports" / "shared" / "tui.txt").read_text(encoding="utf-8")
|
||||
self.assertIn(f'= "{line}" ]', tui)
|
||||
self.assertIn(f'print("{line}", file=sys.stderr)', FAKE_INSTALL)
|
||||
|
||||
@@ -633,6 +633,39 @@ class ADetectedFolderUnderAClosedOne(unittest.TestCase):
|
||||
self.assertEqual(len(kinds), 2)
|
||||
|
||||
|
||||
class AHomeThisUserCannotOpen(unittest.TestCase):
|
||||
"""HOME kept across `su` without `-`: detection stat'ed files under it
|
||||
and ended the run on a PermissionError traceback before any line."""
|
||||
|
||||
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
|
||||
"a folder without search permission is POSIX")
|
||||
def test_detection_says_so_and_the_run_goes_on(self):
|
||||
with FakeRepository() as repo:
|
||||
home = repo.root / "home"
|
||||
home.mkdir()
|
||||
env = {**repo.env(), "HOME": str(home), "RETROBIOS_OS": "linux"}
|
||||
runs = {}
|
||||
os.chmod(home, 0o000)
|
||||
try:
|
||||
for name, extra in (("named", ["--platform", "retroarch", "--dest",
|
||||
str(repo.dest)]),
|
||||
("detected", [])):
|
||||
runs[name] = subprocess.run(
|
||||
[sys.executable, str(REPO_ROOT / "install.py"), "--check",
|
||||
"--no-input", *extra],
|
||||
env=env, capture_output=True, text=True, timeout=60,
|
||||
stdin=subprocess.DEVNULL)
|
||||
finally:
|
||||
os.chmod(home, 0o755)
|
||||
for name, proc in runs.items():
|
||||
with self.subTest(run=name):
|
||||
self.assertNotIn("Traceback", proc.stderr)
|
||||
# A named folder needs no detection and is checked.
|
||||
self.assertIn("0/5 present", runs["named"].stdout)
|
||||
self.assertIn(f"Could not look for setups: this user cannot open {home}",
|
||||
runs["detected"].stderr)
|
||||
|
||||
|
||||
class OutputToAFileAsksNothing(unittest.TestCase):
|
||||
"""`install.py > log.txt` at a terminal: the menu went into the file
|
||||
and the run waited on an answer to a prompt nobody could see."""
|
||||
|
||||
@@ -2930,6 +2930,20 @@ class AFolderThisUserCannotOpen(unittest.TestCase):
|
||||
self.assertIn(f"This user cannot open {install._short_path(self.lock, state.home)} "
|
||||
"(Permission denied)", self.said(state))
|
||||
|
||||
def test_a_home_it_cannot_open_is_said_on_the_first_screen(self):
|
||||
"""HOME kept across su without -: detection raised PermissionError
|
||||
before the first screen."""
|
||||
home = self.lock.parent / "home"
|
||||
home.mkdir()
|
||||
os.chmod(home, 0o000)
|
||||
self.addCleanup(os.chmod, home, 0o755)
|
||||
install._DETECTION_REFUSED.clear()
|
||||
self.addCleanup(install._DETECTION_REFUSED.clear)
|
||||
with mock.patch.dict(os.environ, {"HOME": str(home)}), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
state, _effects = install.tui_start(_args(), "linux")
|
||||
self.assertIn("Could not look for setups: this user cannot open", self.said(state))
|
||||
|
||||
def test_the_nearest_folder_stops_above_it(self):
|
||||
self.assertEqual(install._nearest_folder(self.dest / "deeper"), self.lock)
|
||||
|
||||
|
||||
+1
-1
@@ -1086,7 +1086,7 @@ Each launcher tells the interface which buttons reach it, in
|
||||
A launcher with no mapper at all (a keyboard) leaves it unset.
|
||||
A launcher can also set `RETROBIOS_IDLE_EXIT` to a number of seconds: when
|
||||
no button has reached the interface since it opened, and nothing has run for
|
||||
that long, it closes with `No button pressed, closing.` and exit status 0.
|
||||
that long, it closes with `RetroBIOS: No button pressed, closing.` and exit status 0.
|
||||
Closed on an error or a result screen, it prints that screen's words after
|
||||
the line and exits with its code (1 for `No internet: offline mode`), so
|
||||
the launcher can tell an error from a close with nothing to report.
|
||||
|
||||
Reference in new issue
Block a user