mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 00:27:20 -05:00
fix: one setup decision for line mode and screen
Line mode finds mounted cards, shows its menu and names the folder for a pack, asks the folder instead of taking a default, and lists setups in the screen's order. Devices have one spelling each, matched without case, spaces or punctuation, and a device several systems run lists them all. --tui --dest reads a card as --plain does. Card boundaries read every parent up to a mount point from mountinfo. --offline with --yes counts the files it leaves out as pending, never as failed. The P3 points of code_audit_15 and tui_audit_8 follow.
This commit is contained in:
1 parent
5431a42e23
commit
4fbbd2c760
18 files changed
+1413
-383
No files matched your search
+10
-10
@@ -15,26 +15,26 @@
|
||||
MiSTer FPGA
|
||||
RetroPie
|
||||
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
|
||||
AmberELEC, SD card Anbernic RG351, RG552
|
||||
Anbernic stock firmware, SD card RetroArch files
|
||||
AmberELEC, SD card RG351, RG552
|
||||
Anbernic stock firmware, SD card RG35XX Plus, RG35XX H, RG35XX SP, RG28XX
|
||||
ArchR, SD card R36S, R33S, R36S Soysauce, Odroid Go Advance, Odroid Go Super, RG351V, +6
|
||||
ArkOS, SD card RG353M, RG353V, RG503, RGB30, RK2023, RG351MP, RG351V, R35S, RGB10
|
||||
CrossMix-OS, SD card TrimUI Smart Pro
|
||||
dArkOS, SD card Odroid Go Advance, RGB10, RG351MP, G350
|
||||
EmuELEC, SD card Odroid Go
|
||||
JELOS, SD card RetroArch files
|
||||
KNULLI, SD card Anbernic RG-Arc-S, RG28XX, RG34XX, RG35XX Original, RG35XX Plus, RG35XX H, +16
|
||||
KOLOS, SD card RG353P, RG353M, RG353V, RG353VS, RG503, Powkiddy RK2023, RGB30, RGB10 Max 3
|
||||
JELOS, SD card RG351M, RG351V, RG353M, RG353P, RG353V, RG353VS, RG503, RG552, R33S, R36S, +24
|
||||
KNULLI, SD card RG ARC-S, RG28XX, RG34XX, RG35XX, RG35XX Plus, RG35XX H, RG35XX 2024, +15
|
||||
KOLOS, SD card RG353P, RG353M, RG353V, RG353VS, RG503, RK2023, RGB30, RGB10 Max 3
|
||||
Koriki, SD card Miyoo Mini
|
||||
MinUI, SD card Anbernic, Miyoo, TrimUI
|
||||
MinUI, SD card RG28XX, RG34XX, RG34XX SP, RG35XX, RG35XX Plus, RG35XX H, RG35XX SP, +16
|
||||
MiyooCFW, SD card BittBoy, Pocket Go, PowKiddy Q90, PowKiddy V90, PowKiddy Q20 Mini, Sup M3, +1
|
||||
muOS, SD card RG35XX H, RG35XX Plus, RG35XX SP, RG35XX 2024, RG34XX H, RG40XX H, RG40XX V, +4
|
||||
MyMinUI, SD card Anbernic, Miyoo
|
||||
muOS, SD card RG35XX H, RG35XX Plus, RG35XX SP, RG35XX 2024, RG34XX, RG40XX H, RG40XX V, +4
|
||||
MyMinUI, SD card RG35XX, Miyoo Mini, Miyoo Mini Flip, Miyoo Mini Plus, Miyoo A30, +16
|
||||
NextUI, SD card TrimUI Brick, TrimUI Smart Pro
|
||||
Onion, SD card Miyoo Mini, Miyoo Mini Plus
|
||||
plumOS, SD card MagicX XU20 V32
|
||||
RetroOZ, SD card OGS, OGA, RGB10Max, RGB10Max2
|
||||
ROCKNIX, SD card Anbernic RG351, RG353, RG503, RG552, RG 55G1, RG ARC, RG35XX 2024, +38
|
||||
RetroOZ, SD card Odroid Go Super, Odroid Go Advance, RGB10 Max, RGB10 Max 2
|
||||
ROCKNIX, SD card RG351, RG353, RG503, RG552, RG 55G1, RG ARC, RG35XX 2024, RG35XX Plus, +37
|
||||
spruceOS, SD card Miyoo A30, Miyoo Flip, Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip, +5
|
||||
UnofficialOS, SD card R35S, R36S, RG351M, RG351P, RG351V, RG353M, RG353P, RG353V, RG503, +5
|
||||
A/Enter Choose B/Esc Back / Filter ? Help
|
||||
+2
-2
@@ -13,8 +13,8 @@
|
||||
MiSTer FPGA
|
||||
RetroPie
|
||||
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
|
||||
AmberELEC, SD card Anbernic RG351, RG552
|
||||
Anbernic stock firmware, SD card RetroArch files
|
||||
AmberELEC, SD card RG351, RG552
|
||||
Anbernic stock firmware, SD card RG35XX Plus, RG35XX H, RG35XX SP, RG28XX
|
||||
ArchR, SD card R36S, R33S, R36S Soysauce, Odroid Go Advance, +8
|
||||
ArkOS, SD card RG353M, RG353V, RG503, RGB30, RK2023, RG351MP, RG351V, +2
|
||||
CrossMix-OS, SD card TrimUI Smart Pro
|
||||
|
||||
Vendored
+14
-14
@@ -97,8 +97,8 @@
|
||||
MiSTer FPGA
|
||||
RetroPie
|
||||
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
|
||||
AmberELEC, SD card Anbernic RG351, RG552
|
||||
Anbernic stock firmware, SD card RetroArch files
|
||||
AmberELEC, SD card RG351, RG552
|
||||
Anbernic stock firmware, SD card RG35XX Plus, RG35XX H, RG35XX SP, RG28XX
|
||||
ArchR, SD card R36S, R33S, R36S Soysauce, Odroid Go Advance, +8
|
||||
ArkOS, SD card RG353M, RG353V, RG503, RGB30, RK2023, RG351MP, RG351V, +2
|
||||
CrossMix-OS, SD card TrimUI Smart Pro
|
||||
@@ -980,8 +980,8 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
1-8 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== result 40x15
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -993,11 +993,11 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
--restore
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
1-11 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== result 80x24
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -1007,11 +1007,6 @@
|
||||
failed: scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
@@ -1023,6 +1018,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== error 40x12
|
||||
RetroBIOS: Something went wrong
|
||||
|
||||
+14
-14
@@ -97,8 +97,8 @@
|
||||
MiSTer FPGA
|
||||
RetroPie
|
||||
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
|
||||
AmberELEC, SD card Anbernic RG351, RG552
|
||||
Anbernic stock firmware, SD card RetroArch files
|
||||
AmberELEC, SD card RG351, RG552
|
||||
Anbernic stock firmware, SD card RG35XX Plus, RG35XX H, RG35XX SP, RG28XX
|
||||
ArchR, SD card R36S, R33S, R36S Soysauce, Odroid Go Advance, +8
|
||||
ArkOS, SD card RG353M, RG353V, RG503, RGB30, RK2023, RG351MP, RG351V, +2
|
||||
CrossMix-OS, SD card TrimUI Smart Pro
|
||||
@@ -980,8 +980,8 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
1-8 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== result 40x15
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -993,11 +993,11 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
--restore
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
1-11 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== result 80x24
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -1007,11 +1007,6 @@
|
||||
failed: scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
@@ -1023,6 +1018,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== error 40x12
|
||||
RetroBIOS: Something went wrong
|
||||
|
||||
+14
-14
@@ -97,8 +97,8 @@
|
||||
MiSTer FPGA
|
||||
RetroPie
|
||||
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
|
||||
AmberELEC, SD card Anbernic RG351, RG552
|
||||
Anbernic stock firmware, SD card RetroArch files
|
||||
AmberELEC, SD card RG351, RG552
|
||||
Anbernic stock firmware, SD card RG35XX Plus, RG35XX H, RG35XX SP, RG28XX
|
||||
ArchR, SD card R36S, R33S, R36S Soysauce, Odroid Go Advance, +8
|
||||
ArkOS, SD card RG353M, RG353V, RG503, RGB30, RK2023, RG351MP, RG351V, +2
|
||||
CrossMix-OS, SD card TrimUI Smart Pro
|
||||
@@ -980,8 +980,8 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
1-8 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== result 40x15
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -993,11 +993,11 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
--restore
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
1-11 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== result 80x24
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -1007,11 +1007,6 @@
|
||||
failed: scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
@@ -1023,6 +1018,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== error 40x12
|
||||
RetroBIOS: Something went wrong
|
||||
|
||||
Vendored
+2
-2
@@ -97,8 +97,8 @@
|
||||
MiSTer FPGA
|
||||
RetroPie
|
||||
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
|
||||
AmberELEC, SD card Anbernic RG351, RG552
|
||||
Anbernic stock firmware, SD card RetroArch files
|
||||
AmberELEC, SD card RG351, RG552
|
||||
Anbernic stock firmware, SD card RG35XX Plus, RG35XX H, RG35XX SP, RG28XX
|
||||
ArchR, SD card R36S, R33S, R36S Soysauce, Odroid Go Advance, +8
|
||||
ArkOS, SD card RG353M, RG353V, RG503, RGB30, RK2023, RG351MP, RG351V, +2
|
||||
CrossMix-OS, SD card TrimUI Smart Pro
|
||||
|
||||
Vendored
+14
-14
@@ -97,8 +97,8 @@
|
||||
MiSTer FPGA
|
||||
RetroPie
|
||||
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
|
||||
AmberELEC, SD card Anbernic RG351, RG552
|
||||
Anbernic stock firmware, SD card RetroArch files
|
||||
AmberELEC, SD card RG351, RG552
|
||||
Anbernic stock firmware, SD card RG35XX Plus, RG35XX H, RG35XX SP, RG28XX
|
||||
ArchR, SD card R36S, R33S, R36S Soysauce, Odroid Go Advance, +8
|
||||
ArkOS, SD card RG353M, RG353V, RG503, RGB30, RK2023, RG351MP, RG351V, +2
|
||||
CrossMix-OS, SD card TrimUI Smart Pro
|
||||
@@ -980,8 +980,8 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
1-8 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== result 40x15
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -993,11 +993,11 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
--restore
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
1-11 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== result 80x24
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -1007,11 +1007,6 @@
|
||||
failed: scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
@@ -1023,6 +1018,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A Exit Up/Down Scroll
|
||||
=== error 40x12
|
||||
RetroBIOS: Something went wrong
|
||||
|
||||
Vendored
+14
-14
@@ -97,8 +97,8 @@
|
||||
MiSTer FPGA
|
||||
RetroPie
|
||||
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
|
||||
AmberELEC, SD card Anbernic RG351, RG552
|
||||
Anbernic stock firmware, SD card RetroArch files
|
||||
AmberELEC, SD card RG351, RG552
|
||||
Anbernic stock firmware, SD card RG35XX Plus, RG35XX H, RG35XX SP, RG28XX
|
||||
ArchR, SD card R36S, R33S, R36S Soysauce, Odroid Go Advance, +8
|
||||
ArkOS, SD card RG353M, RG353V, RG503, RGB30, RK2023, RG351MP, RG351V, +2
|
||||
CrossMix-OS, SD card TrimUI Smart Pro
|
||||
@@ -980,8 +980,8 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
1-8 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
Start Exit Up/Down Scroll
|
||||
=== result 40x15
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -993,11 +993,11 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
--restore
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
1-11 of 17
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
|
||||
|
||||
Start Exit Up/Down Scroll
|
||||
=== result 80x24
|
||||
RetroBIOS: Finished with errors
|
||||
@@ -1007,11 +1007,6 @@
|
||||
failed: scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
@@ -1023,6 +1018,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Start Exit Up/Down Scroll
|
||||
=== error 40x12
|
||||
RetroBIOS: Something went wrong
|
||||
|
||||
+18
-3
@@ -1829,10 +1829,25 @@ class TestManualPlatformPrompt(unittest.TestCase):
|
||||
self.assertEqual(chosen, [("batocera", Path("~/my bios").expanduser())])
|
||||
|
||||
def test_default_directory_comes_from_the_table(self):
|
||||
answers = iter(["batocera", ""])
|
||||
with unittest.mock.patch("builtins.input", lambda _prompt="": next(answers)):
|
||||
answers = iter(["lakka", ""])
|
||||
with unittest.mock.patch("builtins.input", lambda _prompt="": next(answers)), \
|
||||
unittest.mock.patch.object(install, "_mounted_cards", return_value=[]), \
|
||||
contextlib.redirect_stdout(io.StringIO()):
|
||||
chosen = install._prompt_manual_platform("linux")
|
||||
self.assertEqual(chosen, [("batocera", install.DEFAULT_DESTS["batocera"])])
|
||||
self.assertEqual(chosen, [("lakka", install.DEFAULT_DESTS["lakka"])])
|
||||
|
||||
def test_batocera_on_a_computer_gets_no_default(self):
|
||||
"""tui_audit_8 P2-4: /userdata/bios, a folder only the Batocera
|
||||
machine has, was offered on a computer, and Enter took it."""
|
||||
answers = iter(["batocera", "", "~/share/bios"])
|
||||
out = io.StringIO()
|
||||
with unittest.mock.patch("builtins.input", lambda _prompt="": next(answers)), \
|
||||
unittest.mock.patch.object(install, "_mounted_cards", return_value=[]), \
|
||||
contextlib.redirect_stdout(out):
|
||||
chosen = install._prompt_manual_platform("linux")
|
||||
self.assertEqual(chosen, [("batocera", Path("~/share/bios").expanduser())])
|
||||
self.assertIn("SHARE drive", out.getvalue())
|
||||
self.assertNotIn("/userdata/bios", out.getvalue())
|
||||
|
||||
def test_quit_stops_like_ctrl_c(self):
|
||||
"""`install.sh && retroarch` went on to start RetroArch with no BIOS
|
||||
|
||||
@@ -370,7 +370,8 @@ class TheQuestion(unittest.TestCase):
|
||||
def test_a_console_without_python_says_so_once(self):
|
||||
asked, put = self.run_finish("y", system="Onion")
|
||||
self.assertEqual((asked, put), ([], []))
|
||||
self.assertIn(install.NO_PYTHON_WORDS, self.output)
|
||||
self.assertIn(install.no_python_words("Onion"), self.output)
|
||||
self.assertNotIn("This console", self.output)
|
||||
|
||||
|
||||
class TheOption(unittest.TestCase):
|
||||
|
||||
@@ -662,7 +662,9 @@ class OfflineMakesNoRequest(Scratch):
|
||||
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
|
||||
self.assertEqual((self.dest / "scph5501.bin").read_bytes(), US)
|
||||
self.assertFalse((self.dest / "gba_bios.bin").exists())
|
||||
self.assertIn("no network request", proc.stdout + proc.stderr)
|
||||
# Missing, never failed (code_audit_15 P3-1): no FAILED line per file.
|
||||
self.assertIn("3 not fetched (--offline)", proc.stdout + proc.stderr)
|
||||
self.assertNotIn("FAILED", proc.stdout + proc.stderr)
|
||||
self.assertIn("without --offline", proc.stdout + proc.stderr)
|
||||
self.assertNotIn("trying again", proc.stderr)
|
||||
|
||||
@@ -839,7 +841,11 @@ class OfflineLeavesFilesMissing(Scratch):
|
||||
self.list.write_text(json.dumps(listed))
|
||||
self.complete()
|
||||
proc = self.run_installer("--offline", "--manifest", str(self.list), "--no-input")
|
||||
self.assertIn("bluemsx: could not read", proc.stderr)
|
||||
# Its files are missing, as the BIOS files --offline leaves out are,
|
||||
# with or without questions.
|
||||
self.assertIn("bluemsx: 0 already up to date, 1 need the internet (--offline)",
|
||||
proc.stdout)
|
||||
self.assertNotIn("could not read", proc.stderr)
|
||||
self.assertNotIn("no longer in the source", proc.stdout)
|
||||
|
||||
|
||||
@@ -936,5 +942,75 @@ class OneOfflineVocabulary(Scratch):
|
||||
self.assertNotIn("install everything", text)
|
||||
|
||||
|
||||
class AuditFifteenOffline(Scratch):
|
||||
"""code_audit_15 P3-1, P3-5 and tui_audit_8 P3-11: what --offline and a
|
||||
pack leave out, and what a folder copied from holds, said as such."""
|
||||
|
||||
def test_yes_offline_counts_the_rest_missing_never_failed(self):
|
||||
"""The screen's own install (--yes): each file left out was
|
||||
"downloading", then FAILED, and pending and failed at once."""
|
||||
old = self.root / "old"
|
||||
old.mkdir()
|
||||
(old / "scph5501.bin").write_bytes(US)
|
||||
proc = self.run_installer("--offline", "--manifest", str(self.list), "--from",
|
||||
str(old), "--yes", "--events")
|
||||
self.assertEqual(proc.returncode, 1, proc.stderr)
|
||||
events = [json.loads(line) for line in proc.stdout.splitlines()]
|
||||
summary = next(event for event in events if event["event"] == "summary")
|
||||
self.assertEqual((summary["installed"], summary["pending"], summary["failed"]),
|
||||
(1, 3, []))
|
||||
self.assertNotIn("FAILED", proc.stderr)
|
||||
self.assertNotIn("downloading", proc.stderr)
|
||||
self.assertIn("1 file installed, 3 not fetched (--offline)", proc.stderr)
|
||||
download = next(event for event in events if event["event"] == "download")
|
||||
self.assertEqual(download["files"], 1)
|
||||
# The screen reads that summary as missing files, not failures.
|
||||
state = install.TuiState()
|
||||
state.summary = summary
|
||||
with unittest.mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}):
|
||||
title, lines = install.tui_outcome(state)
|
||||
self.assertEqual(title, "Finished with files missing")
|
||||
self.assertIn("1 installed, 3 not fetched (--offline).", lines[0])
|
||||
|
||||
def test_a_pack_is_copied_not_downloaded(self):
|
||||
downloads = self.root / "downloads"
|
||||
downloads.mkdir()
|
||||
pack = _pack(downloads, "RetroArch_v1_BIOS_Pack.zip", PAYLOAD)
|
||||
proc = self.run_installer("--from-pack", str(pack), "--dry-run", "--no-input")
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
self.assertIn("Would copy 4 files", proc.stdout)
|
||||
self.assertIn("from the pack to", proc.stdout)
|
||||
self.assertNotIn("download", proc.stdout.lower())
|
||||
|
||||
def test_a_folder_holding_none_of_the_files_is_said_and_left(self):
|
||||
empty = self.root / "empty"
|
||||
(empty / "photos").mkdir(parents=True)
|
||||
(empty / "photos" / "a.jpg").write_bytes(b"jpeg")
|
||||
install.LOCAL.reset()
|
||||
self.addCleanup(install.LOCAL.reset)
|
||||
files = manifest()["files"]
|
||||
answers = iter(["f", f"{self.root}/x/../empty", "q"])
|
||||
with unittest.mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}), \
|
||||
unittest.mock.patch.object(install, "_ask", lambda _prompt: next(answers)), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out, \
|
||||
self.assertRaises(SystemExit):
|
||||
install._prompt_custom_selection("retroarch", files, [], bios_path=self.dest)
|
||||
text = out.getvalue()
|
||||
self.assertIn("empty holds none of these files.", text)
|
||||
self.assertNotIn("install what is found", text)
|
||||
self.assertNotIn("..", text)
|
||||
self.assertEqual(install.LOCAL.folders, [])
|
||||
# One that holds some is counted.
|
||||
(empty / "scph5501.bin").write_bytes(US)
|
||||
answers = iter(["f", str(empty), "q"])
|
||||
with unittest.mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}), \
|
||||
unittest.mock.patch.object(install, "_ask", lambda _prompt: next(answers)), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out, \
|
||||
self.assertRaises(SystemExit):
|
||||
install._prompt_custom_selection("retroarch", files, [], bios_path=self.dest)
|
||||
self.assertIn("1 of 4 found in empty.", out.getvalue())
|
||||
self.assertIn("Enter) install what is found in", out.getvalue())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -704,7 +704,9 @@ class TheOneLinerNarrows(unittest.TestCase):
|
||||
env = dict(repo.env(), HOME=str(home), USERPROFILE=str(home),
|
||||
XDG_CONFIG_HOME=str(home), RETROBIOS_OS="linux")
|
||||
command = f"{shlex.quote(sys.executable)} {shlex.quote(str(REPO_ROOT / 'install.py'))}"
|
||||
for answer, code in (("n", 2), ("y", 0)):
|
||||
# No to the folders asks the folder again, as the screen's
|
||||
# Choose another folder does: the input then ends, Ctrl+D.
|
||||
for answer, code in (("n", 130), ("y", 0)):
|
||||
with self.subTest(answer=answer):
|
||||
proc = subprocess.run(
|
||||
["script", "-qec", command, "/dev/null"],
|
||||
|
||||
+517
-13
@@ -2193,7 +2193,10 @@ class AuditRoundThree(unittest.TestCase):
|
||||
self.assertEqual(proc.returncode, 1)
|
||||
self.assertIn(f"{root} is not a muOS SD card: no muOS files on it. muOS keeps "
|
||||
"MUOS at the root of its card.", proc.stderr)
|
||||
self.assertIn("--dest <card>/MUOS/bios", proc.stderr)
|
||||
# muOS always names its card: <card>/MUOS/bios would be refused the
|
||||
# same (code_audit_15 P3-4), the card's top folder is the remedy.
|
||||
self.assertIn("Name the top folder of the muOS card with --dest", proc.stderr)
|
||||
self.assertNotIn("<card>/", proc.stderr)
|
||||
self.assertEqual(list(root.iterdir()), [root / "roms"])
|
||||
# Its BIOS folder named outright is its card's, checked as the card
|
||||
# is: on a folder with no MUOS it is refused, as typed it was.
|
||||
@@ -2861,7 +2864,8 @@ class ACardSystemNamedOnAComputer(unittest.TestCase):
|
||||
contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
dest = install.card_install_dest("onion", "retroarch", interactive=False)
|
||||
self.assertEqual(dest, onion / "BIOS")
|
||||
self.assertIn(f"Onion card found at {onion}", out.getvalue())
|
||||
self.assertIn(f"Onion card found at {onion.name}, BIOS folder {onion / 'BIOS'}",
|
||||
out.getvalue())
|
||||
|
||||
def test_at_a_terminal_the_card_root_is_asked(self):
|
||||
onion = self.card(self.ONION)
|
||||
@@ -3124,15 +3128,17 @@ class TheSetupFilterKnowsDevices(unittest.TestCase):
|
||||
|
||||
TYPED = {
|
||||
"steam deck": {"retroarch", "batocera", "emudeck", "retrodeck"},
|
||||
"r36s": {"retroarch", "rocknix"},
|
||||
"rg35xx": {"retroarch", "rocknix", "knulli"},
|
||||
# MinUI and MyMinUI list their devices (code_audit_15 P2-1), and on
|
||||
# a computer an Android device is no RetroArch of this machine's.
|
||||
"r36s": {"retroarch", "rocknix", "myminui"},
|
||||
"rg35xx": {"retroarch", "rocknix", "knulli", "minui", "myminui"},
|
||||
"trimui": {"retroarch", "knulli", "minui", "nextui"},
|
||||
"retroid": {"retroarch", "rocknix", "knulli"},
|
||||
"retroid": {"rocknix", "knulli"},
|
||||
"miyoo": {"retroarch", "knulli", "minui", "myminui"},
|
||||
"anbernic": {"retroarch", "rocknix", "amberelec", "knulli", "minui", "myminui"},
|
||||
"odin": {"retroarch", "rocknix"},
|
||||
"odin": {"rocknix"},
|
||||
"ayn": {"retroarch", "rocknix"},
|
||||
"rp5": {"retroarch", "rocknix", "knulli"},
|
||||
"rp5": {"rocknix", "knulli"},
|
||||
"rog ally": {"retroarch", "emudeck", "retrobat", "bizhawk"},
|
||||
"onion": {"retroarch"},
|
||||
}
|
||||
@@ -4387,7 +4393,7 @@ class AppOnTheCard(unittest.TestCase):
|
||||
self.assertEqual(install.tui_rows(offline), [])
|
||||
os.environ.pop(install.OFFLINE_VARIABLE)
|
||||
_title, lines = install.tui_outcome(cases["no launcher"])
|
||||
self.assertIn(install.NO_PYTHON_WORDS, lines)
|
||||
self.assertIn(install.no_python_words("Onion"), lines)
|
||||
|
||||
def test_put_by_the_install_itself_no_second_offer(self):
|
||||
root, bios = _app_card(self, "muOS")
|
||||
@@ -4951,7 +4957,10 @@ class ThePlanNumbersAddUp(unittest.TestCase):
|
||||
state.found = [found("scph5500.bin", str(SYSTEM_DIR), "one folder too deep")]
|
||||
screen = " ".join(" ".join(install.tui_view(state, 120, 40)).split())
|
||||
self.assertIn("3 files to download", screen)
|
||||
self.assertIn("1 in place, 3 missing, 1 wrong copy 4 to install: 1 copied from this "
|
||||
# The backup line under the count it explains, then the split
|
||||
# (tui_audit_8 P3-5).
|
||||
self.assertIn("1 in place, 3 missing, 1 wrong copy The 1 wrong copy is replaced, the "
|
||||
"old one kept in a backup folder. 4 to install: 1 copied from this "
|
||||
"machine, 3 downloaded", screen)
|
||||
|
||||
|
||||
@@ -5390,7 +5399,7 @@ class AuditSix(unittest.TestCase):
|
||||
self.assertEqual(args.dest, Path("/mnt/d"))
|
||||
card = Path("/mnt/d")
|
||||
with mock.patch.object(install, "_card_what", return_value="Onion card"):
|
||||
self.assertEqual(install.card_line(card), "D: (Onion card, /mnt/d)")
|
||||
self.assertEqual(install.card_line(card), "D: (Onion card)")
|
||||
self.assertEqual(install._typed_folder("D:"), Path("D:"))
|
||||
|
||||
def test_a_dest_refused_on_the_setup_list_names_dest(self):
|
||||
@@ -5407,8 +5416,10 @@ class AuditSix(unittest.TestCase):
|
||||
self.assertEqual(install.tui_rows(state)[1].label, "Onion, SD card")
|
||||
self.assertEqual(press(state, "enter"), [])
|
||||
self.assertEqual(state.screen, "platforms")
|
||||
self.assertIn("--dest names it: choose another setup.", state.status)
|
||||
self.assertNotIn("Choose the card's top folder", state.status)
|
||||
# --dest is fixed: a stick that is no card is named again in the
|
||||
# command, another setup would not help (code_audit_15 P3-3).
|
||||
self.assertIn("Run it again with the card's top folder.", state.status)
|
||||
self.assertNotIn("choose another setup", state.status)
|
||||
self.assertEqual((state.platform, state.card_bound), ("", None))
|
||||
|
||||
def test_on_the_device_a_folder_typed_is_taken_as_typed(self):
|
||||
@@ -5780,7 +5791,7 @@ class AuditSevenOneCardReading(unittest.TestCase):
|
||||
text = out.getvalue()
|
||||
self.assertIn("Type its number or its top folder:", text)
|
||||
self.assertNotIn("Mount the card", text)
|
||||
self.assertIn(f"ONION2: Onion files go to {cards[1] / 'BIOS'}", text)
|
||||
self.assertIn(f"Onion card ONION2, BIOS folder {cards[1] / 'BIOS'}", text)
|
||||
self.assertEqual(install.detected_os("retroarch", dest), "Onion")
|
||||
with mock.patch.object(install, "_ask", return_value=""), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
@@ -6043,5 +6054,498 @@ class AuditSevenScreens(unittest.TestCase):
|
||||
self.assertIn("1 wrong copy: backed up, replaced.", view)
|
||||
|
||||
|
||||
class _Decided(Exception):
|
||||
"""Raised where line mode has chosen its platforms and folders."""
|
||||
|
||||
|
||||
def line_decision(args: argparse.Namespace, answer) -> tuple[list, str]:
|
||||
"""(platforms, text) line mode reaches at a terminal for args: the run
|
||||
stops where its platforms are known, before anything is read or written.
|
||||
answer(prompt) answers each question."""
|
||||
def stop(_args, platforms):
|
||||
raise _Decided(platforms)
|
||||
|
||||
install.LOCAL.reset()
|
||||
with mock.patch.object(install, "app_refusal", side_effect=stop), \
|
||||
mock.patch.object(install, "detect_os", return_value="linux"), \
|
||||
mock.patch.object(install.sys.stdin, "isatty", return_value=True), \
|
||||
mock.patch.object(install, "_is_terminal", return_value=True), \
|
||||
mock.patch.object(install, "_ask", side_effect=answer), \
|
||||
mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out, \
|
||||
contextlib.redirect_stderr(io.StringIO()):
|
||||
try:
|
||||
install._install(args, io.StringIO())
|
||||
except _Decided as decided:
|
||||
return decided.args[0], out.getvalue()
|
||||
finally:
|
||||
install.LOCAL.reset()
|
||||
raise AssertionError("line mode chose nothing:\n" + out.getvalue())
|
||||
|
||||
|
||||
def screen_decision(args: argparse.Namespace) -> tuple[str, Path]:
|
||||
"""(platform, folder) the full screen reaches for args, A pressed on
|
||||
whatever it offers first, as Enter is in line mode."""
|
||||
install.LOCAL.reset()
|
||||
with mock.patch.object(install, "detect_os", return_value="linux"), \
|
||||
mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}), \
|
||||
contextlib.redirect_stdout(io.StringIO()), \
|
||||
contextlib.redirect_stderr(io.StringIO()):
|
||||
try:
|
||||
state, effects = install._tui_startup(args)
|
||||
for _press in range(6):
|
||||
if any(effect[0] == "fetch" for effect in effects):
|
||||
return state.platform, state.dest
|
||||
if state.screen == "create":
|
||||
# Line mode creates a default folder where the screen asks.
|
||||
state.focus["create"] = 0
|
||||
effects = install.tui_update(state, Key("enter"))
|
||||
finally:
|
||||
install.LOCAL.reset()
|
||||
raise AssertionError(f"the screen chose nothing: {state.stack}")
|
||||
|
||||
|
||||
class LineModeAndScreenDecideAlike(unittest.TestCase):
|
||||
"""tui_audit_8 P1-1, P2-1 and code_audit_15 P2-2: line mode looked at
|
||||
this machine alone, took this computer's default folder with a card in
|
||||
the reader or nothing found, and the screen put RetroArch under A for a
|
||||
KNULLI card named with --dest. The same inputs now give the same
|
||||
platform and folder on both, A or Enter taking what each offers first."""
|
||||
|
||||
def setUp(self):
|
||||
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, self.root, True)
|
||||
for patcher in (mock.patch.dict(install._DETECTED_OS, {}),
|
||||
mock.patch.dict(os.environ, {"HOME": str(self.root / "home"),
|
||||
"XDG_CONFIG_HOME": str(self.root / "cfg"),
|
||||
"RETROBIOS_OS": "linux"})):
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.list = self.root / "retroarch.json"
|
||||
self.list.write_text(json.dumps({"manifest_version": 2, "platform": "retroarch",
|
||||
"files": []}))
|
||||
self.here = self.root / "pc" / "system"
|
||||
self.here.mkdir(parents=True)
|
||||
|
||||
def card(self, name: str, system: str) -> Path:
|
||||
root = self.root / "media" / name
|
||||
make_card(root, system)
|
||||
return root
|
||||
|
||||
def both(self, mounts: list, found: list, **given) -> tuple:
|
||||
def answer(prompt: str) -> str:
|
||||
# The first entry of a numbered list, the default of a folder.
|
||||
return "1" if prompt.startswith(">") else ""
|
||||
|
||||
extra = {"declared_only": False, "offline": False}
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=mounts), \
|
||||
mock.patch.object(install, "_mount_points", return_value=frozenset()), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=found), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]):
|
||||
platforms, text = line_decision(_args(**extra, **given), answer)
|
||||
install._DETECTED_OS.clear()
|
||||
screen = screen_decision(_args(**extra, **given))
|
||||
return platforms[0], screen, text
|
||||
|
||||
def test_the_same_scenarios_give_the_same_platform_and_folder(self):
|
||||
onion = self.card("ONION", "Onion")
|
||||
muos = self.card("MUOS", "muOS")
|
||||
knulli = self.card("KNULLI", "KNULLI")
|
||||
bare = self.card("ONIONB", "Onion")
|
||||
shutil.rmtree(bare / "BIOS")
|
||||
default = install._default_dest("linux", "retroarch")
|
||||
pc = [("retroarch", self.here)]
|
||||
scenarios = {
|
||||
"a list, an Onion card in the reader": (
|
||||
[onion], [], {"manifest": self.list}, ("retroarch", onion / "BIOS")),
|
||||
"a list, an Onion card without its BIOS folder": (
|
||||
[bare], [], {"manifest": self.list}, ("retroarch", bare / "BIOS")),
|
||||
"a list, a KNULLI card in the reader": (
|
||||
[knulli], [], {"manifest": self.list}, ("retroarch", default)),
|
||||
"a list, nothing here": ([], [], {"manifest": self.list}, ("retroarch", default)),
|
||||
"nothing named, two cards": (
|
||||
[muos, onion], [], {}, ("retroarch", muos / "MUOS" / "bios")),
|
||||
"nothing named, nothing here": ([], [], {}, ("retroarch", default)),
|
||||
"--dest a KNULLI card, RetroArch here": (
|
||||
[knulli], pc, {"dest": knulli}, ("knulli", knulli / "bios")),
|
||||
"--platform onion, its card": (
|
||||
[onion, muos], pc, {"platform": "onion"}, ("retroarch", onion / "BIOS")),
|
||||
}
|
||||
for name, (mounts, found, given, expected) in scenarios.items():
|
||||
with self.subTest(scenario=name):
|
||||
line, screen, text = self.both(mounts, found, **given)
|
||||
self.assertEqual(tuple(line), expected, text)
|
||||
self.assertEqual(screen, expected)
|
||||
self.assertNotIn("using default path", text)
|
||||
|
||||
def test_line_mode_says_the_card_it_found_and_names_its_folder(self):
|
||||
onion = self.card("ONION", "Onion")
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[onion]), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=[]), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]):
|
||||
_platforms, text = line_decision(_args(manifest=self.list, declared_only=False),
|
||||
lambda prompt: "")
|
||||
self.assertIn(f"Found Onion at {onion / 'BIOS'} (RetroArch files)", text)
|
||||
self.assertNotIn("No supported platform detected", text)
|
||||
|
||||
def test_a_pack_gets_the_menu_and_its_folder_named(self):
|
||||
"""P1-1: no menu and no folder before the copy, the pack's list
|
||||
recording no system."""
|
||||
from test_install_offline import _pack, PAYLOAD
|
||||
|
||||
downloads = self.root / "downloads"
|
||||
downloads.mkdir()
|
||||
pack = _pack(downloads, "RetroArch_v1_BIOS_Pack.zip", PAYLOAD)
|
||||
install.LOCAL.reset()
|
||||
self.addCleanup(install.LOCAL.reset)
|
||||
menus = []
|
||||
real = install._prompt_custom_selection
|
||||
|
||||
def menu(*given, **named):
|
||||
with mock.patch.object(install, "_ask", return_value="q"):
|
||||
try:
|
||||
real(*given, **named)
|
||||
except SystemExit as stop:
|
||||
menus.append(stop.code)
|
||||
raise
|
||||
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=[]), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]), \
|
||||
mock.patch.object(install, "detect_os", return_value="linux"), \
|
||||
mock.patch.object(install.sys.stdin, "isatty", return_value=True), \
|
||||
mock.patch.object(install, "_is_terminal", return_value=True), \
|
||||
mock.patch.object(install, "_ask", return_value=str(self.here)), \
|
||||
mock.patch.object(install, "_prompt_custom_selection", menu), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out, \
|
||||
self.assertRaises(SystemExit):
|
||||
install._install(_args(from_pack=[pack], declared_only=False), io.StringIO())
|
||||
text = out.getvalue()
|
||||
self.assertEqual(menus, [130])
|
||||
self.assertIn("RetroArch was not found here.", text)
|
||||
self.assertIn(f"BIOS folder: {self.here}", text)
|
||||
self.assertIn("Enter) install everything", text)
|
||||
self.assertNotIn("c) choose systems", text)
|
||||
self.assertNotIn("Copying", text)
|
||||
|
||||
|
||||
class OneDeviceRegistry(unittest.TestCase):
|
||||
"""code_audit_15 P2-1, tui_audit_8 P3-2, P3-3, P3-4: one device spelled
|
||||
several ways found one system, one press took it, MinUI listed brands."""
|
||||
|
||||
def found(self, typed: str, os_type: str = "linux") -> set:
|
||||
return {row.value[1] if row.action == "cardsystem" else row.value
|
||||
for row in install.setup_list([], set(), typed, os_type)}
|
||||
|
||||
def test_one_spelling_per_device(self):
|
||||
keys = {}
|
||||
for name in install._DEVICES:
|
||||
keys.setdefault(install._device_key(name), []).append(name)
|
||||
self.assertEqual({key: names for key, names in keys.items() if len(names) > 1}, {})
|
||||
for system, devices in install._SETUP_DEVICES.items():
|
||||
with self.subTest(system=system):
|
||||
self.assertEqual([name for name in devices if name not in install._DEVICES], [])
|
||||
|
||||
def test_every_spelling_finds_every_system_that_runs_it(self):
|
||||
rk2023 = {"ArkOS", "KOLOS", "JELOS", "rocknix"}
|
||||
cubexx = {"muOS", "knulli", "rocknix", "minui"}
|
||||
flip2 = {"knulli", "rocknix"}
|
||||
for typed, systems in (("Powkiddy RK2023", rk2023), ("RK2023", rk2023),
|
||||
("rk-2023", rk2023), ("RG CubeXX", cubexx),
|
||||
("RGCUBEXX", cubexx), ("Anbernic RG CubeXX", cubexx),
|
||||
("Retroid Pocket Flip 2", flip2), ("flip2", flip2)):
|
||||
with self.subTest(typed=typed):
|
||||
self.assertEqual(self.found(typed), systems)
|
||||
self.assertIn("ArkOS", self.found("Anbernic RG353V"))
|
||||
self.assertTrue({"Onion", "minui", "myminui", "Allium"} <= self.found("Miyoo Mini"))
|
||||
self.assertTrue({"Onion", "minui"} <= self.found("Miyoo Mini+"))
|
||||
self.assertIn("minui", self.found("TrimUI Brick"))
|
||||
self.assertIn("Anbernic stock firmware", self.found("rg35xx"))
|
||||
self.assertIn("JELOS", self.found("rgb10 max 3"))
|
||||
|
||||
def test_android_is_this_machine_only_on_android(self):
|
||||
self.assertNotIn("retroarch", self.found("android"))
|
||||
self.assertNotIn("retroarch", self.found("retroid pocket 5"))
|
||||
self.assertIn("retroarch", self.found("android", "android"))
|
||||
|
||||
def test_a_device_several_systems_run_is_never_taken_in_one_press(self):
|
||||
out = io.StringIO()
|
||||
with mock.patch.object(install, "_ask",
|
||||
side_effect=["Powkiddy RK2023", "", "0", "35", "q"]), \
|
||||
mock.patch.object(install, "detect_os", return_value="linux"), \
|
||||
contextlib.redirect_stdout(out), self.assertRaises(SystemExit):
|
||||
install._choose_platform("Which?", "retroarch")
|
||||
text = out.getvalue()
|
||||
self.assertIn("4 setups run on 'Powkiddy RK2023':", text)
|
||||
# Enter after the matches takes none, and 0 and 35 are numbers.
|
||||
self.assertEqual(text.count("Type a number from 1 to 34"), 3)
|
||||
self.assertNotIn("run on '0'", text)
|
||||
self.assertNotIn("run on '35'", text)
|
||||
|
||||
def test_the_line_menu_is_the_screen_list_numbered(self):
|
||||
for setups in ([], [("retroarch", SYSTEM_DIR, "RetroArch")]):
|
||||
with self.subTest(found=bool(setups)):
|
||||
rows = install.setup_list(setups, set(), "", "linux")
|
||||
self.assertEqual([name for name, _label in install._line_setups(setups)],
|
||||
[install._row_name(row) for row in rows])
|
||||
labels = dict(install._line_setups())
|
||||
self.assertIn("RG28XX", labels["minui"])
|
||||
self.assertNotIn("(Anbernic, Miyoo", labels["minui"])
|
||||
self.assertIn("RG35XX Plus", labels["Anbernic stock firmware"])
|
||||
self.assertNotIn("RetroArch files", labels["JELOS"])
|
||||
|
||||
def test_forty_columns_keep_the_name_whole_and_a_device_count(self):
|
||||
rows = {row.label: row for row in install.setup_list([], set(), "", "linux")}
|
||||
for label in ("KNULLI, SD card", "ROCKNIX, SD card", "CrossMix-OS, SD card",
|
||||
"AmberELEC, SD card", "JELOS, SD card"):
|
||||
with self.subTest(row=label):
|
||||
line = str(install._row_line(rows[label], False, 38))
|
||||
self.assertIn(label, line)
|
||||
self.assertTrue(line.rstrip() != (" " + label).rstrip(), line)
|
||||
self.assertTrue(str(install._row_line(rows["CrossMix-OS, SD card"], False, 38))
|
||||
.rstrip().endswith("1 device"))
|
||||
|
||||
|
||||
class AuditFifteenAndEightScreens(unittest.TestCase):
|
||||
"""The P3 points of code_audit_15 and tui_audit_8 the screens show."""
|
||||
|
||||
def setUp(self):
|
||||
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, self.root, True)
|
||||
patcher = mock.patch.dict(install._DETECTED_OS, {})
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def start(self, mounts=(), found=(), **given) -> tuple:
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=list(mounts)), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=list(found)), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]):
|
||||
return install.tui_start(_args(**given), "linux")
|
||||
|
||||
def test_a_card_found_by_its_files_gets_its_folder_without_a_question(self):
|
||||
"""tui_audit_8 P3-12: Create the folder? for the Onion card's own
|
||||
BIOS, which line mode created."""
|
||||
onion = self.root / "ONIONB"
|
||||
make_card(onion, "Onion")
|
||||
shutil.rmtree(onion / "BIOS")
|
||||
state, effects = self.start([onion], platform="onion")
|
||||
self.assertEqual(effects, [("fetch", "retroarch")])
|
||||
self.assertEqual(state.dest, onion / "BIOS")
|
||||
self.assertIn("BIOS is created on ONIONB", state.card_note)
|
||||
|
||||
def test_a_refused_dest_says_the_remedy_that_works(self):
|
||||
"""code_audit_15 P3-3, P3-4."""
|
||||
rocknix = self.root / "ROCKNIX"
|
||||
make_card(rocknix, "ROCKNIX")
|
||||
stick = self.root / "STICK"
|
||||
(stick / "photos").mkdir(parents=True)
|
||||
for named, plat, dest, words, never in (
|
||||
("knulli", "knulli", rocknix / "roms" / "bios", "--platform rocknix", "<card>"),
|
||||
("onion", "retroarch", stick, "Name the top folder of the Onion card",
|
||||
"<card>/BIOS")):
|
||||
with self.subTest(named=named):
|
||||
with contextlib.redirect_stderr(io.StringIO()) as err, \
|
||||
self.assertRaises(SystemExit):
|
||||
install.card_dest(named, plat, dest)
|
||||
self.assertIn(words, err.getvalue())
|
||||
self.assertNotIn(never, err.getvalue())
|
||||
minui = self.root / "MINUI"
|
||||
make_card(minui, "MinUI")
|
||||
(minui / "Bios").mkdir()
|
||||
state = install.TuiState()
|
||||
install._card_status(state, install.card_choice("minui", "minui", minui / "Bios"),
|
||||
given=True)
|
||||
self.assertIn(f"Run it again with --dest {minui}.", state.status)
|
||||
|
||||
def test_wsl_names_drives_in_line_mode_messages(self):
|
||||
"""code_audit_15 P3-7: /mnt/f in a refusal where D:\\ was typed."""
|
||||
mnt = self.root / "mnt"
|
||||
make_card(mnt / "f", "muOS")
|
||||
(mnt / "g" / "photos").mkdir(parents=True)
|
||||
with mock.patch.dict(install._WSL, {"active": True, "root": str(mnt)}), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=[]), \
|
||||
mock.patch.object(install, "_mount_points", return_value=frozenset()):
|
||||
with contextlib.redirect_stderr(io.StringIO()) as err, \
|
||||
self.assertRaises(SystemExit):
|
||||
install.card_dest("onion", "retroarch", mnt / "f")
|
||||
self.assertIn("F:\\ is a muOS card", err.getvalue())
|
||||
note = install.card_choice("knulli", "knulli", mnt / "g").note
|
||||
self.assertIn("G:\\bios", note)
|
||||
self.assertNotIn(str(mnt), note)
|
||||
self.assertEqual(install._last_name(str(mnt / "g" / "old")), "old")
|
||||
|
||||
def test_choose_the_card_below_only_when_one_is_mounted(self):
|
||||
"""tui_audit_8 P3-6."""
|
||||
muos = self.root / "MUOS"
|
||||
make_card(muos, "muOS")
|
||||
stick = self.root / "USBSTICK"
|
||||
(stick / "photos").mkdir(parents=True)
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[muos, stick]):
|
||||
state, _effects = self.start([muos, stick], platform="onion")
|
||||
rows = install.tui_rows(state)
|
||||
state.focus["folder"] = next(index for index, row in enumerate(rows)
|
||||
if row.action == "browse")
|
||||
install.tui_update(state, Key("enter"))
|
||||
self.assertEqual(state.screen, "browse")
|
||||
self.assertIn("No Onion card is mounted", state.status)
|
||||
focused = install.tui_rows(state)[state.focus["browse"]]
|
||||
self.assertEqual(focused.value, stick)
|
||||
|
||||
def test_type_a_path_says_f1_and_f1_says_more(self):
|
||||
"""tui_audit_8 P2-2."""
|
||||
muos = self.root / "MUOS"
|
||||
make_card(muos, "muOS")
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
state, _effects = self.start(platform="onion")
|
||||
stick = self.root / "STICK"
|
||||
stick.mkdir()
|
||||
state.stack.append("path")
|
||||
install._card_status(state, install.card_choice("Onion", "retroarch", stick))
|
||||
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
self.assertIn("F1 says more.", text)
|
||||
self.assertNotIn("? says more", text)
|
||||
install.tui_update(state, Key("f1"))
|
||||
self.assertTrue(state.help)
|
||||
help_text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
self.assertIn("Onion keeps .tmp_update at the root of its card", help_text)
|
||||
|
||||
def test_batocera_on_a_computer_offers_its_share(self):
|
||||
"""tui_audit_8 P2-4."""
|
||||
stick = self.root / "SHARE"
|
||||
stick.mkdir()
|
||||
state, _effects = self.start([stick])
|
||||
state.stack = ["platforms"]
|
||||
state.focus["platforms"] = install._platform_focus(state, "batocera")
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[stick]):
|
||||
install.tui_update(state, Key("enter"))
|
||||
rows = install.tui_rows(state)
|
||||
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
self.assertEqual(state.screen, "folder")
|
||||
self.assertNotIn("/userdata/bios", text)
|
||||
self.assertIn("SHARE drive", text)
|
||||
self.assertEqual([row.action for row in rows], ["share", "browse", "type"])
|
||||
self.assertEqual(rows[0].value, stick / "bios")
|
||||
|
||||
def test_the_first_screen_bar_says_quit(self):
|
||||
"""tui_audit_8 P3-10."""
|
||||
state, _effects = self.start()
|
||||
self.assertEqual(state.stack, ["platforms"])
|
||||
self.assertIn("B/Esc Quit", install._TUI_VIEWS["platforms"](state, 80, 24)[2])
|
||||
state.stack = ["setup", "platforms"]
|
||||
self.assertIn("B/Esc Back", install._TUI_VIEWS["platforms"](state, 80, 24)[2])
|
||||
|
||||
def test_a_pad_result_prints_no_command(self):
|
||||
"""tui_audit_8 P3-9: a shell command on a controller-only screen."""
|
||||
state = _installed_state(self.root / "bios")
|
||||
state.summary["kept"] = [{"folder": str(self.root / "kept"), "files": 1}]
|
||||
state.pad = "muxterm"
|
||||
_title, lines = install.tui_outcome(state)
|
||||
text = " ".join(lines)
|
||||
self.assertNotIn("install.py", text)
|
||||
self.assertIn("kept in", text)
|
||||
state.pad = "keys"
|
||||
self.assertIn("Same choice without questions:", install.tui_outcome(state)[1])
|
||||
|
||||
def test_the_checking_screen_is_titled_as_the_plan(self):
|
||||
"""code_audit_15 P3-9.1."""
|
||||
state = install.TuiState()
|
||||
state.platform, state.dest = "retroarch", self.root / "bios"
|
||||
install._DETECTED_OS[("retroarch", state.dest)] = "ArkOS"
|
||||
state.stack = ["checking"]
|
||||
text = " ".join(install.tui_view(state, 80, 24))
|
||||
self.assertIn("ArkOS (RetroArch files):", text)
|
||||
|
||||
def test_the_backup_line_survives_forty_columns(self):
|
||||
"""tui_audit_8 P3-5: under the pack and copy split it was cut."""
|
||||
state = checked_state()
|
||||
state.found = [found("gba_bios.bin", "/media/old")]
|
||||
lines = [str(line) for line in install._plan_lines(state.check, state.dest, state)]
|
||||
self.assertTrue(any("copied from this machine" in line for line in lines), lines)
|
||||
counts = next(index for index, line in enumerate(lines) if " in place, " in line)
|
||||
self.assertTrue(lines[counts + 1].startswith("The 1 wrong copy is replaced"), lines)
|
||||
|
||||
def test_an_empty_folder_is_said_once(self):
|
||||
"""tui_audit_8 P3-7."""
|
||||
old = self.root / "USBSTICK"
|
||||
old.mkdir()
|
||||
state = checked_state()
|
||||
state.from_folders = [old]
|
||||
with mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}):
|
||||
lines = [str(line) for line in install._plan_lines(state.check, state.dest,
|
||||
state)]
|
||||
said = [line for line in lines if "USBSTICK" in line]
|
||||
self.assertEqual(said, ["USBSTICK holds none of these files: run the installer again "
|
||||
"with a pack (--from-pack) or without --offline."])
|
||||
|
||||
def test_amberelec_keeps_its_files_on_the_games_part(self):
|
||||
"""tui_audit_8 P3-13."""
|
||||
self.assertEqual(install.card_where("AmberELEC", ""),
|
||||
"on the games part of its SD card")
|
||||
files = [{"dest": "bios/a.bin"}, {"dest": "bios/Machines/b.xml"}]
|
||||
self.assertEqual(install.shown_bios_folder(self.root, files), self.root / "bios")
|
||||
self.assertEqual(install.shown_bios_folder(self.root, [{"dest": "a.bin"}]),
|
||||
self.root)
|
||||
|
||||
def test_a_save_directory_left_out_has_one_wording(self):
|
||||
"""code_audit_15 P3-9.3, tui_audit_8 P3-8."""
|
||||
files = [{"dest": "keys.txt", "root": "save", "sha1": "0" * 40, "size": 1}]
|
||||
with contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
install.place_save_entries(files, self.root / "BIOS",
|
||||
{"platform": "retroarch", "save_directory": {
|
||||
"folder": "saves", "by_core": False},
|
||||
"base_destination": "system"},
|
||||
None, "RetroArch")
|
||||
self.assertIn("left out: its place is not known. Name it with --save-dir.",
|
||||
out.getvalue())
|
||||
|
||||
|
||||
class CardBoundaries(unittest.TestCase):
|
||||
"""code_audit_15 P3-2, P3-9.5: three folders at most, a bind mount not
|
||||
seen, a folder named MUOS taken for a card, ismount unprobed."""
|
||||
|
||||
def setUp(self):
|
||||
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, self.root, True)
|
||||
|
||||
def choice(self, folder: Path, points=frozenset()) -> install.CardChoice:
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]), \
|
||||
mock.patch.object(install, "_mount_points", return_value=points):
|
||||
return install.card_choice("knulli", "knulli", folder)
|
||||
|
||||
def test_every_folder_up_to_the_mount_is_read(self):
|
||||
muos = self.root / "MUOS"
|
||||
make_card(muos, "muOS")
|
||||
deep = muos / "a" / "b" / "c" / "d"
|
||||
deep.mkdir(parents=True)
|
||||
self.assertIn("muOS", self.choice(deep).refused)
|
||||
|
||||
def test_a_bind_mount_inside_a_card_is_its_own_drive(self):
|
||||
muos = self.root / "MUOS"
|
||||
make_card(muos, "muOS")
|
||||
stick = muos / "x"
|
||||
stick.mkdir()
|
||||
self.assertTrue(self.choice(stick).refused)
|
||||
choice = self.choice(stick, frozenset({str(stick)}))
|
||||
self.assertEqual((choice.refused, choice.folder), ("", stick / "bios"))
|
||||
|
||||
def test_a_folder_named_muos_beside_it_is_no_card(self):
|
||||
backups = self.root / "stage" / "backups"
|
||||
(backups / "MUOS").mkdir(parents=True)
|
||||
(backups / "knulli-new").mkdir()
|
||||
choice = self.choice(backups / "knulli-new")
|
||||
self.assertEqual(choice.refused, "")
|
||||
self.assertEqual(choice.folder, backups / "knulli-new" / "bios")
|
||||
|
||||
def test_mount_points_are_probed(self):
|
||||
with mock.patch.object(install, "_probed", wraps=install._probed) as probed, \
|
||||
mock.patch.object(install, "_mount_points", return_value=frozenset()), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
install._card_boundary(self.root)
|
||||
self.assertTrue(probed.called)
|
||||
points = install._mount_points()
|
||||
if Path("/proc/self/mountinfo").exists():
|
||||
self.assertIn("/", points)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user