mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 12:49:03 -05:00
fix: search past a folder this user cannot open
One closed folder under HOME (~/.config/EmuDeck at 000) made the whole detection return nothing: a RetroArch whose retroarch.cfg was readable was lost and the run fell back to a folder it does not read. Every detection probe now goes through _exists, _is_dir or _is_file, which count a refused place as absent, name the closed folder once, and let the other probes go on. The decorator stays as the last net. The refusal is an unsearched event before detect. The interface keeps the line above the list of setups. At HOME or a drive root A goes into a visible subfolder, never a card or drive row nor .cache, and with none it stays on Use this folder and says where it is. A default folder taken because the platform was not found is no longer called detected.
This commit is contained in:
1 parent
fd33ad0d96
commit
d2ba7e18ba
7 files changed
+387
-83
No files matched your search
@@ -665,6 +665,84 @@ class AHomeThisUserCannotOpen(unittest.TestCase):
|
||||
self.assertIn(f"Could not look for setups: this user cannot open {home}",
|
||||
runs["detected"].stderr)
|
||||
|
||||
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
|
||||
"a folder without search permission is POSIX")
|
||||
def test_an_assumed_folder_is_not_called_detected(self):
|
||||
"""The refusal ended "than the one detected here" for the built-in
|
||||
default taken because RetroArch was not detected."""
|
||||
with FakeRepository() as repo:
|
||||
home = repo.root / "home"
|
||||
home.mkdir()
|
||||
env = {**repo.env(), "HOME": str(home), "RETROBIOS_OS": "linux"}
|
||||
os.chmod(home, 0o000)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, str(REPO_ROOT / "install.py"), "--check",
|
||||
"--no-input", "--platform", "retroarch"],
|
||||
env=env, capture_output=True, text=True, timeout=60,
|
||||
stdin=subprocess.DEVNULL)
|
||||
finally:
|
||||
os.chmod(home, 0o755)
|
||||
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
|
||||
said = " ".join(proc.stderr.split())
|
||||
self.assertNotIn("the one detected here", said)
|
||||
self.assertIn("than the one taken as RetroArch's default, RetroArch not being "
|
||||
"found here (name another with --dest).", said)
|
||||
|
||||
|
||||
class APartlyClosedHome(unittest.TestCase):
|
||||
"""~/.config/EmuDeck at 000 (a sudo run leaves it owned by root) made
|
||||
detection return nothing: the readable retroarch.cfg beside it was
|
||||
never read, and the run fell back to ~/.config/retroarch/system, a
|
||||
folder that RetroArch does not read."""
|
||||
|
||||
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
|
||||
"a folder without search permission is POSIX")
|
||||
def test_retroarch_is_found_past_the_closed_folder(self):
|
||||
with FakeRepository() as repo:
|
||||
home = repo.root / "home"
|
||||
closed = home / ".config" / "EmuDeck"
|
||||
closed.mkdir(parents=True)
|
||||
cfg = home / ".config" / "retroarch" / "retroarch.cfg"
|
||||
cfg.parent.mkdir()
|
||||
system = repo.root / "elsewhere" / "system"
|
||||
system.mkdir(parents=True)
|
||||
cfg.write_text(f'system_directory = "{system}"\n')
|
||||
env = {**repo.env(), "HOME": str(home), "RETROBIOS_OS": "linux"}
|
||||
runs = {}
|
||||
os.chmod(closed, 0o000)
|
||||
try:
|
||||
for name, extra in (("detected", []),
|
||||
("named", ["--platform", "retroarch"]),
|
||||
("events", ["--events"])):
|
||||
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(closed, 0o755)
|
||||
refusal = (f"Could not look for setups: this user cannot open {closed} "
|
||||
"(Permission denied)")
|
||||
for name, proc in runs.items():
|
||||
with self.subTest(run=name):
|
||||
self.assertNotIn("Traceback", proc.stderr)
|
||||
self.assertEqual(" ".join(proc.stderr.split()).count(refusal), 1,
|
||||
proc.stderr)
|
||||
self.assertNotIn(str(home / ".config" / "retroarch" / "system"),
|
||||
proc.stdout + proc.stderr)
|
||||
self.assertIn(f"Found RetroArch at {system}", runs["detected"].stdout)
|
||||
self.assertIn("0/5 present", runs["named"].stdout)
|
||||
self.assertNotIn("not detected", runs["named"].stdout)
|
||||
events = [json.loads(line) for line in runs["events"].stdout.splitlines()]
|
||||
kinds = [event["event"] for event in events]
|
||||
self.assertLess(kinds.index("unsearched"), kinds.index("detect"))
|
||||
unsearched = events[kinds.index("unsearched")]
|
||||
self.assertEqual((unsearched["folder"], unsearched["reason"]),
|
||||
(str(closed), "Permission denied"))
|
||||
detect = events[kinds.index("detect")]
|
||||
self.assertEqual([entry["bios_dir"] for entry in detect["platforms"]], [str(system)])
|
||||
|
||||
|
||||
class OutputToAFileAsksNothing(unittest.TestCase):
|
||||
"""`install.py > log.txt` at a terminal: the menu went into the file
|
||||
|
||||
@@ -2967,6 +2967,112 @@ class AFolderThisUserCannotOpen(unittest.TestCase):
|
||||
def test_the_nearest_folder_stops_above_it(self):
|
||||
self.assertEqual(install._nearest_folder(self.dest / "deeper"), self.lock)
|
||||
|
||||
def test_a_closed_folder_under_home_hides_nothing_else(self):
|
||||
"""~/.config/EmuDeck at 000 made detection return nothing, and the
|
||||
folder screen offered ~/.config/retroarch/system, which this
|
||||
RetroArch does not read: its retroarch.cfg names another."""
|
||||
home = self.lock.parent / "home"
|
||||
closed = home / ".config" / "EmuDeck"
|
||||
closed.mkdir(parents=True)
|
||||
cfg = home / ".config" / "retroarch" / "retroarch.cfg"
|
||||
cfg.parent.mkdir()
|
||||
system = self.lock.parent / "elsewhere" / "system"
|
||||
system.mkdir(parents=True)
|
||||
cfg.write_text(f'system_directory = "{system}"\n')
|
||||
os.chmod(closed, 0o000)
|
||||
self.addCleanup(os.chmod, closed, 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=[]), \
|
||||
mock.patch.object(install, "_RETROARCH_CONFIGS", {}):
|
||||
state, _effects = install.tui_start(_args(), "linux")
|
||||
self.assertEqual([setup[:2] for setup in state.setups], [("retroarch", system)])
|
||||
screen = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
self.assertEqual(screen.count("Could not look for setups: this user cannot open "
|
||||
"~/.config/EmuDeck (Permission denied)"), 1, screen)
|
||||
|
||||
def test_the_refusal_is_drawn_above_a_full_list(self):
|
||||
"""With a closed HOME the first screen is the 18 setups: the line,
|
||||
drawn under them, was dropped at 80x24 and at 40x15."""
|
||||
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.assertEqual(state.screen, "platforms")
|
||||
for cols, rows in ((80, 24), (40, 15)):
|
||||
with self.subTest(size=f"{cols}x{rows}"):
|
||||
lines = install.tui_view(state, cols, rows)
|
||||
self.assertEqual(len(lines), rows)
|
||||
screen = " ".join(" ".join(lines).split())
|
||||
self.assertIn("Could not look for setups", screen)
|
||||
self.assertIn("name the BIOS folder with --dest.", screen)
|
||||
# The list keeps its focused row in view.
|
||||
self.assertIn("RetroArch", screen)
|
||||
|
||||
|
||||
class AGoesIntoASubfolder(unittest.TestCase):
|
||||
"""At HOME or a drive root A went into the first "dir" row: a mounted
|
||||
card or drive listed first (/mnt/d, the Windows D: drive, then took
|
||||
the BIOS files at its root) or .cache."""
|
||||
|
||||
def setUp(self):
|
||||
self.home = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, self.home, True)
|
||||
self.card = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, self.card, True)
|
||||
self.state = install.TuiState(os_type="linux", home=self.home)
|
||||
self.state.platform = "retroarch"
|
||||
self.state.stack = ["folder", "browse"]
|
||||
|
||||
def focused(self) -> install.TuiRow:
|
||||
return self.state.browse_rows[self.state.focus["browse"]]
|
||||
|
||||
def test_a_card_and_cache_are_passed_over(self):
|
||||
(self.home / ".cache").mkdir()
|
||||
(self.home / "Games").mkdir()
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[self.card]):
|
||||
install._browse_to(self.state, self.home)
|
||||
self.assertEqual(self.state.browse_rows[2].value, self.card)
|
||||
self.assertEqual(self.focused().label, "Games/")
|
||||
self.assertEqual(self.state.status, "")
|
||||
|
||||
def test_with_no_visible_subfolder_use_stays_and_says_where(self):
|
||||
(self.home / ".cache").mkdir()
|
||||
(self.home / ".config").mkdir()
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[self.card]):
|
||||
install._browse_to(self.state, self.home)
|
||||
self.assertEqual(self.focused().action, "use")
|
||||
self.assertIn("This is your home folder", self.state.status)
|
||||
screen = " ".join(" ".join(install.tui_view(self.state, 80, 24)).split())
|
||||
self.assertIn("This is your home folder: no setup reads its BIOS files here.",
|
||||
screen)
|
||||
|
||||
def test_on_windows_the_other_drives_are_passed_over(self):
|
||||
"""The card rows are drive roots there (E:\\): at D:\\ the first
|
||||
was E:\\, and at E:\\ it was D:\\."""
|
||||
(self.home / "Games").mkdir()
|
||||
drives = [Path("D:\\"), Path("E:\\")]
|
||||
windows = WindowsOs()
|
||||
windows.path = types.SimpleNamespace(
|
||||
**{name: getattr(os.path, name) for name in dir(os.path)
|
||||
if not name.startswith("_")})
|
||||
windows.path.exists = lambda path: str(path) in ("C:\\", "D:\\", "E:\\")
|
||||
with mock.patch.object(install, "os", windows), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=drives):
|
||||
install._browse_to(self.state, self.home)
|
||||
self.assertEqual(self.focused().label, "Games/")
|
||||
# The drives list keeps its first drive focused.
|
||||
install._browse_to(self.state, None)
|
||||
self.assertEqual([row.label for row in self.state.browse_rows],
|
||||
["C:\\", "D:\\", "E:\\"])
|
||||
self.assertEqual(self.state.focus["browse"], 0)
|
||||
|
||||
|
||||
# POSIX modules a Windows Python does not have: importing one raises there.
|
||||
POSIX_ONLY = ("termios", "tty", "pty", "fcntl", "select", "resource", "pwd", "grp")
|
||||
|
||||
Reference in new issue
Block a user