diff --git a/esphome/espidf/size_summary.py b/esphome/espidf/size_summary.py index 06e0ecba5c..79f8d48848 100644 --- a/esphome/espidf/size_summary.py +++ b/esphome/espidf/size_summary.py @@ -10,10 +10,12 @@ byte-identical to PlatformIO's output: The format matches ``script/ci_memory_impact_extract.py`` so CI memory analysis works unchanged on native ESP-IDF builds. RAM usage comes from -the DRAM (or unified DIRAM) region of the linker map; Flash used is the -size of the app ``.bin`` on disk, which includes esptool's 16-byte -image padding and appended SHA-256, so it reads slightly above the -map-derived ``Total image size`` line and moves in 16-byte steps. +the DRAM (or unified DIRAM) region of the linker map. Flash used is the +json2 ``total_size`` field when present (esp-idf-size >= 2.1, the exact +map-derived figure matching the ``Total image size`` line); older 1.x +json2 lacks it, and the fallback is the size of the app ``.bin`` on +disk, which includes esptool's 16-byte image padding and appended +SHA-256 so it reads slightly high and moves in 16-byte steps. Flash total is taken from ``partitions.csv`` using PlatformIO's rule (first app partition whose subtype is ``factory`` or ``ota_0``; see @@ -101,8 +103,13 @@ def print_summary(size_json: Path, partitions_csv: Path, firmware_bin: Path) -> if ram_total and ram_used is not None: print_size_line("RAM", ram_used, ram_total) + # esp-idf-size >= 2.1 (IDF >= 6.0) reports the exact map-derived image + # size in json2; older 1.x omits it, so fall back to the padded on-disk + # .bin size. + flash_used = data.get("total_size") try: - flash_used = firmware_bin.stat().st_size + if flash_used is None: + flash_used = firmware_bin.stat().st_size app_size = _find_app_partition_size(partitions_csv) except (OSError, ValueError) as e: _LOGGER.debug("Skipping Flash summary: %s", e) diff --git a/tests/unit_tests/test_size_summary.py b/tests/unit_tests/test_size_summary.py index ff5c7b95c1..0eb876da99 100644 --- a/tests/unit_tests/test_size_summary.py +++ b/tests/unit_tests/test_size_summary.py @@ -28,9 +28,11 @@ def _write_partitions(tmp_path: Path) -> Path: def _esp32_size_data() -> dict: - """Synthetic json2 esp_idf_size.json for the original ESP32 (split IRAM/DRAM).""" + """Synthetic json2 for the original ESP32 (split IRAM/DRAM), in the + esp-idf-size >= 2.1 shape that carries ``total_size``.""" return { "version": "1.1", + "total_size": 827455, "layout": [ { "name": "DRAM", @@ -57,7 +59,8 @@ def _esp32_size_data() -> dict: def _s3_size_data() -> dict: - """Synthetic json2 esp_idf_size.json for ESP32-S3 (unified DIRAM).""" + """Synthetic json2 for ESP32-S3 (unified DIRAM), in the esp-idf-size 1.x + shape without ``total_size``.""" return { "version": "1.1", "layout": [ @@ -146,27 +149,40 @@ def test_print_summary_handles_no_layout( assert capsys.readouterr().out == "" -def test_print_summary_flash_line( +def test_print_summary_flash_line_prefers_total_size( tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: - """A partition table with an app row yields the Flash line in the exact - padded shape script/ci_memory_impact_extract.py greps.""" + """With ``total_size`` in the json, the exact figure wins over the padded + bin size, in the exact shape script/ci_memory_impact_extract.py greps.""" size_json = _write_size_json(tmp_path, _esp32_size_data()) partitions = _write_partitions(tmp_path) firmware_bin = tmp_path / "firmware.bin" - firmware_bin.write_bytes(b"\x00" * 827455) + firmware_bin.write_bytes(b"\x00" * 999999) print_summary(size_json, partitions, firmware_bin) out = capsys.readouterr().out assert "Flash: " in out assert "(used 827455 bytes from 1835008 bytes)" in out +def test_print_summary_flash_line_falls_back_to_bin_size( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + """A 1.x json without ``total_size`` uses the on-disk bin size.""" + size_json = _write_size_json(tmp_path, _s3_size_data()) + partitions = _write_partitions(tmp_path) + firmware_bin = tmp_path / "firmware.bin" + firmware_bin.write_bytes(b"\x00" * 724224) + print_summary(size_json, partitions, firmware_bin) + out = capsys.readouterr().out + assert "(used 724224 bytes from 1835008 bytes)" in out + + @pytest.mark.parametrize("missing", ["bin", "partitions"]) def test_print_summary_skips_flash_on_missing_input( missing: str, tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: """A missing firmware bin or partitions.csv skips the Flash line, not the RAM line.""" - size_json = _write_size_json(tmp_path, _esp32_size_data()) + size_json = _write_size_json(tmp_path, _s3_size_data()) firmware_bin = tmp_path / "firmware.bin" if missing == "bin": _write_partitions(tmp_path)