mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00: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