mirror of
https://github.com/esphome/esphome.git
synced 2026-08-24 07:06:20 +00:00
Name the unreadable report's file, tighten the number and region checks
The corrupt-report arm names its file with wording distinct from the backstop, so tests can tell them apart (and now do, plus the non-UTF-8 half). _is_number rejects bool and non-finite floats, and the RAM region selects by key presence, so a falsy-but-present DRAM value is malformed rather than absent -- the size report is the one input nothing upstream validates, so these stay hardened. The not-app_size comment carries the zero case it also swallows.
This commit is contained in:
@@ -26,6 +26,7 @@ from __future__ import annotations
|
||||
import csv
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -104,7 +105,7 @@ def _print_summary(size_json: Path, partitions_csv: Path | None) -> None:
|
||||
data = json.loads(size_json.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError) as e:
|
||||
# ValueError covers JSONDecodeError and a non-UTF-8 (truncated) file
|
||||
_LOGGER.warning("Skipping size summary: %s", e)
|
||||
_LOGGER.warning("Skipping size summary: cannot read %s: %s", size_json, e)
|
||||
return
|
||||
if not isinstance(data, dict):
|
||||
# Non-object JSON has no .get
|
||||
@@ -127,13 +128,25 @@ def _present_but_not_dict(value: object) -> bool:
|
||||
|
||||
|
||||
def _is_number(value: object) -> bool:
|
||||
return isinstance(value, (int, float))
|
||||
# bool subclasses int; NaN/Infinity are valid JSON for json.loads
|
||||
return (
|
||||
isinstance(value, (int, float))
|
||||
and not isinstance(value, bool)
|
||||
and math.isfinite(value)
|
||||
)
|
||||
|
||||
|
||||
def _ram_line(data: dict, size_json: Path) -> str | None:
|
||||
"""The formatted RAM line, or None (already logged) to skip it."""
|
||||
memory_types = data.get("memory_types")
|
||||
ram_region = _dict_get(memory_types, "DRAM") or _dict_get(memory_types, "DIRAM")
|
||||
ram_region = None
|
||||
if isinstance(memory_types, dict):
|
||||
# Key presence, not truthiness: a falsy DRAM value is corrupt, not
|
||||
# absent, and must not fall through to DIRAM
|
||||
for key in ("DRAM", "DIRAM"):
|
||||
if key in memory_types:
|
||||
ram_region = memory_types[key]
|
||||
break
|
||||
used = _dict_get(ram_region, "used")
|
||||
total = _dict_get(ram_region, "size")
|
||||
if _is_number(used) and _is_number(total) and total > 0:
|
||||
@@ -170,7 +183,8 @@ def _flash_line(data: dict, size_json: Path, partitions_csv: Path | None) -> str
|
||||
return None
|
||||
app_size = _find_app_partition_size(partitions_csv)
|
||||
if not app_size:
|
||||
# No table or no qualifying row: legitimate for non-app layouts
|
||||
# No qualifying row (a zero-size row has nothing to report either):
|
||||
# legitimate for non-app layouts
|
||||
_LOGGER.debug("Skipping Flash summary: no app partition in %s", partitions_csv)
|
||||
return None
|
||||
return f"Flash: {_format_bar(int(image_size), app_size)}"
|
||||
|
||||
@@ -133,7 +133,8 @@ def test_print_summary_handles_missing_json(
|
||||
"""Missing size json is non-fatal and prints nothing."""
|
||||
print_summary(tmp_path / "does_not_exist.json", partitions_csv=None)
|
||||
assert capsys.readouterr().out == ""
|
||||
assert "Skipping size summary" in caplog.text
|
||||
assert "cannot read" in caplog.text
|
||||
assert "Skipping size summary for" not in caplog.text
|
||||
|
||||
|
||||
def test_print_summary_handles_no_memory_types(
|
||||
@@ -196,6 +197,8 @@ def test_print_summary_happy_path_prints_both_bars(
|
||||
{"memory_types": []},
|
||||
{"memory_types": {"DRAM": 5}},
|
||||
{"memory_types": {"DRAM": {"used": "x", "size": "y"}}, "image_size": 1},
|
||||
{"memory_types": {"DRAM": []}},
|
||||
{"memory_types": {"DRAM": {"used": True, "size": True}}},
|
||||
],
|
||||
)
|
||||
def test_print_summary_nested_bad_shapes_never_raise(
|
||||
@@ -303,8 +306,12 @@ def test_print_summary_corrupt_size_json_warns(
|
||||
size_json = tmp_path / "size.json"
|
||||
size_json.write_text("not json {{{")
|
||||
print_summary(size_json, None)
|
||||
size_json.write_bytes(b"\xff\xfe\x00")
|
||||
print_summary(size_json, None)
|
||||
assert capsys.readouterr().out == ""
|
||||
assert "Skipping size summary" in caplog.text
|
||||
# The named arm, not the blanket, for both damage classes
|
||||
assert caplog.text.count("cannot read") == 2
|
||||
assert "Skipping size summary for" not in caplog.text
|
||||
|
||||
|
||||
def test_print_summary_missing_partitions_named_in_backstop(
|
||||
|
||||
Reference in New Issue
Block a user