fix: one card reading and one offline wording

card_choice is the one reading of a card's root for --dest, the setup
list with --dest, a card listed, browsed, typed or numbered: a blank
stick named for KNULLI, ROCKNIX or a system no card file names gets its
folder below the root and the same note on the screen and in line mode,
kept on Create the folder? and the plan. The MinUI family refuses a blank
--dest. A card mounted as BIOS is that card, a folder inside another
system's card is refused up to the drive's mount point, a list named for
another system's card gets a note, a refused --dest emits an error
event. AmberELEC and EmuELEC get a card setup.

Offline, every surface says missing, not failed: no retry line, no
--from after --from, Nothing installed when nothing was, --check counts
what needs the internet, a suggested command keeps --offline.

Drive letters under WSL for every path option, shown as D:\.

The setup list names each card system's devices and puts the cards after
RetroArch when nothing is found (Onion: 4 presses at 40x12, was 10), the
filter and the line menu take a device name, one match is chosen in one
press. Line mode names the BIOS folder and the card picked, leaves the
save directory files out before its menu, offers f) to copy from a
folder. Confirm shows no Install while it counts, Back from the systems
lands on Install, help keeps its column, one spelling per device.
This commit is contained in:
Abdessamad Derraz committed 2026-10-27 06:10:48 +01:00
1 parent 88324d595e
commit 74f4e301a6
23 files changed
+1356 -398

No files matched your search

