mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-13 02:26:37 +00:00
fix(update): leave plugins alone when update_core's own rollback fails
update_core returns rollback_failed directly when a partial pull or an update whose health check never started cannot be rolled back. run() only held plugins back for 'verifying', so those devices still got new plugin versions and a display restart on top of a core in an unknown state -- the opposite of what the health-check path does, and of the 3.4.0 changelog (plugins are left alone if the rollback fails). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -414,6 +414,36 @@ class TestUpdateIsVerified:
|
|||||||
assert repo.head() == old
|
assert repo.head() == old
|
||||||
assert 'rolled back' in result['core_message']
|
assert 'rolled back' in result['core_message']
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('how', ['partial_pull', 'check_never_started'])
|
||||||
|
def test_a_failed_rollback_leaves_plugins_and_the_display_alone(self, tmp_path, how):
|
||||||
|
"""update_core returns rollback_failed itself on two paths. The device
|
||||||
|
is then in an unknown state, exactly as when the health check reports
|
||||||
|
rollback_failed, so no plugin updates and no restart may follow."""
|
||||||
|
repo = Repo(tmp_path)
|
||||||
|
repo.publish()
|
||||||
|
store = FakeStore(tmp_path / 'plugins', {'clock': '1.0.0'}, bump={'clock'})
|
||||||
|
pull = real_pull(repo.device)
|
||||||
|
|
||||||
|
def half_failed(**kwargs):
|
||||||
|
pull()
|
||||||
|
return {'status': 'error', 'message': 'Update failed: interrupted'}
|
||||||
|
h = Harness(tmp_path, repo, store=store, pickup=how != 'check_never_started',
|
||||||
|
core_update=half_failed if how == 'partial_pull' else None)
|
||||||
|
real_run = h.updater.run_command
|
||||||
|
|
||||||
|
def reset_fails(args, **kwargs):
|
||||||
|
if args[:3] == ['git', 'reset', '--hard']:
|
||||||
|
return subprocess.CompletedProcess(args, 1, stdout='', stderr='index.lock exists')
|
||||||
|
return real_run(args, **kwargs)
|
||||||
|
h.updater.run_command = reset_fails
|
||||||
|
|
||||||
|
result = h.updater.run()
|
||||||
|
assert result['core_outcome'] == 'rollback_failed'
|
||||||
|
assert store.updated_calls == [], "plugins must not pile onto a core in an unknown state"
|
||||||
|
assert h.restarts == []
|
||||||
|
assert h.state['plugins_pending'] is False and result['plugins_deferred'] is False
|
||||||
|
assert h.state['alert']
|
||||||
|
|
||||||
|
|
||||||
# -- reporting the health check's outcome --------------------------------------
|
# -- reporting the health check's outcome --------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -346,11 +346,16 @@ class AutoUpdater:
|
|||||||
core = {'outcome': 'error', 'message': f'The LEDMatrix update failed unexpectedly: {e}'}
|
core = {'outcome': 'error', 'message': f'The LEDMatrix update failed unexpectedly: {e}'}
|
||||||
|
|
||||||
deferred = core['outcome'] == 'verifying'
|
deferred = core['outcome'] == 'verifying'
|
||||||
updated, failed = ([], []) if deferred else self._update_plugins()
|
# A core whose rollback failed is in an unknown state: as when the
|
||||||
|
# health check reports rollback_failed, plugins are left alone and
|
||||||
|
# nothing is restarted onto it.
|
||||||
|
stranded = core['outcome'] == 'rollback_failed'
|
||||||
|
updated, failed = ([], []) if deferred or stranded else self._update_plugins()
|
||||||
self._store_run(state, core, updated, failed)
|
self._store_run(state, core, updated, failed)
|
||||||
logger.info("Automatic update: core %s (%s); plugins updated=%s failed=%s%s",
|
logger.info("Automatic update: core %s (%s); plugins updated=%s failed=%s%s",
|
||||||
core['outcome'], core['message'], updated, failed,
|
core['outcome'], core['message'], updated, failed,
|
||||||
'; plugins wait for the health check' if deferred else '')
|
'; plugins wait for the health check' if deferred
|
||||||
|
else '; plugins left alone' if stranded else '')
|
||||||
|
|
||||||
# Code restarts belong to the health check; this only covers plugins.
|
# Code restarts belong to the health check; this only covers plugins.
|
||||||
if updated:
|
if updated:
|
||||||
|
|||||||
Reference in New Issue
Block a user