mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[core] Share the stacktrace analyzer resolution in platform_hooks (#18075)
This commit is contained in:
+3
-21
@@ -630,27 +630,9 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||
return 1
|
||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||
|
||||
# Stacktrace analysis is optional; a broken platform import must not
|
||||
# stop serial log streaming, but it is a real breakage and must not
|
||||
# masquerade as an ordinary capability gap.
|
||||
try:
|
||||
process_stacktrace = platform_hooks.get_platform_hook(
|
||||
CORE.target_platform, "process_stacktrace"
|
||||
)
|
||||
except ImportError as err:
|
||||
_LOGGER.debug("Stacktrace analyzer import failed", exc_info=True)
|
||||
_LOGGER.warning(
|
||||
'Stacktrace analysis is unavailable: analyzer for target platform "%s" failed to import: %s',
|
||||
CORE.target_platform,
|
||||
err,
|
||||
)
|
||||
process_stacktrace = None
|
||||
else:
|
||||
if process_stacktrace is None:
|
||||
_LOGGER.info(
|
||||
'Stacktrace analysis is unavailable: no compatible analyzer found for target platform "%s".',
|
||||
CORE.target_platform,
|
||||
)
|
||||
# Stacktrace analysis is optional; platform_hooks owns resolution
|
||||
# and the user-facing messages.
|
||||
process_stacktrace = platform_hooks.get_stacktrace_handler(CORE.target_platform)
|
||||
|
||||
backtrace_state = False
|
||||
ser = serial.Serial()
|
||||
|
||||
@@ -10,9 +10,10 @@ imports each platform package and fails when they drift.
|
||||
|
||||
The compile-path ``run_compile`` hook is deliberately not registered:
|
||||
compiling imports the platform package regardless, so its probe in
|
||||
``__main__.py`` stays eager. The network log client's
|
||||
``process_stacktrace`` probe in ``esphome/api_client.py`` still uses the
|
||||
old importlib pattern; converting it is a separate change.
|
||||
``__main__.py`` stays eager. The serial log path resolves
|
||||
``process_stacktrace`` through get_stacktrace_handler below; the network
|
||||
log client's probe in ``esphome/api_client.py`` still uses the old
|
||||
importlib pattern and is converted separately.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -111,3 +112,30 @@ def get_platform_hook(platform: str, hook: str) -> Callable[..., Any] | None:
|
||||
hook,
|
||||
)
|
||||
return handler
|
||||
|
||||
|
||||
def get_stacktrace_handler(platform: str) -> Callable[..., Any] | None:
|
||||
"""Resolve ``process_stacktrace`` for *platform*, degrading with a log.
|
||||
|
||||
Stacktrace decoding is a diagnostic nicety. This only distinguishes
|
||||
an import failure from an ordinary capability gap so the message is
|
||||
accurate; it returns None for both, and callers own any further
|
||||
containment. Shared so the user-facing message lives in one place.
|
||||
"""
|
||||
try:
|
||||
handler = get_platform_hook(platform, "process_stacktrace")
|
||||
except ImportError as err:
|
||||
# A real breakage, not an ordinary capability gap; say so louder.
|
||||
_LOGGER.debug("Stacktrace analyzer import failed", exc_info=True)
|
||||
_LOGGER.warning(
|
||||
'Stacktrace analysis is unavailable: analyzer for target platform "%s" failed to import: %s',
|
||||
platform,
|
||||
err,
|
||||
)
|
||||
return None
|
||||
if handler is None:
|
||||
_LOGGER.info(
|
||||
'Stacktrace analysis is unavailable: no compatible analyzer found for target platform "%s".',
|
||||
platform,
|
||||
)
|
||||
return handler
|
||||
|
||||
@@ -5732,7 +5732,7 @@ def test_run_miniterm_analyzer_import_failure_keeps_streaming(
|
||||
args = MockArgs()
|
||||
|
||||
with (
|
||||
caplog.at_level("INFO", logger="esphome.__main__"),
|
||||
caplog.at_level("INFO", logger="esphome.platform_hooks"),
|
||||
patch("serial.Serial", return_value=mock_serial),
|
||||
patch(
|
||||
"esphome.platform_hooks.get_platform_hook",
|
||||
@@ -5762,7 +5762,7 @@ def test_run_miniterm_no_stacktrace_analyzer(
|
||||
args = MockArgs()
|
||||
|
||||
with (
|
||||
caplog.at_level("INFO", logger="esphome.__main__"),
|
||||
caplog.at_level("INFO", logger="esphome.platform_hooks"),
|
||||
patch("serial.Serial", return_value=mock_serial),
|
||||
):
|
||||
result = run_miniterm(config, "/dev/ttyUSB0", args)
|
||||
|
||||
@@ -9,12 +9,13 @@ that the fast path really avoids the import.
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import platform_hooks
|
||||
from esphome.const import PLATFORM_ESP32, Platform
|
||||
from esphome.const import PLATFORM_BK72XX, PLATFORM_ESP32, Platform
|
||||
|
||||
|
||||
def test_no_unregistered_platform_exposes_a_hook() -> None:
|
||||
@@ -151,3 +152,35 @@ def test_lookup_miss_does_not_import_platform_package(
|
||||
Mock(side_effect=AssertionError("platform package imported on registry miss")),
|
||||
)
|
||||
assert platform_hooks.get_platform_hook(PLATFORM_ESP32, "show_logs") is None
|
||||
|
||||
|
||||
def test_get_stacktrace_handler_resolves_registered_platform() -> None:
|
||||
hook = platform_hooks.get_stacktrace_handler(PLATFORM_ESP32)
|
||||
from esphome.components import esp32
|
||||
|
||||
assert hook is esp32.process_stacktrace
|
||||
|
||||
|
||||
def test_get_stacktrace_handler_reports_missing_analyzer(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
caplog.set_level("INFO", logger="esphome.platform_hooks")
|
||||
assert platform_hooks.get_stacktrace_handler(PLATFORM_BK72XX) is None
|
||||
assert "no compatible analyzer" in caplog.text
|
||||
# A capability gap is ordinary; it must not warn.
|
||||
assert not any(r.levelno >= logging.WARNING for r in caplog.records)
|
||||
|
||||
|
||||
def test_get_stacktrace_handler_reports_import_failure(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
platform_hooks,
|
||||
"import_module",
|
||||
Mock(side_effect=ImportError("broken install")),
|
||||
)
|
||||
assert platform_hooks.get_stacktrace_handler(PLATFORM_ESP32) is None
|
||||
assert "failed to import: broken install" in caplog.text
|
||||
# A broken install is a real breakage; it must warn, not inform.
|
||||
assert any(r.levelno == logging.WARNING for r in caplog.records)
|
||||
|
||||
Reference in New Issue
Block a user