+1 -1
View File
@@ -11,7 +11,7 @@
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "2127f3c7a1646cc408520e364d2a08c6aa32a8905990c1a2581ce977b39017ab"
$defaultInstallSha256 = "2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+611 -242
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="2127f3c7a1646cc408520e364d2a08c6aa32a8905990c1a2581ce977b39017ab"
DEFAULT_INSTALL_SHA256="2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+8 -8
View File
@@ -27,8 +27,8 @@
{
"dest": "roms/ports/retrobios/install.py",
"repo_path": "install.py",
"size": 673359,
"sha256": "2127f3c7a1646cc408520e364d2a08c6aa32a8905990c1a2581ce977b39017ab",
"size": 692187,
"sha256": "2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9",
"executable": false
},
{
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 725908,
"sha256": "da7fa84e69575d43fbf529c053f43f8f665963c19e25ed44923183a072ed919d"
"size": 744736,
"sha256": "17daef144acc01204912de201bbb6bbf8996d1a866a37074e7cbaffe879e911b"
},
"members": [
{
@@ -92,8 +92,8 @@
{
"name": "RetroBIOS/install.py",
"repo_path": "install.py",
"size": 673359,
"sha256": "2127f3c7a1646cc408520e364d2a08c6aa32a8905990c1a2581ce977b39017ab",
"size": 692187,
"sha256": "2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9",
"executable": false
},
{
@@ -166,8 +166,8 @@
{
"dest": ".config/modules/retrobios/install.py",
"repo_path": "install.py",
"size": 673359,
"sha256": "2127f3c7a1646cc408520e364d2a08c6aa32a8905990c1a2581ce977b39017ab",
"size": 692187,
"sha256": "2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9",
"executable": false
},
{
+2 -2
View File
@@ -160,13 +160,13 @@ handheld_guide:
- name: Onion
folder: '`BIOS` at the root of the card'
folder_source: https://onionui.github.io/docs/emulators
devices: [Miyoo Mini, Miyoo Mini+]
devices: [Miyoo Mini, Miyoo Mini Plus]
devices_source: https://github.com/OnionUI/Onion
- name: spruceOS
folder: '`BIOS` at the root of the card'
folder_source: https://github.com/spruceUI/spruceOS/wiki/10.-Adding-BIOS
devices: [Miyoo A30, Miyoo Flip, Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip,
TrimUI Smart Pro, TrimUI Brick, Anbernic RG35XX Plus, RG40XX H, Powkiddy RGB30]
TrimUI Smart Pro, TrimUI Brick, RG35XX Plus, RG40XX H, Powkiddy RGB30]
devices_source: https://github.com/spruceUI/spruceOS/wiki/Supported-Devices
- name: Allium
folder: '`BIOS` at the root of the card'
+1 -1
View File
@@ -3,8 +3,8 @@
4 files to download, 1.0 MB
BIOS folder: ~/.config/retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
The 1 wrong copy is replaced, the old one kept in a backup folder.
Free space: 50.0 GB
> Install
Choose systems
+1 -1
View File
@@ -3,7 +3,7 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
1 wrong copy: backed up, replaced.
> Install
Choose systems
Copy from your old card or a folder
+2 -2
View File
@@ -3,13 +3,13 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
1 wrong copy: backed up, replaced.
Free space: 50.0 GB
The 1 wrong copy is replaced, the old
one kept in a backup folder.
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
A Install B Back Y Options ? Help
+1 -1
View File
@@ -3,8 +3,8 @@
4 files to download, 1.0 MB
BIOS folder: ~/.config/retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
The 1 wrong copy is replaced, the old one kept in a backup folder.
Free space: 50.0 GB
> Install
Choose systems
+22 -22
View File
@@ -2,7 +2,7 @@
Choose the setup that will use the BIOS files.
Filter (/ to type)
Filter: setup or device (/)
> RetroArch
Batocera
Recalbox
@@ -10,31 +10,31 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
RetroPie
AmberELEC PAN4ELEC
EmuELEC
KNULLI, SD card
MinUI, SD card
NextUI, SD card
MyMinUI, SD card
Allium, SD card RetroArch files
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
AmberELEC, SD card Anbernic RG351, RG552
Anbernic stock firmware, SD card RetroArch files
ArchR, SD card RetroArch files
ArkOS, SD card RetroArch files
CrossMix-OS, SD card RetroArch files
dArkOS, SD card RetroArch files
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
KOLOS, SD card RetroArch files
Koriki, SD card RetroArch files
MiyooCFW, SD card RetroArch files
muOS, SD card RetroArch files
Onion, SD card RetroArch files
plumOS, SD card RetroArch files
RetroOZ, SD card RetroArch files
spruceOS, SD card RetroArch files
UnofficialOS, 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
Koriki, SD card Miyoo Mini
MinUI, SD card Anbernic, Miyoo, TrimUI
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
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
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
+1 -1
View File
@@ -1,6 +1,6 @@
RetroBIOS: Which setup?
Filter (/ to type)
Filter: setup or device (/)
> RetroArch
Batocera
Recalbox
+2 -2
View File
@@ -1,6 +1,6 @@
RetroBIOS: Which setup?
Filter (/ to type)
Filter: setup or device (/)
> RetroArch
Batocera
Recalbox
@@ -8,8 +8,8 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
1-10 of 34
A Choose B Back / Filter ? Help
+9 -9
View File
@@ -1,6 +1,6 @@
RetroBIOS: Which setup?
Filter (/ to type)
Filter: setup or device (/)
> RetroArch
Batocera
Recalbox
@@ -8,17 +8,17 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
RetroPie
AmberELEC PAN4ELEC
EmuELEC
KNULLI, SD card
MinUI, SD card
NextUI, SD card
MyMinUI, SD card
Allium, SD card RetroArch files
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
AmberELEC, SD card Anbernic RG351, RG552
Anbernic stock firmware, SD card RetroArch files
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
dArkOS, SD card Odroid Go Advance, RGB10, RG351MP, G350
EmuELEC, SD card Odroid Go
1-19 of 34
A/Enter Choose B/Esc Back / Filter ? Help
+13 -13
View File
@@ -76,9 +76,9 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
1-10 of 34
A Choose B Back
=== platforms 80x24
@@ -92,18 +92,18 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
RetroPie
AmberELEC PAN4ELEC
EmuELEC
KNULLI, SD card
MinUI, SD card
NextUI, SD card
MyMinUI, SD card
Allium, SD card RetroArch files
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
AmberELEC, SD card Anbernic RG351, RG552
Anbernic stock firmware, SD card RetroArch files
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
dArkOS, SD card Odroid Go Advance, RGB10, RG351MP, G350
EmuELEC, SD card Odroid Go
1-19 of 34
A Choose B Back
=== folder 40x12
@@ -436,7 +436,7 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
1 wrong copy: backed up, replaced.
> Install
Choose systems
Copy from your old card or a folder
@@ -449,15 +449,15 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
1 wrong copy: backed up, replaced.
Free space: 50.0 GB
The 1 wrong copy is replaced, the old
one kept in a backup folder.
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
A Install B Back Y Options
=== plan 80x24
RetroBIOS: RetroArch
@@ -465,8 +465,8 @@
4 files to download, 1.0 MB
BIOS folder: ~/.config/retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
The 1 wrong copy is replaced, the old one kept in a backup folder.
Free space: 50.0 GB
> Install
Choose systems
+13 -13
View File
@@ -76,9 +76,9 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
1-10 of 34
A Choose B Back
=== platforms 80x24
@@ -92,18 +92,18 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
RetroPie
AmberELEC PAN4ELEC
EmuELEC
KNULLI, SD card
MinUI, SD card
NextUI, SD card
MyMinUI, SD card
Allium, SD card RetroArch files
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
AmberELEC, SD card Anbernic RG351, RG552
Anbernic stock firmware, SD card RetroArch files
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
dArkOS, SD card Odroid Go Advance, RGB10, RG351MP, G350
EmuELEC, SD card Odroid Go
1-19 of 34
A Choose B Back
=== folder 40x12
@@ -436,7 +436,7 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
1 wrong copy: backed up, replaced.
> Install
Choose systems
Copy from your old card or a folder
@@ -449,15 +449,15 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
1 wrong copy: backed up, replaced.
Free space: 50.0 GB
The 1 wrong copy is replaced, the old
one kept in a backup folder.
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
A Install B Back Y Options
=== plan 80x24
RetroBIOS: RetroArch
@@ -465,8 +465,8 @@
4 files to download, 1.0 MB
BIOS folder: ~/.config/retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
The 1 wrong copy is replaced, the old one kept in a backup folder.
Free space: 50.0 GB
> Install
Choose systems
+13 -13
View File
@@ -76,9 +76,9 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
1-10 of 34
A Choose B Back
=== platforms 80x24
@@ -92,18 +92,18 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
RetroPie
AmberELEC PAN4ELEC
EmuELEC
KNULLI, SD card
MinUI, SD card
NextUI, SD card
MyMinUI, SD card
Allium, SD card RetroArch files
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
AmberELEC, SD card Anbernic RG351, RG552
Anbernic stock firmware, SD card RetroArch files
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
dArkOS, SD card Odroid Go Advance, RGB10, RG351MP, G350
EmuELEC, SD card Odroid Go
1-19 of 34
A Choose B Back
=== folder 40x12
@@ -436,7 +436,7 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
1 wrong copy: backed up, replaced.
> Install
Choose systems
Copy from your old card or a folder
@@ -449,15 +449,15 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
1 wrong copy: backed up, replaced.
Free space: 50.0 GB
The 1 wrong copy is replaced, the old
one kept in a backup folder.
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
A Install B Back Y Options
=== plan 80x24
RetroBIOS: RetroArch
@@ -465,8 +465,8 @@
4 files to download, 1.0 MB
BIOS folder: ~/.config/retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
The 1 wrong copy is replaced, the old one kept in a backup folder.
Free space: 50.0 GB
> Install
Choose systems
+16 -16
View File
@@ -55,7 +55,7 @@
=== platforms 40x12
RetroBIOS: Which setup?
Filter (/ to type)
Filter: setup or device (/)
> RetroArch
Batocera
Recalbox
@@ -68,7 +68,7 @@
=== platforms 40x15
RetroBIOS: Which setup?
Filter (/ to type)
Filter: setup or device (/)
> RetroArch
Batocera
Recalbox
@@ -76,15 +76,15 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
1-10 of 34
Enter Choose Esc Back / Filter
=== platforms 80x24
RetroBIOS: Which setup?
Filter (/ to type)
Filter: setup or device (/)
> RetroArch
Batocera
Recalbox
@@ -92,18 +92,18 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
RetroPie
AmberELEC PAN4ELEC
EmuELEC
KNULLI, SD card
MinUI, SD card
NextUI, SD card
MyMinUI, SD card
Allium, SD card RetroArch files
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
AmberELEC, SD card Anbernic RG351, RG552
Anbernic stock firmware, SD card RetroArch files
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
dArkOS, SD card Odroid Go Advance, RGB10, RG351MP, G350
EmuELEC, SD card Odroid Go
1-19 of 34
Enter Choose Esc Back / Filter ? Help
=== folder 40x12
@@ -382,7 +382,7 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
1 wrong copy: backed up, replaced.
> Install
Choose systems
Copy from your old card or a folder
@@ -395,15 +395,15 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
1 wrong copy: backed up, replaced.
Free space: 50.0 GB
The 1 wrong copy is replaced, the old
one kept in a backup folder.
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
Enter Install Esc Back o Options
=== plan 80x24
RetroBIOS: RetroArch
@@ -411,8 +411,8 @@
4 files to download, 1.0 MB
BIOS folder: ~/.config/retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
The 1 wrong copy is replaced, the old one kept in a backup folder.
Free space: 50.0 GB
> Install
Choose systems
+13 -13
View File
@@ -76,9 +76,9 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
1-10 of 34
A Choose R2 Back
=== platforms 80x24
@@ -92,18 +92,18 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
RetroPie
AmberELEC PAN4ELEC
EmuELEC
KNULLI, SD card
MinUI, SD card
NextUI, SD card
MyMinUI, SD card
Allium, SD card RetroArch files
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
AmberELEC, SD card Anbernic RG351, RG552
Anbernic stock firmware, SD card RetroArch files
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
dArkOS, SD card Odroid Go Advance, RGB10, RG351MP, G350
EmuELEC, SD card Odroid Go
1-19 of 34
A Choose R2 Back
=== folder 40x12
@@ -436,7 +436,7 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
1 wrong copy: backed up, replaced.
> Install
Choose systems
Copy from your old card or a folder
@@ -449,15 +449,15 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
1 wrong copy: backed up, replaced.
Free space: 50.0 GB
The 1 wrong copy is replaced, the old
one kept in a backup folder.
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
A Install R2 Back L2 Options
=== plan 80x24
RetroBIOS: RetroArch
@@ -465,8 +465,8 @@
4 files to download, 1.0 MB
BIOS folder: ~/.config/retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
The 1 wrong copy is replaced, the old one kept in a backup folder.
Free space: 50.0 GB
> Install
Choose systems
+13 -13
View File
@@ -76,9 +76,9 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
1-10 of 34
Start Choose B Back
=== platforms 80x24
@@ -92,18 +92,18 @@
EmuDeck
Lakka
RetroDECK
ROCKNIX, SD card
RomM
BizHawk
MiSTer FPGA
RetroPie
AmberELEC PAN4ELEC
EmuELEC
KNULLI, SD card
MinUI, SD card
NextUI, SD card
MyMinUI, SD card
Allium, SD card RetroArch files
Allium, SD card Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip
AmberELEC, SD card Anbernic RG351, RG552
Anbernic stock firmware, SD card RetroArch files
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
dArkOS, SD card Odroid Go Advance, RGB10, RG351MP, G350
EmuELEC, SD card Odroid Go
1-19 of 34
Start Choose B Back
=== folder 40x12
@@ -436,7 +436,7 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
1 wrong copy: backed up, replaced.
> Install
Choose systems
Copy from your old card or a folder
@@ -449,15 +449,15 @@
4 files to download, 1.0 MB
BIOS folder: ~/.co.../retroarch/system
1 in place, 3 missing, 1 wrong copy
1 wrong copy: backed up, replaced.
Free space: 50.0 GB
The 1 wrong copy is replaced, the old
one kept in a backup folder.
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
Start Install B Back
=== plan 80x24
RetroBIOS: RetroArch
@@ -465,8 +465,8 @@
4 files to download, 1.0 MB
BIOS folder: ~/.config/retroarch/system
1 in place, 3 missing, 1 wrong copy
Free space: 50.0 GB
The 1 wrong copy is replaced, the old one kept in a backup folder.
Free space: 50.0 GB
> Install
Choose systems
+1 -1
View File
@@ -4249,7 +4249,7 @@ class DestAloneFollowsWhatIsFoundHere(unittest.TestCase):
plat, folder = install._platform_for_dest("linux", root, True)
self.assertEqual((plat, folder), ("retroarch", root))
self.assertIn(f"{root} is not an Onion SD card", out.getvalue())
self.assertIn("Onion, SD card (RetroArch files)", out.getvalue())
self.assertIn("Onion, SD card (Miyoo Mini, Miyoo Mini Plus)", out.getvalue())
def test_nothing_found_is_retroarch_said_aloud(self):
plat, out = self._pick([])
+94 -1
View File
@@ -663,7 +663,7 @@ class OfflineMakesNoRequest(Scratch):
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)
self.assertIn("run without --offline", proc.stdout + proc.stderr)
self.assertIn("without --offline", proc.stdout + proc.stderr)
self.assertNotIn("trying again", proc.stderr)
def test_modes_that_need_the_network_refuse_it(self):
@@ -843,5 +843,98 @@ class OfflineLeavesFilesMissing(Scratch):
self.assertNotIn("no longer in the source", proc.stdout)
class OneOfflineVocabulary(Scratch):
"""code_audit_14 P3-1 to P3-4, tui_audit_7 P3-4: --offline said one
thing at a terminal and another without questions, on the screen and
under --check."""
def old(self) -> Path:
old = self.root / "old"
old.mkdir()
(old / "scph5501.bin").write_bytes(US)
return old
def test_a_run_without_questions_says_missing_not_failed(self):
"""P3-2: "Finished with errors ... 3 files failed" and "Run the
same command again to retry only those"."""
proc = self.run_installer("--offline", "--manifest", str(self.list),
"--from", str(self.old()), "--no-input")
out = proc.stdout + proc.stderr
self.assertEqual(proc.returncode, 1, out)
self.assertIn("Finished with files missing. 1 file installed, 3 not fetched "
"(--offline).", out)
self.assertIn(f"They are not in {self.root / 'old'}", out)
for said in ("Finished with errors", "files failed", "retry",
"copy them from your old card"):
self.assertNotIn(said, out)
def test_nothing_installed_is_said_so(self):
"""P3-3: "Finished with files missing. 0 files installed"."""
failures = [("a.bin", install.NOT_WRITABLE), ("b.bin", install.NOT_WRITABLE)]
with contextlib.redirect_stdout(io.StringIO()) as out, \
self.assertRaises(SystemExit) as stop:
install._report_outcome([self.dest], 0, 0, 2, 0, False, failures=failures,
offline=20)
self.assertEqual(stop.exception.code, 2)
self.assertIn("Nothing installed: 20 files not fetched (--offline), 2 failed.",
out.getvalue())
self.assertNotIn("0 files installed", out.getvalue())
def test_the_command_suggested_keeps_the_offline_setting(self):
"""P3-3: sudo does not pass RETROBIOS_INSTALL_OFFLINE on."""
import test_install_tui as tui
args = tui._args(platform="retroarch", declared_only=False, offline=False)
hint = install.Invocation()
with unittest.mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}):
hint.record(args)
self.assertIn("--offline", hint.command("retroarch", self.dest, elevated=True))
def test_check_offline_says_what_cannot_come(self):
"""P3-4: "Run the same command without --check to fetch them"."""
proc = self.run_installer("--check", "--offline", "--manifest", str(self.list))
out = proc.stdout + proc.stderr
self.assertEqual(proc.returncode, 1, out)
self.assertNotIn("to fetch them", out)
self.assertIn("4 files need the internet, which --offline does not use.", out)
self.assertIn("Copy them from your old card or a folder (--from)", out)
def test_the_screen_says_missing_not_failed(self):
"""P3-2 on the full screen: "Finished with errors, 20 failed"."""
import test_install_tui as tui
reason = f"not fetched: {install.OFFLINE_WORDS}"
state = tui._installed_state(self.dest, 1, [{"dest": f"f{n}.bin", "reason": reason}
for n in range(20)])
state.summary["installed"] = 2
state.from_folders = [self.root / "from1"]
title, lines = install.tui_outcome(state)
self.assertEqual(title, "Finished with files missing")
text = " ".join(lines)
self.assertIn("2 installed, 20 not fetched (--offline).", text)
self.assertIn("They are not in", text)
self.assertNotIn("failed", text)
state.summary["installed"] = 0
self.assertEqual(install.tui_outcome(state)[0], "Nothing installed")
def test_the_line_menu_copies_from_a_folder(self):
"""tui_audit_7 P3-4: no way to a folder, and an install of nothing
offered."""
old = self.old()
install.LOCAL.reset()
self.addCleanup(install.LOCAL.reset)
answers = iter(["f", str(old), ""])
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.assertIsNone(install._prompt_custom_selection("retroarch", [], []))
text = out.getvalue()
self.assertIn("Enter) check the BIOS folder, nothing can be downloaded", text)
self.assertIn("f) copy from a folder or an old card", text)
self.assertIn(f"Enter) install what is found in {old}", text)
self.assertEqual([index.label for index in install.LOCAL.folders], [str(old)])
self.assertNotIn("install everything", text)
if __name__ == "__main__":
unittest.main()
+511 -19
View File
@@ -594,7 +594,8 @@ class Flow(unittest.TestCase):
self.assertEqual(install.tui_rows(state)[state.focus["platforms"]].value, "retroarch")
press(state, "/", "b", "a", "t", "o")
self.assertEqual([row.value for row in install.tui_rows(state)][1:], ["batocera"])
press(state, "enter", "enter")
# tui_audit_7 P3-9.2: the one match is chosen with the first Enter.
press(state, "enter")
self.assertEqual(state.screen, "folder")
self.assertEqual(state.platform, "batocera")
state.focus["folder"] = 2
@@ -2194,9 +2195,13 @@ class AuditRoundThree(unittest.TestCase):
"MUOS at the root of its card.", proc.stderr)
self.assertIn("--dest <card>/MUOS/bios", proc.stderr)
self.assertEqual(list(root.iterdir()), [root / "roms"])
# Its BIOS folder named outright is taken as given.
self.assertEqual(install.card_dest("muos", "retroarch", root / "MUOS" / "bios"),
("retroarch", root / "MUOS" / "bios"))
# 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.
with self.assertRaises(SystemExit), contextlib.redirect_stderr(io.StringIO()):
install.card_dest("muos", "retroarch", root / "MUOS" / "bios")
card = self.card({"MUOS/bios/": ""})
self.assertEqual(install.card_dest("muos", "retroarch", card / "MUOS" / "bios"),
("retroarch", card / "MUOS" / "bios"))
# On the device, the folder muOS binds it to.
bound = Path("/run/muos/storage/bios")
self.assertEqual(install.card_dest("muos", "retroarch", bound), ("retroarch", bound))
@@ -2212,9 +2217,10 @@ class AuditRoundThree(unittest.TestCase):
with self.assertRaises(SystemExit), \
contextlib.redirect_stderr(io.StringIO()) as err:
install.card_dest("muos", "retroarch", stray)
self.assertIn("is not a muOS SD card", err.getvalue())
self.assertEqual(install.card_dest("muos", "retroarch", root / "muos" / "BIOS"),
("retroarch", root / "muos" / "BIOS"))
self.assertIn("does not exist", err.getvalue())
card = self.card({"MUOS/bios/": ""})
self.assertEqual(install.card_dest("muos", "retroarch", card / "muos" / "BIOS"),
("retroarch", card / "muos" / "BIOS"))
def test_p1_1_a_card_two_systems_claim_says_so(self):
root = self.card({"MUOS/": "", "spruce/": ""})
@@ -3042,8 +3048,9 @@ class ACardSystemNamedOnAComputer(unittest.TestCase):
# without a word.
install._browse_to(state, muos)
press(state, "enter")
self.assertEqual(state.status, "That is a muOS card, not an Onion card. Choose "
"the card's top folder.")
# tui_audit_7 P3-1: it is a card's top folder, of another card.
self.assertEqual(state.status, "That is a muOS card. Insert the Onion card, or "
"go back and choose muOS.")
self.assertIsNone(state.dest)
def test_a_typed_card_root_is_checked_on_the_path_screen(self):
@@ -3614,7 +3621,9 @@ class LineModeOffline(unittest.TestCase):
out = term.raw.decode("utf-8", "replace")
self.assertIn("--offline: only the files found on this machine are installed, and no "
"--from names a folder to copy them from.", out)
self.assertIn("Enter) install what is found on this machine", out)
# tui_audit_7 P3-4: nothing to copy from, nothing to install.
self.assertIn("Enter) check the BIOS folder, nothing can be downloaded", out)
self.assertIn("f) copy from a folder or an old card", out)
self.assertIn("5 files need the internet, which --offline does not use.", out)
self.assertNotIn("Downloading", out)
self.assertNotIn("failed", out.lower())
@@ -3945,10 +3954,9 @@ class AGoesIntoASubfolder(unittest.TestCase):
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)
self.assertIn("No setup reads BIOS files in 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)
self.assertIn("No setup reads BIOS files in your home folder.", screen)
def test_on_windows_the_other_drives_are_passed_over(self):
"""The card rows are drive roots there (E:\\): at D:\\ the first
@@ -5222,8 +5230,9 @@ class EveryCardSetupGoesToItsCard(unittest.TestCase):
for index, setup in enumerate(self.setups):
with self.subTest(system=setup.name):
card = _card_from_rules(self.root / f"card{index}", setup)
want = (_setup_folder(setup, card) if setup.known() or not setup.below
else card)
# code_audit_14 P2-1: a card no file names gets its folder
# below the root with --dest too, as typed.
want = _setup_folder(setup, card)
with contextlib.redirect_stdout(io.StringIO()):
by_dest = install.card_dest(setup.name, setup.plat, card)
self.assertEqual(by_dest, (setup.plat, want))
@@ -5244,7 +5253,7 @@ class EveryCardSetupGoesToItsCard(unittest.TestCase):
self.assertEqual(state.dest, want)
# Its card chosen as a card's root: its BIOS folder below it.
if setup.known():
choice = install.card_root_folder(setup.name, setup.plat, card)
choice = install.card_choice(setup.name, setup.plat, card)
self.assertEqual((choice.refused, choice.folder),
("", _setup_folder(setup, card)))
@@ -5261,7 +5270,7 @@ class EveryCardSetupGoesToItsCard(unittest.TestCase):
and setup.plat in install.CARD_PLATFORMS):
continue
with self.subTest(system=setup.name, card=name):
choice = install.card_root_folder(setup.name, setup.plat, card)
choice = install.card_choice(setup.name, setup.plat, card)
self.assertIn(f"is {install._a(name)} card, not "
f"{install._a(setup.name)} card", choice.refused)
self.assertTrue(install.card_choice(setup.name, setup.plat,
@@ -5276,7 +5285,7 @@ class EveryCardSetupGoesToItsCard(unittest.TestCase):
blank.mkdir()
for setup in self.setups:
with self.subTest(system=setup.name):
choice = install.card_root_folder(setup.name, setup.plat, blank)
choice = install.card_choice(setup.name, setup.plat, blank)
if setup.known() and not setup.fresh:
self.assertIn(f"is not {install._a(setup.name)} SD card", choice.refused)
else:
@@ -5462,7 +5471,7 @@ class AuditSix(unittest.TestCase):
rows = install.tui_rows(state)
self.assertEqual(state.screen, "browse")
self.assertEqual(rows[state.focus["browse"]].value, card)
self.assertEqual(state.status, "Choose the card below, or open the folder that holds it.")
self.assertEqual(state.status, "Choose the Onion card below.")
def test_a_number_outside_the_list_is_said(self):
"""tui_audit_6 P3-1: 9 was read as a folder."""
@@ -5551,5 +5560,488 @@ class AuditSix(unittest.TestCase):
self.assertIn(" +2 shared: counted once", install.tui_view(state, 40, 12))
class AuditSevenOneCardReading(unittest.TestCase):
"""code_audit_14 P2-1 to P3-12 and tui_audit_7 P2-5, P3-1: one reading
of a card's root, said the same on the screen and in line mode."""
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 blank(self, name: str = "USBSTICK") -> Path:
stick = self.root / name
(stick / "photos").mkdir(parents=True)
return stick
def start(self, **given) -> install.TuiState:
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=[]):
state, _effects = install.tui_start(_args(**given), "linux")
return state
def test_a_blank_card_gets_one_folder_and_one_note_everywhere(self):
"""code_audit_14 P2-1, tui_audit_7 P2-5: --dest kept the stick's
root, typed it got bios below it, and the screen said nothing."""
stick = self.blank()
for named, plat, below in (("knulli", "knulli", "bios"),
("rocknix", "rocknix", "roms/bios"),
("ArkOS", "retroarch", "bios")):
with self.subTest(system=named):
with contextlib.redirect_stdout(io.StringIO()) as out:
by_dest = install.card_dest(named, plat, stick)
self.assertEqual(by_dest, (plat, stick / below))
note = install.card_choice(named, plat, stick).note
self.assertTrue(note)
self.assertIn(note, out.getvalue())
state = self.start(platform=named, dest=str(stick))
self.assertEqual(state.dest, stick / below)
self.assertEqual(state.card_note, note)
# Kept on Create the folder? and on the plan.
self.assertEqual(state.screen, "create")
self.assertIn(note.split(":")[0],
" ".join(" ".join(install.tui_view(state, 120, 40)).split()))
install.tui_update(state, Key("enter"))
self.assertEqual(state.card_note, note)
def test_the_minui_family_refuses_a_blank_dest(self):
"""code_audit_14 P2-2: taken with --dest, refused typed."""
stick = self.blank()
for plat in ("minui", "nextui", "myminui"):
with self.subTest(platform=plat):
choice = install.card_choice(plat, plat, stick)
self.assertIn("SD card", choice.refused)
with self.assertRaises(SystemExit), \
contextlib.redirect_stderr(io.StringIO()):
install.card_dest(plat, plat, stick)
def test_a_card_mounted_as_its_bios_folder_is_the_card(self):
"""code_audit_14 P3-7: an Onion card mounted as BIOS was refused,
ROCKNIX's roms/bios was taken for KNULLI, a muOS card's folder
too."""
onion = self.root / "BIOS"
make_card(onion, "Onion")
choice = install.card_choice("Onion", "retroarch", onion)
self.assertEqual((choice.refused, choice.folder), ("", onion / "BIOS"))
knulli = self.root / "k" / "bios"
make_card(knulli, "KNULLI")
self.assertEqual(install.card_choice("knulli", "knulli", knulli).folder,
knulli / "bios")
rocknix = self.root / "ROCKNIX"
make_card(rocknix, "ROCKNIX")
refused = install.card_choice("knulli", "knulli", rocknix / "roms" / "bios")
self.assertIn("is a ROCKNIX card, not a KNULLI card", refused.refused)
muos = self.root / "MUOS"
make_card(muos, "muOS")
refused = install.card_choice("knulli", "knulli", muos / "MUOS" / "bios")
self.assertIn("is a muOS card", refused.refused)
def test_no_folder_above_a_mounted_drive_is_its_card(self):
"""A card mounted as MUOS beside a stick: /media/me, which holds
it, is no muOS card for the stick."""
stick = self.blank()
(self.root / "MUOS" / "bios").mkdir(parents=True)
with mock.patch.object(install, "_mounted_cards",
return_value=[stick, self.root / "MUOS"]):
choice = install.card_choice("knulli", "knulli", stick)
self.assertEqual((choice.refused, choice.folder), ("", stick / "bios"))
bios = self.root / "mnt" / "bios"
(bios / "x").mkdir(parents=True)
with mock.patch.object(install, "_mounted_cards", return_value=[bios]):
# A drive mounted at a folder named bios is the card itself.
self.assertEqual(install._card_and_below(
install.card_setup("knulli", "knulli"), bios), (bios, False))
def test_a_refused_dest_says_why_in_the_events(self):
"""code_audit_14 P3-8: code 1 and no reason in the stream."""
stick = self.blank()
proc = install_run("--events", "--platform", "onion", "--dest", str(stick),
"--manifest", str(REPO_ROOT / "install" / "retroarch.json"),
"--dry-run", env={install.OFFLINE_VARIABLE: "1"})
self.assertEqual(proc.returncode, 1, proc.stderr)
events = [json.loads(line) for line in proc.stdout.splitlines()]
error = next(event for event in events if event["event"] == "error")
self.assertEqual((error["stage"], error["reason"]), ("detect", "not_found"))
self.assertIn("is not an Onion SD card", error["message"])
self.assertEqual(events[-1], {"event": "end", "code": 1})
table = (REPO_ROOT / "wiki" / "installer.md").read_text(encoding="utf-8")
self.assertIn("not the card of the system named", table)
def test_a_card_no_file_names_is_titled_by_its_system(self):
"""code_audit_14 P3-9: an ArkOS card chosen read as RetroArch."""
stick = self.blank()
state = self.start(platform="ArkOS", dest=str(stick))
self.assertEqual(install.detected_os("retroarch", state.dest), "ArkOS")
def test_a_list_named_for_another_systems_card_says_so(self):
"""code_audit_14 P3-10: --platform batocera on an Onion card."""
onion = self.root / "ONION"
make_card(onion, "Onion")
choice = install.card_choice("batocera", "batocera", onion)
self.assertEqual((choice.refused, choice.folder), ("", onion))
self.assertIn("is an Onion card", choice.note)
self.assertIn("--platform onion", choice.note)
state = self.start(platform="batocera", dest=str(onion))
self.assertEqual(state.card_note, choice.note)
def test_amberelec_and_emuelec_are_asked_for_their_card(self):
"""code_audit_14 P3-11: "Use this folder" on /storage/roms of a PC."""
for plat, name in (("amberelec", "AmberELEC"), ("emuelec", "EmuELEC")):
with self.subTest(system=name):
setup = install.card_setup(plat, plat)
self.assertEqual((setup.name, setup.below), (name, ""))
state = self.start(platform=plat)
self.assertEqual(state.screen, "folder")
self.assertIsNone(state.folder_default)
self.assertIn("Where is the SD card?",
" ".join(install.tui_view(state, 80, 24)))
stick = self.blank(f"{name}STICK")
choice = install.card_choice(plat, plat, stick)
self.assertEqual(choice.folder, stick)
self.assertIn("cannot tell", choice.note)
def test_install_py_has_two_blank_lines_around_each_definition(self):
"""code_audit_14 P3-12: E305 after _card_mount, and the same slip
before _Interrupted, TuiRow and _setup_label (pycodestyle E302,
E303, E305 at top level)."""
import ast
lines = (REPO_ROOT / "install.py").read_text(encoding="utf-8").splitlines()
body = ast.parse("\n".join(lines)).body
def blank_before(node: ast.stmt) -> int:
# The longest run of blank lines between the code above and the
# statement, its decorators and comments in between, as
# pycodestyle counts them.
index = min([node.lineno] + [item.lineno for item in
getattr(node, "decorator_list", [])]) - 2
runs, run = [], 0
while index >= 0 and (not lines[index].strip() or lines[index].startswith("#")):
if lines[index].strip():
runs.append(run)
run = 0
else:
run += 1
index -= 1
return max(runs + [run])
definitions = (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)
for before, after in zip(body, body[1:]):
count = blank_before(after)
with self.subTest(line=after.lineno):
if isinstance(after, definitions):
self.assertEqual(count, 2)
elif isinstance(before, definitions):
self.assertEqual(count, 2)
def test_a_refusal_is_said_once_and_another_card_names_its_remedy(self):
"""tui_audit_7 P3-1: said twice, and "Choose the card's top folder"
for the top folder of another card."""
stick = self.blank()
state = self.start(platform="onion")
state.pad = "keys"
install._card_status(state, install.card_choice("Onion", "retroarch", stick))
after = install._status_after(state)
self.assertEqual(len(after), 1)
self.assertTrue(after[0].endswith("? says more."))
state.help = True
self.assertIn("Onion keeps .tmp_update",
" ".join(" ".join(install.tui_view(state, 80, 24)).split()))
state.help = False
muos = self.root / "MUOS"
make_card(muos, "muOS")
install._card_status(state, install.card_choice("Onion", "retroarch", muos))
self.assertEqual(state.status, "That is a muOS card. Insert the Onion card, or go "
"back and choose muOS.")
with mock.patch.object(install, "_ask", side_effect=[str(muos), str(stick),
KeyboardInterrupt]), \
mock.patch.object(install, "_mounted_cards", return_value=[]), \
contextlib.redirect_stdout(io.StringIO()) as out, \
self.assertRaises(KeyboardInterrupt):
install.card_install_dest("Onion", "retroarch", True)
self.assertIn("Insert the Onion card, or choose muOS for that one.", out.getvalue())
def test_line_mode_names_the_card_picked_and_its_folder(self):
"""tui_audit_7 P2-3, P3-5: no echo of the card picked, "Mount the
card" under two mounted, /mnt/d where the list says D:."""
cards = [self.root / "ONION", self.root / "ONION2"]
for card in cards:
make_card(card, "Onion")
with mock.patch.object(install, "_ask", return_value="2"), \
mock.patch.object(install, "_mounted_cards", return_value=cards), \
mock.patch.object(install, "_missing_dest", return_value=True), \
contextlib.redirect_stdout(io.StringIO()) as out, \
contextlib.redirect_stderr(io.StringIO()):
dest = install.card_install_dest("Onion", "retroarch", True)
self.assertEqual(dest, cards[1] / "BIOS")
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.assertEqual(install.detected_os("retroarch", dest), "Onion")
with mock.patch.object(install, "_ask", return_value=""), \
contextlib.redirect_stdout(io.StringIO()) as out:
install._prompt_custom_selection("retroarch", [], [], name="Onion",
bios_path=dest)
self.assertIn("Onion: 0 files", out.getvalue())
self.assertIn(f"BIOS folder: {dest}", out.getvalue())
proc = install_run("--platform", "onion", "--dest", str(cards[0]), "--dry-run",
"--manifest", str(REPO_ROOT / "install" / "retroarch.json"),
env={install.OFFLINE_VARIABLE: "1"})
self.assertNotIn("gets the RetroArch file list", proc.stdout)
def test_wsl_names_the_folder_by_its_drive_letter(self):
"""tui_audit_7 P3-6, code_audit_14 P3-5."""
with mock.patch.dict(install._WSL, {"active": True, "root": "/mnt"}):
self.assertEqual(install._short_path(Path("/mnt/d/BIOS"), HOME), "D:\\BIOS")
self.assertEqual(install.shown_path(Path("/mnt/d")), "D:\\")
args = _args(dest=Path("D:\\"), save_dir=Path("F:\\saves"))
setattr(args, "from", [Path("E:\\")])
args.from_pack = [Path("E:\\pack.zip")]
install._wsl_dest(args)
self.assertEqual((args.dest, getattr(args, "from"), args.from_pack, args.save_dir),
(Path("/mnt/d"), [Path("/mnt/e")], [Path("/mnt/e/pack.zip")],
Path("/mnt/f/saves")))
with mock.patch.dict(install._WSL, {"active": False}):
self.assertEqual(install._short_path(Path("/mnt/d/BIOS"), HOME), "/mnt/d/BIOS")
def test_a_multi_word_system_example_can_be_pasted(self):
"""code_audit_14 P3-6."""
with mock.patch.object(install, "_mounted_cards", return_value=[]), \
contextlib.redirect_stderr(io.StringIO()) as err, \
self.assertRaises(SystemExit):
install.card_install_dest("Anbernic stock firmware", "retroarch", False)
self.assertIn("--platform anbernicstockfirmware --dest", err.getvalue())
class AuditSevenScreens(unittest.TestCase):
"""tui_audit_7 P2-1 to P3-9: what each screen offers and says."""
def setUp(self):
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, self.root, True)
def setups(self, pad: str = "keys") -> install.TuiState:
state = install.TuiState(os_type="linux", home=HOME)
state.pad = pad
state.stack = ["platforms"]
state.focus["platforms"] = 1
return state
def test_a_handheld_is_found_by_its_device_in_few_presses(self):
"""P2-1: Onion was row 30 of 34, its row said "RetroArch files"."""
state = self.setups("gptokeyb")
rows = install.tui_rows(state)
labels = [row.label for row in rows]
self.assertEqual(labels[1], "RetroArch")
cards = [index for index, row in enumerate(rows) if row.label.endswith(", SD card")]
# One block, right after RetroArch.
self.assertEqual(cards, list(range(2, 2 + len(cards))))
onion = labels.index("Onion, SD card")
self.assertIn("Miyoo Mini", rows[onion].right)
# At 40x12 a page is 6 rows: L1/R1 then Down, then A.
page = 12 - 6
presses = (onion - 1) // page + (onion - 1) % page + 1
self.assertLessEqual(presses, 4)
text = " ".join(install.tui_view(state, 40, 12))
self.assertIn("handhelds come after RetroArch", " ".join(text.split()))
state.pad = "keys"
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
self.assertIn("type your device's name (/)", text)
self.assertIn("Filter by setup or device name",
[what for _key, what in install._help_lines(state)])
# Something found here: the list keeps its order.
found = fresh_state()
found.stack = ["setup", "platforms"]
self.assertEqual([row.label for row in install.tui_rows(found)][2], "Batocera")
def test_line_mode_takes_a_device_name(self):
"""P2-1: "miyoo" refused at the line menu."""
with mock.patch.object(install, "_ask", side_effect=["bittboy"]), \
contextlib.redirect_stdout(io.StringIO()) as out:
self.assertEqual(install._choose_platform("Which?"), "MiyooCFW")
self.assertIn("your device's name", out.getvalue())
choices = install._line_setups()
onion = [name for name, _label in choices].index("Onion") + 1
with mock.patch.object(install, "_ask", side_effect=["miyoo mini+", str(onion)]), \
contextlib.redirect_stdout(io.StringIO()) as out:
self.assertEqual(install._choose_platform("Which?"), "Onion")
self.assertIn("setups run on 'miyoo mini+'", out.getvalue())
self.assertIn(f"{onion}) Onion, SD card (Miyoo Mini, Miyoo Mini Plus)", out.getvalue())
def test_one_spelling_per_device_and_one_press_for_one_match(self):
"""P3-9.2, P3-9.3, P3-9.4."""
for name, devices in install._SETUP_DEVICES.items():
with self.subTest(system=name):
self.assertFalse(any("+" in device for device in devices))
self.assertFalse(any(device.startswith("Anbernic RG35XX")
for device in install._SETUP_DEVICES["spruceOS"]))
state = self.setups()
state.filters["platforms"] = "mini+"
self.assertIn("Onion, SD card", [row.label for row in install.tui_rows(state)])
state.filters["platforms"] = "koriki"
state.focus["platforms"] = 0
self.assertEqual(install._primary(state), "A/Enter Choose")
with mock.patch.object(install, "_mounted_cards", return_value=[]):
press(state, "enter")
self.assertEqual(state.screen, "folder")
self.assertEqual(state.card_bound[0], "Koriki")
rows = {row.value: row for row in install.tui_rows(self.setups())}
self.assertNotIn("PAN4ELEC", rows["amberelec"].right)
def test_help_rows_keep_their_column_at_40(self):
"""P3-9.1: "/ Filter (Esc clears)" ran back to the left edge."""
for pad in ("gptokeyb", "gptokeyb_muxterm", "miyoo", "evmapy"):
with self.subTest(pad=pad):
state = self.setups(pad)
state.filters["platforms"] = "x"
state.help = True
lines = [line[1:] for line in install.tui_view(state, 40, 12)[2:-1]
if line.strip()]
column = lines[0].index("Move")
for line in lines:
self.assertTrue(line[:column].strip() == "" or line[column - 2:column]
== " ", line)
def test_copy_from_a_folder_with_none_says_so(self):
"""P2-2: the plan came back the same."""
state = checked_state()
old = self.root / "old"
old.mkdir()
state.from_folders = [old]
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
self.assertIn("old holds none of these files.", text)
state.found = [found("gba_bios.bin", str(old))]
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
self.assertNotIn("holds none", text)
stick = self.root / "STICK"
stick.mkdir()
state.home = self.root
with mock.patch.object(install, "_mounted_cards", return_value=[stick]), \
mock.patch.object(install, "_listed", return_value=[]):
install._open_from_browser(state)
rows = install.tui_rows(state)
self.assertEqual(rows[state.focus["browse"]].value, stick)
def test_offline_plan_names_the_folder_already_copied_from(self):
"""code_audit_14 P3-1."""
state = checked_state()
state.from_folders = [self.root / "from1"]
state.found = [found("gba_bios.bin", str(self.root / "from1"))]
with mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}):
text = " ".join(" ".join(install.tui_view(state, 100, 40)).split())
self.assertIn("They are not in from1", text)
self.assertNotIn("Copy them from your old card", text)
def test_the_card_browser_focuses_the_card_asked_for(self):
"""P3-2: the muOS card, listed first, was focused for Onion."""
muos, onion, second = self.root / "A_MUOS", self.root / "B_ONION", self.root / "C"
make_card(muos, "muOS")
make_card(onion, "Onion")
make_card(second, "Onion")
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=[]):
state, _effects = install.tui_start(_args(platform="onion"), "linux")
state.home = self.root
state.focus["folder"] = [row.action for row in install.tui_rows(state)].index(
"browse")
with mock.patch.object(install, "_mounted_cards",
return_value=[muos, onion, second]):
press(state, "enter")
self.assertEqual(install.tui_rows(state)[state.focus["browse"]].value, onion)
self.assertEqual(state.status, "Choose the Onion card below.")
for line in install.tui_view(state, 40, 12):
self.assertNotIn("Go back", line)
def test_confirm_offers_no_install_while_it_counts(self):
"""P3-3."""
state = checked_state()
state.stack.append("confirm")
state.running, state.dry = True, None
self.assertEqual(install.tui_view(state, 80, 24)[-1].split(), ["B/Esc", "Back", "?",
"Help"])
self.assertNotIn("Install", [what for _key, what in install._help_lines(state)])
def test_back_after_choosing_systems_lands_on_install(self):
"""P3-8."""
state = checked_state()
state.focus["plan"] = [row.action for row in install.tui_rows(state)].index("systems")
press(state, "enter")
self.assertEqual(state.screen, "systems")
state.focus["systems"] = next(index for index, row in
enumerate(install.tui_rows(state))
if row.action == "system")
press(state, " ", "esc")
self.assertEqual(state.screen, "plan")
rows = install.tui_rows(state)
self.assertEqual(rows[state.focus["plan"]].action, "install")
def test_a_lone_card_opens_the_first_screen_with_a_list(self):
"""P3-7: with --manifest, the card's folder was the first screen."""
onion = self.root / "ONIONB"
make_card(onion, "Onion")
shutil.rmtree(onion / "BIOS")
args = _args(platform="retroarch", manifest=str(REPO_ROOT / "install" /
"retroarch.json"))
args.platform_from_list = True
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=[]), \
mock.patch.dict(install._DETECTED_OS, {}):
state, effects = install.tui_start(args, "linux")
self.assertEqual((state.screen, effects), ("setup", []))
def test_a_file_left_for_its_save_directory_is_said_so(self):
"""P2-4: the menu counted 23, 22 were checked, and the end blamed
the collection for the file whose save directory is unknown."""
menus = []
def menu(_plat, files, *_rest, **_named):
menus.append([entry["dest"] for entry in files])
return None
with SaveDirectoryRepository() as repo:
argv = ["install.py", "--platform", "retroarch", "--dest", str(repo.dest),
"--manifest", str(repo.root / "install" / "retroarch.json")]
with mock.patch.dict(os.environ, repo.env()), \
mock.patch.object(sys, "argv", argv), \
mock.patch.object(sys.stdin, "isatty", return_value=True), \
mock.patch.object(install, "_is_terminal", return_value=True), \
mock.patch.object(install, "detect_os", return_value="linux"), \
mock.patch.object(install, "records_selection", return_value=True), \
mock.patch.object(install, "_prompt_custom_selection", menu), \
mock.patch.object(install, "_ask", return_value="y"), \
contextlib.redirect_stdout(io.StringIO()) as out, \
contextlib.redirect_stderr(io.StringIO()):
try:
install.main()
except SystemExit:
pass
finally:
install.LOCAL.reset()
self.assertEqual(len(menus), 1)
self.assertNotIn("Azahar/Azahar/sysdata/keys.txt", menus[0])
text = out.getvalue()
self.assertIn("1 file read from RetroArch's save directory left out: its place is "
"not known. Name it with --save-dir next time.", text)
self.assertNotIn("the collection does not hold", text)
def test_plural_game_data_and_the_backup_line_at_40x12(self):
"""P3-9.5, P3-9.6."""
state = checked_state()
games = [{"dest": f"g{index}.dat", "system": "", "category": install.GAME_DATA}
for index in range(2)]
with mock.patch.object(install, "_unavailable", return_value=(games, 0)):
self.assertIn("Copy them from", " ".join(install._unavailable_lines(state)))
with mock.patch.object(install, "_unavailable", return_value=(games[:1], 0)):
self.assertIn("Copy it from", " ".join(install._unavailable_lines(state)))
view = " ".join(" ".join(install.tui_view(state, 40, 12)).split())
self.assertIn("1 wrong copy: backed up, replaced.", view)
if __name__ == "__main__":
unittest.main()
+7 -3
View File
@@ -11,7 +11,9 @@ profiles, placed below that folder.
from __future__ import annotations
import contextlib
import hashlib
import io
import json
import os
import shutil
@@ -406,9 +408,11 @@ class InstallerReadsTheCard(unittest.TestCase):
(self.root / ".system" / "version.txt").write_text("MinUI-20251127-0\n")
(self.root / "Bios").mkdir()
with self.assertRaises(SystemExit):
install._check_card_root("minui", self.root / "Bios")
install._check_card_root("minui", self.root)
with self.assertRaises(SystemExit), contextlib.redirect_stderr(io.StringIO()):
install.card_dest("minui", "minui", self.root / "Bios")
self.assertEqual(install.card_dest("minui", "minui", self.root),
("minui", self.root))
class TheSitePageOfACardPlatform(unittest.TestCase):
"""A platform that names no file showed "0/0 Coverage (0%)" and an empty