Ensure all template errors are caught and the websocket api reports them (#41719)

pull/41703/head
J. Nick Koston 2020-10-12 09:38:24 -05:00 committed by GitHub
parent f7e7f1371e
commit b897ca7260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 11 deletions

View File

@ -257,13 +257,20 @@ async def handle_render_template(hass, connection, msg):
timeout = msg.get("timeout")
info = None
if timeout and await template.async_render_will_timeout(timeout):
connection.send_error(
msg["id"],
const.ERR_TEMPLATE_ERROR,
f"Exceeded maximum execution time of {timeout}s",
)
return
if timeout:
try:
timed_out = await template.async_render_will_timeout(timeout)
except TemplateError as ex:
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
return
if timed_out:
connection.send_error(
msg["id"],
const.ERR_TEMPLATE_ERROR,
f"Exceeded maximum execution time of {timeout}s",
)
return
@callback
def _template_listener(event, updates):

View File

@ -1,8 +1,6 @@
"""The exceptions used by Home Assistant."""
from typing import TYPE_CHECKING, Optional
import jinja2
if TYPE_CHECKING:
from .core import Context # noqa: F401 pylint: disable=unused-import
@ -22,7 +20,7 @@ class NoEntitySpecifiedError(HomeAssistantError):
class TemplateError(HomeAssistantError):
"""Error during template rendering."""
def __init__(self, exception: jinja2.TemplateError) -> None:
def __init__(self, exception: Exception) -> None:
"""Init the error."""
super().__init__(f"{exception.__class__.__name__}: {exception}")

View File

@ -331,7 +331,7 @@ class Template:
try:
render_result = compiled.render(kwargs)
except jinja2.TemplateError as err:
except Exception as err: # pylint: disable=broad-except
raise TemplateError(err) from err
render_result = render_result.strip()

View File

@ -485,6 +485,41 @@ async def test_render_template_with_error(hass, websocket_client, caplog):
assert "TemplateError" not in caplog.text
async def test_render_template_with_timeout_and_error(hass, websocket_client, caplog):
"""Test a template with an error with a timeout."""
await websocket_client.send_json(
{
"id": 5,
"type": "render_template",
"template": "{{ now() | rando }}",
"timeout": 5,
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 5
assert msg["type"] == const.TYPE_RESULT
assert not msg["success"]
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
assert "TemplateError" not in caplog.text
async def test_render_template_error_in_template_code(hass, websocket_client, caplog):
"""Test a template that will throw in template.py."""
await websocket_client.send_json(
{"id": 5, "type": "render_template", "template": "{{ now() | random }}"}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 5
assert msg["type"] == const.TYPE_RESULT
assert not msg["success"]
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
assert "TemplateError" not in caplog.text
async def test_render_template_with_delayed_error(hass, websocket_client, caplog):
"""Test a template with an error that only happens after a state change."""
hass.states.async_set("sensor.test", "on")

View File

@ -2533,6 +2533,16 @@ async def test_lights(hass):
assert f"sensor{i}" in info.result()
async def test_template_errors(hass):
"""Test template rendering wraps exceptions with TemplateError."""
with pytest.raises(TemplateError):
template.Template("{{ now() | rando }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ now() | random }}", hass).async_render()
async def test_state_attributes(hass):
"""Test state attributes."""
hass.states.async_set("sensor.test", "23")