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