mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-14 05:53:47 -06:00
fix: check the install record before using it
A record's choice holds only the options of the choice and their values, or the whole record is said once and ignored: it no longer reaches the commands printed for the user. A recorded games folder is kept only under the folder that holds the BIOS folder. A recorded option the list can no longer apply is left out in one line, never refused. The record is read only when it is a regular file of at most 16 MiB, its day is unknown when no clock can show it, one a newer installer wrote is left as it is, and a setup that cannot be used is dropped alone. The plan opened on the one setup found keeps that screen's notes under Details.
This commit is contained in:
1 parent
8f2018e7c2
commit
5f3f4cff38
7 files changed
+489
-64
No files matched your search
@@ -25,6 +25,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
@@ -619,5 +620,144 @@ class InstallRecord(unittest.TestCase):
|
||||
events(screen, "check")[0]["bytes_needed"])
|
||||
|
||||
|
||||
def record_text(folder: Path, choice: list[str], **setups: object) -> str:
|
||||
"""A record of folder whose retroarch setup chose choice."""
|
||||
setup = {"written": "2026-11-03T12:00:00Z", "list": {}, "choice": choice,
|
||||
"files": [{"dest": "gba_bios.bin", "root": "", "sha1": "0" * 40, "size": 1,
|
||||
"from": "download"}]}
|
||||
return json.dumps({"schema": install.RECORD_SCHEMA, "installer": "x", "folder": str(folder),
|
||||
"setups": {"retroarch": setup, **setups}})
|
||||
|
||||
|
||||
class RecordReview(unittest.TestCase):
|
||||
"""A record is data another program or a person may have written: what
|
||||
it holds is checked before any of it is used or printed."""
|
||||
|
||||
setUp = InstallRecord.setUp
|
||||
run_line = InstallRecord.run_line
|
||||
dests = InstallRecord.dests
|
||||
everything = InstallRecord.everything
|
||||
|
||||
def write(self, choice: list[str], **setups: object) -> None:
|
||||
self.dest.mkdir(parents=True, exist_ok=True)
|
||||
self.record.parent.mkdir(exist_ok=True)
|
||||
self.record.write_text(record_text(self.dest, choice, **setups), encoding="utf-8")
|
||||
|
||||
def test_a_word_outside_the_choice_rejects_the_whole_record(self):
|
||||
for choice in (["--system", "nintendo-gba", "--from", "/mnt/x"],
|
||||
["--manifest", "http://example.invalid/list.json"],
|
||||
["--system", "nintendo-gba", "--dest", "/etc"],
|
||||
["--system", "--yes"], ["--system", "a\nb"], ["nintendo-gba"]):
|
||||
with self.subTest(choice=choice):
|
||||
self.write(choice)
|
||||
proc = self.run_line("--check", "--events")
|
||||
said = events(proc, "record")
|
||||
self.assertEqual([event["status"] for event in said], ["unreadable"], said)
|
||||
self.assertEqual(self.dests(proc), self.everything())
|
||||
text = proc.stdout + proc.stderr
|
||||
for word in ("/mnt/x", "example.invalid", "/etc"):
|
||||
self.assertNotIn(word, text)
|
||||
# The screen reads it through the same function.
|
||||
state = install.TuiState()
|
||||
state.platform, state.dest = "retroarch", self.dest
|
||||
install._tui_record_load(state)
|
||||
self.assertIsNone(state.record)
|
||||
self.assertEqual(install.tui_flags(state), [])
|
||||
|
||||
def test_a_recorded_games_folder_stays_beside_the_bios_folder(self):
|
||||
parent = self.dest.parent
|
||||
self.assertTrue(install.games_folder_kept(str(parent / "roms"), self.dest))
|
||||
self.assertTrue(install.games_folder_kept(str(parent), self.dest))
|
||||
for value in ("/etc", str(parent.parent / "other"), "roms", "\\\\server\\share",
|
||||
"//server/share", ""):
|
||||
with self.subTest(value=value):
|
||||
self.assertFalse(install.games_folder_kept(value, self.dest))
|
||||
self.write(["--for-games", "/etc"])
|
||||
proc = self.run_line("--check", "--events")
|
||||
self.assertEqual(events(proc, "record")[0]["status"], "unreadable")
|
||||
self.assertEqual(self.dests(proc), self.everything())
|
||||
|
||||
def test_a_recorded_option_that_cannot_apply_is_left_out(self):
|
||||
"""A run nobody typed options for went on failing: the list has no
|
||||
cores, no categories and no board list, and a region is unknown."""
|
||||
for choice, typed in ((["--system", "nintendo-gba", "--region", "atlantis"],
|
||||
["--region", "atlantis"]),
|
||||
(["--system", "nintendo-gba", "--emulator", "nosuch"],
|
||||
["--emulator", "nosuch"]),
|
||||
(["--system", "nintendo-gba", "--installed-cores"],
|
||||
["--installed-cores"]),
|
||||
(["--system", "nintendo-gba", "--target", "nosuch"],
|
||||
["--target", "nosuch"]),
|
||||
(["--system", "nintendo-gba", "--no-game-data"],
|
||||
["--no-game-data"])):
|
||||
with self.subTest(choice=choice):
|
||||
self.write(choice)
|
||||
proc = self.run_line("--yes", "--events")
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
self.assertEqual(proc.stderr.count("Left out of the recorded choice"), 1,
|
||||
proc.stderr)
|
||||
self.assertEqual(self.dests(proc), ["gba_bios.bin"])
|
||||
# Written again without it: the next run says nothing.
|
||||
kept = json.loads(self.record.read_text())["setups"]["retroarch"]["choice"]
|
||||
self.assertEqual(kept, ["--system", "nintendo-gba"])
|
||||
# Typed, it is refused as before.
|
||||
self.assertNotEqual(self.run_line(*typed, "--check").returncode, 0)
|
||||
|
||||
def test_a_date_no_clock_can_show_is_an_unknown_day(self):
|
||||
if not hasattr(time, "tzset"):
|
||||
self.skipTest("no tzset")
|
||||
zone = os.environ.get("TZ")
|
||||
|
||||
def back() -> None:
|
||||
if zone is None:
|
||||
os.environ.pop("TZ", None)
|
||||
else:
|
||||
os.environ["TZ"] = zone
|
||||
time.tzset()
|
||||
|
||||
self.addCleanup(back)
|
||||
for tz, written in (("UTC+5", "0001-01-01T00:00:00Z"),
|
||||
("UTC-5", "9999-12-31T23:59:59Z")):
|
||||
with self.subTest(written=written):
|
||||
os.environ["TZ"] = tz
|
||||
time.tzset()
|
||||
self.assertEqual(install.record_day({"written": written}), "")
|
||||
|
||||
def test_a_record_that_is_no_file_is_never_read(self):
|
||||
self.dest.mkdir(parents=True)
|
||||
self.record.parent.mkdir()
|
||||
os.mkfifo(self.record)
|
||||
proc = self.run_line("--check", "--events")
|
||||
self.assertIn("it is not a file", events(proc, "record")[0]["message"])
|
||||
self.assertEqual(self.dests(proc), self.everything())
|
||||
self.record.unlink()
|
||||
with open(self.record, "wb") as handle:
|
||||
handle.truncate(install.MAX_RECORD_BYTES + 1)
|
||||
self.assertIn("larger than any record", install.load_record(self.dest).problem)
|
||||
|
||||
def test_a_newer_record_is_left_as_it_is(self):
|
||||
self.dest.mkdir(parents=True)
|
||||
self.record.parent.mkdir()
|
||||
newer = json.dumps({"schema": install.RECORD_SCHEMA + 1, "setups": {"x": {}}})
|
||||
self.record.write_text(newer, encoding="utf-8")
|
||||
proc = self.run_line("--yes", "--events")
|
||||
self.assertEqual(proc.returncode, 0, proc.stderr)
|
||||
self.assertEqual([event["status"] for event in events(proc, "record")],
|
||||
["unreadable", "not_written"])
|
||||
self.assertEqual(self.record.read_text(encoding="utf-8"), newer)
|
||||
self.assertEqual(proc.stderr.count("written by a newer installer"), 1)
|
||||
|
||||
def test_a_bad_setup_is_dropped_and_the_others_kept(self):
|
||||
good = {"written": "2026-11-03T12:00:00Z", "list": {}, "choice": ["--all-files"],
|
||||
"files": []}
|
||||
self.write(["--system", "nintendo-gba"], lakka=good, batocera={"choice": 3})
|
||||
path, _setup, problem = install.write_record(self.dest, "retropie", {}, ["--all-files"],
|
||||
[])
|
||||
self.assertEqual((path, problem), (self.record, ""))
|
||||
kept = json.loads(self.record.read_text())["setups"]
|
||||
self.assertEqual(sorted(kept), ["lakka", "retroarch", "retropie"])
|
||||
self.assertEqual(kept["retroarch"]["choice"], ["--system", "nintendo-gba"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1148,6 +1148,36 @@ class RecordedPlan(unittest.TestCase):
|
||||
press(state, "enter")
|
||||
self.assertEqual(state.screen, "setup")
|
||||
|
||||
def test_the_notes_of_the_setups_screen_are_on_the_plan(self):
|
||||
"""Opened on the plan, the ES-DE note and the setup's label, which
|
||||
only the setups screen showed, were never seen."""
|
||||
setups = fresh_state().setups[:1]
|
||||
with mock.patch.object(install, "found_setups", return_value=(setups, set())), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=["esde"]), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=[]), \
|
||||
mock.patch.object(install, "_DETECTION_REFUSED", []), \
|
||||
mock.patch.object(install.Path, "exists", return_value=True):
|
||||
state, _effects = install.tui_start(_args(), "linux")
|
||||
self.assertTrue(state.opened_on_plan)
|
||||
install.tui_receive(state, ("manifest", "retroarch", MANIFEST))
|
||||
for event in [*FILES, *SYSTEMS, CHECK]:
|
||||
install.tui_receive(state, ("event", state.run_id, event))
|
||||
with mock.patch.object(install, "_tui_folder_facts", return_value={
|
||||
"free": 50 * 1024 ** 3, "writable": True}):
|
||||
install.tui_receive(state, ("ended", state.run_id, 1, []))
|
||||
state.pad = "both"
|
||||
plan = screen_text(install.tui_view(state, 80, 24))
|
||||
self.assertIn("1 note on this machine: Details.", plan)
|
||||
self.assertNotIn(install.TUI_ESDE_NOTE, plan)
|
||||
# The plan's budget, plus the note line (6 words) and Change where (2).
|
||||
self.assertLessEqual(screen_words(install.tui_view(state, 80, 24)),
|
||||
SCREEN_WORDS["plan"][2] + 8)
|
||||
state.focus["plan"] = [row.action for row in install.tui_rows(state)].index("details")
|
||||
press(state, "enter")
|
||||
details = screen_text(install.tui_view(state, 120, 40))
|
||||
self.assertIn(screen_text([install.TUI_ESDE_NOTE]), details)
|
||||
self.assertIn("Found here: RetroArch.", details)
|
||||
|
||||
def test_several_setups_open_the_list(self):
|
||||
with mock.patch.object(install, "found_setups", return_value=(
|
||||
fresh_state().setups, set())), \
|
||||
|
||||
Reference in new issue
Block a user