Speed up the frame helper (#112562)
parent
3ccbb2c87a
commit
1fb9cfe37e
|
@ -6,15 +6,21 @@ from collections.abc import Callable
|
|||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
import functools
|
||||
import linecache
|
||||
import logging
|
||||
import sys
|
||||
from traceback import FrameSummary, extract_stack
|
||||
from typing import Any, TypeVar, cast
|
||||
from types import FrameType
|
||||
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||
|
||||
from homeassistant.core import HomeAssistant, async_get_hass
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.loader import async_suggest_report_issue
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from functools import cached_property
|
||||
else:
|
||||
from homeassistant.backports.functools import cached_property
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Keep track of integrations already reported to prevent flooding
|
||||
|
@ -28,10 +34,25 @@ class IntegrationFrame:
|
|||
"""Integration frame container."""
|
||||
|
||||
custom_integration: bool
|
||||
frame: FrameSummary
|
||||
integration: str
|
||||
module: str | None
|
||||
relative_filename: str
|
||||
_frame: FrameType
|
||||
|
||||
@cached_property
|
||||
def line_number(self) -> int:
|
||||
"""Return the line number of the frame."""
|
||||
return self._frame.f_lineno
|
||||
|
||||
@cached_property
|
||||
def filename(self) -> str:
|
||||
"""Return the filename of the frame."""
|
||||
return self._frame.f_code.co_filename
|
||||
|
||||
@cached_property
|
||||
def line(self) -> str:
|
||||
"""Return the line of the frame."""
|
||||
return (linecache.getline(self.filename, self.line_number) or "?").strip()
|
||||
|
||||
|
||||
def get_integration_logger(fallback_name: str) -> logging.Logger:
|
||||
|
@ -54,19 +75,28 @@ def get_integration_logger(fallback_name: str) -> logging.Logger:
|
|||
return logging.getLogger(logger_name)
|
||||
|
||||
|
||||
def get_current_frame(depth: int = 0) -> FrameType:
|
||||
"""Return the current frame."""
|
||||
# Add one to depth since get_current_frame is included
|
||||
return sys._getframe(depth + 1) # pylint: disable=protected-access
|
||||
|
||||
|
||||
def get_integration_frame(exclude_integrations: set | None = None) -> IntegrationFrame:
|
||||
"""Return the frame, integration and integration path of the current stack frame."""
|
||||
found_frame = None
|
||||
if not exclude_integrations:
|
||||
exclude_integrations = set()
|
||||
|
||||
for frame in reversed(extract_stack()):
|
||||
frame: FrameType | None = get_current_frame()
|
||||
while frame is not None:
|
||||
filename = frame.f_code.co_filename
|
||||
|
||||
for path in ("custom_components/", "homeassistant/components/"):
|
||||
try:
|
||||
index = frame.filename.index(path)
|
||||
index = filename.index(path)
|
||||
start = index + len(path)
|
||||
end = frame.filename.index("/", start)
|
||||
integration = frame.filename[start:end]
|
||||
end = filename.index("/", start)
|
||||
integration = filename[start:end]
|
||||
if integration not in exclude_integrations:
|
||||
found_frame = frame
|
||||
|
||||
|
@ -77,6 +107,8 @@ def get_integration_frame(exclude_integrations: set | None = None) -> Integratio
|
|||
if found_frame is not None:
|
||||
break
|
||||
|
||||
frame = frame.f_back
|
||||
|
||||
if found_frame is None:
|
||||
raise MissingIntegrationFrame
|
||||
|
||||
|
@ -84,16 +116,16 @@ def get_integration_frame(exclude_integrations: set | None = None) -> Integratio
|
|||
for module, module_obj in dict(sys.modules).items():
|
||||
if not hasattr(module_obj, "__file__"):
|
||||
continue
|
||||
if module_obj.__file__ == found_frame.filename:
|
||||
if module_obj.__file__ == found_frame.f_code.co_filename:
|
||||
found_module = module
|
||||
break
|
||||
|
||||
return IntegrationFrame(
|
||||
custom_integration=path == "custom_components/",
|
||||
frame=found_frame,
|
||||
integration=integration,
|
||||
module=found_module,
|
||||
relative_filename=found_frame.filename[index:],
|
||||
relative_filename=found_frame.f_code.co_filename[index:],
|
||||
_frame=found_frame,
|
||||
)
|
||||
|
||||
|
||||
|
@ -137,9 +169,8 @@ def _report_integration(
|
|||
|
||||
Async friendly.
|
||||
"""
|
||||
found_frame = integration_frame.frame
|
||||
# Keep track of integrations already reported to prevent flooding
|
||||
key = f"{found_frame.filename}:{found_frame.lineno}"
|
||||
key = f"{integration_frame.filename}:{integration_frame.line_number}"
|
||||
if key in _REPORTED_INTEGRATIONS:
|
||||
return
|
||||
_REPORTED_INTEGRATIONS.add(key)
|
||||
|
@ -160,8 +191,8 @@ def _report_integration(
|
|||
integration_frame.integration,
|
||||
what,
|
||||
integration_frame.relative_filename,
|
||||
found_frame.lineno,
|
||||
(found_frame.line or "?").strip(),
|
||||
integration_frame.line_number,
|
||||
integration_frame.line,
|
||||
report_issue,
|
||||
)
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import functools
|
|||
import logging
|
||||
import sys
|
||||
import threading
|
||||
from traceback import extract_stack
|
||||
from typing import Any, ParamSpec, TypeVar, TypeVarTuple
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
@ -116,14 +115,6 @@ def check_loop(
|
|||
The default advisory message is 'Use `await hass.async_add_executor_job()'
|
||||
Set `advise_msg` to an alternate message if the solution differs.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from homeassistant.core import HomeAssistant, async_get_hass
|
||||
from homeassistant.helpers.frame import (
|
||||
MissingIntegrationFrame,
|
||||
get_integration_frame,
|
||||
)
|
||||
from homeassistant.loader import async_suggest_report_issue
|
||||
|
||||
try:
|
||||
get_running_loop()
|
||||
in_loop = True
|
||||
|
@ -133,18 +124,32 @@ def check_loop(
|
|||
if not in_loop:
|
||||
return
|
||||
|
||||
# Import only after we know we are running in the event loop
|
||||
# so threads do not have to pay the late import cost.
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from homeassistant.core import HomeAssistant, async_get_hass
|
||||
from homeassistant.helpers.frame import (
|
||||
MissingIntegrationFrame,
|
||||
get_current_frame,
|
||||
get_integration_frame,
|
||||
)
|
||||
from homeassistant.loader import async_suggest_report_issue
|
||||
|
||||
found_frame = None
|
||||
|
||||
stack = extract_stack()
|
||||
|
||||
if (
|
||||
func.__name__ == "sleep"
|
||||
and len(stack) >= 3
|
||||
and stack[-3].filename.endswith("pydevd.py")
|
||||
):
|
||||
# Don't report `time.sleep` injected by the debugger (pydevd.py)
|
||||
# stack[-1] is us, stack[-2] is protected_loop_func, stack[-3] is the offender
|
||||
return
|
||||
if func.__name__ == "sleep":
|
||||
#
|
||||
# Avoid extracting the stack unless we need to since it
|
||||
# will have to access the linecache which can do blocking
|
||||
# I/O and we are trying to avoid blocking calls.
|
||||
#
|
||||
# frame[1] is us
|
||||
# frame[2] is protected_loop_func
|
||||
# frame[3] is the offender
|
||||
with suppress(ValueError):
|
||||
offender_frame = get_current_frame(3)
|
||||
if offender_frame.f_code.co_filename.endswith("pydevd.py"):
|
||||
return
|
||||
|
||||
try:
|
||||
integration_frame = get_integration_frame()
|
||||
|
@ -167,7 +172,6 @@ def check_loop(
|
|||
module=integration_frame.module,
|
||||
)
|
||||
|
||||
found_frame = integration_frame.frame
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Detected blocking call to %s inside the event loop by %sintegration '%s' "
|
||||
|
@ -177,8 +181,8 @@ def check_loop(
|
|||
"custom " if integration_frame.custom_integration else "",
|
||||
integration_frame.integration,
|
||||
integration_frame.relative_filename,
|
||||
found_frame.lineno,
|
||||
(found_frame.line or "?").strip(),
|
||||
integration_frame.line_number,
|
||||
integration_frame.line,
|
||||
report_issue,
|
||||
)
|
||||
|
||||
|
@ -186,8 +190,8 @@ def check_loop(
|
|||
raise RuntimeError(
|
||||
"Blocking calls must be done in the executor or a separate thread;"
|
||||
f" {advise_msg or 'Use `await hass.async_add_executor_job()`'}; at"
|
||||
f" {integration_frame.relative_filename}, line {found_frame.lineno}:"
|
||||
f" {(found_frame.line or '?').strip()}"
|
||||
f" {integration_frame.relative_filename}, line {integration_frame.line_number}:"
|
||||
f" {integration_frame.line}"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import os
|
|||
import pathlib
|
||||
import threading
|
||||
import time
|
||||
from types import ModuleType
|
||||
from types import FrameType, ModuleType
|
||||
from typing import Any, NoReturn, TypeVar
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
|
@ -1596,3 +1596,20 @@ def help_test_all(module: ModuleType) -> None:
|
|||
assert set(module.__all__) == {
|
||||
itm for itm in module.__dir__() if not itm.startswith("_")
|
||||
}
|
||||
|
||||
|
||||
def extract_stack_to_frame(extract_stack: list[Mock]) -> FrameType:
|
||||
"""Convert an extract stack to a frame list."""
|
||||
stack = list(extract_stack)
|
||||
for frame in stack:
|
||||
frame.f_back = None
|
||||
frame.f_code.co_filename = frame.filename
|
||||
frame.f_lineno = int(frame.lineno)
|
||||
|
||||
top_frame = stack.pop()
|
||||
current_frame = top_frame
|
||||
while stack and (next_frame := stack.pop()):
|
||||
current_frame.f_back = next_frame
|
||||
current_frame = next_frame
|
||||
|
||||
return top_frame
|
||||
|
|
|
@ -9,6 +9,8 @@ from homeassistant.components.zeroconf.usage import install_multiple_zeroconf_ca
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import extract_stack_to_frame
|
||||
|
||||
DOMAIN = "zeroconf"
|
||||
|
||||
|
||||
|
@ -50,25 +52,29 @@ async def test_multiple_zeroconf_instances_gives_shared(
|
|||
line="self.light.is_on",
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/dev/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
correct_frame,
|
||||
Mock(
|
||||
filename="/home/dev/homeassistant/components/zeroconf/usage.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/dev/mdns/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/dev/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
correct_frame,
|
||||
Mock(
|
||||
filename="/home/dev/homeassistant/components/zeroconf/usage.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/dev/mdns/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
assert zeroconf.Zeroconf() == zeroconf_instance
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ from .common import ( # noqa: E402, isort:skip
|
|||
init_recorder_component,
|
||||
mock_storage,
|
||||
patch_yaml_files,
|
||||
extract_stack_to_frame,
|
||||
)
|
||||
from .test_util.aiohttp import ( # noqa: E402, isort:skip
|
||||
AiohttpClientMocker,
|
||||
|
@ -1588,20 +1589,24 @@ def mock_integration_frame() -> Generator[Mock, None, None]:
|
|||
line="self.light.is_on",
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
correct_frame,
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
correct_frame,
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
yield correct_frame
|
||||
|
||||
|
|
|
@ -20,7 +20,12 @@ from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE, HomeAssistant
|
|||
import homeassistant.helpers.aiohttp_client as client
|
||||
from homeassistant.util.color import RGBColor
|
||||
|
||||
from tests.common import MockConfigEntry, MockModule, mock_integration
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
MockModule,
|
||||
extract_stack_to_frame,
|
||||
mock_integration,
|
||||
)
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
|
@ -166,24 +171,29 @@ async def test_warning_close_session_integration(
|
|||
) -> None:
|
||||
"""Test log warning message when closing the session from integration context."""
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline",
|
||||
return_value="await session.close()",
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
session = client.async_get_clientsession(hass)
|
||||
await session.close()
|
||||
|
@ -202,24 +212,29 @@ async def test_warning_close_session_custom(
|
|||
"""Test log warning message when closing the session from custom context."""
|
||||
mock_integration(hass, MockModule("hue"), built_in=False)
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/config/custom_components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline",
|
||||
return_value="await session.close()",
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/config/custom_components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
session = client.async_get_clientsession(hass)
|
||||
await session.close()
|
||||
|
|
|
@ -20,7 +20,7 @@ from homeassistant.helpers.deprecation import (
|
|||
)
|
||||
from homeassistant.helpers.frame import MissingIntegrationFrame
|
||||
|
||||
from tests.common import MockModule, mock_integration
|
||||
from tests.common import MockModule, extract_stack_to_frame, mock_integration
|
||||
|
||||
|
||||
class MockBaseClassDeprecatedProperty:
|
||||
|
@ -178,24 +178,29 @@ def test_deprecated_function_called_from_built_in_integration(
|
|||
pass
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline",
|
||||
return_value="await session.close()",
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
mock_deprecated_function()
|
||||
assert (
|
||||
|
@ -230,24 +235,29 @@ def test_deprecated_function_called_from_custom_integration(
|
|||
pass
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/config/custom_components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline",
|
||||
return_value="await session.close()",
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/config/custom_components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
mock_deprecated_function()
|
||||
assert (
|
||||
|
@ -327,24 +337,29 @@ def test_check_if_deprecated_constant(
|
|||
|
||||
# mock sys.modules for homeassistant/helpers/frame.py#get_integration_frame
|
||||
with patch.dict(sys.modules, {module_name: Mock(__file__=filename)}), patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename=filename,
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline",
|
||||
return_value="await session.close()",
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename=filename,
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
value = check_if_deprecated_constant("TEST_CONSTANT", module_globals)
|
||||
assert value == _get_value(deprecated_constant)
|
||||
|
@ -397,7 +412,8 @@ def test_check_if_deprecated_constant_integration_not_found(
|
|||
}
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack", side_effect=MissingIntegrationFrame
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
side_effect=MissingIntegrationFrame,
|
||||
):
|
||||
value = check_if_deprecated_constant("TEST_CONSTANT", module_globals)
|
||||
assert value == _get_value(deprecated_constant)
|
||||
|
|
|
@ -7,6 +7,8 @@ import pytest
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import frame
|
||||
|
||||
from tests.common import extract_stack_to_frame
|
||||
|
||||
|
||||
async def test_extract_frame_integration(
|
||||
caplog: pytest.LogCaptureFixture, mock_integration_frame: Mock
|
||||
|
@ -15,7 +17,7 @@ async def test_extract_frame_integration(
|
|||
integration_frame = frame.get_integration_frame()
|
||||
assert integration_frame == frame.IntegrationFrame(
|
||||
custom_integration=False,
|
||||
frame=mock_integration_frame,
|
||||
_frame=mock_integration_frame,
|
||||
integration="hue",
|
||||
module=None,
|
||||
relative_filename="homeassistant/components/hue/light.py",
|
||||
|
@ -40,7 +42,7 @@ async def test_extract_frame_resolve_module(
|
|||
|
||||
assert integration_frame == frame.IntegrationFrame(
|
||||
custom_integration=True,
|
||||
frame=ANY,
|
||||
_frame=ANY,
|
||||
integration="test_integration_frame",
|
||||
module="custom_components.test_integration_frame",
|
||||
relative_filename="custom_components/test_integration_frame/__init__.py",
|
||||
|
@ -68,25 +70,27 @@ async def test_extract_frame_integration_with_excluded_integration(
|
|||
line="self.light.is_on",
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/dev/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
correct_frame,
|
||||
Mock(
|
||||
filename="/home/dev/homeassistant/components/zeroconf/usage.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/dev/mdns/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/dev/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
correct_frame,
|
||||
Mock(
|
||||
filename="/home/dev/homeassistant/components/zeroconf/usage.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/dev/mdns/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
integration_frame = frame.get_integration_frame(
|
||||
exclude_integrations={"zeroconf"}
|
||||
|
@ -94,7 +98,7 @@ async def test_extract_frame_integration_with_excluded_integration(
|
|||
|
||||
assert integration_frame == frame.IntegrationFrame(
|
||||
custom_integration=False,
|
||||
frame=correct_frame,
|
||||
_frame=correct_frame,
|
||||
integration="mdns",
|
||||
module=None,
|
||||
relative_filename="homeassistant/components/mdns/light.py",
|
||||
|
@ -104,19 +108,21 @@ async def test_extract_frame_integration_with_excluded_integration(
|
|||
async def test_extract_frame_no_integration(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test extracting the current frame without integration context."""
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
), pytest.raises(frame.MissingIntegrationFrame):
|
||||
frame.get_integration_frame()
|
||||
|
||||
|
@ -126,19 +132,21 @@ async def test_get_integration_logger_no_integration(
|
|||
) -> None:
|
||||
"""Test getting fallback logger without integration context."""
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
logger = frame.get_integration_logger(__name__)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import pytest
|
|||
from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE, HomeAssistant
|
||||
import homeassistant.helpers.httpx_client as client
|
||||
|
||||
from tests.common import MockModule, mock_integration
|
||||
from tests.common import MockModule, extract_stack_to_frame, mock_integration
|
||||
|
||||
|
||||
async def test_get_async_client_with_ssl(hass: HomeAssistant) -> None:
|
||||
|
@ -104,24 +104,29 @@ async def test_warning_close_session_integration(
|
|||
) -> None:
|
||||
"""Test log warning message when closing the session from integration context."""
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.aclose()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline",
|
||||
return_value="await session.aclose()",
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.aclose()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
httpx_session = client.get_async_client(hass)
|
||||
await httpx_session.aclose()
|
||||
|
@ -141,24 +146,29 @@ async def test_warning_close_session_custom(
|
|||
"""Test log warning message when closing the session from custom context."""
|
||||
mock_integration(hass, MockModule("hue"), built_in=False)
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/config/custom_components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.aclose()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline",
|
||||
return_value="await session.aclose()",
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/config/custom_components/hue/light.py",
|
||||
lineno="23",
|
||||
line="await session.aclose()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
httpx_session = client.get_async_client(hass)
|
||||
await httpx_session.aclose()
|
||||
|
|
|
@ -1122,7 +1122,7 @@ async def test_hass_components_use_reported(
|
|||
)
|
||||
integration_frame = frame.IntegrationFrame(
|
||||
custom_integration=True,
|
||||
frame=mock_integration_frame,
|
||||
_frame=mock_integration_frame,
|
||||
integration="test_integration_frame",
|
||||
module="custom_components.test_integration_frame",
|
||||
relative_filename="custom_components/test_integration_frame/__init__.py",
|
||||
|
|
|
@ -10,6 +10,8 @@ from homeassistant import block_async_io
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util import async_ as hasync
|
||||
|
||||
from tests.common import extract_stack_to_frame
|
||||
|
||||
|
||||
@patch("concurrent.futures.Future")
|
||||
@patch("threading.get_ident")
|
||||
|
@ -49,24 +51,28 @@ async def test_check_loop_async() -> None:
|
|||
async def test_check_loop_async_integration(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test check_loop detects and raises when called from event loop from integration context."""
|
||||
with pytest.raises(RuntimeError), patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on"
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
hasync.check_loop(banned_function)
|
||||
assert (
|
||||
|
@ -82,24 +88,28 @@ async def test_check_loop_async_integration_non_strict(
|
|||
) -> None:
|
||||
"""Test check_loop detects when called from event loop from integration context."""
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on"
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/components/hue/light.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
hasync.check_loop(banned_function, strict=False)
|
||||
assert (
|
||||
|
@ -113,24 +123,28 @@ async def test_check_loop_async_integration_non_strict(
|
|||
async def test_check_loop_async_custom(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test check_loop detects when called from event loop with custom component context."""
|
||||
with pytest.raises(RuntimeError), patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/config/custom_components/hue/light.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on"
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/config/custom_components/hue/light.py",
|
||||
lineno="23",
|
||||
line="self.light.is_on",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
hasync.check_loop(banned_function)
|
||||
assert (
|
||||
|
@ -161,24 +175,16 @@ async def test_protect_loop_debugger_sleep(caplog: pytest.LogCaptureFixture) ->
|
|||
block_async_io.enable()
|
||||
|
||||
with patch(
|
||||
"homeassistant.util.async_.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/.venv/blah/pydevd.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/util/async.py",
|
||||
lineno="123",
|
||||
line="protected_loop_func",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/util/async.py",
|
||||
lineno="123",
|
||||
line="check_loop()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/.venv/blah/pydevd.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
time.sleep(0)
|
||||
assert "Detected blocking call inside the event loop" not in caplog.text
|
||||
|
|
|
@ -18,7 +18,7 @@ from homeassistant.exceptions import HomeAssistantError
|
|||
import homeassistant.util.yaml as yaml
|
||||
from homeassistant.util.yaml import loader as yaml_loader
|
||||
|
||||
from tests.common import get_test_config_dir, patch_yaml_files
|
||||
from tests.common import extract_stack_to_frame, get_test_config_dir, patch_yaml_files
|
||||
|
||||
|
||||
@pytest.fixture(params=["enable_c_loader", "disable_c_loader"])
|
||||
|
@ -611,20 +611,24 @@ def mock_integration_frame() -> Generator[Mock, None, None]:
|
|||
line="self.light.is_on",
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
correct_frame,
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line
|
||||
), patch(
|
||||
"homeassistant.helpers.frame.get_current_frame",
|
||||
return_value=extract_stack_to_frame(
|
||||
[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
correct_frame,
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
]
|
||||
),
|
||||
):
|
||||
yield correct_frame
|
||||
|
||||
|
|
Loading…
Reference in New Issue