1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 00:31:58 +00:00

[core] Fix clean all windows (#12217)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Jonathan Swoboda
2025-12-01 12:40:56 -05:00
committed by GitHub
parent 6d336676a2
commit 2b7695ba3f
2 changed files with 129 additions and 9 deletions

View File

@@ -1,7 +1,9 @@
"""Test writer module functionality."""
from collections.abc import Callable
import os
from pathlib import Path
import stat
from typing import Any
from unittest.mock import MagicMock, patch
@@ -15,6 +17,7 @@ from esphome.writer import (
CPP_INCLUDE_BEGIN,
CPP_INCLUDE_END,
GITIGNORE_CONTENT,
clean_all,
clean_build,
clean_cmake_cache,
storage_should_clean,
@@ -1062,3 +1065,103 @@ def test_clean_all_preserves_json_files(
# Verify logging mentions cleaning
assert "Cleaning" in caplog.text
assert str(build_dir) in caplog.text
@patch("esphome.writer.CORE")
def test_clean_build_handles_readonly_files(
mock_core: MagicMock,
tmp_path: Path,
) -> None:
"""Test clean_build handles read-only files (e.g., git pack files on Windows)."""
# Create directory structure with read-only files
pioenvs_dir = tmp_path / ".pioenvs"
pioenvs_dir.mkdir()
git_dir = pioenvs_dir / ".git" / "objects" / "pack"
git_dir.mkdir(parents=True)
# Create a read-only file (simulating git pack files on Windows)
readonly_file = git_dir / "pack-abc123.pack"
readonly_file.write_text("pack data")
os.chmod(readonly_file, stat.S_IRUSR) # Read-only
# Setup mocks
mock_core.relative_pioenvs_path.return_value = pioenvs_dir
mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps"
mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock"
# Verify file is read-only
assert not os.access(readonly_file, os.W_OK)
# Call the function - should not crash
clean_build()
# Verify directory was removed despite read-only files
assert not pioenvs_dir.exists()
@patch("esphome.writer.CORE")
def test_clean_all_handles_readonly_files(
mock_core: MagicMock,
tmp_path: Path,
) -> None:
"""Test clean_all handles read-only files."""
# Create config directory
config_dir = tmp_path / "config"
config_dir.mkdir()
build_dir = config_dir / ".esphome"
build_dir.mkdir()
# Create a subdirectory with read-only files
subdir = build_dir / "subdir"
subdir.mkdir()
readonly_file = subdir / "readonly.txt"
readonly_file.write_text("content")
os.chmod(readonly_file, stat.S_IRUSR) # Read-only
# Verify file is read-only
assert not os.access(readonly_file, os.W_OK)
# Call the function - should not crash
clean_all([str(config_dir)])
# Verify directory was removed despite read-only files
assert not subdir.exists()
assert build_dir.exists() # .esphome dir itself is preserved
@patch("esphome.writer.CORE")
def test_clean_build_reraises_for_other_errors(
mock_core: MagicMock,
tmp_path: Path,
) -> None:
"""Test clean_build re-raises errors that are not read-only permission issues."""
# Create directory structure with a read-only subdirectory
# This prevents file deletion and triggers the error handler
pioenvs_dir = tmp_path / ".pioenvs"
pioenvs_dir.mkdir()
subdir = pioenvs_dir / "subdir"
subdir.mkdir()
test_file = subdir / "test.txt"
test_file.write_text("content")
# Make subdir read-only so files inside can't be deleted
os.chmod(subdir, stat.S_IRUSR | stat.S_IXUSR)
# Setup mocks
mock_core.relative_pioenvs_path.return_value = pioenvs_dir
mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps"
mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock"
try:
# Mock os.access in writer module to return True (writable)
# This simulates a case where the error is NOT due to read-only permissions
# so the error handler should re-raise instead of trying to fix permissions
with (
patch("esphome.writer.os.access", return_value=True),
pytest.raises(PermissionError),
):
clean_build()
finally:
# Cleanup - restore write permission so tmp_path cleanup works
os.chmod(subdir, stat.S_IRWXU)