mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Improve error handling in CoreBackupReaderWriter (#139508)
This commit is contained in:
parent
030a1460de
commit
228a4eb391
@ -14,6 +14,7 @@ from itertools import chain
|
||||
import json
|
||||
from pathlib import Path, PurePath
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
from typing import IO, TYPE_CHECKING, Any, Protocol, TypedDict, cast
|
||||
@ -308,6 +309,12 @@ class DecryptOnDowloadNotSupported(BackupManagerError):
|
||||
_message = "On-the-fly decryption is not supported for this backup."
|
||||
|
||||
|
||||
class BackupManagerExceptionGroup(BackupManagerError, ExceptionGroup):
|
||||
"""Raised when multiple exceptions occur."""
|
||||
|
||||
error_code = "multiple_errors"
|
||||
|
||||
|
||||
class BackupManager:
|
||||
"""Define the format that backup managers can have."""
|
||||
|
||||
@ -1605,10 +1612,24 @@ class CoreBackupReaderWriter(BackupReaderWriter):
|
||||
)
|
||||
finally:
|
||||
# Inform integrations the backup is done
|
||||
# If there's an unhandled exception, we keep it so we can rethrow it in case
|
||||
# the post backup actions also fail.
|
||||
unhandled_exc = sys.exception()
|
||||
try:
|
||||
await manager.async_post_backup_actions()
|
||||
except BackupManagerError as err:
|
||||
raise BackupReaderWriterError(str(err)) from err
|
||||
try:
|
||||
await manager.async_post_backup_actions()
|
||||
except BackupManagerError as err:
|
||||
raise BackupReaderWriterError(str(err)) from err
|
||||
except Exception as err:
|
||||
if not unhandled_exc:
|
||||
raise
|
||||
# If there's an unhandled exception, we wrap both that and the exception
|
||||
# from the post backup actions in an ExceptionGroup so the caller is
|
||||
# aware of both exceptions.
|
||||
raise BackupManagerExceptionGroup(
|
||||
f"Multiple errors when creating backup: {unhandled_exc}, {err}",
|
||||
[unhandled_exc, err],
|
||||
) from None
|
||||
|
||||
def _mkdir_and_generate_backup_contents(
|
||||
self,
|
||||
|
@ -8,6 +8,7 @@ from dataclasses import replace
|
||||
from io import StringIO
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import tarfile
|
||||
from typing import Any
|
||||
from unittest.mock import (
|
||||
@ -35,6 +36,7 @@ from homeassistant.components.backup.agent import BackupAgentError
|
||||
from homeassistant.components.backup.const import DATA_MANAGER
|
||||
from homeassistant.components.backup.manager import (
|
||||
BackupManagerError,
|
||||
BackupManagerExceptionGroup,
|
||||
BackupManagerState,
|
||||
CreateBackupStage,
|
||||
CreateBackupState,
|
||||
@ -1646,34 +1648,60 @@ async def test_exception_platform_pre(hass: HomeAssistant) -> None:
|
||||
assert str(err.value) == "Error during pre-backup: Test exception"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("unhandled_error", "expected_exception", "expected_msg"),
|
||||
[
|
||||
(None, BackupManagerError, "Error during post-backup: Test exception"),
|
||||
(
|
||||
HomeAssistantError("Boom"),
|
||||
BackupManagerExceptionGroup,
|
||||
(
|
||||
"Multiple errors when creating backup: Error during pre-backup: Boom, "
|
||||
"Error during post-backup: Test exception (2 sub-exceptions)"
|
||||
),
|
||||
),
|
||||
(
|
||||
Exception("Boom"),
|
||||
BackupManagerExceptionGroup,
|
||||
(
|
||||
"Multiple errors when creating backup: Error during pre-backup: Boom, "
|
||||
"Error during post-backup: Test exception (2 sub-exceptions)"
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_backup_generation")
|
||||
async def test_exception_platform_post(hass: HomeAssistant) -> None:
|
||||
async def test_exception_platform_post(
|
||||
hass: HomeAssistant,
|
||||
unhandled_error: Exception | None,
|
||||
expected_exception: type[Exception],
|
||||
expected_msg: str,
|
||||
) -> None:
|
||||
"""Test exception in post step."""
|
||||
|
||||
async def _mock_step(hass: HomeAssistant) -> None:
|
||||
raise HomeAssistantError("Test exception")
|
||||
|
||||
remote_agent = mock_backup_agent("remote")
|
||||
await setup_backup_platform(
|
||||
hass,
|
||||
domain="test",
|
||||
platform=Mock(
|
||||
async_pre_backup=AsyncMock(),
|
||||
async_post_backup=_mock_step,
|
||||
# We let the pre_backup fail to test that unhandled errors are not discarded
|
||||
# when post backup fails
|
||||
async_pre_backup=AsyncMock(side_effect=unhandled_error),
|
||||
async_post_backup=AsyncMock(
|
||||
side_effect=HomeAssistantError("Test exception")
|
||||
),
|
||||
async_get_backup_agents=AsyncMock(return_value=[remote_agent]),
|
||||
),
|
||||
)
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
with pytest.raises(BackupManagerError) as err:
|
||||
with pytest.raises(expected_exception, match=re.escape(expected_msg)):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"create",
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert str(err.value) == "Error during post-backup: Test exception"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
|
Loading…
x
Reference in New Issue
Block a user