Fix check config (#43663)

This commit is contained in:
Paulus Schoutsen 2020-11-26 22:25:21 +01:00 committed by GitHub
parent edf70e9f06
commit e1de36fda8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 21 deletions

View file

@ -212,7 +212,13 @@ class DomainBlueprints:
"""Load a blueprint.""" """Load a blueprint."""
try: try:
blueprint_data = yaml.load_yaml(self.blueprint_folder / blueprint_path) blueprint_data = yaml.load_yaml(self.blueprint_folder / blueprint_path)
except (HomeAssistantError, FileNotFoundError) as err: except FileNotFoundError as err:
raise FailedToLoad(
self.domain,
blueprint_path,
FileNotFoundError(f"Unable to find {blueprint_path}"),
) from err
except HomeAssistantError as err:
raise FailedToLoad(self.domain, blueprint_path, err) from err raise FailedToLoad(self.domain, blueprint_path, err) from err
return Blueprint( return Blueprint(
@ -251,13 +257,25 @@ class DomainBlueprints:
async def async_get_blueprint(self, blueprint_path: str) -> Blueprint: async def async_get_blueprint(self, blueprint_path: str) -> Blueprint:
"""Get a blueprint.""" """Get a blueprint."""
def load_from_cache():
"""Load blueprint from cache."""
blueprint = self._blueprints[blueprint_path]
if blueprint is None:
raise FailedToLoad(
self.domain,
blueprint_path,
FileNotFoundError(f"Unable to find {blueprint_path}"),
)
return blueprint
if blueprint_path in self._blueprints: if blueprint_path in self._blueprints:
return self._blueprints[blueprint_path] return load_from_cache()
async with self._load_lock: async with self._load_lock:
# Check it again # Check it again
if blueprint_path in self._blueprints: if blueprint_path in self._blueprints:
return self._blueprints[blueprint_path] return load_from_cache()
try: try:
blueprint = await self.hass.async_add_executor_job( blueprint = await self.hass.async_add_executor_job(

View file

@ -1,9 +1,9 @@
"""Helper to check the configuration file.""" """Helper to check the configuration file."""
from collections import OrderedDict from collections import OrderedDict
import logging
import os import os
from typing import List, NamedTuple, Optional from typing import List, NamedTuple, Optional
import attr
import voluptuous as vol import voluptuous as vol
from homeassistant import loader from homeassistant import loader
@ -36,11 +36,13 @@ class CheckConfigError(NamedTuple):
config: Optional[ConfigType] config: Optional[ConfigType]
@attr.s
class HomeAssistantConfig(OrderedDict): class HomeAssistantConfig(OrderedDict):
"""Configuration result with errors attribute.""" """Configuration result with errors attribute."""
errors: List[CheckConfigError] = attr.ib(factory=list) def __init__(self) -> None:
"""Initialize HA config."""
super().__init__()
self.errors: List[CheckConfigError] = []
def add_error( def add_error(
self, self,
@ -139,14 +141,24 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> HomeAssistantConfig
config_validator, "async_validate_config" config_validator, "async_validate_config"
): ):
try: try:
return await config_validator.async_validate_config( # type: ignore result[domain] = (
hass, config await config_validator.async_validate_config( # type: ignore
) hass, config
)
)[domain]
continue
except (vol.Invalid, HomeAssistantError) as ex: except (vol.Invalid, HomeAssistantError) as ex:
_comp_error(ex, domain, config) _comp_error(ex, domain, config)
continue continue
except Exception: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
result.add_error("Unknown error calling %s config validator", domain) logging.getLogger(__name__).exception(
"Unexpected error validating config"
)
result.add_error(
f"Unexpected error calling config validator: {err}",
domain,
config.get(domain),
)
continue continue
config_schema = getattr(component, "CONFIG_SCHEMA", None) config_schema = getattr(component, "CONFIG_SCHEMA", None)

View file

@ -203,8 +203,8 @@ async def test_domain_blueprints_get_blueprint_errors(hass, domain_bps):
with patch( with patch(
"homeassistant.util.yaml.load_yaml", return_value={"blueprint": "invalid"} "homeassistant.util.yaml.load_yaml", return_value={"blueprint": "invalid"}
): ), pytest.raises(errors.FailedToLoad):
assert await domain_bps.async_get_blueprint("non-existing-path") is None await domain_bps.async_get_blueprint("non-existing-path")
async def test_domain_blueprints_caching(domain_bps): async def test_domain_blueprints_caching(domain_bps):
@ -258,3 +258,11 @@ async def test_domain_blueprints_add_blueprint(domain_bps, blueprint_1):
with patch.object(domain_bps, "_load_blueprint") as mock_load: with patch.object(domain_bps, "_load_blueprint") as mock_load:
assert await domain_bps.async_get_blueprint("something.yaml") == blueprint_1 assert await domain_bps.async_get_blueprint("something.yaml") == blueprint_1
assert not mock_load.mock_calls assert not mock_load.mock_calls
async def test_inputs_from_config_nonexisting_blueprint(domain_bps):
"""Test referring non-existing blueprint."""
with pytest.raises(errors.FailedToLoad):
await domain_bps.async_inputs_from_config(
{"use_blueprint": {"path": "non-existing.yaml"}}
)

View file

@ -7,8 +7,8 @@ from homeassistant.helpers.check_config import (
async_check_ha_config_file, async_check_ha_config_file,
) )
from tests.async_mock import patch from tests.async_mock import Mock, patch
from tests.common import patch_yaml_files from tests.common import mock_platform, patch_yaml_files
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,7 +37,7 @@ def log_ha_config(conf):
_LOGGER.debug("error[%s] = %s", cnt, err) _LOGGER.debug("error[%s] = %s", cnt, err)
async def test_bad_core_config(hass, loop): async def test_bad_core_config(hass):
"""Test a bad core config setup.""" """Test a bad core config setup."""
files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG} files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG}
with patch("os.path.isfile", return_value=True), patch_yaml_files(files): with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
@ -53,7 +53,7 @@ async def test_bad_core_config(hass, loop):
assert not res.errors assert not res.errors
async def test_config_platform_valid(hass, loop): async def test_config_platform_valid(hass):
"""Test a valid platform setup.""" """Test a valid platform setup."""
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"} files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"}
with patch("os.path.isfile", return_value=True), patch_yaml_files(files): with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
@ -65,7 +65,7 @@ async def test_config_platform_valid(hass, loop):
assert not res.errors assert not res.errors
async def test_component_platform_not_found(hass, loop): async def test_component_platform_not_found(hass):
"""Test errors if component or platform not found.""" """Test errors if component or platform not found."""
# Make sure they don't exist # Make sure they don't exist
files = {YAML_CONFIG_FILE: BASE_CONFIG + "beer:"} files = {YAML_CONFIG_FILE: BASE_CONFIG + "beer:"}
@ -83,7 +83,7 @@ async def test_component_platform_not_found(hass, loop):
assert not res.errors assert not res.errors
async def test_component_platform_not_found_2(hass, loop): async def test_component_platform_not_found_2(hass):
"""Test errors if component or platform not found.""" """Test errors if component or platform not found."""
# Make sure they don't exist # Make sure they don't exist
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: beer"} files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: beer"}
@ -103,7 +103,7 @@ async def test_component_platform_not_found_2(hass, loop):
assert not res.errors assert not res.errors
async def test_package_invalid(hass, loop): async def test_package_invalid(hass):
"""Test a valid platform setup.""" """Test a valid platform setup."""
files = { files = {
YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]') YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]')
@ -121,7 +121,7 @@ async def test_package_invalid(hass, loop):
assert res.keys() == {"homeassistant"} assert res.keys() == {"homeassistant"}
async def test_bootstrap_error(hass, loop): async def test_bootstrap_error(hass):
"""Test a valid platform setup.""" """Test a valid platform setup."""
files = {YAML_CONFIG_FILE: BASE_CONFIG + "automation: !include no.yaml"} files = {YAML_CONFIG_FILE: BASE_CONFIG + "automation: !include no.yaml"}
with patch("os.path.isfile", return_value=True), patch_yaml_files(files): with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
@ -146,6 +146,7 @@ automation:
input: input:
trigger_event: blueprint_event trigger_event: blueprint_event
service_to_call: test.automation service_to_call: test.automation
input_datetime:
""", """,
hass.config.path( hass.config.path(
"blueprints/automation/test_event_service.yaml" "blueprints/automation/test_event_service.yaml"
@ -166,3 +167,28 @@ action:
with patch("os.path.isfile", return_value=True), patch_yaml_files(files): with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
res = await async_check_ha_config_file(hass) res = await async_check_ha_config_file(hass)
assert len(res.get("automation", [])) == 1 assert len(res.get("automation", [])) == 1
assert len(res.errors) == 0
assert "input_datetime" in res
async def test_config_platform_raise(hass):
"""Test bad config validation platform."""
mock_platform(
hass,
"bla.config",
Mock(async_validate_config=Mock(side_effect=Exception("Broken"))),
)
files = {
YAML_CONFIG_FILE: BASE_CONFIG
+ """
bla:
value: 1
""",
}
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
res = await async_check_ha_config_file(hass)
assert len(res.errors) == 1
err = res.errors[0]
assert err.domain == "bla"
assert err.message == "Unexpected error calling config validator: Broken"
assert err.config == {"value": 1}