chore: merge games parts and cores ticked

# Conflicts:
#	tests/test_doc_pages.py
This commit is contained in:
Abdessamad Derraz committed 2026-10-28 17:23:11 +01:00
commit 57e0f306e8
31 files changed
+1669 -514

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
# and Invoke-WebRequest throws on a cut transfer before the hash is read:
# the site's build rewrites this line there (scripts/sitebootstrap.py).
$mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
$defaultInstallSha256 = "665534440e8b79c9643e6dc176c4a03da1ec497f151eb33fa0b9d4fb44809511"
$defaultInstallSha256 = "403b6e8a7efcd24858b0f46ffa19c2b9012cdeef149768729d8e48d0b68a296a"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+629 -187
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins
# curl or wget stop on a cut transfer before the hash is read: the site's
# build rewrites this line there (scripts/sitebootstrap.py).
MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
DEFAULT_INSTALL_SHA256="665534440e8b79c9643e6dc176c4a03da1ec497f151eb33fa0b9d4fb44809511"
DEFAULT_INSTALL_SHA256="403b6e8a7efcd24858b0f46ffa19c2b9012cdeef149768729d8e48d0b68a296a"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+8 -8
View File
@@ -27,8 +27,8 @@
{
"dest": "roms/ports/retrobios/install.py",
"repo_path": "install.py",
"size": 961225,
"sha256": "665534440e8b79c9643e6dc176c4a03da1ec497f151eb33fa0b9d4fb44809511",
"size": 984900,
"sha256": "403b6e8a7efcd24858b0f46ffa19c2b9012cdeef149768729d8e48d0b68a296a",
"executable": false
},
{
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 1014402,
"sha256": "cdd06d24d30ad6b564d8a1595b906d50ce05d23093121cc7e5d91b5acd8e08ab"
"size": 1038077,
"sha256": "49b9de6afa23afda4f6c786fa0968c36af1bd2ec9b52cb0927df4c527dcc701c"
},
"members": [
{
@@ -92,8 +92,8 @@
{
"name": "RetroBIOS/install.py",
"repo_path": "install.py",
"size": 961225,
"sha256": "665534440e8b79c9643e6dc176c4a03da1ec497f151eb33fa0b9d4fb44809511",
"size": 984900,
"sha256": "403b6e8a7efcd24858b0f46ffa19c2b9012cdeef149768729d8e48d0b68a296a",
"executable": false
},
{
@@ -166,8 +166,8 @@
{
"dest": ".config/modules/retrobios/install.py",
"repo_path": "install.py",
"size": 961225,
"sha256": "665534440e8b79c9643e6dc176c4a03da1ec497f151eb33fa0b9d4fb44809511",
"size": 984900,
"sha256": "403b6e8a7efcd24858b0f46ffa19c2b9012cdeef149768729d8e48d0b68a296a",
"executable": false
},
{
+9 -3
View File
@@ -177,8 +177,13 @@ def _marker(rule: _install._OsRule) -> str:
path, pattern = rule.values
line = re.sub(r"\\s\*\$$", "", re.sub(r"^(\^|\\A)", "", pattern)).replace("\\", "")
return f"`{path}` holding the line `{line}`"
return ", ".join(f"`{value}`" if PurePosixPath(value).suffix else f"`{value}/`"
for value in rule.values)
named = ", ".join(f"`{value}`" if PurePosixPath(value).suffix else f"`{value}/`"
for value in rule.values)
if rule.method == "games_part":
# Told on the games part beside its bios folder, on a drive the
# system does not call fixed (install.games_part).
return f"{named} beside `bios/`, on a drive that is not a fixed disk"
return named
def _list_words(plat: str) -> str:
@@ -189,7 +194,8 @@ def _list_words(plat: str) -> str:
def card_marker_rows() -> list[list[str]]:
"""The files that name a card or a SHARE drive, in the order they are
read: install.py's card rules, then its SHARE drive files."""
rows = [[_marker(rule), rule.name, f"`{rule.bios}`, {_list_words(rule.manifest)}"]
rows = [[_marker(rule), rule.name,
f"`{rule.bios or 'bios'}`, {_list_words(rule.manifest)}"]
for rule in _install._card_rules()]
hostname = "system.hostname=BATOCERA"
for plat, marker in _install._SHARE_DRIVES:
+5 -2
View File
@@ -3940,8 +3940,11 @@ def first_screen_words() -> list[str]:
showed a line menu (Enter, i, c, q) nobody saw, whose keys had changed
since."""
installer = _installer()
return [f"The command opens a screen. **{installer.TUI_INSTALL_ROW}** gets every "
f"file, **{installer.TUI_SYSTEMS_ROW}** fewer."]
# The screen it opens first: the setup found, or the list when none
# is. Install comes on the next one, Choose systems beside it and named
# above on the page (tui_audit_16 P3-10).
return [f"It asks **{installer.TUI_FOUND_TITLE}** (or **{installer.TUI_CHOOSE_TITLE}**), "
f"then offers **{installer.TUI_INSTALL_ROW}**."]
def check_pass_line() -> str:
+7 -7
View File
@@ -494,7 +494,7 @@
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preferred regions^[[0m
Tick the regions to keep, best first. Where a system has one BIOS per region, the best one found
is installed.
is installed. With none ticked, every region's BIOS is kept.
^[[0;7;35m > [1] North America^[[0m
[ ] Japan
@@ -534,9 +534,12 @@
=== cores
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Cores^[[0m
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use: the platform's own
files are always kept.
^[[0;7;35m > Filter (/ to type)^[[0m
^[[0;7;35m > All^[[0m
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
@@ -568,10 +571,7 @@
^[[0;1;35mA/Enter^[[0m Filter ^[[0;2mB/Esc^[[0m Back ^[[0;2mX/Space^[[0m Tick ^[[0;2m?^[[0m Help
^[[0;1;35mA/Enter^[[0m Tick all ^[[0;2mB/Esc^[[0m Back ^[[0;2mX/Space^[[0m Tick ^[[0;2m?^[[0m Help
=== preview
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preview^[[0m
+10 -10
View File
@@ -160,26 +160,26 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
^[[0;7;35m > [1] North America^[[0m
[ ] Japan
^[[0;1;35mA^[[0m Tick ^[[0;2mB^[[0m Done ^[[0;2m<>^[[0m Order ^[[0;2m?^[[0m Help
=== cores
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Cores^[[0m
Tick the cores you use. With none
ticked, every core's files are kept.
^[[0;7;35m > Filter (/ to type)^[[0m
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
^[[0;7;35m > All^[[0m
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
^[[0;1;35mA^[[0m Filter ^[[0;2mB^[[0m Back ^[[0;2mX^[[0m Tick ^[[0;2m?^[[0m Help
^[[0;1;35mA^[[0m Tick all ^[[0;2mB^[[0m Back ^[[0;2mX^[[0m Tick ^[[0;2m?^[[0m Help
=== preview
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preview^[[0m
+10 -10
View File
@@ -196,7 +196,8 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
^[[0;7;35m > [1] North America^[[0m
[ ] Japan
@@ -204,24 +205,23 @@
^[[0;1;35mA^[[0m Tick ^[[0;2mB^[[0m Done ^[[0;2m<>^[[0m Order ^[[0;2m?^[[0m Help
=== cores
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Cores^[[0m
Tick the cores you use. With none
ticked, every core's files are kept.
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
^[[0;7;35m > Filter (/ to type)^[[0m
^[[0;7;35m > All^[[0m
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
^[[0;1;35mA^[[0m Filter ^[[0;2mB^[[0m Back ^[[0;2mX^[[0m Tick ^[[0;2m?^[[0m Help
^[[0;1;35mA^[[0m Tick all ^[[0;2mB^[[0m Back ^[[0;2mX^[[0m Tick ^[[0;2m?^[[0m Help
=== preview
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preview^[[0m
+8 -8
View File
@@ -302,7 +302,8 @@
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preferred regions^[[0m
Tick the regions to keep, best first. Where a system has one BIOS per region,
the best one found is installed.
the best one found is installed. With none ticked, every region's BIOS is
kept.
^[[0;7;35m > [1] North America^[[0m
[ ] Japan
@@ -321,14 +322,16 @@
^[[0;1;35mA/Enter^[[0m Tick ^[[0;2mB/Esc^[[0m Done ^[[0;2mLeft/Right^[[0m Order ^[[0;2m?^[[0m Help
=== cores
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Cores^[[0m
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use:
the platform's own files are always kept.
^[[0;7;35m > Filter (/ to type)^[[0m
^[[0;7;35m > All^[[0m
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
@@ -344,10 +347,7 @@
^[[0;1;35mA/Enter^[[0m Filter ^[[0;2mB/Esc^[[0m Back ^[[0;2mX/Space^[[0m Tick ^[[0;2m?^[[0m Help
^[[0;1;35mA/Enter^[[0m Tick all ^[[0;2mB/Esc^[[0m Back ^[[0;2mX/Space^[[0m Tick ^[[0;2m?^[[0m Help
=== preview
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preview^[[0m
+6 -6
View File
@@ -1,8 +1,11 @@
RetroBIOS: Cores
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use: the platform's own
files are always kept.
> Filter (/ to type)
> All
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
@@ -34,7 +37,4 @@
A/Enter Filter B/Esc Back X/Space Tick ? Help
A/Enter Tick all B/Esc Back X/Space Tick ? Help
+8 -8
View File
@@ -1,12 +1,12 @@
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
> Filter (/ to type)
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> All
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
A Filter B Back X Tick ? Help
A Tick all B Back X Tick ? Help
+8 -8
View File
@@ -1,15 +1,15 @@
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> Filter (/ to type)
> All
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
A Filter B Back X Tick ? Help
A Tick all B Back X Tick ? Help
+6 -6
View File
@@ -1,8 +1,11 @@
RetroBIOS: Cores
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use:
the platform's own files are always kept.
> Filter (/ to type)
> All
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
@@ -18,7 +21,4 @@
A/Enter Filter B/Esc Back X/Space Tick ? Help
A/Enter Tick all B/Esc Back X/Space Tick ? Help
+1 -1
View File
@@ -1,7 +1,7 @@
RetroBIOS: Preferred regions
Tick the regions to keep, best first. Where a system has one BIOS per region, the best one found
is installed.
is installed. With none ticked, every region's BIOS is kept.
> [1] North America
[ ] Japan
+2 -2
View File
@@ -3,10 +3,10 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
A Tick B Done <> Order ? Help
+2 -2
View File
@@ -3,7 +3,8 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
@@ -11,5 +12,4 @@
A Tick B Done <> Order ? Help
+2 -2
View File
@@ -1,7 +1,8 @@
RetroBIOS: Preferred regions
Tick the regions to keep, best first. Where a system has one BIOS per region,
the best one found is installed.
the best one found is installed. With none ticked, every region's BIOS is
kept.
> [1] North America
[ ] Japan
@@ -20,5 +21,4 @@
A/Enter Tick B/Esc Done Left/Right Order ? Help
+28 -28
View File
@@ -623,12 +623,12 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
A Tick B Done <> Order
=== regions 40x15
RetroBIOS: Preferred regions
@@ -636,7 +636,8 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
@@ -644,13 +645,13 @@
A Tick B Done <> Order
=== regions 80x24
RetroBIOS: Preferred regions
Tick the regions to keep, best first. Where a system has one BIOS per region,
the best one found is installed.
the best one found is installed. With none ticked, every region's BIOS is
kept.
> [1] North America
[ ] Japan
@@ -669,43 +670,45 @@
A Tick B Done Left/Right Order
=== cores 40x12
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
> Filter (with a keyboard)
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
A Filter B Back X Tick
A Tick all B Back X Tick
=== cores 40x15
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> Filter (with a keyboard)
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
A Filter B Back X Tick
A Tick all B Back X Tick
=== cores 80x24
RetroBIOS: Cores
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use:
the platform's own files are always kept.
> Filter (with a keyboard)
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
@@ -721,10 +724,7 @@
A Filter B Back X Tick
A Tick all B Back X Tick
=== preview 80x24
RetroBIOS: Preview
+28 -28
View File
@@ -623,12 +623,12 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
A Tick B Done <> Order
=== regions 40x15
RetroBIOS: Preferred regions
@@ -636,7 +636,8 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
@@ -644,13 +645,13 @@
A Tick B Done <> Order
=== regions 80x24
RetroBIOS: Preferred regions
Tick the regions to keep, best first. Where a system has one BIOS per region,
the best one found is installed.
the best one found is installed. With none ticked, every region's BIOS is
kept.
> [1] North America
[ ] Japan
@@ -669,43 +670,45 @@
A Tick B Done Left/Right Order
=== cores 40x12
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
> Filter (with a keyboard)
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
A Filter B Back X Tick
A Tick all B Back X Tick
=== cores 40x15
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> Filter (with a keyboard)
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
A Filter B Back X Tick
A Tick all B Back X Tick
=== cores 80x24
RetroBIOS: Cores
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use:
the platform's own files are always kept.
> Filter (with a keyboard)
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
@@ -721,10 +724,7 @@
A Filter B Back X Tick
A Tick all B Back X Tick
=== preview 80x24
RetroBIOS: Preview
+28 -28
View File
@@ -623,12 +623,12 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
A Tick B Done <> Order
=== regions 40x15
RetroBIOS: Preferred regions
@@ -636,7 +636,8 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
@@ -644,13 +645,13 @@
A Tick B Done <> Order
=== regions 80x24
RetroBIOS: Preferred regions
Tick the regions to keep, best first. Where a system has one BIOS per region,
the best one found is installed.
the best one found is installed. With none ticked, every region's BIOS is
kept.
> [1] North America
[ ] Japan
@@ -669,43 +670,45 @@
A Tick B Done Left/Right Order
=== cores 40x12
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
> Filter (with a keyboard)
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
A Filter B Back X Tick
A Tick all B Back X Tick
=== cores 40x15
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> Filter (with a keyboard)
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
A Filter B Back X Tick
A Tick all B Back X Tick
=== cores 80x24
RetroBIOS: Cores
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use:
the platform's own files are always kept.
> Filter (with a keyboard)
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
@@ -721,10 +724,7 @@
A Filter B Back X Tick
A Tick all B Back X Tick
=== preview 80x24
RetroBIOS: Preview
+28 -28
View File
@@ -598,12 +598,12 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
Enter Tick Esc Done <> Order ? Help
=== regions 40x15
RetroBIOS: Preferred regions
@@ -611,7 +611,8 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
@@ -619,13 +620,13 @@
Enter Tick Esc Done <> Order ? Help
=== regions 80x24
RetroBIOS: Preferred regions
Tick the regions to keep, best first. Where a system has one BIOS per region,
the best one found is installed.
the best one found is installed. With none ticked, every region's BIOS is
kept.
> [1] North America
[ ] Japan
@@ -644,43 +645,45 @@
Enter Tick Esc Done Left/Right Order ? Help
=== cores 40x12
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
> Filter (/ to type)
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> All
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
Enter Filter Esc Back Space Tick
Enter Tick all Esc Back Space Tick
=== cores 40x15
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> Filter (/ to type)
> All
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
Enter Filter Esc Back Space Tick
Enter Tick all Esc Back Space Tick
=== cores 80x24
RetroBIOS: Cores
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use:
the platform's own files are always kept.
> Filter (/ to type)
> All
None
Filter (/ to type)
[ ] mesen
[x] pcsx_rearmed
@@ -696,10 +699,7 @@
Enter Filter Esc Back Space Tick ? Help
Enter Tick all Esc Back Space Tick ? Help
=== preview 40x12
RetroBIOS: Preview
+28 -28
View File
@@ -652,12 +652,12 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
A Tick R2 Done <> Order
=== regions 40x15
RetroBIOS: Preferred regions
@@ -665,7 +665,8 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
@@ -673,13 +674,13 @@
A Tick R2 Done <> Order
=== regions 80x24
RetroBIOS: Preferred regions
Tick the regions to keep, best first. Where a system has one BIOS per region,
the best one found is installed.
the best one found is installed. With none ticked, every region's BIOS is
kept.
> [1] North America
[ ] Japan
@@ -698,43 +699,45 @@
A Tick R2 Done Left/Right Order
=== cores 40x12
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
> Filter (with a keyboard)
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
A Filter R2 Back
A Tick all R2 Back
=== cores 40x15
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> Filter (with a keyboard)
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
A Filter R2 Back
A Tick all R2 Back
=== cores 80x24
RetroBIOS: Cores
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use:
the platform's own files are always kept.
> Filter (with a keyboard)
> All
None
Filter (with a keyboard)
[ ] mesen
[x] pcsx_rearmed
@@ -750,10 +753,7 @@
A Filter R2 Back
A Tick all R2 Back
=== preview 80x24
RetroBIOS: Preview
+28 -28
View File
@@ -652,12 +652,12 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
Start Tick B Done <> Order
=== regions 40x15
RetroBIOS: Preferred regions
@@ -665,7 +665,8 @@
Tick the regions to keep, best first.
Where a system has one BIOS per
region, the best one found is
installed.
installed. With none ticked, every
region's BIOS is kept.
> [1] North America
[ ] Japan
@@ -673,13 +674,13 @@
Start Tick B Done <> Order
=== regions 80x24
RetroBIOS: Preferred regions
Tick the regions to keep, best first. Where a system has one BIOS per region,
the best one found is installed.
the best one found is installed. With none ticked, every region's BIOS is
kept.
> [1] North America
[ ] Japan
@@ -698,43 +699,45 @@
Start Tick B Done Left/Right Order
=== cores 40x12
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
> Filter (Select shows a keyboard)
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> All
None
Filter (Select shows a keyboard)
[ ] mesen
[x] pcsx_rearmed
Start Filter B Back Y Tick
Start Tick all B Back Y Tick
=== cores 40x15
RetroBIOS: Cores
Tick the cores you use. With none
ticked, every core's files are kept.
Every core ticked keeps every core's
files. Untick the cores you do not
use: the platform's own files are
always kept.
> Filter (Select shows a keyboard)
> All
None
Filter (Select shows a keyboard)
[ ] mesen
[x] pcsx_rearmed
Start Filter B Back Y Tick
Start Tick all B Back Y Tick
=== cores 80x24
RetroBIOS: Cores
Tick the cores you use. With none ticked, every core's files are kept.
Every core ticked keeps every core's files. Untick the cores you do not use:
the platform's own files are always kept.
> Filter (Select shows a keyboard)
> All
None
Filter (Select shows a keyboard)
[ ] mesen
[x] pcsx_rearmed
@@ -750,10 +753,7 @@
Start Filter B Back Y Tick
Start Tick all B Back Y Tick
=== preview 40x12
RetroBIOS: Preview
+7 -4
View File
@@ -133,11 +133,14 @@ class InstallerOutputExample(unittest.TestCase):
text = _page("installer.md")
run = _generated(text, "dry-run")
manifest = json.loads((ROOT / "install" / "retroarch.json").read_text(encoding="utf-8"))
omitted = re.search(r"Not installable: \d+ files? \(\d+ required, (.*)\)\.", run)
# The line as the installer writes it (not_installable_words), or
# its earlier "(1 required, not found: 37)" form.
omitted = re.search(r"Not installable: \d+ files? (?:\(\d+ required, (.*)\)\.|(?:is|are) "
r"not in the collection|.*Why: (.*)\.)", run)
self.assertEqual(omitted is not None, bool(manifest.get("omitted_files")))
if omitted:
for part in omitted.group(1).split(", "):
self.assertIn(part.rsplit(":", 1)[0], install.OMISSION_REASONS.values())
if omitted and (omitted.group(1) or omitted.group(2)):
for part in (omitted.group(1) or omitted.group(2)).split(", "):
self.assertIn(re.split(r":| \(", part)[0], install.OMISSION_REASONS.values())
count, size = re.search(r"\n (\d+) files \(([\d.]+ [KMG]B)\)", run).groups()
self.assertIn(f"0 in place, {count} missing", run)
self.assertSplitAddsUp(run, count, size)
+21 -1
View File
@@ -1806,7 +1806,27 @@ class TestDetectedPlatformPrompt(unittest.TestCase):
class TestManualPlatformPrompt(unittest.TestCase):
"""With nothing detected, an interactive run asks instead of exiting."""
"""With nothing detected, an interactive run asks instead of exiting.
Every test runs on no drive and a home of its own: two read the host's
drives and home, and failed with a card in the reader (code_audit_23
P3-7)."""
def setUp(self):
home = Path(_tempdir(self))
for patcher in (unittest.mock.patch.object(install, "_mounted_cards", return_value=[]),
unittest.mock.patch.object(install, "_mount_points",
return_value=frozenset()),
unittest.mock.patch.object(install.Path, "home", return_value=home),
unittest.mock.patch.dict(os.environ, {"HOME": str(home)}),
unittest.mock.patch("sys.stdout", io.StringIO())):
patcher.start()
self.addCleanup(patcher.stop)
def test_the_host_is_not_read(self):
self.assertEqual(install._mounted_cards(), [])
self.assertEqual(install._default_dest("linux", "retroarch"),
Path.home() / ".config" / "retroarch" / "system")
def test_number_picks_the_platform_and_default_directory(self):
answers = iter(["1", ""])
+2 -2
View File
@@ -148,7 +148,7 @@ class CopyFromAnOldCard(FromFolder):
proc = self.install()
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
self.assertEqual(self.installed(), {"scph5500.bin": JP, "pcsx/extra.bin": EXTRA})
self.assertIn(f"2 files copied from {self.old}, 2 still missing.", proc.stdout)
self.assertIn(f"2 files copied from {self.old}, 2 failed.", proc.stdout)
def test_a_link_out_of_the_folder_is_not_followed(self):
outside = self.root / "elsewhere"
@@ -196,7 +196,7 @@ class CopyFromAnOldCard(FromFolder):
(repo.root / "bios" / "mgba_extra.bin").unlink()
proc = repo.run("--from", str(old), "--no-input")
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
self.assertIn(f"2 files copied from {old}, 2 downloaded, 1 still missing.",
self.assertIn(f"2 files copied from {old}, 2 downloaded, 1 failed.",
proc.stdout)
self.assertEqual(sorted(repo.installed()), sorted(
name for name in payloads if name != "mgba/extra.bin"))
+6 -4
View File
@@ -234,15 +234,17 @@ class CheckNamesWhyAFileIsRefused(Scratch):
self.complete("scph5501.bin", "pcsx/extra.bin")
out = self.check()
here = self.dest.resolve()
self.assertIn(f"opened at {here / 'scph5501.bin'} by beetle_psx, pcsx_rearmed", out)
self.assertIn(f"opened at {here / 'pcsx' / 'extra.bin'} by beetle_psx, pcsx_rearmed",
self.assertIn(f"read by beetle_psx, pcsx_rearmed at {here / 'scph5501.bin'}", out)
self.assertIn(f"read by beetle_psx, pcsx_rearmed at {here / 'pcsx' / 'extra.bin'}",
out)
# Never "opened" of a file that is not there (tui_audit_16 P3-12).
self.assertNotIn("opened at", out)
def test_a_complete_folder_says_nothing_more(self):
self.complete()
proc = self.run_installer("--check", "--manifest", str(self.list))
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertNotIn("opened at", proc.stdout)
self.assertNotIn("read by", proc.stdout)
class ArchivesInTheBiosFolder(Scratch):
@@ -845,7 +847,7 @@ class OfflineLeavesFilesMissing(Scratch):
proc = self.run_installer("--offline", "--manifest", str(self.list), "--no-input")
# Its files are missing, as the BIOS files --offline leaves out are,
# with or without questions.
self.assertIn("bluemsx: 0 already in place, 1 need the internet (--offline)",
self.assertIn("bluemsx: 0 already in place, 1 needs the internet (--offline)",
proc.stdout)
self.assertNotIn("could not read", proc.stderr)
self.assertNotIn("no longer in the source", proc.stdout)
+710 -46
View File
@@ -527,18 +527,20 @@ class Flow(unittest.TestCase):
state.focus["options"] = 3
press(state, "enter")
self.assertEqual(state.screen, "cores")
self.assertEqual([row.value for row in install.tui_rows(state)][1:],
self.assertEqual([row.value for row in install.tui_rows(state)][3:],
["mesen", "pcsx_rearmed"])
self.assertEqual(install.tui_rows(state)[state.focus["cores"]].value, "mesen")
# Every core ticked while none narrows the run: one untick keeps
# the others, ticked back it is every core again.
press(state, " ")
self.assertEqual(state.cores, ["mesen"])
press(state, "left")
self.assertEqual(state.cores, ["pcsx_rearmed"])
press(state, "right")
self.assertEqual(state.cores, [])
press(state, "/", "p", "c")
self.assertEqual([row.value for row in install.tui_rows(state)][1:], ["pcsx_rearmed"])
press(state, "enter", "right", "enter")
self.assertEqual([row.value for row in install.tui_rows(state)][3:], ["pcsx_rearmed"])
press(state, "enter", "left", "enter")
self.assertEqual(state.screen, "options")
self.assertEqual(install.tui_flags(state), [("--core", "pcsx_rearmed")])
self.assertEqual(install.tui_flags(state), [("--core", "mesen")])
def test_preview_is_a_dry_run(self):
state = checked_state()
@@ -2537,10 +2539,11 @@ class AuditRoundThree(unittest.TestCase):
state.summary = {"installed": 4, "up_to_date": 0, "failed": [
{"dest": "fc/disksys.rom", "reason": "HTTP 404"}]}
_title, lines = install.tui_outcome(state)
self.assertEqual(lines[1], "2 files copied from OLD, 1 copied from the wrong place "
"in the BIOS folder, 1 downloaded, 1 still missing.")
# One count, the failed file said as failed (tui_audit_16 P3-11).
self.assertEqual(lines[0], "2 files copied from OLD, 1 copied from the wrong place "
"in the BIOS folder, 1 downloaded, 1 failed.")
# The line mode's own sentence, from the same counts.
self.assertEqual(lines[1], install.sources_line([("OLD", 2)], 1, 1, 1))
self.assertEqual(lines[0], install.sources_line([("OLD", 2)], 1, 1, 0, failed=1))
def test_p2_3_a_run_that_copied_nothing_adds_no_line(self):
state = install.TuiState()
@@ -5100,9 +5103,13 @@ class EndToEnd(unittest.TestCase):
term = self.start()
term.wait("Enter for RetroArch")
term.send("\r")
term.wait("Enter) install everything")
term.send("\r")
# Installed by the first run, in place for the second.
if value is None:
# In place since the first run: no menu, nothing asked
# (tui_audit_16 P3-4).
term.wait("every one in place: nothing to install")
else:
term.wait("Enter) install everything")
term.send("\r")
term.wait("Done.")
term.close()
self.assertNotIn(b"\x1b", term.raw)
@@ -6411,7 +6418,11 @@ class AuditSevenOneCardReading(unittest.TestCase):
stick = self.blank(f"{name}STICK")
choice = install.card_choice(plat, plat, stick)
self.assertEqual(choice.folder, stick)
self.assertIn("cannot tell", choice.note)
# Its card is told by what it writes at every boot: a stick
# holding none of it is one not started yet, never "cannot
# tell" (tui_audit_16 P2-1).
self.assertIn(f"says it is {install._a(name)} card yet", choice.note)
self.assertNotIn("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
@@ -7438,8 +7449,9 @@ class LineModeAndScreenDecideAlike(_LineModeFindings, _OutsideTextFindings,
self.assertRaises(SystemExit):
install.ask_platform_folder("linux", "retroarch")
text = out.getvalue()
self.assertIn(f"1) EASYROMS: bios (card or drive) ({stick / 'bios'})", text)
self.assertIn(f"2) ONION: BIOS (Onion SD card) ({onion / 'BIOS'})", text)
# One parenthesis a row (tui_audit_16 P3-9).
self.assertIn(f"1) EASYROMS: bios (drive, {stick / 'bios'})", text)
self.assertIn(f"2) ONION: BIOS (Onion SD card, {onion / 'BIOS'})", text)
self.assertNotIn("3)", text)
self.assertIn("Not offered here, each reads its own system's files: SHARE (Batocera "
f"SHARE drive: --platform batocera --dest {share}), KNULLI (KNULLI SD "
@@ -8259,7 +8271,9 @@ class LineModeAndScreenDecideAlike(_LineModeFindings, _OutsideTextFindings,
found here.\""""
text, _asked = self.line_run(_args(declared_only=False), [])
self.assertIn("Looking for setups here...", text)
self.assertIn("Nothing was found here. Which setup?", text)
# Said once, the question alone after it (tui_audit_16 P3-2).
self.assertEqual(text.count("Nothing was found here."), 1)
self.assertIn("\nWhich setup?\n", text)
self.assertNotIn("Detecting platform", text)
self.assertNotIn("No supported platform", text)
state = install.TuiState(os_type="linux", home=HOME)
@@ -9866,6 +9880,7 @@ class AuditEighteenAndEleven(_CardFixtures, unittest.TestCase):
"""P3-11."""
state = checked_state()
state.cli_systems, state.cores, state.regions = ["sony-playstation"], ["x"], ["us"]
state.cli_cores = ["x"]
text = " ".join(" ".join(install.tui_view(state, 120, 40)).split())
self.assertIn("(--system), --core, --region.", text)
@@ -11633,7 +11648,10 @@ class AuditTwentyAndThirteen(unittest.TestCase):
with mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE:
install.OFFLINE_FLAG_VALUE}):
_title, lines = install.tui_outcome(state)
self.assertIn("Still missing: 2 BIOS files and 1 game data file.", lines)
# The wrong copy the check found is left in place: named as one,
# never counted as missing (code_audit_23 P3-3).
self.assertIn("1 file copied from OLD, 2 still missing, 1 wrong copy left.", lines)
self.assertIn("Still missing: 1 BIOS file and 1 game data file.", lines)
# Root cause 6: every other word.
@@ -12278,15 +12296,16 @@ class AuditTwentyOneAndFourteen(unittest.TestCase):
def easyroms(self, name: str = "EASYROMS") -> Path:
"""A handheld's games part as a computer mounts it, with the folders
its system writes there (install._GAMES_PART_MARKERS): ArkOS's
its system writes there (install._GAMES_PART_RULES): ArkOS's
EASYROMS, AmberELEC's GAMES."""
return games_part(self.root / "media" / name)
def test_an_arkos_card_is_found_and_comes_first(self):
"""tui_audit_14 P2-3: an EASYROMS partition mounted, "Nothing was
found here", and two presses of Enter filled this computer's
~/.config/retroarch/system for a RetroArch that was not there."""
for name in ("EASYROMS", "GAMES"):
~/.config/retroarch/system for a RetroArch that was not there. An
AmberELEC GAMES is AmberELEC's own card (tui_audit_16 P2-1)."""
for name in ("EASYROMS",):
with self.subTest(card=name):
card = self.easyroms(name)
home = self.root / "home"
@@ -12562,15 +12581,14 @@ class AuditTwentyOneAndFourteen(unittest.TestCase):
text = out.getvalue()
self.assertNotIn(", created)", text)
self.assertNotIn("(created)", text)
self.assertIn(f"D: bios (card or drive) ({drives[0] / 'bios'}, does not exist yet)", text)
self.assertIn(f"E: bios (card or drive) ({drives[1] / 'bios'})\n", text)
self.assertIn(f"D: bios (drive, {drives[0] / 'bios'}, does not exist yet)", text)
self.assertIn(f"E: bios (drive, {drives[1] / 'bios'})\n", text)
state = fresh_state()
state.platform = "retroarch"
with mock.patch.object(install, "_mounted_cards", return_value=drives):
install._open_folder_screen(state, SYSTEM_DIR)
labels = [row.label for row in install.tui_rows(state) if row.action == "drive"]
self.assertEqual(labels, ["D: bios (card or drive) (does not exist yet)",
"E: bios (card or drive)"])
self.assertEqual(labels, ["D: bios (drive, does not exist yet)", "E: bios (drive)"])
def test_line_mode_puts_the_arkos_card_first(self):
card = self.easyroms()
@@ -12578,9 +12596,9 @@ class AuditTwentyOneAndFourteen(unittest.TestCase):
declared_only=False), ["1", ""], mounts=[card])
self.assertIn("A card is in the reader (EASYROMS): choose its system.", text)
self.assertNotIn("Nothing was found here", text)
self.assertIn(f"The card in the reader keeps them in its bios folder: {card / 'bios'}.",
text)
self.assertIn(f"1) EASYROMS: bios (card or drive) ({card / 'bios'})", text)
self.assertIn(f"The card in the reader keeps BIOS files in its bios folder: "
f"{card / 'bios'}.", text)
self.assertIn(f"1) EASYROMS: bios (SD card, {card / 'bios'})", text)
self.assertIn("Enter for the card's bios folder", asked[-2])
self.assertIn(f"BIOS folder: {card / 'bios'}", text)
@@ -12643,20 +12661,26 @@ class AuditTwentyTwoAndFifteen(unittest.TestCase):
self.assertIn("Nothing was found here", self.screen(state))
state, text, rows = self.folder_screen(mounts)
self.assertNotIn(rows[0].value, (data / "bios", stick / "bios"))
self.assertIn("DATA, STICK have a bios folder, and nothing on them shows a "
"handheld's card: listed, not chosen.", text)
self.assertIn(self.UNPROVEN_TWO, text)
self.assertIn(data / "bios", [row.value for row in rows])
text, asked = self.line_run(_args(manifest=self.game_listing(), dry_run=True,
declared_only=False), ["1", ""], mounts=mounts)
self.assertNotIn("in the reader", text)
self.assertIn("DATA, STICK have a bios folder, and nothing on them shows a "
"handheld's card: listed, not chosen.", text)
self.assertIn(self.UNPROVEN_TWO, text)
self.assertIn("Enter for the default above", asked[-2])
self.assertNotIn(f"BIOS folder: {data / 'bios'}", text)
# The note on drives with a bios folder and no handheld's folder.
UNPROVEN_TWO = ("DATA and STICK have a bios folder but no folder a handheld system "
"writes. Pick one below if your files go there.")
UNPROVEN_ONE = ("DATA has a bios folder but no folder a handheld system writes. Pick "
"it below if your files go there.")
def test_the_layouts_handhelds_write_are_cards(self):
"""ArkOS's EASYROMS, AmberELEC's GAMES and EmuELEC's EEROMS, each
with the folders its own image writes there."""
with the folders its own image writes there. ArkOS's reads
RetroArch's list, the two others are their systems' own cards
(tui_audit_16 P2-1)."""
arkos = games_part(self.root / "media" / "EASYROMS")
amber = games_part(self.root / "media" / "GAMES")
emuelec = self.drive("EEROMS", ("bios", "nes", "bezels"))
@@ -12664,12 +12688,17 @@ class AuditTwentyTwoAndFifteen(unittest.TestCase):
mounts = [arkos, amber, emuelec]
with mock.patch.object(install, "_mounted_cards", return_value=mounts), \
mock.patch.object(install, "_mount_points", return_value=frozenset()):
self.assertEqual(install.unnamed_cards(), mounts)
self.assertEqual([install._games_part_system(mount) for mount in mounts],
self.assertEqual(install.unnamed_cards(), [arkos])
self.assertEqual([install.games_part(mount).name for mount in mounts],
["ArkOS", "AmberELEC", "EmuELEC"])
self.assertEqual([install.card_system(mount) for mount in mounts],
[None, ("amberelec", amber, "AmberELEC"),
("emuelec", emuelec, "EmuELEC")])
# One of these the system calls fixed is a disk, not a card.
with mock.patch.object(install, "_drive_not_removable", return_value=True):
self.assertEqual(install.unnamed_cards(), [])
self.assertEqual([install.card_system(mount) for mount in mounts],
[None, None, None])
def test_a_card_beside_a_data_drive_comes_first(self):
"""code_audit_22 P2-1: the ArkOS card and the data drive mounted,
@@ -12683,31 +12712,31 @@ class AuditTwentyTwoAndFifteen(unittest.TestCase):
self.assertIn("A card is in the reader (EASYROMS): choose its system", self.screen(state))
state, text, rows = self.folder_screen(mounts)
self.assertEqual(rows[0].value, card / "bios")
self.assertIn("DATA has a bios folder, and nothing on it shows a handheld's card", text)
self.assertIn(self.UNPROVEN_ONE, text)
text, asked = self.line_run(_args(manifest=self.game_listing(), dry_run=True,
declared_only=False), ["1", ""], mounts=mounts)
self.assertIn("A card is in the reader (EASYROMS): choose its system.", text)
self.assertIn(f"The card in the reader keeps them in its bios folder: {card / 'bios'}.",
text)
self.assertIn(f"1) EASYROMS: bios (card or drive) ({card / 'bios'})", text)
self.assertIn("DATA has a bios folder, and nothing on it shows a handheld's card", text)
self.assertIn(f"The card in the reader keeps BIOS files in its bios folder: "
f"{card / 'bios'}.", text)
self.assertIn(f"1) EASYROMS: bios (SD card, {card / 'bios'})", text)
self.assertIn(self.UNPROVEN_ONE, text)
self.assertIn(f"BIOS folder: {card / 'bios'}", text)
def test_two_cards_are_spoken_of_in_the_plural(self):
"""code_audit_22 P3-2: "Cards are in the reader (DATA, EASYROMS):
choose its system", then "The card in the reader" of one of two."""
arkos = games_part(self.root / "media" / "EASYROMS")
amber = games_part(self.root / "media" / "GAMES")
mounts = [arkos, amber]
second = games_part(self.root / "media" / "ROMS2")
mounts = [arkos, second]
state = self.first_screen(mounts)
self.assertIn("Cards are in the reader (EASYROMS, GAMES): choose their systems",
self.assertIn("Cards are in the reader (EASYROMS, ROMS2): choose their systems",
self.screen(state))
state, text, rows = self.folder_screen(mounts)
self.assertIn("EASYROMS keeps them in its bios folder, first below.", text)
self.assertIn("EASYROMS keeps BIOS files in its bios folder, first below.", text)
text, asked = self.line_run(_args(manifest=self.game_listing(), dry_run=True,
declared_only=False), ["1", ""], mounts=mounts)
self.assertIn("Cards are in the reader (EASYROMS, GAMES): choose their systems.", text)
self.assertIn(f"EASYROMS keeps them in its bios folder: {arkos / 'bios'}.", text)
self.assertIn("Cards are in the reader (EASYROMS, ROMS2): choose their systems.", text)
self.assertIn(f"EASYROMS keeps BIOS files in its bios folder: {arkos / 'bios'}.", text)
self.assertNotIn("The card in the reader", text)
def test_a_fixed_partition_is_told_from_a_card(self):
@@ -13280,8 +13309,16 @@ class TheSystemsQuestionFits(unittest.TestCase):
[f for f in files if install._chosen(f, {system})]))
# One line a system, a second for its shared files: the
# 80-column question took 127 lines for 66 systems.
limit = len(systems) + shared + 10 if cols == 80 else 2 * len(systems) + 12
# At 40 columns a shared size takes a line more: its first
# part alone was printed beside a long name, the shared
# files unsaid (tui_audit_16 P3-5).
limit = (len(systems) + shared + 10 if cols == 80
else 2 * len(systems) + shared + 12)
self.assertLessEqual(len(lines), limit)
for line, after in zip(lines, lines[1:] + [""]):
if line.rstrip().endswith(","):
self.assertTrue(after.startswith(" ") and not re.match(
r" +\d+\) ", after), (cols, line, after))
def test_a_row_that_fits_keeps_one_line(self):
lines, _systems, _files = self.question(80)
@@ -13290,5 +13327,632 @@ class TheSystemsQuestionFits(unittest.TestCase):
self.assertTrue(re.match(r" +\d+\) ", lines[row + 1]))
class AuditTwentyThreeAndSixteen(unittest.TestCase):
"""code_audit_23 and tui_audit_16: a card its markers prove is that
system's card everywhere, counts and states that tell the truth, one
width for every layout, line mode's order and words as the screen's,
and a tap that shows it was seen."""
setUp = LineModeAndScreenDecideAlike.setUp
line_run = LineModeAndScreenDecideAlike.line_run
listing = LineModeAndScreenDecideAlike.listing
entry = staticmethod(LineModeAndScreenDecideAlike.entry)
both = LineModeAndScreenDecideAlike.both
drive = AuditTwentyTwoAndFifteen.drive
first_screen = AuditTwentyTwoAndFifteen.first_screen
folder_screen = AuditTwentyTwoAndFifteen.folder_screen
game_listing = AuditTwentyOneAndFourteen.game_listing
game_manifest = AuditTwentyAndThirteen.game_manifest
receive = staticmethod(AuditTwentyAndThirteen.receive)
screen = staticmethod(AuditTwentyOneAndFourteen.screen)
def amberelec(self, name: str = "GAMES") -> Path:
return games_part(self.root / "media" / name)
def emuelec(self, name: str = "EEROMS") -> Path:
card = self.drive(name, ("bios", "nes", "bezels"))
(card / "bezels" / "default.cfg").write_text("")
return card
def line_from(self, args: argparse.Namespace, answers: list,
folders: list[Path] = ()) -> tuple[str, list]:
"""(text, prompts) of line mode at a terminal, the result included,
the folders --from names read first, as main() reads them."""
given, asked = iter(answers), []
def answer(prompt: str) -> str:
asked.append(prompt)
try:
return next(given)
except StopIteration:
raise _Decided(prompt) from None
install.LOCAL.reset()
self.addCleanup(install.LOCAL.reset)
if folders:
setattr(args, "from", [str(folder) for folder in folders])
invocation = install.Invocation()
invocation.os_type = "linux"
invocation.record(args)
stream = io.StringIO()
with mock.patch.object(install, "_INVOCATION", invocation), \
mock.patch.object(install, "_mounted_cards", return_value=[]), \
mock.patch.object(install, "_mount_points", return_value=frozenset()), \
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", side_effect=answer), \
contextlib.redirect_stdout(io.StringIO()) as out, \
contextlib.redirect_stderr(io.StringIO()) as err, \
contextlib.suppress(_Decided, SystemExit):
install._install(args, stream)
return out.getvalue() + err.getvalue() + stream.getvalue(), asked
# Root cause 1: a card its markers prove is that system's card.
def test_a_games_part_is_its_systems_card_in_both_modes(self):
"""tui_audit_16 P2-1: the GAMES card AmberELEC's folders prove, and
the EEROMS card EmuELEC's prove, read "A card is in the reader:
choose its system" with RetroArch focused, and two presses of Enter
put RetroArch's list on them."""
for plat, name, make in (("amberelec", "AmberELEC", self.amberelec),
("emuelec", "EmuELEC", self.emuelec)):
with self.subTest(system=name):
card = make()
state = self.first_screen([card])
text = self.screen(state)
self.assertIn(f"{name} SD card ({card.name})", text)
self.assertNotIn("in the reader", text)
self.assertIn(f"BIOSfolder:{card / 'bios'}", "".join(text.split()))
(line, screen, said) = self.both([card], [])
self.assertEqual(line, (plat, card))
self.assertEqual(screen, (plat, card))
self.assertIn(f"Found {name}", said)
shutil.rmtree(card)
def test_their_own_routes_take_the_card_they_prove(self):
"""tui_audit_16 P2-1: --platform amberelec said it "cannot tell an
AmberELEC card by its files" of the card holding gamedata/ and
homebrew/, and line mode offered no Enter for it."""
for plat, name, make in (("amberelec", "AmberELEC", self.amberelec),
("emuelec", "EmuELEC", self.emuelec)):
with self.subTest(system=name):
card = make()
stick = self.drive(f"{name}STICK", ("bios",))
with mock.patch.object(install, "_mounted_cards", return_value=[card, stick]), \
mock.patch.object(install, "_mount_points", return_value=frozenset()):
self.assertEqual(install.mounted_cards_of(name, plat), [card])
self.assertEqual(install.blank_cards_of(name, plat), [stick])
choice = install.card_choice(name, plat, card)
self.assertEqual((choice.refused, choice.folder), ("", card))
self.assertNotIn("cannot tell", choice.note)
(line, screen, said) = self.both([card, stick], [], platform=plat)
self.assertEqual(line, (plat, card))
self.assertEqual(screen, (plat, card))
self.assertIn(f"Found {name} at {card / 'bios'}", said)
self.assertNotIn("cannot tell", said)
# Asked for its card, the one its files prove is offered.
with mock.patch.object(install, "_mounted_cards", return_value=[card, stick]), \
mock.patch.object(install, "_mount_points", return_value=frozenset()), \
contextlib.redirect_stdout(io.StringIO()) as out:
dest = install.card_install_dest(name, plat, True, False)
self.assertEqual(dest, card)
self.assertIn(f"{name} card found at {card.name}", out.getvalue())
with mock.patch.object(install, "_mounted_cards", return_value=[card, stick]), \
mock.patch.object(install, "_mount_points", return_value=frozenset()):
state = screen_keys(_args(platform=plat, declared_only=False), [])
view = " ".join(install.tui_view(state, 80, 24))
self.assertNotIn("cannot tell", view)
shutil.rmtree(card)
shutil.rmtree(stick)
def test_an_arkos_card_from_any_year_is_a_card(self):
"""code_audit_23 P3-1: ArkOS's default trees of 2020 hold no themes/
(9664c869 none of themes and launchimages, 495eb210 and c14afbe5
launchimages only), and the 12262020 update skipped the themes of a
card with under 1 GB free. Every tree holds bgmusic/. A data drive
holding neither stays no card."""
layouts = {
"ARK_9664c869": ("bios", "bgmusic", "ports", "snes", "System Volume Information"),
"ARK_495eb210": ("bios", "bgmusic", "launchimages", "ports", "snes"),
"ARK_c14afbe5": ("bios", "bgmusic", "launchimages", "psx"),
"ARK_f41b9485": ("bios", "bgmusic", "launchimages", "themes", "backup", "nes"),
"ARK_FULL1226": ("bios", "launchimages", "nes"),
}
cards = [self.drive(name, folders) for name, folders in layouts.items()]
data = self.drive("DATA", ("bios", "snes", "Music", "themes"))
music = self.drive("MUSIC", ("bgmusic", "launchimages"))
mounts = [*cards, data, music]
with mock.patch.object(install, "_mounted_cards", return_value=mounts), \
mock.patch.object(install, "_mount_points", return_value=frozenset()):
self.assertEqual(install.unnamed_cards(), cards)
self.assertEqual([install.games_part(card).name for card in cards],
["ArkOS"] * len(cards))
self.assertIsNone(install.games_part(data))
self.assertIsNone(install.games_part(music))
self.assertEqual(install.unproven_drives(), [data])
with mock.patch.object(install, "_drive_not_removable", return_value=True):
self.assertEqual(install.unnamed_cards(), [])
# Root cause 2: counts and states that tell the truth.
def wrong_copy_run(self, answers: list) -> tuple[str, list, Path]:
"""An offline run whose BIOS folder holds a wrong copy of the right
size of a.bin, a folder named with --from holding b.bin."""
good_a, good_b = b"A" * 64, b"B" * 64
listing = self.listing("retroarch", [self.entry("a.bin", good_a, ["nintendo-gba"]),
self.entry("b.bin", good_b, ["nintendo-gba"])])
dest = self.root / "dest"
dest.mkdir()
(dest / "a.bin").write_bytes(b"Z" * 64)
old = self.root / "old"
old.mkdir()
(old / "b.bin").write_bytes(good_b)
with mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}):
text, asked = self.line_from(_args(manifest=listing, dest=dest, platform="retroarch",
declared_only=False), answers, [old])
return text, asked, dest
def test_the_head_never_calls_a_wrong_copy_in_place(self):
"""code_audit_23 P3-2: a wrong copy of the right size was in place
for the head, which said "1 found in old, nothing to download" over
a run that left it."""
text, _asked, dest = self.wrong_copy_run([""])
self.assertIn("RetroArch: 1 found in old, 1 needs the internet.", text)
self.assertNotIn("nothing to download", text)
self.assertEqual((dest / "a.bin").read_bytes(), b"Z" * 64)
def test_a_wrong_copy_left_is_said_as_one(self):
"""code_audit_23 P3-3: the result said "1 still missing" of a file
--check counts as a wrong copy."""
text, _asked, _dest = self.wrong_copy_run([""])
self.assertIn("Finished with files missing.", text)
self.assertIn("1 file copied from", text)
self.assertIn(", 1 wrong copy left.", text)
self.assertNotIn("still missing", text.rsplit("Finished", 1)[1])
def test_a_count_agrees_with_its_verb(self):
"""code_audit_23 P3-4: "1 need the internet" on the plan, and on
line mode's data tree line."""
self.assertEqual(install.agree(1, "needs", "need"), "1 needs")
self.assertEqual(install.agree(2, "needs", "need"), "2 need")
self.assertEqual(install._plural(2, "wrong copy"), "2 wrong copies")
self.assertEqual(install._plural(1, "wrong copy"), "1 wrong copy")
self.assertEqual(install._plural(2, "day"), "2 days")
state = checked_state()
folder = "/media/OLD"
state.from_folders = [Path(folder)]
state.found = [found(dest, folder) for dest in ("scph5500.bin", "pcsx/extra.bin",
"gba_bios.bin")]
with mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}):
text = self.screen(state, 100, 30)
self.assertIn("3 found in OLD, 1 needs the internet", text)
source = (REPO_ROOT / "install.py").read_text(encoding="utf-8")
self.assertNotRegex(source, r"\} need the internet")
def test_line_mode_says_what_is_still_missing_before_the_remedy(self):
"""tui_audit_16 P3-3: "They are not in OLD" followed "Installed: 6
BIOS files", where the screen puts "Still missing:" between them."""
bios, game = b"B" * 32, b"G" * 32
listing = self.listing("retroarch", [
self.entry("a.bin", bios, ["nintendo-gba"]),
self.entry("b.bin", b"C" * 32, ["nintendo-gba"]),
self.entry("g.dat", game, ["sega-dreamcast"], category="game_data")],
categorized=True)
dest = self.root / "dest"
dest.mkdir()
old = self.root / "OLD"
old.mkdir()
(old / "a.bin").write_bytes(bios)
with mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}):
text, _asked = self.line_from(_args(manifest=listing, dest=dest,
platform="retroarch", declared_only=False),
[""], [old])
end = text.rsplit("Finished with files missing", 1)[-1]
self.assertIn("Still missing: 1 BIOS file and 1 game data file.", end)
self.assertLess(end.index("Still missing:"), end.index(f"They are not in {old}"))
self.assertLess(end.index("Installed:"), end.index("Still missing:"))
def test_a_rerun_with_everything_in_place_offers_nothing(self):
"""tui_audit_16 P3-4: line mode offered "Enter) install what is
found", then said "Everything is in place." twice."""
data = b"A" * 64
listing = self.listing("retroarch", [self.entry("a.bin", data, ["nintendo-gba"])])
dest = self.root / "dest"
dest.mkdir()
(dest / "a.bin").write_bytes(data)
text, asked = self.line_from(_args(manifest=listing, dest=dest, platform="retroarch",
declared_only=False), [])
self.assertEqual(asked, [])
self.assertNotIn("Enter)", text)
self.assertIn("RetroArch: 1 file, every one in place: nothing to install.", text)
self.assertEqual(text.count(install.EVERYTHING_IN_PLACE), 1)
def test_line_mode_names_the_files_it_cannot_supply(self):
"""tui_audit_16 P3-6: "Not installable: 37 files (1 required, not
found: 37)" read as a sum, and line mode never named the file the
screen names, nor its folder."""
listing = self.listing("retroarch", [
self.entry("a.bin", b"A" * 8, ["nintendo-gba"])], categorized=True,
omitted_files=[
{"dest": "jazz2/Source/Anims.j2a", "name": "Anims.j2a",
"system": "jazz-jackrabbit-2", "required": True, "reason": "not_found",
"category": "game_data"},
{"dest": "x.bin", "name": "x.bin", "system": "nintendo-gba",
"required": False, "reason": "not_found"}])
dest = self.root / "dest"
dest.mkdir()
text, _asked = self.line_run(_args(manifest=listing, dest=dest, platform="retroarch",
declared_only=False, dry_run=True), [])
self.assertIn("Not available here: jazz2/Source/Anims.j2a (Jazz Jackrabbit 2), game "
"data its game reads. Copy it from your own copy of the game into "
f"{dest / 'jazz2' / 'Source'}{os.sep}.", text)
self.assertEqual(install.not_installable_words(37, 1, {"not_found": 37}),
"Not installable: 37 files are not in the collection, 1 of them "
"required.")
self.assertEqual(install.not_installable_words(1, 1, {"not_found": 1}),
"Not installable: 1 file is not in the collection, required.")
self.assertIn("Why: no save directory (2), not found (3).",
install.not_installable_words(5, 0, {"not_found": 3,
"no_save_directory": 2}))
def test_a_system_emptied_by_no_game_data_is_named(self):
"""tui_audit_16 P3-7: --system nintendo-ds,sega-dreamcast with
--no-game-data: "For 1 of 8 systems (--system)", Dreamcast unsaid
until after Enter."""
manifest = self.game_manifest()
words = (f"{install.system_title('sega-dreamcast', manifest)} holds only game data: "
"left out (--no-game-data).")
listing = self.game_listing()
text, _asked = self.line_run(_args(manifest=listing, dest=self.here,
declared_only=False, dry_run=True,
system=["nintendo-ds", "sega-dreamcast"],
no_game_data=True), [])
head = text.split("Enter)", 1)[0]
self.assertIn(words, head)
state = install.TuiState(os_type="linux", home=HOME)
state.manifest = manifest
state.no_game_data = True
state.cli_systems = ["nintendo-ds", "sega-dreamcast"]
state.systems = [{"system": "nintendo-ds", "name": "Nintendo DS", "files": 1,
"present": 0, "missing": 1, "wrong_hash": 0, "bytes_needed": 1}]
state.ticked = {"nintendo-ds"}
state.dest = self.here
state.platform = "retroarch"
state.check = {**CHECK, "files": 1, "present": 0, "missing": 1, "wrong_hash": 0}
state.stack = ["plan"]
self.assertIn(words, self.screen(state, 100, 40))
def test_a_run_with_failures_counts_once(self):
"""tui_audit_16 P3-11: "Done. 44 files installed." then "38 files
copied from E:\\, 4 downloaded, 2 still missing." gave two counts,
the two that failed as missing. Each mode says one, failures named."""
out = io.StringIO()
sources = install.sources_line([("/media/OLD", 38)], 0, 4, 0, failed=2)
self.assertEqual(sources, "38 files copied from /media/OLD, 4 downloaded, 2 failed.")
with contextlib.redirect_stdout(out), contextlib.suppress(SystemExit):
install._report_outcome([self.here], 42, 0, 2, 0, False,
failures=[("x.bin", "timed out"), ("y.bin", "timed out")],
sources=sources)
text = out.getvalue()
self.assertIn(f"Finished with errors. {sources}", text)
self.assertNotIn("42 files installed", text)
self.assertNotIn("still missing", text)
state = checked_state()
state.from_folders = [Path("/media/OLD")]
state.local_dests = {"gba_bios.bin": ("/media/OLD", "")}
state.finished = [("gba_bios.bin", True, ""), ("scph5500.bin", False, "timed out")]
state.stack, state.run = ["result"], "install"
state.summary = {"event": "summary", "mode": "install", "installed": 1,
"up_to_date": 0, "pending": 1,
"failed": [{"dest": "scph5500.bin", "reason": "timed out"}],
"skipped": [], "left_out": [], "kept": [], "interrupted": False}
title, lines = install.tui_outcome(state)
self.assertEqual(title, "Finished with errors")
self.assertEqual(lines[0], "1 file copied from OLD, 1 failed.")
self.assertNotIn("1 installed, 1 failed.", lines)
def test_check_never_says_a_missing_file_was_opened(self):
"""tui_audit_16 P3-12: "missing: nintendo-ds_0.bin" then "opened at
..." read as a file that was opened."""
entry = {"dest": "a.bin", "systems": ["nintendo-gba"], "cores": None}
self.assertEqual(install.opened_line(entry, {}, self.here),
f"expected at {self.here / 'a.bin'}")
named = {**entry, "cores": ["mgba"]}
self.assertEqual(install.opened_line(named, {}, self.here),
f"read by mgba at {self.here / 'a.bin'}")
# Root cause 3: one width with a floor.
def test_a_terminal_of_no_size_has_the_fallback(self):
"""code_audit_23 P3-5: Python 3.8 gives a pty of no size as 0 and 0.
The plan printed "0 systems, the largest first:" and the progress
line lost every figure but its percentage."""
with mock.patch.object(install.shutil, "get_terminal_size",
return_value=os.terminal_size((0, 0))):
self.assertEqual(install.terminal_size(), (80, 24))
out = io.StringIO()
term = types.SimpleNamespace(live=True, quiet=False, stream=out,
bold=str, dim=str)
progress = install.Progress(1, 1400, term)
progress.bytes = progress.files = 1400
progress.files = 1
progress._draw()
self.assertIn("(1/1 files)", out.getvalue())
self.assertIn("[####", out.getvalue())
with contextlib.redirect_stdout(io.StringIO()) as plan:
install._print_plan([], self.here, term, copied=[" 1 file copied"])
self.assertNotIn("the largest first", plan.getvalue())
with mock.patch.object(install.shutil, "get_terminal_size",
return_value=os.terminal_size((8, 3))):
self.assertEqual(install.terminal_size(), (install.LINE_MIN_COLS, 3))
def systems_question(self, cols: int) -> list[str]:
manifest = json.loads((REPO_ROOT / "install" / "retroarch.json").read_text())
out = io.StringIO()
with mock.patch.object(install.shutil, "get_terminal_size",
return_value=os.terminal_size((cols, 24))), \
mock.patch.object(install, "_ask", return_value=""), \
contextlib.redirect_stdout(out):
install._choose_narrowing(manifest["files"], [], manifest, None)
text = out.getvalue()
ends = [at for at in (text.find("\nCores:"), text.find("\nThe platform's own"))
if at >= 0]
return text[text.index("Systems:"):min(ends, default=len(text))].splitlines()
def test_the_systems_question_stays_inside_the_terminal(self):
"""tui_audit_16 P3-5: 6 lines of 81 cells at 80 columns, 16 of 41 at
40, "shared" and "platform" alone on a line."""
for cols in (100, 80, 60, 40, 30):
with self.subTest(cols=cols):
lines = self.systems_question(cols)
for line in lines:
self.assertLess(install.cell_width(line), cols, line)
if line.strip() and not line.endswith(":"):
self.assertTrue(len(install._glued_words(line)) > 1
or re.match(r" *\d+\) ", line), (cols, line))
note = " ".join(line.strip() for line in lines[lines.index(
next(line for line in lines if line.startswith("'Other"))):])
self.assertIn("this platform does not list", note)
def test_the_setup_list_never_folds_inside_a_word(self):
"""tui_audit_16 P3-15: at 39 columns "Miy" / "oo Flip"."""
for cols in (80, 39, 30):
with self.subTest(cols=cols):
with mock.patch.object(install.shutil, "get_terminal_size",
return_value=os.terminal_size((cols, 11))):
text, _asked = self.line_run(_args(declared_only=False), [])
block = text.split("Which setup?", 1)[1].split(" q) quit", 1)[0]
lines = [line for line in block.splitlines() if line.strip()]
self.assertTrue(lines)
for line in lines:
self.assertLess(install.cell_width(line), cols, line)
self.assertIn("Miyoo Flip", " ".join(" ".join(lines).split()))
# Root cause 4: line mode in the screen's order and words.
def test_on_wsl_the_card_comes_first_in_both_modes(self):
"""code_audit_23 P3-6: line mode numbered Windows RetroArch 1) and
the card 2), where the screen put the card first."""
card = games_part(self.root / "media" / "EASYROMS")
windows = Path("/mnt/c/RetroArch-Win64/system")
side = [("Windows RetroArch: C:\\RetroArch-Win64\\system", windows)]
with mock.patch.object(install, "_mounted_cards", return_value=[card]), \
mock.patch.object(install, "_mount_points", return_value=frozenset()), \
mock.patch.object(install, "other_side", return_value=side), \
mock.patch.object(install, "_ask", return_value="q"), \
contextlib.redirect_stdout(io.StringIO()) as out, \
contextlib.suppress(SystemExit):
install.ask_platform_folder("wsl", "retroarch")
state = self.first_screen([card])
self.assertRegex(out.getvalue(), r"\n 1\) EASYROMS: bios \(SD card")
self.assertRegex(out.getvalue(), r"\n 2\) Windows RetroArch")
with mock.patch.object(install, "_mounted_cards", return_value=[card]), \
mock.patch.object(install, "_mount_points", return_value=frozenset()), \
mock.patch.object(install, "other_side", return_value=side):
state.os_type = "wsl"
install.tui_update(state, Key("enter"))
rows = [row for row in install.tui_rows(state) if row.action == "drive"]
self.assertEqual([row.value for row in rows], [card / "bios", windows])
def test_nothing_found_is_said_once(self):
"""tui_audit_16 P3-2: "Nothing was found here." twice, and after "A
card is in the reader"."""
text, _asked = self.line_run(_args(declared_only=False), [])
self.assertEqual(text.count("Nothing was found here."), 1)
card = games_part(self.root / "media" / "EASYROMS")
text, _asked = self.line_run(_args(declared_only=False), [], mounts=[card])
self.assertIn("A card is in the reader (EASYROMS): choose its system.", text)
self.assertNotIn("Nothing was found here", text)
def test_the_bios_only_drive_note_is_one_plain_sentence(self):
"""tui_audit_16 P3-9: "listed, not chosen", "(card or drive)" under
a note saying no card, two parentheses side by side, and "keeps
them" after a title."""
data = self.drive("DATA", ("bios", "snes", "Music"))
stick = self.drive("STICK", ("bios",))
card = games_part(self.root / "media" / "EASYROMS")
mounts = [card, data, stick]
state, text, rows = self.folder_screen(mounts)
self.assertIn("DATA and STICK have a bios folder but no folder a handheld system "
"writes. Pick one below if your files go there.", text)
self.assertIn("The card in the reader keeps BIOS files in its bios folder", text)
labels = [row.label for row in rows]
self.assertIn("DATA: bios (drive)", labels)
self.assertIn("EASYROMS: bios (SD card)", labels)
line, _asked = self.line_run(_args(manifest=self.game_listing(), dry_run=True,
declared_only=False), ["1", ""], mounts=mounts)
for said in (text, line):
self.assertNotIn("listed, not chosen", said)
self.assertNotIn("card or drive", said)
self.assertNotIn(") (", said)
self.assertNotIn("keeps them", said)
def test_the_same_choice_is_one_command_in_both_modes(self):
"""tui_audit_16 P3-14: the screen wrote --manifest before --from,
line mode after it."""
hint = install.Invocation()
hint.os_type = "linux"
hint.options = [("--from", "/media/OLD"), ("--offline", None),
("--manifest", "/x/list.json"), ("--system", "a")]
self.assertEqual(hint.command("retroarch", "/dest"),
"python3 install.py --platform retroarch --dest /dest --manifest "
"/x/list.json --system a --from /media/OLD --offline")
state = checked_state()
state.manifest_given, state.manifest_path = True, Path("/x/list.json")
state.from_folders = [Path("/media/OLD")]
args = _args(manifest="/x/list.json", platform="retroarch", dest=SYSTEM_DIR,
declared_only=False, offline=False)
setattr(args, "from", ["/media/OLD"])
line = install.Invocation()
line.os_type = "linux"
line.record(args)
with mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}):
line.options.append(("--offline", None))
self.assertEqual(install.tui_command(state), line.command("retroarch", SYSTEM_DIR))
# Root cause 5: interaction.
def test_a_tap_on_a_focused_row_shows_it_was_seen(self):
"""tui_audit_16 P3-8: one tap on the focused Onion row showed
nothing, a second continued."""
state = fresh_state()
state.stack = ["setup"]
state.mouse = True
lines = drawn(state, 80, 24)
row = next(index for index, line in enumerate(lines)
if str(line).lstrip().startswith("> "))
self.assertEqual(tap(state, 4, row), [])
lines = drawn(state, 80, 24)
self.assertIn(f"A/Enter Continue: {install.TUI_ARMED}", str(lines[-1]))
self.assertIn(f"RetroArch: {install.TUI_ARMED}", str(lines[row]))
self.assertNotEqual(tap(state, 4, row), [])
def test_a_phone_bar_names_the_enter_key(self):
"""tui_audit_16 P3-13: Termux at 40 columns said "A Continue", and
the a key does nothing."""
state = install.TuiState(os_type="android", home=HOME)
state.pad = install.tui_pad_route("android", {})
self.assertEqual(state.pad, "both")
bar = str(install.tui_view(state, 40, 12)[-1])
self.assertIn("Enter", bar)
self.assertNotRegex(bar, r"(^| )A ")
self.assertIn("A/Enter", str(install.tui_view(state, 80, 24)[-1]))
state.pad = "gptokeyb"
self.assertRegex(str(install.tui_view(state, 40, 12)[-1]), r"(^| )A ")
def test_which_folder_keeps_its_folder_at_40_by_12(self):
"""tui_audit_16 P3-1: with two drives holding bios, Which folder?
at 40x12 dropped the folder "Use this folder" stands for."""
data = self.drive("DATA", ("bios", "snes", "Music"))
stick = self.drive("STICK", ("bios",))
for mounts in ([data, stick], [games_part(self.root / "media" / "EASYROMS"), data]):
with self.subTest(mounts=[mount.name for mount in mounts]):
state, _text, _rows = self.folder_screen(mounts)
with mock.patch.object(install, "_mounted_cards", return_value=mounts), \
mock.patch.object(install, "_mount_points", return_value=frozenset()):
small = " ".join(str(line) for line in install.tui_view(state, 40, 12))
folder = str(state.folder_default)
self.assertIn("reads its BIOS files", small)
self.assertIn(folder[-12:], small)
# The owner's note: Cores drew every box empty when no core narrows the
# run, read as "nothing is ticked, tick them all".
def cores_screen(self, **given) -> install.TuiState:
state = checked_state()
state.cores = list(given.get("cores", []))
state.cli_cores = list(state.cores)
press(state, "o")
state.focus["options"] = next(index for index, row in enumerate(
install.tui_rows(state)) if row.action == "cores")
press(state, "enter")
self.assertEqual(state.screen, "cores")
return state
@staticmethod
def boxes(state: install.TuiState) -> dict:
return {row.value: row.label[:3] for row in install.tui_rows(state)
if row.action == "core"}
def test_cores_open_every_one_ticked(self):
state = self.cores_screen()
self.assertEqual(self.boxes(state), {"mesen": "[x]", "pcsx_rearmed": "[x]"})
self.assertEqual(install.tui_rows(state)[state.focus["cores"]].value, "mesen")
self.assertEqual(install.tui_flags(state), [])
def test_one_untick_narrows_to_the_others(self):
state = self.cores_screen()
press(state, " ")
self.assertEqual(self.boxes(state), {"mesen": "[ ]", "pcsx_rearmed": "[x]"})
self.assertEqual(install.tui_flags(state), [("--core", "pcsx_rearmed")])
press(state, " ")
self.assertEqual(self.boxes(state), {"mesen": "[x]", "pcsx_rearmed": "[x]"})
self.assertEqual(state.cores, [])
press(state, " ", "enter")
self.assertEqual(state.screen, "options")
row = next(row for row in install.tui_rows(state) if row.action == "cores")
self.assertEqual(row.label, "Cores: 1 of 2")
state.stack = ["setup", "plan"]
self.assertIn("1 of 2 cores", " ".join(install.tui_view(state, 100, 30)))
def test_none_then_all_as_the_systems_list(self):
state = self.cores_screen()
rows = install.tui_rows(state)
state.focus["cores"] = next(i for i, row in enumerate(rows) if row.action == "none")
press(state, "enter")
self.assertEqual(self.boxes(state), {"mesen": "[ ]", "pcsx_rearmed": "[ ]"})
press(state, "enter")
self.assertEqual(state.screen, "cores")
self.assertIn("No core is ticked", str(state.status))
state.focus["cores"] = next(i for i, row in enumerate(rows) if row.action == "all")
press(state, "enter")
self.assertEqual(self.boxes(state), {"mesen": "[x]", "pcsx_rearmed": "[x]"})
self.assertEqual(install.tui_flags(state), [])
def test_core_ticks_only_the_cores_it_names(self):
state = self.cores_screen(cores=["pcsx_rearmed"])
self.assertEqual(self.boxes(state), {"mesen": "[ ]", "pcsx_rearmed": "[x]"})
state.stack = ["setup", "plan"]
self.assertIn("--core", " ".join(install.tui_view(state, 100, 30)))
def test_both_modes_count_the_cores_kept_alike(self):
listing = self.listing("retroarch", [
self.entry("a.bin", b"A" * 8, ["nintendo-gba"]),
self.entry("m.bin", b"M" * 8, ["nintendo-gba"], cores=["mgba"]),
self.entry("v.bin", b"V" * 8, ["nintendo-gba"], cores=["vba_next"]),
self.entry("p.bin", b"P" * 8, ["sony-playstation"])])
text, _asked = self.line_run(_args(manifest=listing, dest=self.here,
declared_only=False, dry_run=True),
["c", "", "2"])
self.assertIn("Every core's extra files are kept: Enter keeps them all", text)
head = text.rsplit("Counted again for your choices", 1)[1]
self.assertIn("1 of 2 cores", head)
self.assertEqual(install.cores_words(0, 2), "all cores")
self.assertEqual(install.cores_words(2, 2), "all cores")
# Root cause 6: the pages say what the installer shows.
@unittest.skipIf(sys.version_info < (3, 9),
"the site generator needs Python 3.9")
def test_which_pack_names_the_first_screen(self):
"""tui_audit_16 P3-10: which-pack said the command opens a screen
where Install and Choose systems are, the second screen."""
sys.path.insert(0, str(REPO_ROOT / "scripts"))
self.addCleanup(sys.path.remove, str(REPO_ROOT / "scripts"))
import generate_site # noqa: PLC0415
words = " ".join(generate_site.first_screen_words())
found, nothing = fresh_state(), install.TuiState(os_type="linux", home=HOME)
found.stack = ["setup"]
self.assertIn(install.tui_view(found, 80, 24)[0].split(": ", 1)[1].strip(), words)
self.assertIn(install.tui_view(nothing, 80, 24)[0].split(": ", 1)[1].strip(), words)
self.assertLess(words.index(install.TUI_FOUND_TITLE),
words.index(install.TUI_INSTALL_ROW))
self.assertNotIn("opens a screen", words)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -167,7 +167,7 @@ class GetYourFilesOrder(unittest.TestCase):
text = _page("which-pack.md")
order = [text.index(mark) for mark in (
"## Pick your setup", "| Setup | Runs on | BIOS folder | ZIP |", "<FilePicker",
"## Not in the list? One command finds it", "The command opens a screen",
"## Not in the list? One command finds it", "It asks **",
"<summary>Is it safe?</summary>",
)]
self.assertEqual(order, sorted(order))
+31 -16
View File
@@ -210,7 +210,9 @@ in the names of what is held. On a computer it names the keys (`Enter Install`,
launcher (`A Install`, `B Back`, see `RETROBIOS_PAD` below), and `?` shows
both the buttons and the keys. Where neither is known, in SteamOS game mode or
on a console no launcher named, it gives both spellings (`A/Enter`, `B/Esc`),
and a narrow screen shortens them to `A`, `B` and `X`. On the
and a narrow screen shortens them to `A`, `B` and `X`. In Termux on Android,
with no launcher named, a narrow screen keeps the keys (`Enter`, `Esc`) instead,
a phone having an Enter key and no A button. On the
regions screen, Left and Right move the region under the cursor up and down the
order of preference. During the download, a stop waits for the files in
progress, and a second one stops at once. Select+Start on a handheld ends the
@@ -267,7 +269,8 @@ says why.
A mouse or a touch screen works too, beside the keys and buttons, in a
terminal that reports clicks: those of Linux, macOS, WSL and Termux. A click
or a tap on a box (a system, a core, a region) ticks or unticks it, as Space
does. On any other row it moves the cursor there, and a second one on that
does. On any other row it moves the cursor there and adds `click or tap
again` to the row and the bottom line, and a second one on that
row does what Enter does: one row of a terminal is far smaller than a finger,
and the row a screen opens on (**Install** on Confirm) is never chosen by one
tap. The entries of the bottom line press their key, **Exit** on the result
@@ -732,6 +735,7 @@ RetroArch: 10994 files, 6.7 GB.
3269 BIOS files (4.1 GB), 5089 game data files (2.5 GB) and 2636 data files (38.5 MB).
Game data: files a game or a game engine reads, of no use to the other cores. Data files: the files an emulator reads from a data folder of its own, such as Dolphin's Sys and PPSSPP's assets.
BIOS folder: /home/me/.config/retroarch/system
Not available here: jazz2/Source/Anims.j2a (Jazz Jackrabbit 2), game data its game reads. Copy it from your own copy of the game into /home/me/.config/retroarch/system/jazz2/Source/.
Enter) install everything
c) choose systems, cores and regions
f) copy from a folder or an old card
@@ -786,7 +790,11 @@ answer is not asked.
chosen too, as `generate_pack.py --system` does.
- **Cores.** The platform's own BIOS list for the chosen systems is always
kept. This narrows only the extra files each emulator core loads beyond it.
Asked only when the chosen systems have such files.
Asked only when the chosen systems have such files. Every core is kept
until numbers name some, and the head then says how many of how many
(`1 of 2 cores`). The full screen's **Cores** opens with every core
ticked, as Choose systems does: untick the ones you do not use, **All**
and **None** tick or untick them all.
- **Regions.** Asked only when a chosen system has one BIOS per region. The
numbers give an order of preference: `2,1` keeps the second region where a
file for it exists, and the first one elsewhere.
@@ -1048,6 +1056,8 @@ gives for the device, below the card's root instead of its mount point:
| `.tmp_update/onionVersion/version.txt` | Onion | `BIOS`, RetroArch's list |
| `.allium/` | Allium | `BIOS`, RetroArch's list |
| `Koriki/version.txt` | Koriki | `BIOS`, RetroArch's list |
| `gamedata/`, `homebrew/` beside `bios/`, on a drive that is not a fixed disk | AmberELEC | `bios`, AmberELEC's list |
| `bezels/default.cfg` beside `bios/`, on a drive that is not a fixed disk | EmuELEC | `bios`, EmuELEC's list |
| `system/batocera.conf` holding `system.hostname=BATOCERA` or no hostname line, and no KNULLI file above | Batocera SHARE drive | `bios`, Batocera's list |
| `system/recalbox.conf` | Recalbox SHARE drive | `bios`, Recalbox's list |
| `system/batocera.conf` with any other hostname, and no KNULLI file above, or the files of two SHARE drives | named by neither: the run says which systems it could be | the folder of the system named with `--platform` |
@@ -1186,9 +1196,10 @@ folder inside another system's card (ROCKNIX's `roms/bios` named for KNULLI).
A card whose own files name it is that card whatever its name: an Onion card
mounted as `BIOS` gets `BIOS/BIOS`. One without the system's own files is
refused for the MinUI family, Onion, muOS, spruceOS, CrossMix-OS, Allium and
Koriki. KNULLI and ROCKNIX fill their card when they first start, and no file
on the card names ArkOS, dArkOS, JELOS, KOLOS, UnofficialOS, ArchR, RetroOZ,
plumOS, MiyooCFW, AmberELEC, EmuELEC or the Anbernic firmware: such a folder
Koriki. KNULLI, ROCKNIX, AmberELEC and EmuELEC fill their card when they first
start (AmberELEC's `gamedata` and `homebrew`, EmuELEC's `bezels/default.cfg`),
and no file on the card names ArkOS, dArkOS, JELOS, KOLOS, UnofficialOS, ArchR,
RetroOZ, plumOS, MiyooCFW or the Anbernic firmware: such a folder
gets the system's folder below it, and a note says so, kept on **Create the
folder?** and the plan. No to creating its folders asks for the card again.
Under WSL a drive letter (`D:`, `D:\`) is that Windows drive, typed or given
@@ -1217,16 +1228,20 @@ that question a drive is numbered by the folder its system reads (`ONION:
BIOS`), a drive nothing names by its `bios` folder (`EASYROMS: bios`), never
by its root, which no setup reads. A handheld's card whose own files sit on a
part a computer does not open is told by the folders its system writes beside
`bios` on the games part: `themes` and `launchimages` on ArkOS's `EASYROMS`,
`gamedata` and `homebrew` on AmberELEC's `GAMES`, `bezels/default.cfg` on
EmuELEC's `EEROMS`. A disk Windows or Linux calls fixed is never one. With
nothing found here the first screen and line mode say `A card is in the reader
(EASYROMS): choose its system` (two cards: `choose their systems`), and its
`bios` folder comes first at the folder question, under Enter, before this
computer's usual folder. A drive with a `bios` folder and none of those
folders, a data drive or a stick, is numbered with a note (`DATA has a bios
folder, and nothing on it shows a handheld's card: listed, not chosen.`) and
never taken under Enter. Another system's card or SHARE drive, and a
`bios` on the games part: `bgmusic` or `launchimages` on ArkOS's `EASYROMS`
(every ArkOS card has one of them from its first images, `themes` only since
an update a full card skipped), `gamedata` and `homebrew` on AmberELEC's
`GAMES`, `bezels/default.cfg` on EmuELEC's `EEROMS`. A disk Windows or Linux
calls fixed is never one. AmberELEC's and EmuELEC's card is found as theirs
(`AmberELEC SD card (GAMES)` under **Found on this machine**), in both modes
and on their own setup's card question, and gets their list. An ArkOS card
reads RetroArch's list: with nothing found here the first screen and line mode
say `A card is in the reader (EASYROMS): choose its system` (two cards: `choose
their systems`), and its `bios` folder comes first at the folder question,
under Enter, before this computer's usual folder. A drive with a `bios` folder
and none of those folders, a data drive or a stick, is numbered with a note
(`DATA has a bios folder but no folder a handheld system writes. Pick it below
if your files go there.`) and never taken under Enter. Another system's card or SHARE drive, and a
drive two systems fit, are left out, and a line names them: each is reached
through its own setup. A folder typed is read from the current
one when it is not a full path, and named in full after. `q` quits there too