Repository navigation
A heap of tests, and the bugs they found - #354
Merged
Merged
Conversation
- PortMasterV3.update() emptied the port list before checking whether it had already updated this run, so "Update ports" right after the startup update showed nothing until PortMaster restarted. - When ports.json can't be fetched, the cached ports are loaded instead of leaving the list empty for the session. - net.fetch() caught only ConnectionError: a read timeout (a slow first-boot network) went up through HarbourMaster's startup. It now catches any requests error, and fetch_json() returns None for a body that isn't JSON (a captive portal's page). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
…they found New tests: test_cli_commands.py (every command in-process, ConsoleCallback, fifo_control, main's flags), test_hm_info.py (load_info against a fake PortMaster-Info tree) and test_hm_ports.py (load_ports' repairs, renamed scripts, broken/unknown ports, attributes, requirements, listing, sorting, images). Fixed: - `harbourmaster ports.json` crashed on any port that isn't installed (KeyError: 'status'). - `harbourmaster help device_info` crashed: it had no docstring. - A corrupt runtimes.json crashed every start. - A PortMaster-Info file over 1MB that failed validation was deleted and then copied over the good one: FileNotFoundError. - A port.json without a name took the first letter of its port's name from ports_info.json (whose items are mostly strings), and missed scripts whose names aren't lower case. - An unsigned renamed script found by its md5 got its names swapped, so its port showed as broken. - A signed renamed script of a port without a port.json was also listed as an unknown port. - A port.json made for such a port left out its own 'port.json' entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
…ck, and the bugs they found New tests: test_platform_hooks.py (every CFW's hooks against a fake device root), test_hm_runtimes.py (list_runtimes, check_runtime, featured ports) and test_update_check.py (the self-update check, broken config files). Fixed: - A broken config.json (cut off by a power off while saving) stopped PortMaster starting: HarbourMaster, the update check and the GUI now start from the defaults. themes.json likewise, and one missing keys no longer takes the theme list down. New harbourmaster.json_load_dict() for these; json_safe_load() also survives a file that isn't text. - A version.json without the channel's version/url/md5 crashed the update check. - muOS/TrimUI catalogue errors failed the port install: the "a broken gamelist never fails an install" guard now covers them too. - muOS: a catalogue without its box/preview/text folders failed every install with a gameinfo.xml; they're made inside an existing catalogue. - RetroDECK without a retrodeck.json or .cfg failed every such install too. - list_runtimes() saved config.json on every call, and never noticed a runtime copied in after it was listed as Not Installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
…o, config, hardware
Uninstalling deletes each name in the port's port.json, and a `..` in its items
(or a `../x` or absolute path in its files) deleted outside the ports folder:
`..` took the folder holding it, on muOS the SD card. Install was safe (it takes
the items from the zip), but a hand-edited or broken port.json on the device
wasn't. port_info_load() now drops any item that isn't a plain relative name
(new info.is_safe_port_item()), and uninstall skips anything else whatever
port.json says.
Also fixed:
- runtime_nicename() crashed on runtimes not named the usual way
("mono.squashfs" in a hand-written port.json).
- datetime_compare() crashed on a broken or timezone-aware time (a source's
last_checked): it now counts as long ago, so the check runs.
- cprint() crashed when stdout isn't a real file (in_terminal()).
- nice_size() divided once too often past TB.
- items_opt that isn't a list, and non-string items, crashed port_info_load().
New tests: test_util_edges.py (text, files, net, port_info_load/merge, safe
uninstall in a sandbox), test_hardware_edges.py (the quick CFW/device probe
against a fake root, get_info details) and more detect_paths cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
test_gui_scenes.py: the main menu (every entry, exit, the Konami code), ports lists (moving, selecting, empty lists, no HarbourMaster), filters (genres, attributes, porters, sorting and reversing, clearing), featured ports (categories, lists), every option (toggles, release channels, restore, GCD modes, gamelist, TrimUI's port location, the secret options), the language list and the on screen keyboard. Also: - Options' "Delete PortMaster Config" crashed when the config folder was already gone; it and "Delete PortMaster Runtimes" use the HarbourMaster's folders (the same ones on a device). - LanguageScene: A on a blank row can't crash (the d-pad skips them, a theme's list might not). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
test_gui_screens.py: the startup warnings for unsupported firmware and low RAM (against a fake device root), the disclaimer and warning screens (waiting, accepting, quitting), which first screen TempMenuScene picks, port info (install, uninstall, next/previous port, the popup and its links), the runtime manager (details, check, download all, uninstall, ports using a runtime) and the theme and colour scheme lists. ThemeSchemeScene: A on a blank row can't crash, as in LanguageScene. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
- `harbourmaster --no-log` with no PortMaster folder at import raised NameError (LOG_FILE_HANDLE was never set), which logger.catch swallowed: the command never ran. - pugwash: the same case called logger.remove(None), which removes every handler, stderr's too. - EventManager.buttons was seeded with SDL keycodes and axis names instead of button names, so SELECT, SCRN and the stick directions were missing until first pressed. New tests: test_main.py (flags, logging, offline detection, fifo_control, the update check, the reboot file, the frontend refresh), test_events.py (keyboard, controller buttons, sticks and triggers, key repeat, hotplug, the xbox fix, pushed onto SDL's real event queue). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
hex_color_decode() raised ValueError on a colour that isn't hex ("#ggg" in a
custom theme); it is white now, as a bad length already was. It also handed
out the same cached list to every caller, it's a tuple now.
test_sdl_helpers.py: Rect maths (fit, clip, inflate, edges, scaling), theme
areas (NamedRects: fractions, pixels, negative offsets, errors), colours, dict
merging, autoscroll, timers and animations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
- A fifo command that raised ended fifo_control with the done file on WAIT, so the shell script waited for good. Each command now answers FAIL instead and the loop carries on. - register_clear_info unpacked three values from two: it always raised. - progress put its "data"/"%" format into the total: the format was never used, and with a non-numeric total it crashed comparing an int and a str. - do_fifo_control checked the length of its second path, not of its arguments: one argument raised IndexError. test_fifo_commands.py: registers, messages, progress, message_box and selection_list option parsing, and the do_fifo_control loop driven through a real named pipe, as PortMasterDialog.txt drives it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
- The selected theme's theme.json cut off or hand-edited into bad JSON: PortMaster crashed on every start, and reinstalling didn't help (themes survive it). It now falls back to the default theme, as for a missing one. - A theme that is valid JSON but fails to load (eg: a typo in an element's area) likewise: ThemeEngine.gui_init() uses the default theme instead. A forced theme (the theme editor) still raises, so the error shows. - Any installed theme with a broken theme.json took the theme list down; it's left out now. test_theme_engine.py: requirement tags in theme keys, theme_update/merge, the installed and downloadable themes list (updates, images, order), the current theme's fallbacks, forced themes and schemes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
…lt language The tests came from the beta branch, where these apply at once (reload_theme / reload_language) and there are port links and a theme editor. Here they ask, save, and quit to apply at the next start, so the tests check that instead. The theme fallback is tested with Theme.gui_init stood in for, which needs no reloading. Choosing the default language crashed (KeyError) when the language came from the system rather than the config: del cfg_data['language'] is a pop now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
Even if it was fetched when PortMaster started: a release may have been made since. It used to stop at "Up to date already". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
It checked exists() then is_file(): if harbourmaster removed the stale file in between, the test took the missing file for the pipe, and its open() made a plain file there, so mkfifo failed with FileExistsError (seen on Python 3.13 in CI). One stat() and S_ISFIFO instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxABmR1rUUgi4iicVLZchY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds tests across harbourmaster and pugwash: 882 → 1,601 tests, coverage 61% → 86%. Every bug below has a test that fails without its fix. The suite passes on Python 3.7 and 3.11, and the errors-only lint is clean.
Includes
fix-source-update(its own PR), which some tests depend on.Data loss
..in an installedport.json'sitems(or../xor an absolute path in itsfiles) was deleted along with the port, and..is the SD card root on muOS. Install was safe because it buildsitemsfrom the zip.port_info_load()now drops unsafe item names (info.is_safe_port_item()), and uninstall skips them whateverport.jsonsays.PortMaster wouldn't start
config.json, for example after a power cut while saving: HarbourMaster, the update check and the GUI now start from defaults (newharbourmaster.json_load_dict()). Same forthemes.json.runtimes.json.version.jsonmissing the channel's version, url or md5.Installs failing because of the frontend
box/preview/textfolders. They're created inside an existing catalogue.retrodeck.jsonnorretrodeck.cfg.fifo_control
WAIT, so the shell script waited forever. Each command now answersFAILand the loop carries on.register_clear_infoalways raised.data/%format argument ofprogresswent into the total instead.do_fifo_controlraisedIndexError.Ports and runtimes
port.jsonwith no name took the first letter of its port's name (ports_info.json's items are mostly strings).list_runtimes()savedconfig.jsonon every call, and never noticed a runtime copied in after it was listed as Not Installed.FileNotFoundError.Smaller fixes
harbourmaster ports.jsoncrashed on ports that aren't installed.harbourmaster help device_infocrashed because the command had no docstring.--no-logwithout a log file: the CLI silently didn't run the command, and pugwash removed every log handler.#ggg)EventManager.buttonswas missing SELECT, SCRN and the stick directions until first pressed.New test files
test_cli_commands,test_hm_info,test_hm_ports,test_hm_runtimestest_platform_hooks: every CFW's hooks, against a fake device roottest_update_check,test_util_edges,test_hardware_edgestest_gui_scenesandtest_gui_screens: the menus driven a button at a timetest_main,test_events: real SDL eventstest_sdl_helpers,test_fifo_commands: through a real named pipetest_theme_engineThe tests that use absolute device paths point them into a temp folder, because this suite also runs on devices as root.