Repository navigation
fix(predbat): don't read db_primary off a None ha_interface in update_time_loop #5137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # ----------------------------------------------------------------------------- | ||
| # Predbat Home Battery System | ||
| # Copyright Trefor Southwell 2026 - All Rights Reserved | ||
| # This application maybe used for personal use only and not for commercial use | ||
| # ----------------------------------------------------------------------------- | ||
| # fmt off | ||
| # pylint: disable=consider-using-f-string | ||
| # pylint: disable=line-too-long | ||
| # pylint: disable=attribute-defined-outside-init | ||
|
|
||
|
|
||
| def _run_guard(my_predbat, loop): | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: |
||
| """Run a run-loop callback that is expected to refuse to run, returning the exception it raised.""" | ||
| try: | ||
| loop(None) | ||
| except Exception as e: | ||
| return e | ||
| return None | ||
|
|
||
|
|
||
| def test_time_loop_guard(my_predbat): | ||
| """ | ||
| Verify the run-loop HA interface guards report the missing interface and set fatal_error when | ||
| ha_interface is None. update_time_loop's log line used to read ha_interface.db_primary | ||
| unconditionally, so the one case the guard exists for raised AttributeError before | ||
| fatal_error could be set, leaving Predbat running with no way to stop it (#5135). | ||
| """ | ||
| print("*** Running test: Run-loop guard with no HA interface") | ||
| failed = 0 | ||
|
|
||
| for loop_name in ("update_time_loop", "run_time_loop"): | ||
| # Each test gets its own PredBat, so clearing the interface here cannot reach another test | ||
| my_predbat.ha_interface = None | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coverage gap: this test only drives the |
||
| my_predbat.fatal_error = False | ||
|
|
||
| error = _run_guard(my_predbat, getattr(my_predbat, loop_name)) | ||
|
|
||
| if isinstance(error, AttributeError): | ||
| print("ERROR: {} raised AttributeError ({}) rather than reporting the missing HA interface".format(loop_name, error)) | ||
| failed = 1 | ||
| elif error is None: | ||
| print("ERROR: {} did not raise when ha_interface is None".format(loop_name)) | ||
| failed = 1 | ||
| elif "HA interface not active" not in str(error): | ||
| print("ERROR: {} raised an unexpected exception: {}".format(loop_name, error)) | ||
| failed = 1 | ||
| else: | ||
| print("OK: {} reported the missing HA interface".format(loop_name)) | ||
|
|
||
| if not my_predbat.fatal_error: | ||
| print("ERROR: {} did not set fatal_error when ha_interface is None".format(loop_name)) | ||
| failed = 1 | ||
| else: | ||
| print("OK: {} set fatal_error when ha_interface is None".format(loop_name)) | ||
|
|
||
| return failed | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fix now leaves three hand-maintained variants of the same guard semantics: this two-branch log here,
run_time_loop's shorter copy at predbat.py:2175 (different wording, no db_primary), and the None-branch message chosen to match it. The PR body documents why it didn't dedupe, and the new test pins both copies so divergence is caught — but any future change to the guard's contract (new attribute in the message, different flag, changed exception) still has to be made in two places. If another guard tweak lands, consider extracting one_check_ha_interface()helper returning a reason string that both loops log and raise from.