mirror of
https://github.com/esphome/esphome.git
synced 2026-09-27 15:00:24 +00:00
Rebuild quietly on an absent build_info.json, log a failed unlink
FileNotFoundError is an OSError, so a merely missing file logged a 'damaged' warning; it now takes its own silent-stale branch, which also makes missing_ok on the unlink unnecessary. An unlink failure on a truly damaged file is logged by cause, since the kept copy makes the later write fail with a misattributed error.
This commit is contained in:
+11
-3
@@ -1,4 +1,3 @@
|
||||
from contextlib import suppress
|
||||
import importlib
|
||||
import json
|
||||
import logging
|
||||
@@ -344,12 +343,21 @@ def copy_src_tree():
|
||||
):
|
||||
# Non-object JSON is stale like every other damage case
|
||||
sources_changed = True
|
||||
except FileNotFoundError:
|
||||
# An absent build_info.json is stale, not damaged; rebuild quietly
|
||||
sources_changed = True
|
||||
except (ValueError, OSError) as err:
|
||||
# ValueError covers both JSONDecodeError and UnicodeDecodeError;
|
||||
# unlink so the regenerating write never re-reads the damage
|
||||
_LOGGER.warning("Replacing damaged build_info.json: %s", err)
|
||||
with suppress(OSError):
|
||||
build_info_json_path.unlink(missing_ok=True)
|
||||
try:
|
||||
build_info_json_path.unlink()
|
||||
except OSError as unlink_err:
|
||||
# The later write re-reads the file, so a kept damaged copy
|
||||
# fails again with a misattributed error; name the real cause
|
||||
_LOGGER.warning(
|
||||
"Could not remove damaged build_info.json: %s", unlink_err
|
||||
)
|
||||
sources_changed = True
|
||||
|
||||
# Write build_info header and JSON metadata
|
||||
|
||||
@@ -2143,6 +2143,56 @@ def test_copy_src_tree_regenerates_damaged_build_info(
|
||||
assert new_json["config_hash"] == 0xDEADBEEF
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
@patch("esphome.writer.iter_components")
|
||||
@patch("esphome.writer.walk_files")
|
||||
def test_copy_src_tree_missing_build_info_rebuilds_quietly(
|
||||
mock_walk_files: MagicMock,
|
||||
mock_iter_components: MagicMock,
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""An absent build_info.json regenerates without claiming damage."""
|
||||
build_info_json_path = _setup_build_info_mocks(
|
||||
mock_core, mock_iter_components, mock_walk_files, tmp_path
|
||||
)
|
||||
_run_copy_src_tree()
|
||||
build_info_json_path.unlink()
|
||||
_run_copy_src_tree()
|
||||
assert json.loads(build_info_json_path.read_text())["config_hash"] == 0xDEADBEEF
|
||||
assert "damaged" not in caplog.text
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
@patch("esphome.writer.iter_components")
|
||||
@patch("esphome.writer.walk_files")
|
||||
def test_copy_src_tree_unremovable_damaged_build_info_is_logged(
|
||||
mock_walk_files: MagicMock,
|
||||
mock_iter_components: MagicMock,
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""A failed unlink of the damaged file names the real cause."""
|
||||
build_info_json_path = _setup_build_info_mocks(
|
||||
mock_core, mock_iter_components, mock_walk_files, tmp_path
|
||||
)
|
||||
_run_copy_src_tree()
|
||||
build_info_json_path.write_text("invalid json {{{")
|
||||
real_unlink = Path.unlink
|
||||
|
||||
def fail_on_build_info(self: Path, missing_ok: bool = False) -> None:
|
||||
if self.name == "build_info.json":
|
||||
raise OSError("simulated EACCES")
|
||||
real_unlink(self, missing_ok=missing_ok)
|
||||
|
||||
with patch.object(Path, "unlink", fail_on_build_info):
|
||||
_run_copy_src_tree()
|
||||
assert "Could not remove damaged build_info.json" in caplog.text
|
||||
assert json.loads(build_info_json_path.read_text())["config_hash"] == 0xDEADBEEF
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
@patch("esphome.writer.iter_components")
|
||||
@patch("esphome.writer.walk_files")
|
||||
|
||||
Reference in New Issue
Block a user