mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 12:33:53 -06:00
fix: hold the device screen choices
Every board always sets --every-board. A board list that failed is fetched again, once and with the list's timeout. A pack is read only once the screen takes it, and a ZIP that is no pack is closed. Another setup drops the board and emulators, and names a new list lacks are left out and said. PgUp and Home never rest on a heading. Without the internet is refused for a target whose board list is not held.
This commit is contained in:
1 parent
be2b243e1b
commit
a285be7930
5 files changed
+279
-50
No files matched your search
+1
-1
@@ -16,7 +16,7 @@
|
||||
# and Invoke-WebRequest throws on a cut transfer before the hash is read:
|
||||
# the site's build rewrites this line there (scripts/sitebootstrap.py).
|
||||
$mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
|
||||
$defaultInstallSha256 = "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b"
|
||||
$defaultInstallSha256 = "67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa"
|
||||
# The pin above refuses any changed byte: this cap only stops a body that
|
||||
# cannot be the installer (a sign-in portal's page) before it is hashed.
|
||||
$maximumInstallerBytes = 8MB
|
||||
|
||||
+107
-33
@@ -4734,7 +4734,8 @@ def local_targets(plat: str) -> "dict | None":
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def fetch_targets(plat: str) -> "dict | None":
|
||||
def fetch_targets(plat: str, attempts: int = MAX_RETRIES,
|
||||
timeout: float | None = None) -> "dict | None":
|
||||
"""The target core list: the one this run has at hand, else downloaded.
|
||||
|
||||
Empty on 404, where the platform publishes no targets. None when the list
|
||||
@@ -4757,7 +4758,8 @@ def fetch_targets(plat: str) -> "dict | None":
|
||||
sys.exit(2)
|
||||
url = TARGETS_URL.format(platform=plat)
|
||||
try:
|
||||
return _validate_targets(_fetch_json(url, MAX_TARGETS_BYTES, "target list"))
|
||||
return _validate_targets(_fetch_json(url, MAX_TARGETS_BYTES, "target list",
|
||||
attempts=attempts, timeout=timeout))
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code == 404:
|
||||
return {}
|
||||
@@ -12443,7 +12445,12 @@ class PackSource:
|
||||
self.close()
|
||||
raise ValueError(f"{path.name} cannot be read as a ZIP ({exc}). A download "
|
||||
"cut short gives this: download it again.") from exc
|
||||
self.listed = self._pack_list()
|
||||
try:
|
||||
self.listed = self._pack_list()
|
||||
except ValueError:
|
||||
# A ZIP that is no pack: its archives closed, not left open.
|
||||
self.close()
|
||||
raise
|
||||
self.by_sha1 = {f["sha1"]: f["path"] for f in self.listed}
|
||||
|
||||
@staticmethod
|
||||
@@ -20117,11 +20124,13 @@ def _device_rows(state: TuiState) -> list[TuiRow]:
|
||||
|
||||
def _choose_board(state: TuiState, name: str) -> list[tuple]:
|
||||
"""A board chosen on Device, back on Change. The board the run reads
|
||||
itself is no option: the command stays the one with nothing chosen."""
|
||||
itself is no option: the command stays the one with nothing chosen.
|
||||
Every board is always said, the run otherwise reading a board itself
|
||||
(one started with --target or --every-board read none)."""
|
||||
seen = _board_seen(state)
|
||||
read = seen[0] if seen is not None and seen[1] else ""
|
||||
state.target = "" if name == read else name
|
||||
state.every_board = not name and bool(read)
|
||||
state.every_board = not name
|
||||
state.target = "" if not name or name == read else name
|
||||
return _back(state)
|
||||
|
||||
|
||||
@@ -20412,6 +20421,37 @@ def _closed_status(state: TuiState, closed: tuple[Path, str]) -> TuiLine:
|
||||
"or give this user access to it.", "refusal")
|
||||
|
||||
|
||||
def _set_platform(state: TuiState, plat: str) -> None:
|
||||
"""Another setup chosen: its list names other boards and emulators, so
|
||||
the ones chosen for the setup before are not carried over."""
|
||||
if state.platform and plat != state.platform:
|
||||
state.target, state.every_board, state.emulators = "", False, []
|
||||
state.targets, state.targets_for, state.targets_problem = None, "", ""
|
||||
state.platform = plat
|
||||
|
||||
|
||||
def _known_choice(state: TuiState, plat: str, manifest: dict) -> None:
|
||||
"""The emulators and the board chosen, kept to those the list now read
|
||||
names, the others left out and said in one line: a pack's list can
|
||||
lack what the network's held, and the check then failed."""
|
||||
names = emulator_names(manifest, manifest.get("files") or [],
|
||||
manifest.get("omitted_files") or [])
|
||||
gone = [f"--emulator {name}" for name in state.emulators if name not in names]
|
||||
state.emulators = [name for name in state.emulators if name in names]
|
||||
boards = state.targets if state.targets_for == plat else None
|
||||
if LOCAL.manifest is not None or LOCAL.pack is not None:
|
||||
try:
|
||||
boards = local_targets(plat)
|
||||
except SystemExit:
|
||||
# A board list beside the list that is no list: the check says so.
|
||||
boards = None
|
||||
if state.target and boards is not None and state.target not in boards:
|
||||
gone.append(f"--target {state.target}")
|
||||
state.target = ""
|
||||
if gone:
|
||||
state.status = f"Left out, not in this list: {', '.join(gone)}."
|
||||
|
||||
|
||||
def _choose(state: TuiState, plat: str, dest: Path, proposed: bool = False) -> list[tuple]:
|
||||
"""dest taken for plat. proposed is a folder the installer offered (its
|
||||
usual or detected folder, a share drive's bios): created without the
|
||||
@@ -20422,7 +20462,8 @@ def _choose(state: TuiState, plat: str, dest: Path, proposed: bool = False) -> l
|
||||
if closed is not None:
|
||||
# Nothing can be checked or written there: the folder is asked
|
||||
# again, on the screen that chose it when it can say why.
|
||||
state.platform, state.status = plat, _closed_status(state, closed)
|
||||
_set_platform(state, plat)
|
||||
state.status = _closed_status(state, closed)
|
||||
if state.stack and state.screen in ("folder", "browse", "path"):
|
||||
return []
|
||||
# The folder found here is the one the frontend reads: the built-in
|
||||
@@ -20434,7 +20475,8 @@ def _choose(state: TuiState, plat: str, dest: Path, proposed: bool = False) -> l
|
||||
if card and card[0] == plat and card[1] != dest:
|
||||
# A muOS or KNULLI card's root: that system reads a folder below it.
|
||||
dest = card[1]
|
||||
state.platform, state.dest = plat, dest
|
||||
_set_platform(state, plat)
|
||||
state.dest = dest
|
||||
if plat in CARD_PLATFORMS and card_platform(dest) is None:
|
||||
# The engine refuses a folder inside the card, the list writing
|
||||
# Bios/<TAG>/ below the root: the root is offered instead.
|
||||
@@ -20871,7 +20913,7 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
|
||||
# Taken as the system chosen, as --platform with --dest takes it.
|
||||
plat, mount = row.value
|
||||
state.card_bound = card_bound(plat, plat)
|
||||
state.platform = plat
|
||||
_set_platform(state, plat)
|
||||
choice = card_choice(plat, plat, mount)
|
||||
if choice.refused:
|
||||
_card_status(state, choice)
|
||||
@@ -20884,7 +20926,7 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
|
||||
return _choose(state, plat, path, proposed=True)
|
||||
if screen == "platforms":
|
||||
plat, system = row.value if action == "cardsystem" else (row.value, row.value)
|
||||
state.platform = plat
|
||||
_set_platform(state, plat)
|
||||
state.card_bound = card_bound(system, plat)
|
||||
if state.dest_given is not None:
|
||||
# Read as --platform with --dest reads it, for the same folder:
|
||||
@@ -21599,9 +21641,18 @@ def _option(state: TuiState, action: str, step: int) -> list[tuple]:
|
||||
elif action == "addapp":
|
||||
state.add_app = step > 0 if step else not state.add_app
|
||||
elif action == "offline" and LOCAL.pack is None:
|
||||
on = step > 0 if step else not offline_requested()
|
||||
if (on and state.target and not state.manifest_given and not state.packs
|
||||
and not (state.targets_for == state.platform and state.targets)):
|
||||
# The run reads the board list beside the file list it is
|
||||
# given, written only once Device has read it: refused before
|
||||
# a check that would stop on it.
|
||||
state.status = _styled("Open Device once first: without the internet the "
|
||||
"board list has to be read already.", "refusal")
|
||||
return []
|
||||
# Set in this process by the effect, as --offline sets it: the
|
||||
# runs started next inherit it, and their command names it.
|
||||
return [("offline", step > 0 if step else not offline_requested())]
|
||||
return [("offline", on)]
|
||||
elif action == "device":
|
||||
_push(state, "device")
|
||||
state.focus["device"] = _device_focus(state)
|
||||
@@ -21664,23 +21715,28 @@ def _open_pack_browser(state: TuiState) -> list[tuple]:
|
||||
return []
|
||||
|
||||
|
||||
def open_tui_pack(path: Path, plat: str) -> str:
|
||||
"""The pack the screen chose, opened in this process in place of the
|
||||
network as --from-pack opens it: "" once it is, else why not."""
|
||||
def open_tui_pack(path: Path, plat: str) -> tuple[PackSource | None, str]:
|
||||
"""The pack the screen chose, opened as --from-pack opens it, and why
|
||||
it is refused, "" when it is not. The run reads it only once the screen
|
||||
takes it (use_tui_pack): a Back before then leaves the run as it was."""
|
||||
try:
|
||||
pack = PackSource([path])
|
||||
except ValueError as exc:
|
||||
return str(exc)
|
||||
return None, str(exc)
|
||||
spelled = pack.platforms()
|
||||
if spelled and plat not in spelled:
|
||||
pack.close()
|
||||
return (f"{pack.name} is a pack for "
|
||||
f"{' and '.join(display_name(name) for name in spelled)}, "
|
||||
f"not {display_name(plat)}.")
|
||||
if LOCAL.pack is not None:
|
||||
return None, (f"{pack.name} is a pack for "
|
||||
f"{' and '.join(display_name(name) for name in spelled)}, "
|
||||
f"not {display_name(plat)}.")
|
||||
return pack, ""
|
||||
|
||||
|
||||
def use_tui_pack(pack: PackSource) -> None:
|
||||
"""The pack the screen took, read by this run in place of the network."""
|
||||
if LOCAL.pack is not None and LOCAL.pack is not pack:
|
||||
LOCAL.pack.close()
|
||||
LOCAL.pack = pack
|
||||
return ""
|
||||
|
||||
|
||||
def set_offline(on: bool) -> None:
|
||||
@@ -22066,8 +22122,12 @@ def tui_update(state: TuiState, key: TuiKey) -> list[tuple]:
|
||||
# A heading names the rows under it and is never in focus: the
|
||||
# move goes on to the next row its way, else stays where it was.
|
||||
step = -1 if name in ("up", "pgup", "end") or char in ("k", "G") else 1
|
||||
moved = name in ("up", "down") or char in ("k", "j")
|
||||
if not moved and focus in (0, last):
|
||||
# A page or Home clamped to an end: the first row from that end.
|
||||
step = 1 if focus == 0 else -1
|
||||
order = list(range(focus, -1, -1) if step < 0 else range(focus, len(rows)))
|
||||
if wrap:
|
||||
if wrap and moved:
|
||||
order += range(last, focus, -1) if step < 0 else range(0, focus)
|
||||
focus = next((index for index in order if rows[index].action != "heading"),
|
||||
max(0, min(state.focus.get(screen, 0), last)))
|
||||
@@ -22490,20 +22550,25 @@ def tui_receive(state: TuiState, message: tuple) -> list[tuple]:
|
||||
if plat != state.platform or state.screen != "checking":
|
||||
return []
|
||||
state.manifest, state.manifest_for = manifest, plat
|
||||
_known_choice(state, plat, manifest)
|
||||
if state.games_probed is None:
|
||||
state.games_probed = (plat, state.dest)
|
||||
return _start_run(state, "check")
|
||||
if kind == "targets":
|
||||
_kind, plat, targets, problem = message
|
||||
if plat == state.platform:
|
||||
state.targets, state.targets_for, state.targets_problem = targets, plat, problem
|
||||
# Held only once read: a list that could not be read is fetched
|
||||
# again the next time Device opens.
|
||||
state.targets, state.targets_problem = targets, problem
|
||||
state.targets_for = plat if targets is not None else ""
|
||||
if state.screen == "device":
|
||||
state.focus["device"] = _device_focus(state)
|
||||
return []
|
||||
if kind == "pack_opened":
|
||||
_kind, path, problem = message
|
||||
_kind, path, pack, problem = message
|
||||
if state.screen != "browse" or state.browse_for != "pack":
|
||||
return []
|
||||
# Left before it opened: never read, closed.
|
||||
return [("close_pack", pack)] if pack is not None else []
|
||||
if problem:
|
||||
state.status = _styled(problem, "refusal")
|
||||
return []
|
||||
@@ -22515,7 +22580,7 @@ def tui_receive(state: TuiState, message: tuple) -> list[tuple]:
|
||||
if not state.manifest_given:
|
||||
state.manifest_path = None
|
||||
state.targets_for = ""
|
||||
return _start_check(state)
|
||||
return [("use_pack", pack), *_start_check(state)]
|
||||
if kind == "app":
|
||||
# Put from the result screen, in this process.
|
||||
state.app, state.app_running = message[1], False
|
||||
@@ -23975,9 +24040,9 @@ def _view_options(state: TuiState, width: int, height: int) -> tuple:
|
||||
# A folder is cut in its middle, its last levels being what tells it apart.
|
||||
room = width - 4 - (0 if width < 60 else 2 + max(cell_width(r.right) for r in rows))
|
||||
rows = [_middle_label(row, room) if row.style == "path" else row for row in rows]
|
||||
if state.status:
|
||||
after.append(state.status)
|
||||
return ("Change", _compose(state, width, height, [], rows, after),
|
||||
# In the lines the rows leave: below them it went once they filled
|
||||
# the screen.
|
||||
return ("Change", _compose(state, width, height, [], rows, after, _status_footer(state)),
|
||||
("A/Enter Change", "B/Esc Back", "Left/Right Set", "? Help"))
|
||||
|
||||
|
||||
@@ -23986,11 +24051,13 @@ def _view_device(state: TuiState, width: int, height: int) -> tuple:
|
||||
pinned = ["Choose the board, the chip of the device the games run on. "
|
||||
"Every board fits any device."]
|
||||
rows = tui_rows(state)
|
||||
if state.targets_for != state.platform:
|
||||
if state.targets_problem:
|
||||
after = [state.targets_problem]
|
||||
elif state.targets_for != state.platform:
|
||||
after = ["Reading the board list..."]
|
||||
elif not rows:
|
||||
after = [state.targets_problem or f"{display_name(state.platform)} publishes "
|
||||
"no board list: every board's files are installed."]
|
||||
after = [f"{display_name(state.platform)} publishes no board list: every "
|
||||
"board's files are installed."]
|
||||
else:
|
||||
after = []
|
||||
return ("Device", _compose(state, width, height, [], rows, after,
|
||||
@@ -25421,7 +25488,9 @@ def _tui_fetch_targets(plat: str, inbox: queue.Queue, log: io.StringIO) -> None:
|
||||
the network's. None and why when it cannot be read."""
|
||||
mark = len(log.getvalue())
|
||||
try:
|
||||
targets = fetch_targets(plat)
|
||||
# One attempt and the list's own timeout, as the file list is
|
||||
# fetched here: Device says Reading while it waits.
|
||||
targets = fetch_targets(plat, attempts=1, timeout=TUI_FETCH_TIMEOUT)
|
||||
except SystemExit:
|
||||
targets = None
|
||||
said = [line.strip() for line in log.getvalue()[mark:].splitlines() if line.strip()]
|
||||
@@ -25900,7 +25969,12 @@ def run_tui(args: argparse.Namespace) -> int:
|
||||
threading.Thread(target=_tui_fetch_targets, args=(effect[1], inbox, buffer),
|
||||
daemon=True).start()
|
||||
elif kind == "pack":
|
||||
inbox.put(("pack_opened", effect[1], open_tui_pack(effect[1], state.platform)))
|
||||
inbox.put(("pack_opened", effect[1],
|
||||
*open_tui_pack(effect[1], state.platform)))
|
||||
elif kind == "use_pack":
|
||||
use_tui_pack(effect[1])
|
||||
elif kind == "close_pack":
|
||||
effect[1].close()
|
||||
elif kind == "offline":
|
||||
set_offline(effect[1])
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins
|
||||
# curl or wget stop on a cut transfer before the hash is read: the site's
|
||||
# build rewrites this line there (scripts/sitebootstrap.py).
|
||||
MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
|
||||
DEFAULT_INSTALL_SHA256="18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b"
|
||||
DEFAULT_INSTALL_SHA256="67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa"
|
||||
# The pin above refuses any changed byte: this cap only stops a body that
|
||||
# cannot be the installer (a sign-in portal's page) before it is hashed.
|
||||
MAX_INSTALLER_BYTES=8388608
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
{
|
||||
"dest": "roms/ports/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 1217065,
|
||||
"sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b",
|
||||
"size": 1220974,
|
||||
"sha256": "67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -71,8 +71,8 @@
|
||||
"muos": {
|
||||
"archive": {
|
||||
"dest": "ARCHIVE/RetroBIOS.muxapp",
|
||||
"size": 1270690,
|
||||
"sha256": "dbda647988b00ef42e70c82165cbbe8ea11fcfeec59ab8d753dfbfe7914e07cb"
|
||||
"size": 1274599,
|
||||
"sha256": "8ea3d79c9bbcec747555c3970edef0692db5a00ada0ff45892aaf68dcfc38eb6"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
@@ -92,8 +92,8 @@
|
||||
{
|
||||
"name": "RetroBIOS/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 1217065,
|
||||
"sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b",
|
||||
"size": 1220974,
|
||||
"sha256": "67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -166,8 +166,8 @@
|
||||
{
|
||||
"dest": ".config/modules/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 1217065,
|
||||
"sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b",
|
||||
"size": 1220974,
|
||||
"sha256": "67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
|
||||
+162
-7
@@ -15511,13 +15511,17 @@ class DeviceAndEmulatorScreens(unittest.TestCase):
|
||||
[("--every-board", None)])
|
||||
self.assertIn("Device: every board", self.text(state))
|
||||
|
||||
def test_with_no_board_read_every_board_is_the_choice_and_no_flag(self):
|
||||
def test_with_no_board_read_every_board_is_the_choice(self):
|
||||
state = device_state(board="")
|
||||
self.assertEqual(install.tui_rows(state)[-1].label, "(x) Every board")
|
||||
self.assertEqual(state.focus["device"], 3)
|
||||
press(state, "enter")
|
||||
self.assertEqual([flag for flag in install.tui_flags(state)
|
||||
if flag[0] in ("--target", "--every-board")], [])
|
||||
# Chosen, it is said: a card put in the reader later names none.
|
||||
press(state, "enter")
|
||||
self.assertEqual([flag for flag in install.tui_flags(state)
|
||||
if flag[0] in ("--target", "--every-board")],
|
||||
[("--every-board", None)])
|
||||
|
||||
def test_a_board_list_that_cannot_be_read_is_said(self):
|
||||
state = checked_state()
|
||||
@@ -15596,11 +15600,12 @@ class DeviceAndEmulatorScreens(unittest.TestCase):
|
||||
self.assertIn("A/Enter Use this pack", self.text(state))
|
||||
self.assertEqual(press(state, "enter"), [("pack", pack)])
|
||||
# Refused: said, the browser stays.
|
||||
install.tui_receive(state, ("pack_opened", pack, "Not a pack."))
|
||||
install.tui_receive(state, ("pack_opened", pack, None, "Not a pack."))
|
||||
self.assertEqual(state.screen, "browse")
|
||||
self.assertIn("Not a pack.", self.text(state))
|
||||
effects = install.tui_receive(state, ("pack_opened", pack, ""))
|
||||
self.assertEqual(effects, [("fetch", "retroarch")])
|
||||
opened = mock.Mock()
|
||||
effects = install.tui_receive(state, ("pack_opened", pack, opened, ""))
|
||||
self.assertEqual(effects, [("use_pack", opened), ("fetch", "retroarch")])
|
||||
self.assertEqual(state.packs, [pack])
|
||||
self.assertIn(("--from-pack", str(pack)), install.tui_flags(state))
|
||||
|
||||
@@ -15612,9 +15617,9 @@ class DeviceAndEmulatorScreens(unittest.TestCase):
|
||||
archive.writestr("manifest.json", json.dumps({"platform": "batocera",
|
||||
"files": []}))
|
||||
previous = install.LOCAL.pack
|
||||
self.addCleanup(setattr, install.LOCAL, "pack", previous)
|
||||
problem = install.open_tui_pack(pack, "retroarch")
|
||||
opened, problem = install.open_tui_pack(pack, "retroarch")
|
||||
self.assertIs(install.LOCAL.pack, previous)
|
||||
self.assertIsNone(opened)
|
||||
self.assertTrue(problem)
|
||||
|
||||
|
||||
@@ -15828,5 +15833,155 @@ class EmulatorChoiceMatchesTheEmulatorPack(unittest.TestCase):
|
||||
entry = next(entry for entry in files if entry["dest"] == dest)
|
||||
self.assertIsNone(entry.get("cores"), dest)
|
||||
|
||||
|
||||
class DeviceAndEmulatorReview(unittest.TestCase):
|
||||
"""The review of 2026-11-11: each finding, held."""
|
||||
|
||||
def setUp(self):
|
||||
patcher = mock.patch.object(install, "_mounted_cards", return_value=[])
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@staticmethod
|
||||
def focus(state: install.TuiState, action: str, value: object = None) -> None:
|
||||
rows = install.tui_rows(state)
|
||||
state.focus[state.screen] = next(
|
||||
index for index, row in enumerate(rows)
|
||||
if row.action == action and (value is None or row.value == value))
|
||||
|
||||
def text(self, state: install.TuiState) -> str:
|
||||
return "\n".join(str(line) for line in install.tui_view(state, 80, 24))
|
||||
|
||||
def test_every_board_is_said_whatever_the_run_started_with(self):
|
||||
for start in ({"target": "bcm2711"}, {"every_board": True}, {}):
|
||||
with self.subTest(start=start):
|
||||
state = device_state(board="")
|
||||
state.target = start.get("target", "")
|
||||
state.every_board = start.get("every_board", False)
|
||||
self.focus(state, "board", "")
|
||||
press(state, "enter")
|
||||
self.assertEqual([flag for flag in install.tui_flags(state)
|
||||
if flag[0] in ("--target", "--every-board")],
|
||||
[("--every-board", None)])
|
||||
# The board the run reads itself clears both.
|
||||
state = device_state()
|
||||
state.every_board = True
|
||||
self.focus(state, "board", "h700")
|
||||
press(state, "enter")
|
||||
self.assertEqual((state.target, state.every_board), ("", False))
|
||||
|
||||
def test_a_board_list_that_failed_is_fetched_again(self):
|
||||
state = checked_state()
|
||||
state.stack.append("options")
|
||||
self.focus(state, "device")
|
||||
self.assertEqual(press(state, "enter"), [("targets", "retroarch")])
|
||||
install.tui_receive(state, ("targets", "retroarch", None,
|
||||
"The board list could not be read."))
|
||||
self.assertIn("The board list could not be read.", self.text(state))
|
||||
press(state, "esc")
|
||||
self.assertEqual(press(state, "enter"), [("targets", "retroarch")])
|
||||
self.assertIn("Reading the board list...", self.text(state))
|
||||
|
||||
def test_a_pack_is_read_only_once_the_screen_takes_it(self):
|
||||
state = checked_state()
|
||||
state.stack += ["options", "browse"]
|
||||
state.browse_for = "pack"
|
||||
pack = mock.Mock()
|
||||
effects = install.tui_receive(state, ("pack_opened", Path("p.zip"), pack, ""))
|
||||
self.assertEqual(effects[0], ("use_pack", pack))
|
||||
# Left before it opened: closed, never read.
|
||||
state = checked_state()
|
||||
effects = install.tui_receive(state, ("pack_opened", Path("p.zip"), pack, ""))
|
||||
self.assertEqual(effects, [("close_pack", pack)])
|
||||
self.assertEqual(state.packs, [])
|
||||
|
||||
def test_open_tui_pack_leaves_the_run_s_pack_alone(self):
|
||||
root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, root, True)
|
||||
pack = root / "RetroArch_BIOS_Pack.zip"
|
||||
with zipfile.ZipFile(pack, "w") as archive:
|
||||
archive.writestr("manifest.json", json.dumps({
|
||||
"platform": "retroarch", "files": [
|
||||
{"dest": "a.bin", "path": "a.bin", "sha1": "0" * 40, "size": 1}]}))
|
||||
archive.writestr("a.bin", b"a")
|
||||
previous = install.LOCAL.pack
|
||||
opened, problem = install.open_tui_pack(pack, "retroarch")
|
||||
self.addCleanup(lambda: opened is not None and opened.close())
|
||||
self.assertEqual(problem, "", problem)
|
||||
self.assertIsNotNone(opened)
|
||||
self.assertIs(install.LOCAL.pack, previous)
|
||||
|
||||
def test_a_zip_that_is_no_pack_is_closed(self):
|
||||
root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, root, True)
|
||||
notpack = root / "photos.zip"
|
||||
with zipfile.ZipFile(notpack, "w") as archive:
|
||||
archive.writestr("a.jpg", b"x")
|
||||
opened = []
|
||||
real = zipfile.ZipFile
|
||||
|
||||
def tracked(*args, **kwargs):
|
||||
archive = real(*args, **kwargs)
|
||||
opened.append(archive)
|
||||
return archive
|
||||
|
||||
with mock.patch.object(install.zipfile, "ZipFile", side_effect=tracked):
|
||||
with self.assertRaises(ValueError):
|
||||
install.PackSource([notpack])
|
||||
self.assertTrue(opened)
|
||||
self.assertTrue(all(archive.fp is None for archive in opened))
|
||||
|
||||
def test_another_setup_drops_the_board_and_emulators(self):
|
||||
state = checked_state()
|
||||
state.target, state.every_board, state.emulators = "bcm2711", False, ["mesen"]
|
||||
install._set_platform(state, "batocera")
|
||||
self.assertEqual((state.target, state.every_board, state.emulators),
|
||||
("", False, []))
|
||||
state = checked_state()
|
||||
state.target, state.emulators = "bcm2711", ["mesen"]
|
||||
install._set_platform(state, "retroarch")
|
||||
self.assertEqual((state.target, state.emulators), ("bcm2711", ["mesen"]))
|
||||
|
||||
def test_names_the_new_list_lacks_are_left_out_and_said(self):
|
||||
state = checked_state()
|
||||
state.emulators, state.target = ["mesen", "gone_core"], "nowhere"
|
||||
state.targets, state.targets_for = dict(TARGETS), "retroarch"
|
||||
install._known_choice(state, "retroarch", MANIFEST)
|
||||
self.assertEqual((state.emulators, state.target), (["mesen"], ""))
|
||||
self.assertEqual(state.status, "Left out, not in this list: --emulator gone_core, "
|
||||
"--target nowhere.")
|
||||
|
||||
def test_page_up_and_home_never_rest_on_a_heading(self):
|
||||
state = checked_state()
|
||||
press(state, "o")
|
||||
rows = install.tui_rows(state)
|
||||
for start in (1, 6, len(rows) - 1):
|
||||
for key in ("pgup", "home"):
|
||||
with self.subTest(start=start, key=key):
|
||||
state.focus["options"] = start
|
||||
press(state, key)
|
||||
self.assertEqual(state.focus["options"], 1)
|
||||
|
||||
def test_offline_is_refused_for_a_target_with_no_board_list_held(self):
|
||||
state = checked_state()
|
||||
state.target = "bcm2711"
|
||||
state.stack.append("options")
|
||||
self.focus(state, "offline")
|
||||
with mock.patch.dict(os.environ, {}, clear=False):
|
||||
os.environ.pop(install.OFFLINE_VARIABLE, None)
|
||||
self.assertEqual(press(state, "enter"), [])
|
||||
self.assertIn("Open Device once first", self.text(state))
|
||||
state.targets, state.targets_for = dict(TARGETS), "retroarch"
|
||||
self.assertEqual(press(state, "enter"), [("offline", True)])
|
||||
|
||||
def test_the_board_list_is_fetched_once_with_the_list_s_timeout(self):
|
||||
inbox: queue.Queue = queue.Queue()
|
||||
with mock.patch.object(install, "_fetch_json", return_value={}) as fetched, \
|
||||
mock.patch.object(install, "local_targets", return_value=None):
|
||||
install._tui_fetch_targets("retroarch", inbox, io.StringIO())
|
||||
self.assertEqual(fetched.call_args.kwargs["attempts"], 1)
|
||||
self.assertEqual(fetched.call_args.kwargs["timeout"], install.TUI_FETCH_TIMEOUT)
|
||||
self.assertEqual(inbox.get_nowait(), ("targets", "retroarch", {}, ""))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user