Make it possible to list debug traces for a specific automation (#47744)
parent
5f627df6f8
commit
f82e59c32a
homeassistant
components/automation
tests/components/automation
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Reference in New Issue