mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[vscode] Report the origin of an unexpected exception during validation (#18494)
This commit is contained in:
+18
-2
@@ -3,12 +3,14 @@ from __future__ import annotations
|
||||
from io import StringIO
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Any
|
||||
|
||||
from esphome.config import Config, _format_vol_invalid, validate_config
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import __version__ as ESPHOME_VERSION
|
||||
from esphome.core import CORE, DocumentRange
|
||||
from esphome.core import CORE, DocumentRange, EsphomeError
|
||||
from esphome.yaml_util import parse_yaml
|
||||
|
||||
|
||||
@@ -97,6 +99,16 @@ def _ace_loader(fname: Path) -> dict[str, Any]:
|
||||
return parse_yaml(fname, raw_yaml_stream)
|
||||
|
||||
|
||||
def _format_unexpected_error(err: Exception) -> str:
|
||||
"""Describe a crash inside validation with the frame it came from."""
|
||||
message = f"Unexpected error while validating: {type(err).__name__}: {err}"
|
||||
frames = traceback.extract_tb(err.__traceback__)
|
||||
if not frames:
|
||||
return message
|
||||
frame = frames[-1]
|
||||
return f"{message} ({frame.filename}:{frame.lineno} in {frame.name})"
|
||||
|
||||
|
||||
def _print_version():
|
||||
"""Print ESPHome version."""
|
||||
print(
|
||||
@@ -134,8 +146,12 @@ def read_config(args):
|
||||
try:
|
||||
config = loader(file_name)
|
||||
res = validate_config(config, command_line_substitutions)
|
||||
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
|
||||
except (EsphomeError, cv.Invalid) as err:
|
||||
vs.add_yaml_error(str(err))
|
||||
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
|
||||
# stdout carries the JSON protocol; the full chain goes to stderr.
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
vs.add_yaml_error(_format_unexpected_error(err))
|
||||
else:
|
||||
for err in res.errors:
|
||||
try:
|
||||
|
||||
@@ -3,6 +3,8 @@ from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from esphome import vscode
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import EsphomeError
|
||||
|
||||
|
||||
def _run_repl_test(input_data):
|
||||
@@ -126,3 +128,67 @@ packages:
|
||||
assert range["start_col"] == 2
|
||||
assert range["end_line"] == 1
|
||||
assert range["end_col"] == 7
|
||||
|
||||
|
||||
def _explode(*_args: object, **_kwargs: object) -> None:
|
||||
raise AttributeError("'NoneType' object has no attribute 'get'")
|
||||
|
||||
|
||||
def test_unexpected_error_reports_origin() -> None:
|
||||
source_path = str(Path("dir_path", "x.yaml"))
|
||||
with patch("esphome.vscode.validate_config", _explode):
|
||||
output_lines = _run_repl_test(
|
||||
[
|
||||
_validate(source_path),
|
||||
_file_response("""esphome:
|
||||
name: test1
|
||||
"""),
|
||||
]
|
||||
)
|
||||
|
||||
result = json.loads(output_lines[-1])
|
||||
assert result["validation_errors"] == []
|
||||
(error,) = result["yaml_errors"]
|
||||
assert error["message"].startswith(
|
||||
"Unexpected error while validating: AttributeError: "
|
||||
"'NoneType' object has no attribute 'get' ("
|
||||
)
|
||||
assert "test_vscode.py" in error["message"]
|
||||
assert error["message"].endswith(" in _explode)")
|
||||
|
||||
|
||||
def test_esphome_error_stays_plain() -> None:
|
||||
source_path = str(Path("dir_path", "x.yaml"))
|
||||
with patch("esphome.vscode.validate_config", side_effect=EsphomeError("boom")):
|
||||
output_lines = _run_repl_test(
|
||||
[
|
||||
_validate(source_path),
|
||||
_file_response("""esphome:
|
||||
name: test1
|
||||
"""),
|
||||
]
|
||||
)
|
||||
|
||||
result = json.loads(output_lines[-1])
|
||||
assert result["yaml_errors"] == [{"message": "boom"}]
|
||||
|
||||
|
||||
def test_invalid_stays_plain() -> None:
|
||||
source_path = str(Path("dir_path", "x.yaml"))
|
||||
with patch("esphome.vscode.validate_config", side_effect=cv.Invalid("bad value")):
|
||||
output_lines = _run_repl_test(
|
||||
[
|
||||
_validate(source_path),
|
||||
_file_response("""esphome:
|
||||
name: test1
|
||||
"""),
|
||||
]
|
||||
)
|
||||
|
||||
result = json.loads(output_lines[-1])
|
||||
assert result["yaml_errors"] == [{"message": "bad value"}]
|
||||
|
||||
|
||||
def test_format_unexpected_error_without_traceback() -> None:
|
||||
message = vscode._format_unexpected_error(ValueError("boom"))
|
||||
assert message == "Unexpected error while validating: ValueError: boom"
|
||||
|
||||
Reference in New Issue
Block a user