From f89755e0293719a3495829aa8696d5ac8208c7b1 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sun, 9 Feb 2025 23:44:55 +0000 Subject: [PATCH] Ensure errors logged --- pyheos/connection.py | 10 +++++++ .../player.get_now_playing_media_failed.json | 1 + tests/test_heos.py | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 tests/fixtures/player.get_now_playing_media_failed.json diff --git a/pyheos/connection.py b/pyheos/connection.py index 4ac6646..6f0692e 100644 --- a/pyheos/connection.py +++ b/pyheos/connection.py @@ -89,6 +89,15 @@ async def _on_command_error(self, error: CommandFailedError) -> None: for callback in self._on_command_error_callbacks: await callback(error) + def _log_callback_exception(self, future: asyncio.Future[Any]) -> None: + """Log uncaught exception that occurs in a callback.""" + if not future.cancelled() and future.exception(): + _LOGGER.exception( + "Unexpected exception in task: %s", + future, + exc_info=future.exception(), + ) + def _register_task( self, future: Coroutine[Any, Any, None], name: str | None = None ) -> None: @@ -96,6 +105,7 @@ def _register_task( task: asyncio.Task[None] = asyncio.create_task(future, name=name) self._running_tasks.add(task) task.add_done_callback(self._running_tasks.discard) + task.add_done_callback(self._log_callback_exception) async def _reset(self) -> None: """Reset the state of the connection.""" diff --git a/tests/fixtures/player.get_now_playing_media_failed.json b/tests/fixtures/player.get_now_playing_media_failed.json new file mode 100644 index 0000000..6f0c160 --- /dev/null +++ b/tests/fixtures/player.get_now_playing_media_failed.json @@ -0,0 +1 @@ +{"heos": {"command": "player/get_now_playing_media", "result": "fail", "message": "eid=2&text=ID Not Valid&pid=1"}} \ No newline at end of file diff --git a/tests/test_heos.py b/tests/test_heos.py index f268b62..93fad0d 100644 --- a/tests/test_heos.py +++ b/tests/test_heos.py @@ -1518,3 +1518,29 @@ async def test_unrecognized_event_logs( await heos.dispatcher.wait_all() assert "Unrecognized event: " in caplog.text + + +@calls_player_commands() +async def test_uncaught_error_in_event_callback_logs( + mock_device: MockHeosDevice, heos: Heos, caplog: pytest.LogCaptureFixture +) -> None: + """Test unexpected exception during event callback execution logs.""" + await heos.get_players() + player = heos.players[1] + + # Register command that results in an exception + command = mock_device.register( + c.COMMAND_GET_NOW_PLAYING_MEDIA, + None, + "player.get_now_playing_media_failed", + replace=True, + ) + # Write event through mock device + await mock_device.write_event( + "event.player_now_playing_changed", {"player_id": player.player_id} + ) + + while "Unexpected exception in task:" not in caplog.text: + await asyncio.sleep(0.1) + + command.assert_called()