mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 00:27:20 -05:00
fix: refuse only the axis a list lacks
The MinUI family's lists record cores and regions and no system: every narrowing was refused with a remedy that fetched the same list. Each option is now refused by the axis missing, with what works, and a pack names its platform's list only when that list records the axis. Line mode names what c chooses and skips a question with one answer, and Options leaves out parallel downloads when nothing is downloaded.
This commit is contained in:
1 parent
5704dd6793
commit
0fa4552dae
10 files changed
+314
-63
No files matched your search
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "f7928407478aae79bd9cd7740521e5c83b4fdd26046521c1c49b2f278c3bf22d"
|
||||
$defaultInstallSha256 = "00ad7b4c93b5d69e17943e904121485da924ee681c2796a6bbd45aae6052e2b5"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+103
-33
@@ -4000,9 +4000,31 @@ def _chosen(entry: dict, wanted: set[str]) -> bool:
|
||||
return bool(wanted & set(systems))
|
||||
|
||||
|
||||
def recorded_axes(files: list[dict], omitted: list[dict] = ()) -> frozenset:
|
||||
"""What a file list lets --system, --core and --region narrow by: its
|
||||
systems, its cores, its regions. A list from an older release records
|
||||
none. The MinUI family's records cores and regions and no system: all
|
||||
three were refused on it, by a remedy that fetched the same list
|
||||
(tui_audit_11 P2-1)."""
|
||||
entries = [*files, *omitted]
|
||||
systems = any("systems" in entry for entry in entries)
|
||||
if not systems and not any("regions" in entry for entry in entries):
|
||||
# Systems and regions came in one release: a list with neither is
|
||||
# older, its cores read by nothing that narrows.
|
||||
return frozenset()
|
||||
# A current list records the regions of the files a region selects, and
|
||||
# none when it has no such file: --region then keeps every file, as the
|
||||
# pack built with it does.
|
||||
axes = {"regions", *(["systems"] if systems else [])}
|
||||
if any(copy.get("cores") for entry in files
|
||||
for copy in [entry, *entry.get("alternatives", []), *entry.get("slot_choices", [])]):
|
||||
axes.add("cores")
|
||||
return frozenset(axes)
|
||||
|
||||
|
||||
def records_selection(files: list[dict]) -> bool:
|
||||
"""Whether a manifest carries what narrowing reads. Older ones do not."""
|
||||
return any("systems" in entry for entry in files)
|
||||
"""Whether a manifest records anything narrowing reads. Older ones do not."""
|
||||
return bool(recorded_axes(files))
|
||||
|
||||
|
||||
def available_choices(files: list[dict], omitted: list[dict]) -> dict[str, list[str]]:
|
||||
@@ -7235,9 +7257,12 @@ def _prompt_custom_selection(
|
||||
print(f" Enter) install {whole}")
|
||||
if installed is not None and current != INSTALLED_ONLY:
|
||||
print(f" i) only what the installed cores read: {_sized(installed[0])}")
|
||||
# What c asks, named: "systems, cores and regions" was offered with
|
||||
# a pack that records systems only (tui_audit_11 P3-10).
|
||||
axes = narrowing_words(files, omitted) if choosable else ""
|
||||
choosable = bool(axes)
|
||||
if choosable:
|
||||
print(" c) choose systems, cores and regions"
|
||||
+ (" again" if current is not given else ""))
|
||||
print(f" c) choose {axes}" + (" again" if current is not given else ""))
|
||||
# The screen's "Copy the BIOS files from your old card or a folder".
|
||||
copying = not dry_run
|
||||
if copying:
|
||||
@@ -7312,11 +7337,27 @@ def _print_menu_head(
|
||||
print(f" {line}")
|
||||
|
||||
|
||||
def narrowing_words(files: list[dict], omitted: list[dict]) -> str:
|
||||
"""What c chooses among for these files, in words, "" for nothing: a
|
||||
question with one answer is not asked (tui_audit_11 P3-10)."""
|
||||
axes = recorded_axes(files, omitted)
|
||||
choices = available_choices(files, omitted)
|
||||
named = [word for word, axis, enough in (
|
||||
("systems", "systems", len(listed_systems(files)) > 1),
|
||||
("cores", "cores", len(choices["cores"]) > 1),
|
||||
("regions", "regions", bool(choices["regions"]))) if axis in axes and enough]
|
||||
return f"{', '.join(named[:-1])} and {named[-1]}" if len(named) > 1 else "".join(named)
|
||||
|
||||
|
||||
def _choose_narrowing(files: list[dict], omitted: list[dict], manifest: dict | None,
|
||||
bios_path: Path | None) -> tuple[list[str], list[str], list[str]]:
|
||||
"""Systems, cores and regions asked one after the other, the systems
|
||||
titled and ordered as the screen's Choose systems lists them."""
|
||||
systems_listed = systems_in_order(listed_systems(files), files, manifest, bios_path)
|
||||
titled and ordered as the screen's Choose systems lists them. A question
|
||||
with one answer is skipped: "Cores: 1) azahar" was asked after the 3DS
|
||||
alone was chosen."""
|
||||
listed = listed_systems(files) if "systems" in recorded_axes(files, omitted) else []
|
||||
systems_listed = (systems_in_order(listed, files, manifest, bios_path)
|
||||
if len(listed) > 1 else [])
|
||||
if OTHER_SYSTEMS in systems_listed:
|
||||
print(f"\n'{OTHER_TITLE}' holds what cores load for systems this platform")
|
||||
print("does not list, such as game engines and extra computers.")
|
||||
@@ -7332,7 +7373,7 @@ def _choose_narrowing(files: list[dict], omitted: list[dict], manifest: dict | N
|
||||
|
||||
cores: list[str] = []
|
||||
core_names = available_choices(kept, kept_omitted)["cores"]
|
||||
if core_names:
|
||||
if len(core_names) > 1:
|
||||
print("\nThe platform's own files for these systems are always kept.")
|
||||
print("The extra files each core loads can be narrowed.")
|
||||
cores = _pick("Cores:", [
|
||||
@@ -7567,6 +7608,28 @@ def _older_list_advice(plat: str = "") -> str:
|
||||
"installer again to fetch the current one.")
|
||||
|
||||
|
||||
def _narrowing_advice(plat: str, axis: str, flag: str, recorded: frozenset,
|
||||
pack_alone: bool) -> str:
|
||||
"""What to do when a list or a pack records no axis: only a remedy
|
||||
that changes the outcome. The pack's remedy named install/minui.json,
|
||||
refused the same way, and the list's fetched the same list again."""
|
||||
works = [name for name, field in (("--system", "systems"), ("--core", "cores"),
|
||||
("--region", "regions")) if field in recorded]
|
||||
rest = f"Run it without {flag}" + (f", or narrow with {' and '.join(works)}"
|
||||
if works else "") + "."
|
||||
if pack_alone:
|
||||
listed = LOCAL.pack.list_records if LOCAL.pack is not None else None
|
||||
if listed is not None and axis not in listed:
|
||||
# The pack says its platform's own list lacks the axis too.
|
||||
return f" {display_name(plat)}'s own file list records no {axis} either. {rest}"
|
||||
when = "which records them" if listed is not None else f"when that list records {axis}"
|
||||
return f" Add --manifest FILE, a copy of install/{plat}.json, {when}. {rest}"
|
||||
if recorded:
|
||||
# A current list: the platform has no such axis.
|
||||
return f" {rest}"
|
||||
return _older_list_advice(plat)
|
||||
|
||||
|
||||
def _narrow_to_emulators(
|
||||
names: list[str], manifest: dict, plat: str,
|
||||
files: list[dict], omitted: list[dict],
|
||||
@@ -7613,25 +7676,26 @@ def _requested_narrowing(
|
||||
"""The systems, cores and regions named on the command line, checked."""
|
||||
if not (args.system or args.core or args.region):
|
||||
return None
|
||||
if not records_selection(files):
|
||||
# Without the fields, a region filter would match nothing and install
|
||||
# everything: refused rather than ignored.
|
||||
print(
|
||||
f"Error: the {plat} file list records no systems or regions, so "
|
||||
"--system, --core and --region cannot narrow it.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(_older_list_advice(plat), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if LOCAL.pack is not None and LOCAL.manifest is None:
|
||||
pack_alone = LOCAL.pack is not None and LOCAL.manifest is None
|
||||
recorded = recorded_axes(files, omitted)
|
||||
if pack_alone:
|
||||
# A pack records the systems of its files, not their cores or
|
||||
# regions: either filter would keep everything, and is refused.
|
||||
for flag, given in (("--core", args.core), ("--region", args.region)):
|
||||
if given:
|
||||
print(f"Error: {LOCAL.pack.name} records the systems of its files, not "
|
||||
f"what {flag} narrows by.", file=sys.stderr)
|
||||
print(_older_list_advice(plat), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
recorded = recorded & {"systems"}
|
||||
asked = [(flag, axis) for flag, given, axis in (
|
||||
("--system", args.system, "systems"), ("--core", args.core, "cores"),
|
||||
("--region", args.region, "regions")) if given]
|
||||
for flag, axis in asked:
|
||||
if axis in recorded:
|
||||
continue
|
||||
# Without the field, a filter would match nothing and install
|
||||
# everything: refused rather than ignored, by the axis the list
|
||||
# lacks, with what works on it.
|
||||
source = LOCAL.pack.name if pack_alone else f"the {plat} file list"
|
||||
print(f"Error: {source} records no {axis}, so {flag} cannot narrow it.",
|
||||
file=sys.stderr)
|
||||
print(_narrowing_advice(plat, axis, flag, recorded, pack_alone), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
choices = available_choices(files, omitted)
|
||||
try:
|
||||
systems = resolve_choices(
|
||||
@@ -7780,6 +7844,10 @@ class PackSource:
|
||||
self.members: dict[str, tuple[zipfile.ZipFile, zipfile.ZipInfo]] = {}
|
||||
# The system titles and names the pack's list records, if any.
|
||||
self.names: dict[str, dict] = {}
|
||||
# What its platform's install list narrows by (systems, cores,
|
||||
# regions), None for a pack that does not say: the remedy for an
|
||||
# option the pack cannot apply names that list only when it can.
|
||||
self.list_records: frozenset | None = None
|
||||
try:
|
||||
for path in self.files:
|
||||
archive = zipfile.ZipFile(path)
|
||||
@@ -7867,6 +7935,9 @@ class PackSource:
|
||||
# A pack is a file anyone can hand over: an id or a name holding a
|
||||
# control character would reach the terminal as an escape, and is
|
||||
# dropped.
|
||||
records = data.get("list_records")
|
||||
if isinstance(records, list) and all(isinstance(axis, str) for axis in records):
|
||||
self.list_records = frozenset(records) & {"systems", "cores", "regions"}
|
||||
for key in ("system_titles", "system_names"):
|
||||
held = data.get(key)
|
||||
if isinstance(held, dict):
|
||||
@@ -10260,9 +10331,9 @@ def _read_from_folder(label: str) -> FolderIndex:
|
||||
return index
|
||||
|
||||
|
||||
def _refuse_closed(dest: Path, named: str, then: str = "") -> None:
|
||||
def _refuse_closed(dest: Path, named: str) -> None:
|
||||
"""End the run, with its name, on a folder above dest this user cannot
|
||||
open. then is a line said after the error, which it continues."""
|
||||
open."""
|
||||
closed = closed_folder(dest)
|
||||
if closed is None:
|
||||
return
|
||||
@@ -10271,8 +10342,6 @@ def _refuse_closed(dest: Path, named: str, then: str = "") -> None:
|
||||
print(f"Error: {screen_text(message)}. Give this user access to it, run as "
|
||||
f"its owner, or choose another folder than the one {named}.",
|
||||
file=sys.stderr)
|
||||
if then:
|
||||
print(f" {then}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -11478,9 +11547,8 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
named = (f"used by default, since {display_name(name)} was not found"
|
||||
if path in assumed_dests
|
||||
else f"{found_in[0]} names" if found_in else "detected here")
|
||||
_refuse_closed(path, f"{named} (name another with --dest)",
|
||||
"Nothing was installed. Name one platform with --platform "
|
||||
"to install it alone." if len(platforms) > 1 else "")
|
||||
# One setup by now: two found without questions stopped above.
|
||||
_refuse_closed(path, f"{named} (name another with --dest)")
|
||||
|
||||
total_downloaded = 0
|
||||
total_up_to_date = 0
|
||||
@@ -13221,7 +13289,7 @@ def _plan_rows(state: TuiState) -> list[TuiRow]:
|
||||
*([TuiRow("install", "Install anyway")] if rows else [])]
|
||||
# --system given: the choice stays offered, the list widened when it is
|
||||
# opened. With one system named it was neither shown nor changeable.
|
||||
if records_selection((state.manifest or {}).get("files", [])) and (
|
||||
if "systems" in recorded_axes((state.manifest or {}).get("files", [])) and (
|
||||
len(state.systems) > 1 or state.cli_systems):
|
||||
rows.append(TuiRow("systems", "Choose systems"))
|
||||
rows.append(TuiRow("fromcard", "Copy the BIOS files from your old card or a folder"))
|
||||
@@ -13341,8 +13409,10 @@ def _option_rows(state: TuiState) -> list[TuiRow]:
|
||||
# The interface said "Name it with --save-dir" and had no row for it.
|
||||
rows.append(TuiRow("savedir", f"Save folder: {_save_folder_words(state)}",
|
||||
"--save-dir", style="path"))
|
||||
if not LOCAL.offline:
|
||||
# From a pack, or offline, nothing is downloaded (tui_audit_11 P3-16).
|
||||
rows.append(TuiRow("jobs", f"Parallel downloads: {state.jobs}", "--jobs"))
|
||||
rows += [
|
||||
TuiRow("jobs", f"Parallel downloads: {state.jobs}", "--jobs"),
|
||||
TuiRow("folder", f"BIOS folder: {_short_path(state.dest, state.home)}", "--dest",
|
||||
style="path"),
|
||||
TuiRow("from", f"Copy from: {copied}", "--from", style="path"),
|
||||
|
||||
+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="f7928407478aae79bd9cd7740521e5c83b4fdd26046521c1c49b2f278c3bf22d"
|
||||
DEFAULT_INSTALL_SHA256="00ad7b4c93b5d69e17943e904121485da924ee681c2796a6bbd45aae6052e2b5"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
{
|
||||
"dest": "roms/ports/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 790778,
|
||||
"sha256": "f7928407478aae79bd9cd7740521e5c83b4fdd26046521c1c49b2f278c3bf22d",
|
||||
"size": 794767,
|
||||
"sha256": "00ad7b4c93b5d69e17943e904121485da924ee681c2796a6bbd45aae6052e2b5",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -71,8 +71,8 @@
|
||||
"muos": {
|
||||
"archive": {
|
||||
"dest": "ARCHIVE/RetroBIOS.muxapp",
|
||||
"size": 843327,
|
||||
"sha256": "edf0948281063d1b9abf271f778a4486adf83b18924965eff68308e1540f25ab"
|
||||
"size": 847316,
|
||||
"sha256": "56cac10af6b7afaac0d1b3c0964d54caeabda46e75d35580dcedda264e2daac8"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
@@ -92,8 +92,8 @@
|
||||
{
|
||||
"name": "RetroBIOS/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 790778,
|
||||
"sha256": "f7928407478aae79bd9cd7740521e5c83b4fdd26046521c1c49b2f278c3bf22d",
|
||||
"size": 794767,
|
||||
"sha256": "00ad7b4c93b5d69e17943e904121485da924ee681c2796a6bbd45aae6052e2b5",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -166,8 +166,8 @@
|
||||
{
|
||||
"dest": ".config/modules/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 790778,
|
||||
"sha256": "f7928407478aae79bd9cd7740521e5c83b4fdd26046521c1c49b2f278c3bf22d",
|
||||
"size": 794767,
|
||||
"sha256": "00ad7b4c93b5d69e17943e904121485da924ee681c2796a6bbd45aae6052e2b5",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
|
||||
@@ -43,6 +43,12 @@
|
||||
"system_names": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "array", "items": { "type": "string" } }
|
||||
},
|
||||
"list_records": {
|
||||
"description": "What the platform's install list narrows by: the installer names that list as the remedy for --core or --region given with the pack alone only when it records them.",
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": { "enum": ["systems", "cores", "regions"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4759,11 +4759,25 @@ def pack_systems(install_manifest: dict) -> dict:
|
||||
("the file list comes from an older release") and offered no Choose
|
||||
systems, the route of a handheld without Wi-Fi.
|
||||
"""
|
||||
files = install_manifest.get("files", [])
|
||||
entries = [*files, *install_manifest.get("omitted_files", [])]
|
||||
# What the install list narrows by, the installer's recorded_axes: with
|
||||
# the pack alone it names that list as the way to --core and --region
|
||||
# only when the list records them. It named install/misterfpga.json for
|
||||
# --core, a list that records no core.
|
||||
records = {"systems"} if any("systems" in entry for entry in entries) else set()
|
||||
if any(copy.get("cores") for entry in files
|
||||
for copy in [entry, *entry.get("alternatives", []),
|
||||
*entry.get("slot_choices", [])]):
|
||||
records.add("cores")
|
||||
if records or any("regions" in entry for entry in entries):
|
||||
records.add("regions")
|
||||
return {
|
||||
"files": {entry["dest"]: list(entry.get("systems") or [])
|
||||
for entry in install_manifest.get("files", []) if "systems" in entry},
|
||||
for entry in files if "systems" in entry},
|
||||
"system_titles": dict(install_manifest.get("system_titles") or {}),
|
||||
"system_names": dict(install_manifest.get("system_names") or {}),
|
||||
"list_records": sorted(records),
|
||||
}
|
||||
|
||||
|
||||
@@ -4779,6 +4793,8 @@ def annotate_pack_systems(manifest: dict, systems: dict | None) -> None:
|
||||
for key in ("system_titles", "system_names"):
|
||||
if systems.get(key):
|
||||
manifest[key] = systems[key]
|
||||
if "list_records" in systems:
|
||||
manifest["list_records"] = systems["list_records"]
|
||||
|
||||
|
||||
def inject_manifest(zip_path: str, manifest: dict) -> None:
|
||||
|
||||
+25
-13
@@ -370,18 +370,28 @@ class RefusedBeforeAnyInstall(unittest.TestCase):
|
||||
(tmp / "retroarch.cfg").write_text("")
|
||||
(tmp / "cores").mkdir()
|
||||
(tmp / "cores" / "mgba_libretro.so").write_bytes(b"")
|
||||
with mock.patch.object(install, "detect_os", return_value="linux"), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=detected), \
|
||||
mock.patch.object(install, "fetch_manifest") as fetch, \
|
||||
mock.patch.object(sys, "argv", ["install.py", "--installed-cores", "--no-input"]), \
|
||||
contextlib.redirect_stdout(io.StringIO()), \
|
||||
contextlib.redirect_stderr(io.StringIO()) as err:
|
||||
with self.assertRaises(SystemExit) as stop:
|
||||
install.main()
|
||||
self.assertEqual(stop.exception.code, 1)
|
||||
self.assertIn("batocera ships its cores", err.getvalue())
|
||||
fetch.assert_not_called()
|
||||
# Two setups found stop a run without questions before either is
|
||||
# read (code_audit_18 P2-3): the refusal is checked with Batocera
|
||||
# named, and with both found nothing is fetched either.
|
||||
for argv, said in (
|
||||
(["--platform", "batocera", "--dest", str(tmp / "bios")],
|
||||
"batocera ships its cores"),
|
||||
([], "2 setups were found here")):
|
||||
with self.subTest(argv=argv), \
|
||||
mock.patch.object(install, "detect_os", return_value="linux"), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=detected), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=[]), \
|
||||
mock.patch.object(install, "fetch_manifest") as fetch, \
|
||||
mock.patch.object(sys, "argv", ["install.py", "--installed-cores",
|
||||
"--no-input", *argv]), \
|
||||
contextlib.redirect_stdout(io.StringIO()), \
|
||||
contextlib.redirect_stderr(io.StringIO()) as err:
|
||||
with self.assertRaises(SystemExit) as stop:
|
||||
install.main()
|
||||
self.assertEqual(stop.exception.code, 1)
|
||||
self.assertIn(said, err.getvalue())
|
||||
fetch.assert_not_called()
|
||||
|
||||
|
||||
class AnUnknownTargetStopsTheRunFirst(unittest.TestCase):
|
||||
@@ -393,12 +403,14 @@ class AnUnknownTargetStopsTheRunFirst(unittest.TestCase):
|
||||
self.addCleanup(shutil.rmtree, tmp, True)
|
||||
detected = [("emudeck", tmp / "bios"), ("retroarch", tmp / "system")]
|
||||
lists = {"emudeck": {"steamos": {"cores": []}}, "retroarch": {"x86_64": {"cores": []}}}
|
||||
# Listed for both platforms: a listing reads every list found.
|
||||
with mock.patch.object(install, "detect_os", return_value="linux"), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=detected), \
|
||||
mock.patch.object(install, "fetch_targets", side_effect=lists.get), \
|
||||
mock.patch.object(install, "fetch_manifest") as fetch, \
|
||||
mock.patch.object(sys, "argv", ["install.py", "--target", "steamos", "--no-input"]), \
|
||||
mock.patch.object(sys, "argv", ["install.py", "--target", "steamos",
|
||||
"--list-systems", "--no-input"]), \
|
||||
contextlib.redirect_stdout(io.StringIO()), \
|
||||
contextlib.redirect_stderr(io.StringIO()) as err:
|
||||
with self.assertRaises(SystemExit) as stop:
|
||||
|
||||
@@ -612,7 +612,8 @@ class TheOneLinerNarrows(unittest.TestCase):
|
||||
with FakeRepository(selection=False) as repo:
|
||||
proc = repo.run("--region", "us")
|
||||
self.assertEqual(proc.returncode, 1)
|
||||
self.assertIn("records no systems or regions", proc.stderr)
|
||||
self.assertIn("records no regions, so --region cannot narrow it", proc.stderr)
|
||||
self.assertIn("older release", proc.stderr)
|
||||
self.assertEqual(repo.installed(), [])
|
||||
|
||||
def test_a_pinned_ref_is_named_as_the_way_out(self):
|
||||
@@ -675,18 +676,19 @@ class TheOneLinerNarrows(unittest.TestCase):
|
||||
|
||||
@unittest.skipUnless(shutil.which("script"), "script(1) provides the terminal")
|
||||
def test_the_custom_mode_at_a_terminal(self):
|
||||
"""Typed answers: customise, PlayStation only, no core, US first."""
|
||||
"""Typed answers: customise, PlayStation only, US first. Its one
|
||||
core is not asked about (tui_audit_11 P3-10)."""
|
||||
with FakeRepository() as repo:
|
||||
command = " ".join(shlex.quote(part) for part in repo.command())
|
||||
proc = subprocess.run(
|
||||
["script", "-qec", command, "/dev/null"],
|
||||
input="c\n2\n\n2\n\n", env=repo.env(), capture_output=True,
|
||||
input="c\n2\n2\n\n", env=repo.env(), capture_output=True,
|
||||
text=True, timeout=60, check=False,
|
||||
)
|
||||
out = proc.stdout
|
||||
self.assertEqual(proc.returncode, 0, out + proc.stderr)
|
||||
self.assertIn("choose systems, cores and regions", out)
|
||||
self.assertIn("pcsx_rearmed", out)
|
||||
self.assertNotIn("Cores:", out)
|
||||
self.assertIn("--system sony-playstation --region north-america", out)
|
||||
self.assertEqual(repo.installed(), ["pcsx/extra.bin", "scph5501.bin"])
|
||||
|
||||
|
||||
+134
-1
@@ -7432,7 +7432,7 @@ class AuditSeventeenAndTen(unittest.TestCase):
|
||||
with contextlib.redirect_stderr(err), self.assertRaises(SystemExit):
|
||||
install._requested_narrowing(_args(system=["nintendo-gba"]), "retroarch",
|
||||
install.LOCAL.pack.manifest("retroarch")["files"], [])
|
||||
self.assertIn("add --manifest FILE, a copy of install/retroarch.json", err.getvalue())
|
||||
self.assertIn("Add --manifest FILE, a copy of install/retroarch.json", err.getvalue())
|
||||
self.assertNotIn("older release", err.getvalue())
|
||||
|
||||
def test_a_pack_cannot_write_escapes_through_its_system_names(self):
|
||||
@@ -8660,6 +8660,139 @@ class AuditEighteenAndEleven(unittest.TestCase):
|
||||
["Onion SD card (ONION, no BIOS folder)",
|
||||
"Onion SD card (OLDONION, 6 BIOS files)"])
|
||||
|
||||
# tui_audit_11 P2-1: a list or a pack that records some axes only.
|
||||
|
||||
def minui(self, plat: str = "minui", systems: bool = False, cores: bool = True,
|
||||
regions: bool = True) -> tuple[Path, Path, dict]:
|
||||
"""A list of plat's shape (the MinUI family records cores and regions,
|
||||
no system), the pack built from it and the files' payloads."""
|
||||
from test_install_offline import GBA, JP, US # noqa: PLC0415
|
||||
|
||||
payloads = {"Bios/MGBA/gba_bios.bin": (GBA, "nintendo-gba", "mgba", None),
|
||||
"Bios/PS/scph5501.bin": (US, "sony-playstation", "pcsx_rearmed",
|
||||
"north-america"),
|
||||
"Bios/PS/scph5500.bin": (JP, "sony-playstation", "pcsx_rearmed", "japan")}
|
||||
files = []
|
||||
for dest, (data, system, core, region) in payloads.items():
|
||||
entry = {"dest": dest, "sha1": hashlib.sha1(data).hexdigest(), "size": len(data),
|
||||
"repo_path": f"bios/{dest}", "cores": [core] if cores else None}
|
||||
if systems:
|
||||
entry["systems"] = [system]
|
||||
if regions and region:
|
||||
entry.update(regions=[region], region_groups=["ps"])
|
||||
files.append(entry)
|
||||
listing = self.root / f"{plat}.json"
|
||||
install_list = {"manifest_version": 2, "platform": plat, "files": files}
|
||||
listing.write_text(json.dumps(install_list))
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
self.addCleanup(sys.path.remove, str(REPO_ROOT / "scripts"))
|
||||
import generate_pack # noqa: PLC0415
|
||||
|
||||
pack_list = {"files": [{"path": dest, "sha1": entry["sha1"], "size": entry["size"]}
|
||||
for dest, entry in zip(payloads, files)]}
|
||||
generate_pack.annotate_pack_systems(pack_list, generate_pack.pack_systems(install_list))
|
||||
name = {"minui": "MinUI", "misterfpga": "MiSTer_FPGA"}[plat]
|
||||
pack = self.root / f"{name}_v1_BIOS_Pack.zip"
|
||||
with zipfile.ZipFile(pack, "w") as archive:
|
||||
for dest, (data, *_rest) in payloads.items():
|
||||
archive.writestr(dest, data)
|
||||
archive.writestr("manifest.json", json.dumps(pack_list))
|
||||
return listing, pack, pack_list
|
||||
|
||||
def narrowed(self, plat: str, **given) -> tuple[int, str, list]:
|
||||
"""(exit code, what was said, the files kept) of a --dry-run narrowed
|
||||
as given."""
|
||||
dest = self.root / "dest"
|
||||
if not dest.exists():
|
||||
make_card(dest, "MinUI")
|
||||
stream = io.StringIO()
|
||||
code = 0
|
||||
with mock.patch.object(install.EVENTS, "stream", stream), \
|
||||
mock.patch.object(install, "detect_os", return_value="linux"), \
|
||||
mock.patch.dict(os.environ, {install.OFFLINE_VARIABLE: "1"}), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out, \
|
||||
contextlib.redirect_stderr(io.StringIO()) as err:
|
||||
try:
|
||||
install._install(_args(platform=plat, dest=dest, dry_run=True, events=True,
|
||||
no_input=True, declared_only=False, **given),
|
||||
io.StringIO())
|
||||
except SystemExit as stop:
|
||||
code = stop.code
|
||||
finally:
|
||||
install.LOCAL.reset()
|
||||
kept = [json.loads(line)["dest"] for line in stream.getvalue().splitlines()
|
||||
if '"event": "file"' in line]
|
||||
return code, out.getvalue() + err.getvalue(), kept
|
||||
|
||||
def test_a_list_without_systems_narrows_by_what_it_records(self):
|
||||
"""tui_audit_11 P2-1: install/minui.json records cores and regions,
|
||||
no system, and all three options were refused with a remedy that
|
||||
fetched the same list."""
|
||||
listing, _pack, _listed = self.minui()
|
||||
code, text, kept = self.narrowed("minui", manifest=listing, core=["mgba"])
|
||||
self.assertEqual(code, 0, text)
|
||||
self.assertEqual(kept, ["Bios/MGBA/gba_bios.bin"])
|
||||
code, text, kept = self.narrowed("minui", manifest=listing, region=["us"])
|
||||
self.assertEqual(code, 0, text)
|
||||
self.assertNotIn("Bios/PS/scph5500.bin", kept)
|
||||
code, text, _kept = self.narrowed("minui", manifest=listing,
|
||||
system=["nintendo-gba"])
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("records no systems, so --system cannot narrow it", text)
|
||||
self.assertIn("Run it without --system, or narrow with --core and --region", text)
|
||||
self.assertNotIn("one-line installer", text)
|
||||
# The remedy followed: what it names works.
|
||||
code, text, _kept = self.narrowed("minui", manifest=listing, core=["mgba"],
|
||||
region=["us"])
|
||||
self.assertEqual(code, 0, text)
|
||||
|
||||
def test_a_pack_names_its_list_only_when_that_list_records_it(self):
|
||||
"""tui_audit_11 P2-1: a MinUI pack sent --core to install/minui.json,
|
||||
refused the same way, and a MiSTer pack to install/misterfpga.json,
|
||||
which records no core. The pack now records what its list narrows
|
||||
by, and each remedy changes the outcome."""
|
||||
listing, pack, listed = self.minui()
|
||||
self.assertEqual(listed["list_records"], ["cores", "regions"])
|
||||
code, text, _kept = self.narrowed("minui", from_pack=[pack], core=["mgba"])
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("Add --manifest FILE, a copy of install/minui.json, which records them",
|
||||
text)
|
||||
code, text, kept = self.narrowed("minui", from_pack=[pack], manifest=listing,
|
||||
core=["mgba"])
|
||||
self.assertEqual((code, kept), (0, ["Bios/MGBA/gba_bios.bin"]), text)
|
||||
mister_list, mister, listed = self.minui("misterfpga", systems=True, cores=False,
|
||||
regions=False)
|
||||
self.assertEqual(listed["list_records"], ["regions", "systems"])
|
||||
code, text, _kept = self.narrowed("misterfpga", from_pack=[mister], core=["x"])
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("MiSTer FPGA's own file list records no cores either. Run it without "
|
||||
"--core, or narrow with --system.", text)
|
||||
self.assertNotIn("--manifest", text)
|
||||
code, text, kept = self.narrowed("misterfpga", from_pack=[mister],
|
||||
system=["nintendo-gba"])
|
||||
self.assertEqual((code, kept), (0, ["Bios/MGBA/gba_bios.bin"]), text)
|
||||
self.assertTrue(mister_list.exists())
|
||||
|
||||
def test_line_mode_offers_what_can_be_chosen(self):
|
||||
"""tui_audit_11 P3-10: "c) choose systems, cores and regions" with a
|
||||
pack that records systems only, and "Cores: 1) azahar" asked with
|
||||
one answer."""
|
||||
_listing, _pack, listed = self.minui("misterfpga", systems=True, cores=False,
|
||||
regions=False)
|
||||
files = [{"dest": f["path"], "sha1": f["sha1"], "size": f["size"],
|
||||
"repo_path": "x", "systems": f["systems"]} for f in listed["files"]]
|
||||
self.assertEqual(install.narrowing_words(files, []), "systems")
|
||||
listing, _pack, _listed = self.minui()
|
||||
minui = json.loads(listing.read_text())["files"]
|
||||
self.assertEqual(install.narrowing_words(minui, []), "cores and regions")
|
||||
one_core = [{**entry, "systems": ["nintendo-3ds"], "cores": ["azahar"]}
|
||||
for entry in minui[:1]]
|
||||
with mock.patch.object(install, "_ask", side_effect=["", ""]) as asked, \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
install._choose_narrowing(one_core, [], {}, None)
|
||||
self.assertNotIn("Cores:", out.getvalue())
|
||||
self.assertEqual(asked.call_count, 0)
|
||||
|
||||
def test_the_card_marker_table_is_written_from_the_rules(self):
|
||||
"""code_audit_18 P3-6, tui_audit_11 P2-2: the table left out the
|
||||
SHARE drives. It is generated: every card rule and SHARE drive file
|
||||
|
||||
+13
-1
@@ -615,7 +615,9 @@ It keeps what those cores read and drops the systems none of them emulates,
|
||||
the rule `--installed-cores` applies.
|
||||
|
||||
`c` walks through three numbered lists, each answered with numbers such as
|
||||
`1,4,7-9`, or Enter to keep everything on that axis.
|
||||
`1,4,7-9`, or Enter to keep everything on that axis. The letter names the
|
||||
lists it asks (`c) choose systems` with a MiSTer pack), and a list with one
|
||||
answer is not asked.
|
||||
|
||||
- **Systems.** The consoles and computers the platform declares, titled and
|
||||
ordered as the full screen's Choose systems lists them, the id `--system`
|
||||
@@ -637,6 +639,16 @@ without questions, every other option of the run in it, for instance
|
||||
--system sony-playstation --region north-america`. Enter then installs, and
|
||||
`c` chooses again.
|
||||
|
||||
An option is refused by the axis the list does not record, with what works
|
||||
instead. The MinUI, NextUI and MyMinUI lists record cores and regions and no
|
||||
system: `--core` and `--region` narrow them, `--system` is refused with
|
||||
`Run it without --system, or narrow with --core and --region`. A pack records
|
||||
the systems of its files and what its platform's list narrows by: with the
|
||||
pack alone, `--core` or `--region` names that list (`--manifest FILE`, a copy
|
||||
of `install/minui.json`) only when it records them, and MiSTer FPGA's pack
|
||||
says its list records no cores. A list from an older release records none of
|
||||
the three, and the one-line installer fetches the current one.
|
||||
|
||||
The same choices on the command line:
|
||||
|
||||
```bash
|
||||
|
||||
Reference in new issue
Block a user