Make it possible to list debug traces for a specific automation ()

pull/48000/head
Erik Montnemery 2021-03-16 00:51:04 +01:00 committed by GitHub
parent 5f627df6f8
commit f82e59c32a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 39 deletions
homeassistant
tests/components/automation

View File

@ -109,6 +109,7 @@ class AutomationTrace:
trigger = self._variables.get("trigger", {}).get("description")
result = {
"automation_id": self._unique_id,
"last_action": last_action,
"last_condition": last_condition,
"run_id": self.run_id,
@ -196,11 +197,9 @@ def get_debug_traces_for_automation(hass, automation_id, summary=False):
@callback
def get_debug_traces(hass, summary=False):
"""Return a serializable list of debug traces."""
traces = {}
traces = []
for automation_id in hass.data[DATA_AUTOMATION_TRACE]:
traces[automation_id] = get_debug_traces_for_automation(
hass, automation_id, summary
)
traces.extend(get_debug_traces_for_automation(hass, automation_id, summary))
return traces

View File

@ -21,7 +21,7 @@ from homeassistant.helpers.script import (
debug_stop,
)
from .trace import get_debug_trace, get_debug_traces
from .trace import get_debug_trace, get_debug_traces, get_debug_traces_for_automation
# mypy: allow-untyped-calls, allow-untyped-defs
@ -50,7 +50,7 @@ def async_setup(hass: HomeAssistant) -> None:
}
)
def websocket_automation_trace_get(hass, connection, msg):
"""Get automation traces."""
"""Get an automation trace."""
automation_id = msg["automation_id"]
run_id = msg["run_id"]
@ -61,10 +61,19 @@ def websocket_automation_trace_get(hass, connection, msg):
@callback
@websocket_api.require_admin
@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"})
@websocket_api.websocket_command(
{vol.Required("type"): "automation/trace/list", vol.Optional("automation_id"): str}
)
def websocket_automation_trace_list(hass, connection, msg):
"""Summarize automation traces."""
automation_traces = get_debug_traces(hass, summary=True)
automation_id = msg.get("automation_id")
if not automation_id:
automation_traces = get_debug_traces(hass, summary=True)
else:
automation_traces = get_debug_traces_for_automation(
hass, automation_id, summary=True
)
connection.send_result(msg["id"], automation_traces)

View File

@ -77,8 +77,8 @@ ConditionCheckerType = Callable[[HomeAssistant, TemplateVarsType], bool]
def condition_trace_append(variables: TemplateVarsType, path: str) -> TraceElement:
"""Append a TraceElement to trace[path]."""
trace_element = TraceElement(variables)
trace_append_element(trace_element, path)
trace_element = TraceElement(variables, path)
trace_append_element(trace_element)
return trace_element

View File

@ -137,8 +137,8 @@ SCRIPT_DEBUG_CONTINUE_ALL = "script_debug_continue_all"
def action_trace_append(variables, path):
"""Append a TraceElement to trace[path]."""
trace_element = TraceElement(variables)
trace_append_element(trace_element, path, ACTION_TRACE_NODE_MAX_LEN)
trace_element = TraceElement(variables, path)
trace_append_element(trace_element, ACTION_TRACE_NODE_MAX_LEN)
return trace_element

View File

@ -11,9 +11,10 @@ import homeassistant.util.dt as dt_util
class TraceElement:
"""Container for trace data."""
def __init__(self, variables: TemplateVarsType):
def __init__(self, variables: TemplateVarsType, path: str):
"""Container for trace data."""
self._error: Optional[Exception] = None
self.path: str = path
self._result: Optional[dict] = None
self._timestamp = dt_util.utcnow()
@ -42,7 +43,7 @@ class TraceElement:
def as_dict(self) -> Dict[str, Any]:
"""Return dictionary version of this TraceElement."""
result: Dict[str, Any] = {"timestamp": self._timestamp}
result: Dict[str, Any] = {"path": self.path, "timestamp": self._timestamp}
if self._variables:
result["changed_variables"] = self._variables
if self._error is not None:
@ -129,10 +130,10 @@ def trace_path_get() -> str:
def trace_append_element(
trace_element: TraceElement,
path: str,
maxlen: Optional[int] = None,
) -> None:
"""Append a TraceElement to trace[path]."""
path = trace_element.path
trace = trace_cv.get()
if trace is None:
trace = {}

View File

