Use relative paths in configuration error messages (#182427)

This commit is contained in:
Erik Montnemery
2026-09-17 11:59:54 +02:00
committed by GitHub
parent c8ed5b7a6e
commit 2fe169a7ae
7 changed files with 62 additions and 41 deletions
+4 -4
View File
@@ -826,8 +826,8 @@ def _get_log_message_and_stack_print_pref(
hass, exception, platform_path, platform_config, link
)
if annotation := find_annotation(platform_config, exception.path):
placeholders["config_file"], line = annotation
placeholders["line"] = str(line)
placeholders["config_file"] = _relpath(hass, annotation[0])
placeholders["line"] = str(annotation[1])
else:
if TYPE_CHECKING:
assert isinstance(exception, HomeAssistantError)
@@ -835,8 +835,8 @@ def _get_log_message_and_stack_print_pref(
hass, exception, platform_path, platform_config, link
)
if annotation := find_annotation(platform_config, [platform_path]):
placeholders["config_file"], line = annotation
placeholders["line"] = str(line)
placeholders["config_file"] = _relpath(hass, annotation[0])
placeholders["line"] = str(annotation[1])
show_stack_trace = True
return (log_message, show_stack_trace, placeholders)
@@ -0,0 +1,5 @@
# 'other_domain' keeps the root mapping's line different from the 'test_domain'
# key's line, so an asserted line number says which of the two find_annotation
# could answer with actually answered.
other_domain: {}
test_domain: {}
@@ -0,0 +1,6 @@
# 'other_domain' keeps the root mapping's line different from the 'test_domain'
# key's line: an error reported at 'test_domain' gets the key's line, while an
# error reported at a path absent from the config falls back to the root
# container's line.
other_domain: {}
test_domain: []
@@ -0,0 +1,3 @@
test_domain:
- platform: test_platform1
- platform: test_platform2
@@ -0,0 +1,2 @@
test_domain:
platform: test_platform
+3 -3
View File
@@ -3,13 +3,13 @@
'Unable to import test_domain: bla'
# ---
# name: test_component_config_error_processing[exception_info_list1-bla-messages1-True-config_validation_err]
'Invalid config for integration test_domain at configuration.yaml, line 140: bla'
'Invalid config for integration test_domain at error_processing.yaml, line 6: bla'
# ---
# name: test_component_config_error_processing[exception_info_list2-bla at 'path'-messages2-False-config_validation_err]
"Invalid config for integration test_domain at configuration.yaml, line 140: bla at 'path'"
"Invalid config for integration test_domain at error_processing.yaml, line 5: bla at 'path'"
# ---
# name: test_component_config_error_processing[exception_info_list3-bla at 'path'-messages3-False-platform_config_validation_err]
"Invalid config for test_domain from integration test_domain at file configuration.yaml, line 140: bla at 'path'. Check the logs for more information"
"Invalid config for test_domain from integration test_domain at file error_processing.yaml, line 5: bla at 'path'. Check the logs for more information"
# ---
# name: test_component_config_error_processing[exception_info_list4-bla-messages4-False-platform_component_load_err]
'Platform error: test_domain - bla'
+39 -34
View File
@@ -22,8 +22,7 @@ from homeassistant.exceptions import ConfigValidationError, HomeAssistantError
from homeassistant.helpers import check_config, config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import Integration, async_get_integration
from homeassistant.util.yaml import SECRET_YAML
from homeassistant.util.yaml.objects import NodeDictClass
from homeassistant.util.yaml import SECRET_YAML, load_yaml_dict
from .common import (
MockModule,
@@ -41,6 +40,13 @@ AUTOMATIONS_PATH = os.path.join(CONFIG_DIR, config_util.AUTOMATION_CONFIG_PATH)
SCRIPTS_PATH = os.path.join(CONFIG_DIR, config_util.SCRIPT_CONFIG_PATH)
SCENES_PATH = os.path.join(CONFIG_DIR, config_util.SCENE_CONFIG_PATH)
SAFE_MODE_PATH = os.path.join(CONFIG_DIR, config_util.SAFE_MODE_FILENAME)
COMPONENT_EXCEPTIONS_DIR = os.path.join(
os.path.dirname(__file__), "fixtures", "core", "config", "component_exceptions"
)
COMPONENT_EXCEPTIONS_YAML = os.path.join(COMPONENT_EXCEPTIONS_DIR, "configuration.yaml")
ERROR_PROCESSING_CONFIG = load_yaml_dict(
os.path.join(COMPONENT_EXCEPTIONS_DIR, "error_processing.yaml")
)
def create_file(path):
@@ -349,13 +355,6 @@ async def mock_custom_validator_integrations_with_docs(
)
class ConfigTestClass(NodeDictClass):
"""Test class for config with wrapper."""
__line__ = 140
__config_file__ = "configuration.yaml"
async def test_create_default_config(hass: HomeAssistant) -> None:
"""Test creation of default config."""
assert not os.path.isfile(YAML_PATH)
@@ -839,20 +838,19 @@ async def test_merge_split_component_definition(hass: HomeAssistant) -> None:
async def test_component_config_exceptions(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test unexpected exceptions validating component config."""
"""Test unexpected exceptions validating component config.
# Create test config with embedded info
test_config = ConfigTestClass({"test_domain": {}})
test_platform_config = ConfigTestClass(
{"test_domain": {"platform": "test_platform"}}
The fixture puts the root mapping on line 4 and the 'test_domain' key on line 5,
so asserting line 5 pins that find_annotation answered from the key.
"""
hass.config.config_dir = COMPONENT_EXCEPTIONS_DIR
test_config = load_yaml_dict(COMPONENT_EXCEPTIONS_YAML)
test_platform_config = load_yaml_dict(
os.path.join(COMPONENT_EXCEPTIONS_DIR, "platform.yaml")
)
test_multi_platform_config = ConfigTestClass(
{
"test_domain": [
{"platform": "test_platform1"},
{"platform": "test_platform2"},
]
},
test_multi_platform_config = load_yaml_dict(
os.path.join(COMPONENT_EXCEPTIONS_DIR, "multi_platform.yaml")
)
test_integration = Mock(
@@ -901,8 +899,8 @@ async def test_component_config_exceptions(
is None
)
assert (
"Invalid config for 'test_domain' at ../../configuration.yaml, "
"line 140: broken, please check the docs at" in caplog.text
"Invalid config for 'test_domain' at configuration.yaml, "
"line 5: broken, please check the docs at" in caplog.text
)
with pytest.raises(HomeAssistantError) as ex:
await config_util.async_process_component_and_handle_errors(
@@ -911,7 +909,7 @@ async def test_component_config_exceptions(
assert (
str(ex.value)
== "Invalid config for integration test_domain at configuration.yaml, "
"line 140: broken"
"line 5: broken"
)
# component.CONFIG_SCHEMA
caplog.clear()
@@ -1191,7 +1189,7 @@ async def test_component_config_exceptions(
ImportError("bla"),
"component_import_err",
"test_domain",
ConfigTestClass({"test_domain": []}),
ERROR_PROCESSING_CONFIG,
"https://example.com",
)
],
@@ -1206,14 +1204,14 @@ async def test_component_config_exceptions(
HomeAssistantError("bla"),
"config_validation_err",
"test_domain",
ConfigTestClass({"test_domain": []}),
ERROR_PROCESSING_CONFIG,
"https://example.com",
)
],
"bla",
[
"Invalid config for 'test_domain' at "
"../../configuration.yaml, line 140: bla, "
"error_processing.yaml, line 6: bla, "
"please check the docs at https://example.com",
"bla",
],
@@ -1226,14 +1224,14 @@ async def test_component_config_exceptions(
probatio.Invalid("bla", ["path"]),
"config_validation_err",
"test_domain",
ConfigTestClass({"test_domain": []}),
ERROR_PROCESSING_CONFIG,
"https://example.com",
)
],
"bla at 'path'",
[
"Invalid config for 'test_domain' at "
"../../configuration.yaml, line 140: bla 'path', "
"error_processing.yaml, line 5: bla 'path', "
"got None, please check the docs at https://example.com",
"bla",
],
@@ -1246,14 +1244,14 @@ async def test_component_config_exceptions(
probatio.Invalid("bla", ["path"]),
"platform_config_validation_err",
"test_domain",
ConfigTestClass({"test_domain": []}),
ERROR_PROCESSING_CONFIG,
"https://alt.example.com",
)
],
"bla at 'path'",
[
"Invalid config for 'test_domain' at "
"../../configuration.yaml, line 140: bla 'path', "
"error_processing.yaml, line 5: bla 'path', "
"got None, please check the docs at https://alt.example.com",
"bla",
],
@@ -1266,7 +1264,7 @@ async def test_component_config_exceptions(
ImportError("bla"),
"platform_component_load_err",
"test_domain",
ConfigTestClass({"test_domain": []}),
ERROR_PROCESSING_CONFIG,
"https://example.com",
)
],
@@ -1287,8 +1285,15 @@ async def test_component_config_error_processing(
show_stack_trace: bool,
translation_key: str,
) -> None:
"""Test component config error processing."""
"""Test component config error processing.
The fixture puts the root mapping on line 5 and the 'test_domain' key on line 6.
An expected message naming line 6 therefore pins that find_annotation answered
from the key, and one naming line 5 that it fell back to the container, which is
what happens when the reported path is absent from the config.
"""
hass.config.config_dir = COMPONENT_EXCEPTIONS_DIR
test_integration = Mock(
domain="test_domain",
documentation="https://example.com",
@@ -1323,7 +1328,7 @@ async def test_component_config_error_processing(
return_value=config_util.IntegrationConfigInfo(None, exception_info_list),
):
await config_util.async_process_component_and_handle_errors(
hass, ConfigTestClass({}), test_integration
hass, {}, test_integration
)
assert all(message in caplog.text for message in messages)