Skip to content

fix(gui): JobRunner surfaces FailedToStart + UTF-8/unbuffered child env (#117, #118)#134

Open
prekabreki wants to merge 1 commit into
mainfrom
fix/117-118-jobrunner-hardening
Open

fix(gui): JobRunner surfaces FailedToStart + UTF-8/unbuffered child env (#117, #118)#134
prekabreki wants to merge 1 commit into
mainfrom
fix/117-118-jobrunner-hardening

Conversation

@prekabreki

Copy link
Copy Markdown
Owner

What & why

Two GUI audit findings against JobRunner (the hours-long pipeline console runner in src/deciwaves/gui/jobs.py). CaptureRunner (setup/doctor) already handled both correctly; this brings JobRunner in line and dedupes the shared piece.

#117 (audit H2) — FailedToStart wedged the UI

JobRunner.start() fired started.emit() unconditionally and never connected errorOccurred. On QProcess::FailedToStart (e.g. a missing/mistyped program), QProcess fires only errorOccurred, never finished — so the finish handler never ran, the chip stayed "running" forever, the poll timer looped, and controls stayed disabled.

Fix: connect errorOccurred; on FailedToStart clear _proc and emit finished(-1), mirroring CaptureRunner. Guarded on _proc is not None so a non-start error that also fires finished() can't emit finished twice.

#118 (audit H3) — no UTF-8/unbuffered child env

JobRunner decoded child stdout as UTF-8 but set no process environment, so on Windows a Python child writing to a pipe fell back to the ANSI code page (mojibake for non-ASCII paths / issue #59's em-dash) and block-buffered its output.

Fix: factored CaptureRunner's local _utf8_environment() into a small shared module deciwaves.gui.proc_env.utf8_environment() (matching this package's cli_command.py shared-helper idiom), added PYTHONUNBUFFERED=1, and used it in both runners.

Tests

Added to tests/gui/test_jobs.py:

  • test_failed_to_start_finishes_instead_of_hanging — starting a non-existent program emits finished(-1) rather than hanging.
  • test_child_env_forces_utf8_unbuffered — the shared helper sets PYTHONUTF8=1 / PYTHONIOENCODING=utf-8 / PYTHONUNBUFFERED=1, and JobRunner actually passes all three through to its child.

ruff check src tests clean; full pytest -q green (1129 passed, 33 skipped).

Did not touch shell.py (owned by another task).

Closes #117
Closes #118

🤖 Generated with Claude Code

…child env (#117, #118)

JobRunner fired started.emit() unconditionally but never connected
errorOccurred, so a QProcess::FailedToStart -- which fires ONLY
errorOccurred, never finished() -- left the chip "running" forever, the
poll timer looping and the controls disabled (#117). Connect
errorOccurred and, on FailedToStart, clear _proc and emit finished(-1),
mirroring CaptureRunner. Guarded on `_proc is not None` so a non-start
error that also fires finished() can't emit finished twice.

JobRunner also decoded the child's stdout as UTF-8 but set no process
environment, so on Windows a Python child writing to a pipe used the ANSI
code page (mojibake in the hours-long console) and block-buffered its
output (#118). Factor CaptureRunner's local _utf8_environment() into a
shared deciwaves.gui.proc_env.utf8_environment(), add PYTHONUNBUFFERED=1,
and use it in BOTH runners.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant