mirror of
https://github.com/esphome/esphome.git
synced 2025-09-14 17:22:20 +01:00
Add additional coverage for util and writer (#10683)
This commit is contained in:
@@ -141,3 +141,170 @@ def test_list_yaml_files_mixed_extensions(tmp_path: Path) -> None:
|
||||
str(yaml_file),
|
||||
str(yml_file),
|
||||
}
|
||||
|
||||
|
||||
def test_list_yaml_files_does_not_recurse_into_subdirectories(tmp_path: Path) -> None:
|
||||
"""Test that list_yaml_files only finds files in specified directory, not subdirectories."""
|
||||
# Create directory structure with YAML files at different depths
|
||||
root = tmp_path / "configs"
|
||||
root.mkdir()
|
||||
|
||||
# Create YAML files in the root directory
|
||||
(root / "config1.yaml").write_text("test: 1")
|
||||
(root / "config2.yml").write_text("test: 2")
|
||||
(root / "device.yaml").write_text("test: device")
|
||||
|
||||
# Create subdirectory with YAML files (should NOT be found)
|
||||
subdir = root / "subdir"
|
||||
subdir.mkdir()
|
||||
(subdir / "nested1.yaml").write_text("test: nested1")
|
||||
(subdir / "nested2.yml").write_text("test: nested2")
|
||||
|
||||
# Create deeper subdirectory (should NOT be found)
|
||||
deep_subdir = subdir / "deeper"
|
||||
deep_subdir.mkdir()
|
||||
(deep_subdir / "very_nested.yaml").write_text("test: very_nested")
|
||||
|
||||
# Test listing files from the root directory
|
||||
result = util.list_yaml_files([str(root)])
|
||||
|
||||
# Should only find the 3 files in root, not the 3 in subdirectories
|
||||
assert len(result) == 3
|
||||
|
||||
# Check that only root-level files are found
|
||||
assert str(root / "config1.yaml") in result
|
||||
assert str(root / "config2.yml") in result
|
||||
assert str(root / "device.yaml") in result
|
||||
|
||||
# Ensure nested files are NOT found
|
||||
for r in result:
|
||||
assert "subdir" not in r
|
||||
assert "deeper" not in r
|
||||
assert "nested1.yaml" not in r
|
||||
assert "nested2.yml" not in r
|
||||
assert "very_nested.yaml" not in r
|
||||
|
||||
|
||||
def test_list_yaml_files_excludes_secrets(tmp_path: Path) -> None:
|
||||
"""Test that secrets.yaml and secrets.yml are excluded."""
|
||||
root = tmp_path / "configs"
|
||||
root.mkdir()
|
||||
|
||||
# Create various YAML files including secrets
|
||||
(root / "config.yaml").write_text("test: config")
|
||||
(root / "secrets.yaml").write_text("wifi_password: secret123")
|
||||
(root / "secrets.yml").write_text("api_key: secret456")
|
||||
(root / "device.yaml").write_text("test: device")
|
||||
|
||||
result = util.list_yaml_files([str(root)])
|
||||
|
||||
# Should find 2 files (config.yaml and device.yaml), not secrets
|
||||
assert len(result) == 2
|
||||
assert str(root / "config.yaml") in result
|
||||
assert str(root / "device.yaml") in result
|
||||
assert str(root / "secrets.yaml") not in result
|
||||
assert str(root / "secrets.yml") not in result
|
||||
|
||||
|
||||
def test_list_yaml_files_excludes_hidden_files(tmp_path: Path) -> None:
|
||||
"""Test that hidden files (starting with .) are excluded."""
|
||||
root = tmp_path / "configs"
|
||||
root.mkdir()
|
||||
|
||||
# Create regular and hidden YAML files
|
||||
(root / "config.yaml").write_text("test: config")
|
||||
(root / ".hidden.yaml").write_text("test: hidden")
|
||||
(root / ".backup.yml").write_text("test: backup")
|
||||
(root / "device.yaml").write_text("test: device")
|
||||
|
||||
result = util.list_yaml_files([str(root)])
|
||||
|
||||
# Should find only non-hidden files
|
||||
assert len(result) == 2
|
||||
assert str(root / "config.yaml") in result
|
||||
assert str(root / "device.yaml") in result
|
||||
assert str(root / ".hidden.yaml") not in result
|
||||
assert str(root / ".backup.yml") not in result
|
||||
|
||||
|
||||
def test_filter_yaml_files_basic() -> None:
|
||||
"""Test filter_yaml_files function."""
|
||||
files = [
|
||||
"/path/to/config.yaml",
|
||||
"/path/to/device.yml",
|
||||
"/path/to/readme.txt",
|
||||
"/path/to/script.py",
|
||||
"/path/to/data.json",
|
||||
"/path/to/another.yaml",
|
||||
]
|
||||
|
||||
result = util.filter_yaml_files(files)
|
||||
|
||||
assert len(result) == 3
|
||||
assert "/path/to/config.yaml" in result
|
||||
assert "/path/to/device.yml" in result
|
||||
assert "/path/to/another.yaml" in result
|
||||
assert "/path/to/readme.txt" not in result
|
||||
assert "/path/to/script.py" not in result
|
||||
assert "/path/to/data.json" not in result
|
||||
|
||||
|
||||
def test_filter_yaml_files_excludes_secrets() -> None:
|
||||
"""Test that filter_yaml_files excludes secrets files."""
|
||||
files = [
|
||||
"/path/to/config.yaml",
|
||||
"/path/to/secrets.yaml",
|
||||
"/path/to/secrets.yml",
|
||||
"/path/to/device.yaml",
|
||||
"/some/dir/secrets.yaml",
|
||||
]
|
||||
|
||||
result = util.filter_yaml_files(files)
|
||||
|
||||
assert len(result) == 2
|
||||
assert "/path/to/config.yaml" in result
|
||||
assert "/path/to/device.yaml" in result
|
||||
assert "/path/to/secrets.yaml" not in result
|
||||
assert "/path/to/secrets.yml" not in result
|
||||
assert "/some/dir/secrets.yaml" not in result
|
||||
|
||||
|
||||
def test_filter_yaml_files_excludes_hidden() -> None:
|
||||
"""Test that filter_yaml_files excludes hidden files."""
|
||||
files = [
|
||||
"/path/to/config.yaml",
|
||||
"/path/to/.hidden.yaml",
|
||||
"/path/to/.backup.yml",
|
||||
"/path/to/device.yaml",
|
||||
"/some/dir/.config.yaml",
|
||||
]
|
||||
|
||||
result = util.filter_yaml_files(files)
|
||||
|
||||
assert len(result) == 2
|
||||
assert "/path/to/config.yaml" in result
|
||||
assert "/path/to/device.yaml" in result
|
||||
assert "/path/to/.hidden.yaml" not in result
|
||||
assert "/path/to/.backup.yml" not in result
|
||||
assert "/some/dir/.config.yaml" not in result
|
||||
|
||||
|
||||
def test_filter_yaml_files_case_sensitive() -> None:
|
||||
"""Test that filter_yaml_files is case-sensitive for extensions."""
|
||||
files = [
|
||||
"/path/to/config.yaml",
|
||||
"/path/to/config.YAML",
|
||||
"/path/to/config.YML",
|
||||
"/path/to/config.Yaml",
|
||||
"/path/to/config.yml",
|
||||
]
|
||||
|
||||
result = util.filter_yaml_files(files)
|
||||
|
||||
# Should only match lowercase .yaml and .yml
|
||||
assert len(result) == 2
|
||||
assert "/path/to/config.yaml" in result
|
||||
assert "/path/to/config.yml" in result
|
||||
assert "/path/to/config.YAML" not in result
|
||||
assert "/path/to/config.YML" not in result
|
||||
assert "/path/to/config.Yaml" not in result
|
||||
|
@@ -1,13 +1,34 @@
|
||||
"""Test writer module functionality."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.storage_json import StorageJSON
|
||||
from esphome.writer import storage_should_clean, update_storage_json
|
||||
from esphome.writer import (
|
||||
CPP_AUTO_GENERATE_BEGIN,
|
||||
CPP_AUTO_GENERATE_END,
|
||||
CPP_INCLUDE_BEGIN,
|
||||
CPP_INCLUDE_END,
|
||||
GITIGNORE_CONTENT,
|
||||
clean_build,
|
||||
clean_cmake_cache,
|
||||
storage_should_clean,
|
||||
update_storage_json,
|
||||
write_cpp,
|
||||
write_gitignore,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_copy_src_tree():
|
||||
"""Mock copy_src_tree to avoid side effects during tests."""
|
||||
with patch("esphome.writer.copy_src_tree"):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -218,3 +239,396 @@ def test_update_storage_json_logging_components_removed(
|
||||
|
||||
# Verify save was called
|
||||
new_storage.save.assert_called_once_with("/test/path")
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_clean_cmake_cache(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test clean_cmake_cache removes CMakeCache.txt file."""
|
||||
# Create directory structure
|
||||
pioenvs_dir = tmp_path / ".pioenvs"
|
||||
pioenvs_dir.mkdir()
|
||||
device_dir = pioenvs_dir / "test_device"
|
||||
device_dir.mkdir()
|
||||
cmake_cache_file = device_dir / "CMakeCache.txt"
|
||||
cmake_cache_file.write_text("# CMake cache file")
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_pioenvs_path.side_effect = [
|
||||
str(pioenvs_dir), # First call for directory check
|
||||
str(cmake_cache_file), # Second call for file path
|
||||
]
|
||||
mock_core.name = "test_device"
|
||||
|
||||
# Verify file exists before
|
||||
assert cmake_cache_file.exists()
|
||||
|
||||
# Call the function
|
||||
with caplog.at_level("INFO"):
|
||||
clean_cmake_cache()
|
||||
|
||||
# Verify file was removed
|
||||
assert not cmake_cache_file.exists()
|
||||
|
||||
# Verify logging
|
||||
assert "Deleting" in caplog.text
|
||||
assert "CMakeCache.txt" in caplog.text
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_clean_cmake_cache_no_pioenvs_dir(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test clean_cmake_cache when pioenvs directory doesn't exist."""
|
||||
# Setup non-existent directory path
|
||||
pioenvs_dir = tmp_path / ".pioenvs"
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_pioenvs_path.return_value = str(pioenvs_dir)
|
||||
|
||||
# Verify directory doesn't exist
|
||||
assert not pioenvs_dir.exists()
|
||||
|
||||
# Call the function - should not crash
|
||||
clean_cmake_cache()
|
||||
|
||||
# Verify directory still doesn't exist
|
||||
assert not pioenvs_dir.exists()
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_clean_cmake_cache_no_cmake_file(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test clean_cmake_cache when CMakeCache.txt doesn't exist."""
|
||||
# Create directory structure without CMakeCache.txt
|
||||
pioenvs_dir = tmp_path / ".pioenvs"
|
||||
pioenvs_dir.mkdir()
|
||||
device_dir = pioenvs_dir / "test_device"
|
||||
device_dir.mkdir()
|
||||
cmake_cache_file = device_dir / "CMakeCache.txt"
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_pioenvs_path.side_effect = [
|
||||
str(pioenvs_dir), # First call for directory check
|
||||
str(cmake_cache_file), # Second call for file path
|
||||
]
|
||||
mock_core.name = "test_device"
|
||||
|
||||
# Verify file doesn't exist
|
||||
assert not cmake_cache_file.exists()
|
||||
|
||||
# Call the function - should not crash
|
||||
clean_cmake_cache()
|
||||
|
||||
# Verify file still doesn't exist
|
||||
assert not cmake_cache_file.exists()
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_clean_build(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test clean_build removes all build artifacts."""
|
||||
# Create directory structure and files
|
||||
pioenvs_dir = tmp_path / ".pioenvs"
|
||||
pioenvs_dir.mkdir()
|
||||
(pioenvs_dir / "test_file.o").write_text("object file")
|
||||
|
||||
piolibdeps_dir = tmp_path / ".piolibdeps"
|
||||
piolibdeps_dir.mkdir()
|
||||
(piolibdeps_dir / "library").mkdir()
|
||||
|
||||
dependencies_lock = tmp_path / "dependencies.lock"
|
||||
dependencies_lock.write_text("lock file")
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_pioenvs_path.return_value = str(pioenvs_dir)
|
||||
mock_core.relative_piolibdeps_path.return_value = str(piolibdeps_dir)
|
||||
mock_core.relative_build_path.return_value = str(dependencies_lock)
|
||||
|
||||
# Verify all exist before
|
||||
assert pioenvs_dir.exists()
|
||||
assert piolibdeps_dir.exists()
|
||||
assert dependencies_lock.exists()
|
||||
|
||||
# Call the function
|
||||
with caplog.at_level("INFO"):
|
||||
clean_build()
|
||||
|
||||
# Verify all were removed
|
||||
assert not pioenvs_dir.exists()
|
||||
assert not piolibdeps_dir.exists()
|
||||
assert not dependencies_lock.exists()
|
||||
|
||||
# Verify logging
|
||||
assert "Deleting" in caplog.text
|
||||
assert ".pioenvs" in caplog.text
|
||||
assert ".piolibdeps" in caplog.text
|
||||
assert "dependencies.lock" in caplog.text
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_clean_build_partial_exists(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test clean_build when only some paths exist."""
|
||||
# Create only pioenvs directory
|
||||
pioenvs_dir = tmp_path / ".pioenvs"
|
||||
pioenvs_dir.mkdir()
|
||||
(pioenvs_dir / "test_file.o").write_text("object file")
|
||||
|
||||
piolibdeps_dir = tmp_path / ".piolibdeps"
|
||||
dependencies_lock = tmp_path / "dependencies.lock"
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_pioenvs_path.return_value = str(pioenvs_dir)
|
||||
mock_core.relative_piolibdeps_path.return_value = str(piolibdeps_dir)
|
||||
mock_core.relative_build_path.return_value = str(dependencies_lock)
|
||||
|
||||
# Verify only pioenvs exists
|
||||
assert pioenvs_dir.exists()
|
||||
assert not piolibdeps_dir.exists()
|
||||
assert not dependencies_lock.exists()
|
||||
|
||||
# Call the function
|
||||
with caplog.at_level("INFO"):
|
||||
clean_build()
|
||||
|
||||
# Verify only existing path was removed
|
||||
assert not pioenvs_dir.exists()
|
||||
assert not piolibdeps_dir.exists()
|
||||
assert not dependencies_lock.exists()
|
||||
|
||||
# Verify logging - only pioenvs should be logged
|
||||
assert "Deleting" in caplog.text
|
||||
assert ".pioenvs" in caplog.text
|
||||
assert ".piolibdeps" not in caplog.text
|
||||
assert "dependencies.lock" not in caplog.text
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_clean_build_nothing_exists(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test clean_build when no build artifacts exist."""
|
||||
# Setup paths that don't exist
|
||||
pioenvs_dir = tmp_path / ".pioenvs"
|
||||
piolibdeps_dir = tmp_path / ".piolibdeps"
|
||||
dependencies_lock = tmp_path / "dependencies.lock"
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_pioenvs_path.return_value = str(pioenvs_dir)
|
||||
mock_core.relative_piolibdeps_path.return_value = str(piolibdeps_dir)
|
||||
mock_core.relative_build_path.return_value = str(dependencies_lock)
|
||||
|
||||
# Verify nothing exists
|
||||
assert not pioenvs_dir.exists()
|
||||
assert not piolibdeps_dir.exists()
|
||||
assert not dependencies_lock.exists()
|
||||
|
||||
# Call the function - should not crash
|
||||
clean_build()
|
||||
|
||||
# Verify nothing was created
|
||||
assert not pioenvs_dir.exists()
|
||||
assert not piolibdeps_dir.exists()
|
||||
assert not dependencies_lock.exists()
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_write_gitignore_creates_new_file(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test write_gitignore creates a new .gitignore file when it doesn't exist."""
|
||||
gitignore_path = tmp_path / ".gitignore"
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_config_path.return_value = str(gitignore_path)
|
||||
|
||||
# Verify file doesn't exist
|
||||
assert not gitignore_path.exists()
|
||||
|
||||
# Call the function
|
||||
write_gitignore()
|
||||
|
||||
# Verify file was created with correct content
|
||||
assert gitignore_path.exists()
|
||||
assert gitignore_path.read_text() == GITIGNORE_CONTENT
|
||||
|
||||
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_write_gitignore_skips_existing_file(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test write_gitignore doesn't overwrite existing .gitignore file."""
|
||||
gitignore_path = tmp_path / ".gitignore"
|
||||
existing_content = "# Custom gitignore\n/custom_dir/\n"
|
||||
gitignore_path.write_text(existing_content)
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_config_path.return_value = str(gitignore_path)
|
||||
|
||||
# Verify file exists with custom content
|
||||
assert gitignore_path.exists()
|
||||
assert gitignore_path.read_text() == existing_content
|
||||
|
||||
# Call the function
|
||||
write_gitignore()
|
||||
|
||||
# Verify file was not modified
|
||||
assert gitignore_path.exists()
|
||||
assert gitignore_path.read_text() == existing_content
|
||||
|
||||
|
||||
@patch("esphome.writer.write_file_if_changed") # Mock to capture output
|
||||
@patch("esphome.writer.copy_src_tree") # Keep this mock as it's complex
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_write_cpp_with_existing_file(
|
||||
mock_core: MagicMock,
|
||||
mock_copy_src_tree: MagicMock,
|
||||
mock_write_file: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test write_cpp when main.cpp already exists."""
|
||||
# Create a real file with markers
|
||||
main_cpp = tmp_path / "main.cpp"
|
||||
existing_content = f"""#include "esphome.h"
|
||||
{CPP_INCLUDE_BEGIN}
|
||||
// Old includes
|
||||
{CPP_INCLUDE_END}
|
||||
void setup() {{
|
||||
{CPP_AUTO_GENERATE_BEGIN}
|
||||
// Old code
|
||||
{CPP_AUTO_GENERATE_END}
|
||||
}}
|
||||
void loop() {{}}"""
|
||||
main_cpp.write_text(existing_content)
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_src_path.return_value = str(main_cpp)
|
||||
mock_core.cpp_global_section = "// Global section"
|
||||
|
||||
# Call the function
|
||||
test_code = " // New generated code"
|
||||
write_cpp(test_code)
|
||||
|
||||
# Verify copy_src_tree was called
|
||||
mock_copy_src_tree.assert_called_once()
|
||||
|
||||
# Get the content that would be written
|
||||
mock_write_file.assert_called_once()
|
||||
written_path, written_content = mock_write_file.call_args[0]
|
||||
|
||||
# Check that markers are preserved and content is updated
|
||||
assert CPP_INCLUDE_BEGIN in written_content
|
||||
assert CPP_INCLUDE_END in written_content
|
||||
assert CPP_AUTO_GENERATE_BEGIN in written_content
|
||||
assert CPP_AUTO_GENERATE_END in written_content
|
||||
assert test_code in written_content
|
||||
assert "// Global section" in written_content
|
||||
|
||||
|
||||
@patch("esphome.writer.write_file_if_changed") # Mock to capture output
|
||||
@patch("esphome.writer.copy_src_tree") # Keep this mock as it's complex
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_write_cpp_creates_new_file(
|
||||
mock_core: MagicMock,
|
||||
mock_copy_src_tree: MagicMock,
|
||||
mock_write_file: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test write_cpp when main.cpp doesn't exist."""
|
||||
# Setup path for new file
|
||||
main_cpp = tmp_path / "main.cpp"
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_src_path.return_value = str(main_cpp)
|
||||
mock_core.cpp_global_section = "// Global section"
|
||||
|
||||
# Verify file doesn't exist
|
||||
assert not main_cpp.exists()
|
||||
|
||||
# Call the function
|
||||
test_code = " // Generated code"
|
||||
write_cpp(test_code)
|
||||
|
||||
# Verify copy_src_tree was called
|
||||
mock_copy_src_tree.assert_called_once()
|
||||
|
||||
# Get the content that would be written
|
||||
mock_write_file.assert_called_once()
|
||||
written_path, written_content = mock_write_file.call_args[0]
|
||||
assert written_path == str(main_cpp)
|
||||
|
||||
# Check that all necessary parts are in the new file
|
||||
assert '#include "esphome.h"' in written_content
|
||||
assert CPP_INCLUDE_BEGIN in written_content
|
||||
assert CPP_INCLUDE_END in written_content
|
||||
assert CPP_AUTO_GENERATE_BEGIN in written_content
|
||||
assert CPP_AUTO_GENERATE_END in written_content
|
||||
assert test_code in written_content
|
||||
assert "void setup()" in written_content
|
||||
assert "void loop()" in written_content
|
||||
assert "App.setup();" in written_content
|
||||
assert "App.loop();" in written_content
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_copy_src_tree")
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_write_cpp_with_missing_end_marker(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test write_cpp raises error when end marker is missing."""
|
||||
# Create a file with begin marker but no end marker
|
||||
main_cpp = tmp_path / "main.cpp"
|
||||
existing_content = f"""#include "esphome.h"
|
||||
{CPP_AUTO_GENERATE_BEGIN}
|
||||
// Code without end marker"""
|
||||
main_cpp.write_text(existing_content)
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_src_path.return_value = str(main_cpp)
|
||||
|
||||
# Call should raise an error
|
||||
with pytest.raises(EsphomeError, match="Could not find auto generated code end"):
|
||||
write_cpp("// New code")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_copy_src_tree")
|
||||
@patch("esphome.writer.CORE")
|
||||
def test_write_cpp_with_duplicate_markers(
|
||||
mock_core: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test write_cpp raises error when duplicate markers exist."""
|
||||
# Create a file with duplicate begin markers
|
||||
main_cpp = tmp_path / "main.cpp"
|
||||
existing_content = f"""#include "esphome.h"
|
||||
{CPP_AUTO_GENERATE_BEGIN}
|
||||
// First section
|
||||
{CPP_AUTO_GENERATE_END}
|
||||
{CPP_AUTO_GENERATE_BEGIN}
|
||||
// Duplicate section
|
||||
{CPP_AUTO_GENERATE_END}"""
|
||||
main_cpp.write_text(existing_content)
|
||||
|
||||
# Setup mocks
|
||||
mock_core.relative_src_path.return_value = str(main_cpp)
|
||||
|
||||
# Call should raise an error
|
||||
with pytest.raises(EsphomeError, match="Found multiple auto generate code begins"):
|
||||
write_cpp("// New code")
|
||||
|
Reference in New Issue
Block a user