From e8e2215a492d7c9c34c33dee69c0e911fedceb20 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 10 Nov 2026 08:43:20 +0100 Subject: [PATCH] fix: clear the raw error flag on each run --verbose set it and no run cleared it, so a later run in the same process ended its clock message with the raw SSL text. Also: --target named again in its refusal, three names mypy read as another type, a semicolon and a tooling file named in test comments. --- install.ps1 | 2 +- install.py | 24 ++++++++++++++---------- install.sh | 2 +- install/launchers/index.json | 16 ++++++++-------- mypy-baseline.txt | 10 ---------- tests/test_install.py | 18 ++++++++++++++++++ tests/test_install_journeys.py | 6 ++---- tests/test_install_tui.py | 2 +- 8 files changed, 45 insertions(+), 35 deletions(-) diff --git a/install.ps1 b/install.ps1 index d051781fa..3e38caf74 100644 --- a/install.ps1 +++ b/install.ps1 @@ -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 = "7a9074e972e7247ab3fbe3486624a4eacf6b0d090aa6d754f95aef03a93f6faf" + $defaultInstallSha256 = "97cd447f646ffe259805b98bfbdc98b2d5c63acccd823480dadfe331005208f5" # The pin above refuses any changed byte: this cap only stops a body that # cannot be the installer (a sign-in portal's page) before it is hashed. $maximumInstallerBytes = 8MB diff --git a/install.py b/install.py index 3569383f3..0b4b1f26c 100644 --- a/install.py +++ b/install.py @@ -4742,7 +4742,7 @@ def fetch_targets(plat: str) -> "dict | None": if LOCAL.offline: source = (f"{LOCAL.pack.name} carries none" if LOCAL.pack is not None else f"{offline_named()} makes no request") - print(f"Error: no {display_name(plat)} board list here ({source}). Put " + print(f"Error: --target needs the {display_name(plat)} board list ({source}). Put " f"install/targets/{plat}.json beside the --manifest file, or go online.", file=sys.stderr) sys.exit(2) @@ -7226,15 +7226,15 @@ def _report_outcome( say(f"BIOS folder: {_locations(paths)}") for line in kept: say(line) - left = [entry for entry in omitted_entries or [] - if entry.get("reason") != "no_save_directory"] + skipped = [entry for entry in omitted_entries or [] + if entry.get("reason") != "no_save_directory"] if omitted > len(unplaced) and OUTPUT.verbose: - for line in _omission_lines(omitted - len(unplaced), left, + for line in _omission_lines(omitted - len(unplaced), skipped, Path(paths[0]) if len(paths) == 1 else None): say(line) - elif omitted > len(unplaced) and any(entry.get("required") for entry in left): + elif omitted > len(unplaced) and any(entry.get("required") for entry in skipped): # A file an emulator cannot start without is no detail: counted. - say(needed_absent_words(sum(1 for entry in left if entry.get("required")), + say(needed_absent_words(sum(1 for entry in skipped if entry.get("required")), "--verbose")) # RomM's scan is the step after the install: the other hints say how to # install less, a detail. @@ -11347,9 +11347,9 @@ def plan_games(plat: str, bios_path: Path, manifest: dict, folder: Path | None = continue setup = setup or candidate if folder is None: - found = games_root(candidate, bios_path) - if found is not None: - setup, root = candidate, found + found_root = games_root(candidate, bios_path) + if found_root is not None: + setup, root = candidate, found_root break if root is None: return None @@ -14250,8 +14250,12 @@ def main() -> None: _INVOCATION.os_type = detect_os() parser = build_parser() args = parser.parse_args() + # Each run says it from its own options: a run with --verbose before it + # in the same process left every later message with its raw cause. if args.verbose: _RAW_ERRORS.set() + else: + _RAW_ERRORS.clear() if args.cores_dir: args.installed_cores = True if args.yes: @@ -23783,7 +23787,7 @@ def tui_view(state: TuiState, cols: int, rows: int) -> list[str]: if cols < TUI_MIN_COLS or rows < TUI_MIN_ROWS: # How large, and how large it is now: "larger" alone left the # reader resizing by guess. - lines = [_styled("")] * rows + lines: list[str] = [_styled("")] * rows said = [fit(text, cols) for text in ( "Make the window larger:", f"{TUI_MIN_COLS}x{TUI_MIN_ROWS} or more, now {cols}x{rows}")][:rows] diff --git a/install.sh b/install.sh index a3a64c5f8..1720bc469 100755 --- a/install.sh +++ b/install.sh @@ -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="7a9074e972e7247ab3fbe3486624a4eacf6b0d090aa6d754f95aef03a93f6faf" +DEFAULT_INSTALL_SHA256="97cd447f646ffe259805b98bfbdc98b2d5c63acccd823480dadfe331005208f5" # The pin above refuses any changed byte: this cap only stops a body that # cannot be the installer (a sign-in portal's page) before it is hashed. MAX_INSTALLER_BYTES=8388608 diff --git a/install/launchers/index.json b/install/launchers/index.json index f14a6cb25..cba1b71cb 100644 --- a/install/launchers/index.json +++ b/install/launchers/index.json @@ -27,8 +27,8 @@ { "dest": "roms/ports/retrobios/install.py", "repo_path": "install.py", - "size": 1162136, - "sha256": "7a9074e972e7247ab3fbe3486624a4eacf6b0d090aa6d754f95aef03a93f6faf", + "size": 1162373, + "sha256": "97cd447f646ffe259805b98bfbdc98b2d5c63acccd823480dadfe331005208f5", "executable": false }, { @@ -71,8 +71,8 @@ "muos": { "archive": { "dest": "ARCHIVE/RetroBIOS.muxapp", - "size": 1215761, - "sha256": "112292319b9b3181bfd124ec211512b0f8d507b46bf878e1f84552c4980bc260" + "size": 1215998, + "sha256": "996a6e24cc5e2d067d86f8f368313e8afd1ba17391b3c595cb8f72e73b208433" }, "members": [ { @@ -92,8 +92,8 @@ { "name": "RetroBIOS/install.py", "repo_path": "install.py", - "size": 1162136, - "sha256": "7a9074e972e7247ab3fbe3486624a4eacf6b0d090aa6d754f95aef03a93f6faf", + "size": 1162373, + "sha256": "97cd447f646ffe259805b98bfbdc98b2d5c63acccd823480dadfe331005208f5", "executable": false }, { @@ -166,8 +166,8 @@ { "dest": ".config/modules/retrobios/install.py", "repo_path": "install.py", - "size": 1162136, - "sha256": "7a9074e972e7247ab3fbe3486624a4eacf6b0d090aa6d754f95aef03a93f6faf", + "size": 1162373, + "sha256": "97cd447f646ffe259805b98bfbdc98b2d5c63acccd823480dadfe331005208f5", "executable": false }, { diff --git a/mypy-baseline.txt b/mypy-baseline.txt index d0a73e3ad..4814cbb7d 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -78,7 +78,6 @@ install.py:0: error: Cannot infer type of lambda [misc] install.py:0: error: Cannot infer type of lambda [misc] install.py:0: error: Cannot infer type of lambda [misc] install.py:0: error: Cannot infer type of lambda [misc] -install.py:0: error: Generator has incompatible item type "int"; expected "bool" [misc] install.py:0: error: Incompatible default for argument "after" (default has type "tuple[()]", argument has type "list[Any]") [assignment] install.py:0: error: Incompatible default for argument "copied" (default has type "tuple[()]", argument has type "list[str]") [assignment] install.py:0: error: Incompatible default for argument "cores" (default has type "tuple[()]", argument has type "list[str]") [assignment] @@ -92,7 +91,6 @@ install.py:0: error: Incompatible return value type (got "IO[bytes]", expected " install.py:0: error: Incompatible return value type (got "Optional[Path]", expected "Path") [return-value] install.py:0: error: Incompatible return value type (got "Union[Any, object]", expected "str") [return-value] install.py:0: error: Incompatible return value type (got "list[TuiLine]", expected "list[str]") [return-value] -install.py:0: error: Incompatible return value type (got "list[TuiLine]", expected "list[str]") [return-value] install.py:0: error: Incompatible return value type (got "list[tuple[Optional[str], str]]", expected "list[tuple[str, str]]") [return-value] install.py:0: error: Incompatible return value type (got "object", expected "dict[Any, Any]") [return-value] install.py:0: error: Incompatible types in "yield" (actual type "IO[bytes]", expected type "BinaryIO") [misc] @@ -107,7 +105,6 @@ install.py:0: error: Incompatible types in assignment (expression has type "Game install.py:0: error: Incompatible types in assignment (expression has type "Optional[Progress]", variable has type "Progress") [assignment] install.py:0: error: Incompatible types in assignment (expression has type "Optional[frozenset[Any]]", variable has type "Optional[tuple[int, str]]") [assignment] install.py:0: error: Incompatible types in assignment (expression has type "Optional[frozenset[Any]]", variable has type "list[str]") [assignment] -install.py:0: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") [assignment] install.py:0: error: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str") [assignment] install.py:0: error: Incompatible types in assignment (expression has type "Path", variable has type "bool") [assignment] install.py:0: error: Incompatible types in assignment (expression has type "Union[Any, bool]", variable has type "str") [assignment] @@ -136,9 +133,6 @@ install.py:0: error: Item "None" of "Optional[BinaryIO]" has no attribute "trunc install.py:0: error: Item "None" of "Optional[Element[str]]" has no attribute "iter" [union-attr] install.py:0: error: Item "None" of "Optional[PackSource]" has no attribute "name" [union-attr] install.py:0: error: Item "None" of "Optional[PackSource]" has no attribute "name" [union-attr] -install.py:0: error: Item "None" of "Optional[Path]" has no attribute "__iter__" (not iterable) [union-attr] -install.py:0: error: Item "None" of "Optional[Path]" has no attribute "__iter__" (not iterable) [union-attr] -install.py:0: error: Item "None" of "Optional[Path]" has no attribute "append" [union-attr] install.py:0: error: Item "None" of "Optional[Path]" has no attribute "read_text" [union-attr] install.py:0: error: Item "None" of "Optional[Path]" has no attribute "write_text" [union-attr] install.py:0: error: Item "None" of "Optional[Terminal]" has no attribute "stream" [union-attr] @@ -147,9 +141,6 @@ install.py:0: error: Item "None" of "Optional[Terminal]" has no attribute "strea install.py:0: error: Item "None" of "Optional[Terminal]" has no attribute "stream" [union-attr] install.py:0: error: Item "None" of "Optional[TuiHits]" has no attribute "frame" [union-attr] install.py:0: error: Item "None" of "Optional[TuiHits]" has no attribute "shown" [union-attr] -install.py:0: error: Item "Path" of "Optional[Path]" has no attribute "__iter__" (not iterable) [union-attr] -install.py:0: error: Item "Path" of "Optional[Path]" has no attribute "__iter__" (not iterable) [union-attr] -install.py:0: error: Item "Path" of "Optional[Path]" has no attribute "append" [union-attr] install.py:0: error: List comprehension has incompatible type List[object]; expected List[str] [misc] install.py:0: error: List item 0 has incompatible type "int"; expected "FolderIndex" [list-item] install.py:0: error: List item 0 has incompatible type "tuple[Optional[str], str]"; expected "tuple[str, str]" [list-item] @@ -161,7 +152,6 @@ install.py:0: error: Module has no attribute "LK_NBLCK" [attr-defined] install.py:0: error: Module has no attribute "OpenKey" [attr-defined] install.py:0: error: Module has no attribute "QueryValueEx" [attr-defined] install.py:0: error: Module has no attribute "locking" [attr-defined] -install.py:0: error: Name "found" already defined on line 0 [no-redef] install.py:0: error: Name "top" already defined on line 0 [no-redef] install.py:0: error: Need type annotation for "removed" (hint: "removed: list[] = ...") [var-annotated] install.py:0: error: Need type annotation for "words" (hint: "words: list[] = ...") [var-annotated] diff --git a/tests/test_install.py b/tests/test_install.py index 6d39f88a1..60b12173f 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -7834,5 +7834,23 @@ class TheNextStepFitsWhereTheInstallerRuns(unittest.TestCase): install.NEXT_ON_CARD) +class EachRunSaysErrorsFromItsOwnOptions(unittest.TestCase): + """--verbose set the raw-cause flag and nothing cleared it: a later run + in the same process ended its clock message with the raw SSL text.""" + + def test_a_run_without_verbose_clears_it(self): + install._RAW_ERRORS.set() + self.addCleanup(install._RAW_ERRORS.clear) + with unittest.mock.patch.object(sys, "argv", + ["install.py", "--list-platforms", "--offline"]), \ + contextlib.redirect_stdout(io.StringIO()), \ + contextlib.redirect_stderr(io.StringIO()): + try: + install.main() + except SystemExit: + pass + self.assertFalse(install._RAW_ERRORS.is_set()) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_install_journeys.py b/tests/test_install_journeys.py index 1582290c6..ee3595afa 100644 --- a/tests/test_install_journeys.py +++ b/tests/test_install_journeys.py @@ -55,10 +55,8 @@ JOURNEY_KEYS = { # Words each journey's screens show by default, summed over the screens it # stops on, and the most one screen of the default path may show (owner, # 2026-11-09: a beginner lost the important line in text). A path is no -# word: its length is the machine's. The counts from before the Details -# screen took the plan's and the result's notes are in claudedocs/ -# user_research/installer_ux/phase1_measure.md. A change that adds words -# raises a number here and says why. +# word: its length is the machine's. A change that adds words raises a +# number here and says why. JOURNEY_WORDS = { "first_install": 63, "card_with_games": 106, diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index 017a46519..eae7a1ed3 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -14934,7 +14934,7 @@ class GamesEndToEnd(unittest.TestCase): # The most words each screen may show by default, one count per size of # SIZES (owner, 2026-11-09: a beginner lost the important line in text). A # path is no word. The plan, Confirm and the result say what happens, where -# and what next; their counts, notes and commands wait on Details. A list +# and what next, and their counts, notes and commands wait on Details. A list # shows more rows on a taller window. A change that adds words to a screen # raises its numbers here and says why: the result names its failed files # (review of 2026-11-10), what the user acts on.