@ -9,6 +9,20 @@ from tests.common import assert_lists_same
from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401
def _find_run_id(traces, automation_id):
"""Find newest run_id for an automation."""
for trace in reversed(traces):
if trace["automation_id"] == automation_id:
return trace["run_id"]
return None
def _find_traces_for_automation(traces, automation_id):
"""Find traces for an automation."""
return [trace for trace in traces if trace["automation_id"] == automation_id]
async def test_get_automation_trace(hass, hass_ws_client):
"""Test tracing an automation."""
id = 1
@ -61,7 +75,7 @@ async def test_get_automation_trace(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
run_id = response["result"]["sun"][-1]["run_id"]
run_id = _find_run_id(response["result"], "sun")
# Get trace
await client.send_json(
@ -97,7 +111,7 @@ async def test_get_automation_trace(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
run_id = response["result"]["moon"][-1]["run_id"]
run_id = _find_run_id(response["result"], "moon")
# Get trace
await client.send_json(
@ -134,7 +148,7 @@ async def test_get_automation_trace(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
run_id = response["result"]["moon"][-1]["run_id"]
run_id = _find_run_id(response["result"], "moon")
# Get trace
await client.send_json(
@ -168,7 +182,7 @@ async def test_get_automation_trace(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
run_id = response["result"]["moon"][-1]["run_id"]
run_id = _find_run_id(response["result"], "moon")
# Get trace
await client.send_json(
@ -237,7 +251,7 @@ async def test_automation_trace_overflow(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
assert response["result"] == []
# Trigger "sun" and "moon" automation once
hass.bus.async_fire("test_event")
@ -248,9 +262,9 @@ async def test_automation_trace_overflow(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
assert len(response["result"]["moon"]) == 1
moon_run_id = response["result"]["moon"][0]["run_id"]
assert len(response["result"]["sun"]) == 1
assert len(_find_traces_for_automation(response["result"], "moon")) == 1
moon_run_id = _find_run_id(response["result"], "moon")
assert len(_find_traces_for_automation(response["result"], "sun")) == 1
# Trigger "moon" automation enough times to overflow the number of stored traces
for _ in range(automation.trace.STORED_TRACES):
@ -260,13 +274,15 @@ async def test_automation_trace_overflow(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
assert len(response["result"]["moon"]) == automation.trace.STORED_TRACES
assert len(response["result"]["sun"]) == 1
assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1
moon_traces = _find_traces_for_automation(response["result"], "moon")
assert len(moon_traces) == automation.trace.STORED_TRACES
assert moon_traces[0]
assert int(moon_traces[0]["run_id"]) == int(moon_run_id) + 1
assert (
int(response["result"]["moon"][-1]["run_id"])
int(moon_traces[-1]["run_id"])
== int(moon_run_id) + automation.trace.STORED_TRACES
)
assert len(_find_traces_for_automation(response["result"], "sun")) == 1
async def test_list_automation_traces(hass, hass_ws_client):
@ -315,7 +331,14 @@ async def test_list_automation_traces(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
assert response["result"] == []
await client.send_json(
{"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == []
# Trigger "sun" automation
hass.bus.async_fire("test_event")
@ -325,8 +348,23 @@ async def test_list_automation_traces(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
assert "moon" not in response["result"]
assert len(response["result"]["sun"]) == 1
assert len(response["result"]) == 1
assert len(_find_traces_for_automation(response["result"], "sun")) == 1
await client.send_json(
{"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"}
)
response = await client.receive_json()
assert response["success"]
assert len(response["result"]) == 1
assert len(_find_traces_for_automation(response["result"], "sun")) == 1
await client.send_json(
{"id": next_id(), "type": "automation/trace/list", "automation_id": "moon"}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == []
# Trigger "moon" automation, with passing condition
hass.bus.async_fire("test_event2")
@ -344,9 +382,9 @@ async def test_list_automation_traces(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
assert len(response["result"]["moon"]) == 3
assert len(response["result"]["sun"]) == 1
trace = response["result"]["sun"][0]
assert len(_find_traces_for_automation(response["result"], "moon")) == 3
assert len(_find_traces_for_automation(response["result"], "sun")) == 1
trace = _find_traces_for_automation(response["result"], "sun")[0]
assert trace["last_action"] == "action/0"
assert trace["last_condition"] is None
assert trace["error"] == "Unable to find service test.automation"
@ -355,7 +393,7 @@ async def test_list_automation_traces(hass, hass_ws_client):
assert trace["trigger"] == "event 'test_event'"
assert trace["unique_id"] == "sun"
trace = response["result"]["moon"][0]
trace = _find_traces_for_automation(response["result"], "moon")[0]
assert trace["last_action"] == "action/0"
assert trace["last_condition"] == "condition/0"
assert "error" not in trace
@ -364,7 +402,7 @@ async def test_list_automation_traces(hass, hass_ws_client):
assert trace["trigger"] == "event 'test_event2'"
assert trace["unique_id"] == "moon"
trace = response["result"]["moon"][1]
trace = _find_traces_for_automation(response["result"], "moon")[1]
assert trace["last_action"] is None
assert trace["last_condition"] == "condition/0"
assert "error" not in trace
@ -373,7 +411,7 @@ async def test_list_automation_traces(hass, hass_ws_client):
assert trace["trigger"] == "event 'test_event3'"
assert trace["unique_id"] == "moon"
trace = response["result"]["moon"][2]
trace = _find_traces_for_automation(response["result"], "moon")[2]
assert trace["last_action"] == "action/0"
assert trace["last_condition"] == "condition/0"
assert "error" not in trace
@ -396,7 +434,7 @@ async def test_automation_breakpoints(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
trace = response["result"][automation_id][-1]
trace = _find_traces_for_automation(response["result"], automation_id)[-1]
assert trace["last_action"] == expected_action
assert trace["state"] == expected_state
return trace["run_id"]
@ -567,7 +605,7 @@ async def test_automation_breakpoints_2(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
trace = response["result"][automation_id][-1]
trace = _find_traces_for_automation(response["result"], automation_id)[-1]
assert trace["last_action"] == expected_action
assert trace["state"] == expected_state
return trace["run_id"]
@ -674,7 +712,7 @@ async def test_automation_breakpoints_3(hass, hass_ws_client):
await client.send_json({"id": next_id(), "type": "automation/trace/list"})
response = await client.receive_json()
assert response["success"]
trace = response["result"][automation_id][-1]
trace = _find_traces_for_automation(response["result"], automation_id)[-1]
assert trace["last_action"] == expected_action
assert trace["state"] == expected_state
return trace["run_id"]