From 74f09a2b5986d1b05d71db22e38cf6250bdaa970 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:55:43 -0400 Subject: [PATCH] [core] Rename to clean-platform to clean-all (#10876) --- esphome/__main__.py | 19 ++++----- esphome/dashboard/web_server.py | 10 +++-- esphome/writer.py | 10 +++-- tests/unit_tests/test_main.py | 74 +++++++++++++++------------------ tests/unit_tests/test_writer.py | 61 +++++++++++++++------------ 5 files changed, 89 insertions(+), 85 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 27aced5f33..42880e6cfc 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -731,11 +731,11 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None: return clean_mqtt(config, args) -def command_clean_platform(args: ArgsProtocol, config: ConfigType) -> int | None: +def command_clean_all(args: ArgsProtocol) -> int | None: try: - writer.clean_platform() + writer.clean_all(args.configuration) except OSError as err: - _LOGGER.error("Error deleting platform files: %s", err) + _LOGGER.error("Error cleaning all files: %s", err) return 1 _LOGGER.info("Done!") return 0 @@ -931,6 +931,7 @@ PRE_CONFIG_ACTIONS = { "dashboard": command_dashboard, "vscode": command_vscode, "update-all": command_update_all, + "clean-all": command_clean_all, } POST_CONFIG_ACTIONS = { @@ -941,7 +942,6 @@ POST_CONFIG_ACTIONS = { "run": command_run, "clean": command_clean, "clean-mqtt": command_clean_mqtt, - "clean-platform": command_clean_platform, "mqtt-fingerprint": command_mqtt_fingerprint, "idedata": command_idedata, "rename": command_rename, @@ -951,7 +951,6 @@ POST_CONFIG_ACTIONS = { SIMPLE_CONFIG_ACTIONS = [ "clean", "clean-mqtt", - "clean-platform", "config", ] @@ -1156,11 +1155,9 @@ def parse_args(argv): "configuration", help="Your YAML configuration file(s).", nargs="+" ) - parser_clean = subparsers.add_parser( - "clean-platform", help="Delete all platform files." - ) - parser_clean.add_argument( - "configuration", help="Your YAML configuration file(s).", nargs="+" + parser_clean_all = subparsers.add_parser("clean-all", help="Clean all files.") + parser_clean_all.add_argument( + "configuration", help="Your YAML configuration directory.", nargs="*" ) parser_dashboard = subparsers.add_parser( @@ -1209,7 +1206,7 @@ def parse_args(argv): parser_update = subparsers.add_parser("update-all") parser_update.add_argument( - "configuration", help="Your YAML configuration file directories.", nargs="+" + "configuration", help="Your YAML configuration file or directory.", nargs="+" ) parser_idedata = subparsers.add_parser("idedata") diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index a4c24369a3..e3a0013e2f 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -479,10 +479,12 @@ class EsphomeCleanMqttHandler(EsphomeCommandWebSocket): return [*DASHBOARD_COMMAND, "clean-mqtt", config_file] -class EsphomeCleanPlatformHandler(EsphomeCommandWebSocket): +class EsphomeCleanAllHandler(EsphomeCommandWebSocket): async def build_command(self, json_message: dict[str, Any]) -> list[str]: - config_file = settings.rel_path(json_message["configuration"]) - return [*DASHBOARD_COMMAND, "clean-platform", config_file] + clean_build_dir = json_message.get("clean_build_dir", True) + if clean_build_dir: + return [*DASHBOARD_COMMAND, "clean-all", settings.config_dir] + return [*DASHBOARD_COMMAND, "clean-all"] class EsphomeCleanHandler(EsphomeCommandWebSocket): @@ -1319,7 +1321,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application: (f"{rel}compile", EsphomeCompileHandler), (f"{rel}validate", EsphomeValidateHandler), (f"{rel}clean-mqtt", EsphomeCleanMqttHandler), - (f"{rel}clean-platform", EsphomeCleanPlatformHandler), + (f"{rel}clean-all", EsphomeCleanAllHandler), (f"{rel}clean", EsphomeCleanHandler), (f"{rel}vscode", EsphomeVscodeHandler), (f"{rel}ace", EsphomeAceEditorHandler), diff --git a/esphome/writer.py b/esphome/writer.py index 718041876a..403cd8165d 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -335,13 +335,15 @@ def clean_build(): shutil.rmtree(cache_dir) -def clean_platform(): +def clean_all(configuration: list[str]): import shutil # Clean entire build dir - if CORE.build_path.is_dir(): - _LOGGER.info("Deleting %s", CORE.build_path) - shutil.rmtree(CORE.build_path) + for dir in configuration: + buid_dir = Path(dir) / ".esphome" + if buid_dir.is_dir(): + _LOGGER.info("Deleting %s", buid_dir) + shutil.rmtree(buid_dir) # Clean PlatformIO project files try: diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 8799ac56ff..e35378145a 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -17,7 +17,7 @@ from esphome import platformio_api from esphome.__main__ import ( Purpose, choose_upload_log_host, - command_clean_platform, + command_clean_all, command_rename, command_update_all, command_wizard, @@ -1857,33 +1857,31 @@ esp32: assert "can only concatenate str" not in clean_output -def test_command_clean_platform_success( +def test_command_clean_all_success( caplog: pytest.LogCaptureFixture, ) -> None: - """Test command_clean_platform when writer.clean_platform() succeeds.""" - args = MockArgs() - config = {} + """Test command_clean_all when writer.clean_all() succeeds.""" + args = MockArgs(configuration=["/path/to/config1", "/path/to/config2"]) # Set logger level to capture INFO messages with ( caplog.at_level(logging.INFO), - patch("esphome.writer.clean_platform") as mock_clean_platform, + patch("esphome.writer.clean_all") as mock_clean_all, ): - result = command_clean_platform(args, config) + result = command_clean_all(args) assert result == 0 - mock_clean_platform.assert_called_once() + mock_clean_all.assert_called_once_with(["/path/to/config1", "/path/to/config2"]) # Check that success message was logged assert "Done!" in caplog.text -def test_command_clean_platform_oserror( +def test_command_clean_all_oserror( caplog: pytest.LogCaptureFixture, ) -> None: - """Test command_clean_platform when writer.clean_platform() raises OSError.""" - args = MockArgs() - config = {} + """Test command_clean_all when writer.clean_all() raises OSError.""" + args = MockArgs(configuration=["/path/to/config1"]) # Create a mock OSError with a specific message mock_error = OSError("Permission denied: cannot delete directory") @@ -1891,30 +1889,27 @@ def test_command_clean_platform_oserror( # Set logger level to capture ERROR and INFO messages with ( caplog.at_level(logging.INFO), - patch( - "esphome.writer.clean_platform", side_effect=mock_error - ) as mock_clean_platform, + patch("esphome.writer.clean_all", side_effect=mock_error) as mock_clean_all, ): - result = command_clean_platform(args, config) + result = command_clean_all(args) assert result == 1 - mock_clean_platform.assert_called_once() + mock_clean_all.assert_called_once_with(["/path/to/config1"]) # Check that error message was logged assert ( - "Error deleting platform files: Permission denied: cannot delete directory" + "Error cleaning all files: Permission denied: cannot delete directory" in caplog.text ) # Should not have success message assert "Done!" not in caplog.text -def test_command_clean_platform_oserror_no_message( +def test_command_clean_all_oserror_no_message( caplog: pytest.LogCaptureFixture, ) -> None: - """Test command_clean_platform when writer.clean_platform() raises OSError without message.""" - args = MockArgs() - config = {} + """Test command_clean_all when writer.clean_all() raises OSError without message.""" + args = MockArgs(configuration=["/path/to/config1"]) # Create a mock OSError without a message mock_error = OSError() @@ -1922,34 +1917,33 @@ def test_command_clean_platform_oserror_no_message( # Set logger level to capture ERROR and INFO messages with ( caplog.at_level(logging.INFO), - patch( - "esphome.writer.clean_platform", side_effect=mock_error - ) as mock_clean_platform, + patch("esphome.writer.clean_all", side_effect=mock_error) as mock_clean_all, ): - result = command_clean_platform(args, config) + result = command_clean_all(args) assert result == 1 - mock_clean_platform.assert_called_once() + mock_clean_all.assert_called_once_with(["/path/to/config1"]) # Check that error message was logged (should show empty string for OSError without message) - assert "Error deleting platform files:" in caplog.text + assert "Error cleaning all files:" in caplog.text # Should not have success message assert "Done!" not in caplog.text -def test_command_clean_platform_args_and_config_ignored() -> None: - """Test that command_clean_platform ignores args and config parameters.""" - # Test with various args and config to ensure they don't affect the function - args1 = MockArgs(name="test1", file="test.bin") - config1 = {"wifi": {"ssid": "test"}} +def test_command_clean_all_args_used() -> None: + """Test that command_clean_all uses args.configuration parameter.""" + # Test with different configuration paths + args1 = MockArgs(configuration=["/path/to/config1"]) + args2 = MockArgs(configuration=["/path/to/config2", "/path/to/config3"]) - args2 = MockArgs(name="test2", dashboard=True) - config2 = {"api": {}, "ota": {}} - - with patch("esphome.writer.clean_platform") as mock_clean_platform: - result1 = command_clean_platform(args1, config1) - result2 = command_clean_platform(args2, config2) + with patch("esphome.writer.clean_all") as mock_clean_all: + result1 = command_clean_all(args1) + result2 = command_clean_all(args2) assert result1 == 0 assert result2 == 0 - assert mock_clean_platform.call_count == 2 + assert mock_clean_all.call_count == 2 + + # Verify the correct configuration paths were passed + mock_clean_all.assert_any_call(["/path/to/config1"]) + mock_clean_all.assert_any_call(["/path/to/config2", "/path/to/config3"]) diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index dc5fbf8db5..66e0b6cb67 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -738,16 +738,24 @@ def test_write_cpp_with_duplicate_markers( @patch("esphome.writer.CORE") -def test_clean_platform( +def test_clean_all( mock_core: MagicMock, tmp_path: Path, caplog: pytest.LogCaptureFixture, ) -> None: - """Test clean_platform removes build and PlatformIO dirs.""" - # Create build directory - build_dir = tmp_path / "build" - build_dir.mkdir() - (build_dir / "dummy.txt").write_text("x") + """Test clean_all removes build and PlatformIO dirs.""" + # Create build directories for multiple configurations + config1_dir = tmp_path / "config1" + config2_dir = tmp_path / "config2" + config1_dir.mkdir() + config2_dir.mkdir() + + build_dir1 = config1_dir / ".esphome" + build_dir2 = config2_dir / ".esphome" + build_dir1.mkdir() + build_dir2.mkdir() + (build_dir1 / "dummy.txt").write_text("x") + (build_dir2 / "dummy.txt").write_text("x") # Create PlatformIO directories pio_cache = tmp_path / "pio_cache" @@ -758,9 +766,6 @@ def test_clean_platform( d.mkdir() (d / "keep").write_text("x") - # Setup CORE - mock_core.build_path = build_dir - # Mock ProjectConfig with patch( "platformio.project.config.ProjectConfig.get_instance" @@ -780,13 +785,14 @@ def test_clean_platform( mock_config.get.side_effect = cfg_get # Call - from esphome.writer import clean_platform + from esphome.writer import clean_all with caplog.at_level("INFO"): - clean_platform() + clean_all([str(config1_dir), str(config2_dir)]) # Verify deletions - assert not build_dir.exists() + assert not build_dir1.exists() + assert not build_dir2.exists() assert not pio_cache.exists() assert not pio_packages.exists() assert not pio_platforms.exists() @@ -794,7 +800,8 @@ def test_clean_platform( # Verify logging mentions each assert "Deleting" in caplog.text - assert str(build_dir) in caplog.text + assert str(build_dir1) in caplog.text + assert str(build_dir2) in caplog.text assert "PlatformIO cache" in caplog.text assert "PlatformIO packages" in caplog.text assert "PlatformIO platforms" in caplog.text @@ -802,28 +809,29 @@ def test_clean_platform( @patch("esphome.writer.CORE") -def test_clean_platform_platformio_not_available( +def test_clean_all_platformio_not_available( mock_core: MagicMock, tmp_path: Path, caplog: pytest.LogCaptureFixture, ) -> None: - """Test clean_platform when PlatformIO is not available.""" - # Build dir - build_dir = tmp_path / "build" + """Test clean_all when PlatformIO is not available.""" + # Build dirs + config_dir = tmp_path / "config" + config_dir.mkdir() + build_dir = config_dir / ".esphome" build_dir.mkdir() - mock_core.build_path = build_dir # PlatformIO dirs that should remain untouched pio_cache = tmp_path / "pio_cache" pio_cache.mkdir() - from esphome.writer import clean_platform + from esphome.writer import clean_all with ( patch.dict("sys.modules", {"platformio.project.config": None}), caplog.at_level("INFO"), ): - clean_platform() + clean_all([str(config_dir)]) # Build dir removed, PlatformIO dirs remain assert not build_dir.exists() @@ -834,14 +842,15 @@ def test_clean_platform_platformio_not_available( @patch("esphome.writer.CORE") -def test_clean_platform_partial_exists( +def test_clean_all_partial_exists( mock_core: MagicMock, tmp_path: Path, ) -> None: - """Test clean_platform when only build dir exists.""" - build_dir = tmp_path / "build" + """Test clean_all when only some build dirs exist.""" + config_dir = tmp_path / "config" + config_dir.mkdir() + build_dir = config_dir / ".esphome" build_dir.mkdir() - mock_core.build_path = build_dir with patch( "platformio.project.config.ProjectConfig.get_instance" @@ -853,8 +862,8 @@ def test_clean_platform_partial_exists( tmp_path / "does_not_exist" ) - from esphome.writer import clean_platform + from esphome.writer import clean_all - clean_platform() + clean_all([str(config_dir)]) assert not build_dir.exists()