mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 00:27:20 -05:00
chore: merge origin/main
This commit is contained in:
commit
9c34e8891f
22 files changed
+1445
-403
No files matched your search
@@ -193,8 +193,10 @@ jobs:
|
||||
fi
|
||||
python scripts/profile_sync.py --added --base "$base" --strict
|
||||
|
||||
# ubuntu-latest moved to 26.04, where setup-python has no 3.8 build: the
|
||||
# job is held on the last image that still carries the floor interpreter.
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9"
|
||||
$defaultInstallSha256 = "89451086ee0a434877b56d78734aed95be74e62ec5c1a5ab4e54769902ed8f44"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+687
-263
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9"
|
||||
DEFAULT_INSTALL_SHA256="89451086ee0a434877b56d78734aed95be74e62ec5c1a5ab4e54769902ed8f44"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
{
|
||||
"dest": "roms/ports/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 692187,
|
||||
"sha256": "2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9",
|
||||
"size": 715303,
|
||||
"sha256": "89451086ee0a434877b56d78734aed95be74e62ec5c1a5ab4e54769902ed8f44",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -71,8 +71,8 @@
|
||||
"muos": {
|
||||
"archive": {
|
||||
"dest": "ARCHIVE/RetroBIOS.muxapp",
|
||||
"size": 744736,
|
||||
"sha256": "17daef144acc01204912de201bbb6bbf8996d1a866a37074e7cbaffe879e911b"
|
||||
"size": 767852,
|
||||
"sha256": "07caac3bd999de7eea34292d8ecc806100f6e950a51805d7db3df37af141bc94"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
@@ -92,8 +92,8 @@
|
||||
{
|
||||
"name": "RetroBIOS/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 692187,
|
||||
"sha256": "2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9",
|
||||
"size": 715303,
|
||||
"sha256": "89451086ee0a434877b56d78734aed95be74e62ec5c1a5ab4e54769902ed8f44",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -166,8 +166,8 @@
|
||||
{
|
||||
"dest": ".config/modules/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 692187,
|
||||
"sha256": "2e88f22d2f3cd933d9b198b841367d6e2d7084de228fa0b12dc2f11c71c0c1e9",
|
||||
"size": 715303,
|
||||
"sha256": "89451086ee0a434877b56d78734aed95be74e62ec5c1a5ab4e54769902ed8f44",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
|
||||
+14
-6
@@ -154,8 +154,8 @@ handheld_guide:
|
||||
- name: muOS
|
||||
folder: '`MUOS/bios` on the card'
|
||||
folder_source: https://github.com/MustardOS/internal/blob/main/share/web/tools/bios-checker.html
|
||||
devices: [RG35XX H, RG35XX Plus, RG35XX SP, RG35XX 2024, RG34XX H, RG40XX H, RG40XX V,
|
||||
RG28XX H, RGCUBEXX, TrimUI Brick, TrimUI Smart Pro]
|
||||
devices: [RG35XX H, RG35XX Plus, RG35XX SP, RG35XX 2024, RG34XX, RG40XX H, RG40XX V,
|
||||
RG28XX, RG CubeXX, TrimUI Brick, TrimUI Smart Pro]
|
||||
devices_source: https://muos.dev/devices
|
||||
- name: Onion
|
||||
folder: '`BIOS` at the root of the card'
|
||||
@@ -166,7 +166,7 @@ handheld_guide:
|
||||
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, RG35XX Plus, RG40XX H, Powkiddy RGB30]
|
||||
TrimUI Smart Pro, TrimUI Brick, RG35XX Plus, RG40XX H, RGB30]
|
||||
devices_source: https://github.com/spruceUI/spruceOS/wiki/Supported-Devices
|
||||
- name: Allium
|
||||
folder: '`BIOS` at the root of the card'
|
||||
@@ -174,12 +174,14 @@ handheld_guide:
|
||||
devices: [Miyoo Mini, Miyoo Mini Plus, Miyoo Mini Flip]
|
||||
devices_source: https://github.com/goweiwen/Allium
|
||||
- name: Anbernic stock firmware
|
||||
devices: [RG35XX Plus, RG35XX H, RG35XX SP, RG28XX]
|
||||
devices_source: https://github.com/GarlicOS/bootloader_anbernic_rg35xxplus/blob/45b489b41dc3dd1738b14486cda0f2dce66e714f/README.md
|
||||
- name: ArchR
|
||||
folder: '`/storage/roms/bios`, on a part only Linux can open'
|
||||
folder_source: https://arch-r.io/docs/roms/
|
||||
linux_only: true
|
||||
devices: [R36S, R33S, R36S Soysauce, Odroid Go Advance, Odroid Go Super, RG351V,
|
||||
RG351M, GameForce Chi, MagicX XU10, Powkiddy RGB10, Powkiddy RGB10X, K36]
|
||||
RG351M, GameForce Chi, MagicX XU10, RGB10, RGB10X, K36]
|
||||
devices_source: https://github.com/archr-linux/Arch-R
|
||||
- name: CrossMix-OS
|
||||
folder: '`BIOS` at the root of the card'
|
||||
@@ -194,10 +196,16 @@ handheld_guide:
|
||||
- name: JELOS
|
||||
folder: '`roms/bios` on the games card'
|
||||
folder_source: https://github.com/JustEnoughLinuxOS/distribution/blob/7b2e116ce6996d56e642a0e2a07288c926db926b/packages/emulators/standalone/retroarch/sources/RK3566/retroarch.cfg#L716
|
||||
devices: [RG351M, RG351V, RG353M, RG353P, RG353V, RG353VS, RG503, RG552, R33S, R36S,
|
||||
GameForce Ace, GameForce Chi, Odroid Go Advance, Odroid Go Super, Odroid Go Ultra,
|
||||
MagicX XU10, RGB10, RGB10 Max 3, RGB10 Max 3 Pro, RGB20S, RGB30, RK2023, X55,
|
||||
AYANEO Air, AYANEO Air Plus, AYANEO Air Pro, AYANEO 2, AYANEO 2S, AYANEO 2021,
|
||||
AYANEO Geek, AYANEO Next, AYN Loki Max, AYN Loki Mini Pro, AYN Loki Zero]
|
||||
devices_source: https://github.com/JustEnoughLinuxOS/distribution/tree/7b2e116ce6996d56e642a0e2a07288c926db926b/packages/hardware/quirks/devices
|
||||
- name: KOLOS
|
||||
folder: '`roms/bios` on the games card'
|
||||
folder_source: https://github.com/AnonLinuxOS/KOLOS/blob/7daf5950ae79bb8775f129468164f730dbc32298/packages/emulators/standalone/retroarch/sources/RK3566/retroarch.cfg#L716
|
||||
devices: [RG353P, RG353M, RG353V, RG353VS, RG503, Powkiddy RK2023, RGB30, RGB10 Max 3]
|
||||
devices: [RG353P, RG353M, RG353V, RG353VS, RG503, RK2023, RGB30, RGB10 Max 3]
|
||||
devices_source: https://github.com/AnonLinuxOS/KOLOS
|
||||
- name: Koriki
|
||||
folder: '`BIOS` at the root of the card'
|
||||
@@ -222,7 +230,7 @@ handheld_guide:
|
||||
- name: RetroOZ
|
||||
folder: '`/roms/bios`, as the device names it'
|
||||
folder_source: https://github.com/southoz/RetroOZ/wiki/Home/4ce4fafb78d647d422e9e468d5bd9cbc63dbe3ea
|
||||
devices: [OGS, OGA, RGB10Max, RGB10Max2]
|
||||
devices: [Odroid Go Super, Odroid Go Advance, RGB10 Max, RGB10 Max 2]
|
||||
devices_source: https://github.com/southoz/RetroOZ/wiki
|
||||
- name: UnofficialOS
|
||||
folder: '`roms/bios` on the games card'
|
||||
|
||||
@@ -160,7 +160,7 @@ def seal(slim: dict, sidecar: dict, rid: str | None = None) -> tuple[dict, bytes
|
||||
"""The slim record with its data reference and id, and the sidecar bytes.
|
||||
|
||||
``rid`` keeps an id given before (a record carried over from the first
|
||||
layout); otherwise the id is computed.
|
||||
layout), otherwise the id is computed.
|
||||
"""
|
||||
blob = sidecar_bytes(sidecar)
|
||||
sealed = {**slim, "data": {"sha256": sha256_hex(blob), "bytes": len(blob)}}
|
||||
@@ -231,7 +231,7 @@ def parse_lines(text: str, label: str) -> list[dict]:
|
||||
|
||||
def appended_only(before: str, after: str) -> str | None:
|
||||
"""None when ``after`` keeps every line of ``before``, in place, and
|
||||
only adds lines at its end; otherwise the first line that moved."""
|
||||
only adds lines at its end, otherwise the first line that moved."""
|
||||
old, new = before.splitlines(), after.splitlines()
|
||||
for number, line in enumerate(old, 1):
|
||||
if number > len(new):
|
||||
|
||||
+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()
|
||||
@@ -81,7 +81,7 @@ It opens a menu:
|
||||
|
||||
- **Right folder?** Press Enter.
|
||||
- **Wrong folder?** Choose **Another setup or folder**, then browse to yours.
|
||||
- **A handheld's SD card?** Type its name (Miyoo Mini).
|
||||
- **A handheld's SD card?** There, type its name (Miyoo Mini).
|
||||
|
||||
Arrows move, Enter chooses, Esc goes back, as A and B on a handheld.
|
||||
|
||||
|
||||
+26
-16
@@ -109,7 +109,9 @@ SHA-1 and size, and every file is checked against it before it takes its
|
||||
place. A data tree (Dolphin's `Sys/`, PPSSPP's assets) comes from the pack
|
||||
too, not from its source archive. `--target` reads a core list from the
|
||||
network and is refused with `--from-pack`. A board found on the device is not
|
||||
applied either: every board's files are installed.
|
||||
applied either: every board's files are installed. At a terminal the run
|
||||
finds a card mounted here, asks the folder when nothing is found, and names
|
||||
the BIOS folder on its menu before anything is copied.
|
||||
|
||||
`--manifest FILE` reads the file list from a copy of `install/<platform>.json`
|
||||
instead of downloading it. Alone, the files still come from the repository.
|
||||
@@ -125,7 +127,8 @@ internet instead of offering to fetch them. The run installs the copies found
|
||||
on this machine and downloads nothing. The files left out are missing, not
|
||||
failed: it ends `Finished with files missing` (code 1), or `Nothing installed`
|
||||
(code 2) when nothing was installed, at a terminal, without questions and on
|
||||
the full screen alike. The advice that follows names a pack or a run without
|
||||
the full screen alike. The `summary` event counts them in `pending`, never in
|
||||
`failed`. The advice that follows names a pack or a run without
|
||||
`--offline`, and no `--from` folder already read. A command it suggests keeps
|
||||
`--offline` when `RETROBIOS_INSTALL_OFFLINE` set it, sudo not passing that on.
|
||||
The `--plain` menu then offers `f) copy from a folder or an old card`, and its
|
||||
@@ -166,16 +169,16 @@ install starts.
|
||||
| Action | Keyboard | Controller |
|
||||
|--------|----------|------------|
|
||||
| Move | Up and Down, `k` and `j` | D-pad Up and Down |
|
||||
| Page | PgUp and PgDn | L1 and R1 |
|
||||
| Page | PgUp and PgDn | L1 and R1 (muxterm: D-pad Left and Right) |
|
||||
| First, last | Home and End, `g` and `G` | |
|
||||
| Continue, open, choose | Enter, and Space outside a list of ticks | A, Start |
|
||||
| Tick, untick | Space, Right, Left | X, D-pad Right and Left |
|
||||
| Back | Esc, Backspace | B, Select |
|
||||
| Options | `o`, Tab | Y |
|
||||
| Filter a long list | `/` then type, Enter on a lone match chooses it | none: a pad cannot type (the setup list puts the SD cards after RetroArch) |
|
||||
| Continue, open, choose | Enter, and Space outside a list of ticks | A, Start (muxterm: Start) |
|
||||
| Tick, untick | Space, Right, Left | X, D-pad Right and Left (muxterm: Y, Miyoo: A) |
|
||||
| Back | Esc, Backspace | B, Select (Miyoo: R2) |
|
||||
| Options | `o`, Tab | Y (Miyoo: L2, muxterm: none) |
|
||||
| Filter a long list | `/` then type, Enter on a lone match chooses it | muxterm: Select shows a keyboard. Elsewhere none (the setup list puts the SD cards after RetroArch) |
|
||||
| Keys of the screen | `?`, F1 | |
|
||||
| Stop an install | Esc, `q`, Ctrl+C | B, Start+Select |
|
||||
| Quit | `q`, Ctrl+C | B on the first screen |
|
||||
| Stop an install | Esc, `q`, Ctrl+C | B, Start+Select (Miyoo: R2) |
|
||||
| Quit | `q`, Ctrl+C | B on the first screen (Miyoo: R2) |
|
||||
|
||||
The bottom line of each screen names what A does on the row under the cursor,
|
||||
in the names of what is held. On a computer it names the keys (`Enter Install`,
|
||||
@@ -699,7 +702,7 @@ what the options name, everything by default.
|
||||
| Option | Effect |
|
||||
|--------|--------|
|
||||
| <span id="option-platform"></span>`--platform NAME` | Install for this platform instead of the detected one. A system `--list-platforms` names under its own files (`muos`, `arkos`) stands for the file list it gets, and the run says which. Unknown names are refused with the closest ones |
|
||||
| <span id="option-dest"></span>`--dest PATH` | Destination directory, overriding detection. Without `--platform`, the file list is the platform detected here: at a terminal the installer asks, Enter taking the one found, and otherwise it uses the one found, RetroArch's when nothing is found, and refuses when several are. The run prints whose list it is. A folder that does not exist is named with the nearest one that does, then created. When more than its last level is missing (a mistyped path), a run at a terminal asks first, and the answer defaults to no |
|
||||
| <span id="option-dest"></span>`--dest PATH` | Destination directory, overriding detection. Without `--platform`, the file list is the platform detected here: at a terminal the installer asks, Enter taking the one found, and otherwise it uses the one found, RetroArch's when nothing is found, and refuses when several are. The run prints whose list it is. A folder that does not exist is named with the nearest one that does, then created, except for a system that runs from its SD card, which refuses it. When more than its last level is missing (a mistyped path), a run at a terminal asks first, and the answer defaults to no |
|
||||
| <span id="option-target"></span>`--target NAME` | Keep only the files the cores of that hardware target need, and the systems they emulate. An unknown target is refused rather than ignored, since carrying on would install everything. On Batocera, Recalbox, ROCKNIX and Lakka the board is read from the system (`batocera.arch`, `recalbox.arch`, `HW_DEVICE`, `LIBREELEC_DEVICE` or `LIBREELEC_PROJECT`) and applied when no `--target` is given |
|
||||
| <span id="option-every-board"></span>`--every-board` | Install the files of every board instead of the one the system names, for a card that moves between devices |
|
||||
| <span id="option-installed-cores"></span>`--installed-cores` | Keep only what the cores already installed read (RetroArch, Lakka, RetroPie). See [Installed cores](#installed-cores) |
|
||||
@@ -939,7 +942,8 @@ The filter takes a device name, and so does the `--plain` menu, which lists
|
||||
the setups it matches by number. With nothing found here, the SD card rows
|
||||
come right after RetroArch: Onion is four presses away with L1 and R1 at
|
||||
40x12, where it was row 30 of 34. That row, `--platform onion` and the menu
|
||||
all ask for the card: one card of that system mounted is taken, otherwise
|
||||
all ask for the card: one card of that system mounted when the run starts is
|
||||
taken, otherwise
|
||||
the screen opens **Where is the SD card?**, which lists only that system's
|
||||
cards and offers **Look again**, and `--plain` asks `Card root`, listing the
|
||||
same cards by number and naming the one picked and its folder. The RetroArch
|
||||
@@ -971,8 +975,14 @@ home folder or above the default, no default is taken: the frontend's own
|
||||
configuration there may name another folder, so the run asks for `--dest`.
|
||||
|
||||
With nothing detected, the setup screen lists the platforms to choose from,
|
||||
and `--plain` gives the same list numbered. With several detected, the setup
|
||||
screen lists each one, and `--plain` asks which. Both need a terminal: piped into a script
|
||||
and `--plain` gives the same list numbered, in the same order. Both find the
|
||||
SD cards mounted here, the screen under **Found on this machine**. With
|
||||
several found, the setup screen lists each one, and `--plain` asks which. A
|
||||
platform named, or read from a list or a pack, and found nowhere gets its
|
||||
folder asked, its usual one under Enter, the mounted drives numbered. On a
|
||||
computer Batocera and Recalbox have none: the `bios` folder of their SHARE
|
||||
drive or network share is theirs. A device several systems run lists them
|
||||
all. Both need a terminal: piped into a script
|
||||
with no platform to install for, it prints the manual invocation, in the form
|
||||
the run was started with (the one-line command for a one-liner user), and
|
||||
exits 1.
|
||||
@@ -1333,7 +1343,7 @@ fields, the installer refuses to narrow rather than install everything.
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `0` | everything asked for is installed or already up to date |
|
||||
| `1` | a failure: the one-liner could not download or verify `install.py`, or found no Python 3.8 (nothing was run), an unusable base URL, an unknown platform or target, `--installed-cores` with no core found, a manifest that fails validation, a fetch that never succeeded, no platform to install for in a non-interactive run, some files failed or were left out by `--offline` while others installed, `--check` found files to fetch, a `--from-pack` pack that cannot be read, lacks a part or is for another platform, a `--from` folder that does not exist or cannot be read, a `--dest` that is not the card of the system named (another system's card, a folder inside one, or a drive with none of its files where its card always has them), `--restore` could not put a file back, or `--add-app` could not put every file of the app on the card |
|
||||
| `1` | a failure: the one-liner could not download or verify `install.py`, or found no Python 3.8 (nothing was run), an unusable base URL, an unknown platform or target, `--installed-cores` with no core found, a manifest that fails validation, a fetch that never succeeded, no platform to install for in a non-interactive run, some files failed or were left out by `--offline` while others installed, `--check` found files to fetch, a `--from-pack` pack that cannot be read, lacks a part or is for another platform, a `--from` folder that does not exist or cannot be read, a `--dest` that is not the card of the system named (another system's card, a folder inside one, a drive with none of its files where its card always has them, or a folder that does not exist), `--restore` could not put a file back, or `--add-app` could not put every file of the app on the card |
|
||||
| `2` | nothing was installed: every file the run tried failed (a BIOS folder that cannot be written, for one), or `--offline` left every file out, the drive has too little free space for the download (checked before any folder is created), or the question about a folder that does not exist, named with `--dest` or typed at the folder question, was answered no. Also an option the installer refused before any download: an unknown option, `--jobs` outside 1 to 32, `--target` with `--every-board` or `--from-pack`, `--target` or `--list-targets` with `--offline` (or `RETROBIOS_INSTALL_OFFLINE` set), which read a core list from the network, `--add-app` with `--check`, `--dry-run`, a listing, `--from-pack` or `--offline`. The message then starts with `install.py: error:`. Also `--add-app` for a folder on no card that takes the app, said in an `Error:` line and an `app` event before anything is written. With `--restore`, a folder that is not a dated folder inside `retrobios-backup` |
|
||||
| `130` | stopped with Ctrl+C, or with `q`, Ctrl+C or Ctrl+D at a question, or the full screen closed with no button pressed (`RETROBIOS_IDLE_EXIT`) before an install. Run the same command again to carry on |
|
||||
| `141` | the output was piped to a reader that closed early (`--list-platforms \| head`) |
|
||||
@@ -1376,7 +1386,7 @@ other schemas under `api/v1/schemas/`.
|
||||
| `start` | `schema`, `installer`, `mode` | First line. `schema` is 1, `installer` the identity `--version` prints, `mode` one of `install`, `check`, `dry-run`, `list` |
|
||||
| `unsearched` | `folder`, `reason`, `message` | Before `detect`, once for each folder the search for setups could not look in, this user being refused it. What it would hold counts as absent, and the other places are still searched: `detect` then lists what they hold, and an empty `platforms` after an `unsearched` line means nothing was found where the installer could look. `reason` says why in words (`Permission denied`), `message` says it in a sentence |
|
||||
| `detect` | `os`, `os_name`, `platforms` | Once the platforms are known. `os_name` comes from `/etc/os-release`. `platforms` lists `{platform, name, bios_dir}`, and is empty when nothing supported was found |
|
||||
| `error` | `stage`, `reason`, `platform`, `message` | The file list could not be read (`stage` is `manifest`): `reason` is `network`, `not_found` or `invalid`, `clock` when the server's certificate was refused and the system clock reads a date before the installer was written, `no_ca` when the machine holds no list of trusted certificates (`SSL_CERT_FILE` names one), or `offline` when `--offline` forbids the request. A folder named with `--from` cannot be read (`stage` is `from`, `reason` `not_found`, `not_a_folder` for a file that is no ZIP, `unreadable`, or `system` for the root of the system, `/proc`, `/sys` or `/dev`). The BIOS folder sits under a folder this user cannot open (`stage` is `dest`, `reason` `unreadable`), and the run ends with code 1 before anything is written. Two systems are detected on one machine and no `--dest` names the folder (`stage` is `detect`, `reason` `conflict`). The platform named with `--platform` is found nowhere and no folder can be assumed for it: a card system with no card named, a portable emulator, or no usual folder on this system or one set aside behind a folder this user cannot open (`stage` is `detect`, `reason` `not_found`). `message` says it in words |
|
||||
| `error` | `stage`, `reason`, `platform`, `message` | The file list could not be read (`stage` is `manifest`): `reason` is `network`, `not_found` or `invalid`, `clock` when the server's certificate was refused and the system clock reads a date before the installer was written, `no_ca` when the machine holds no list of trusted certificates (`SSL_CERT_FILE` names one), or `offline` when `--offline` forbids the request. A folder named with `--from` cannot be read (`stage` is `from`, `reason` `not_found`, `not_a_folder` for a file that is no ZIP, `unreadable`, or `system` for the root of the system, `/proc`, `/sys` or `/dev`). The BIOS folder sits under a folder this user cannot open (`stage` is `dest`, `reason` `unreadable`), and the run ends with code 1 before anything is written. Two systems are detected on one machine and no `--dest` names the folder (`stage` is `detect`, `reason` `conflict`). The platform named with `--platform` is found nowhere and no folder can be assumed for it: a card system with no card named, a portable emulator, or no usual folder on this system or one set aside behind a folder this user cannot open (`stage` is `detect`, `reason` `not_found`). A `--dest` refused as the card of the system named carries the same `stage` and `reason`: another system's card, a folder inside a card, a drive with none of the files its card always has, or a folder that does not exist. `message` says it in words, and is the only thing that tells these cases apart |
|
||||
| `scanned` | `folder`, `files`, `truncated`, `other_drives` | Each folder named with `--from`, once read and before `detect`. `files` counts the files found, `truncated` is true when the search stopped at 200,000 of them, and `other_drives` lists the folders below it on another drive, which were not entered |
|
||||
| `checking` | `files_done`, `files_total` | While the files already in the folder are read and hashed, at most every half second and once at the end. `files_total` counts the files found in place |
|
||||
| `file` | `platform`, `dest`, `systems`, `size`, `state` | Each file of the list after the check. `state` is `present` (hash verified), `wrong_hash` or `missing` |
|
||||
|
||||
Reference in new issue
Block a user