mirror of
https://github.com/home-assistant/core.git
synced 2025-11-09 02:49:40 +00:00
Detect blocking module imports in the event loop (#114488)
This commit is contained in:
@@ -6,12 +6,9 @@ from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
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")
|
||||
@@ -38,172 +35,6 @@ def test_run_callback_threadsafe_from_inside_event_loop(mock_ident, _) -> None:
|
||||
assert len(loop.call_soon_threadsafe.mock_calls) == 2
|
||||
|
||||
|
||||
def banned_function():
|
||||
"""Mock banned function."""
|
||||
|
||||
|
||||
async def test_check_loop_async() -> None:
|
||||
"""Test check_loop detects when called from event loop without integration context."""
|
||||
with pytest.raises(RuntimeError):
|
||||
hasync.check_loop(banned_function)
|
||||
|
||||
|
||||
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.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 (
|
||||
"Detected blocking call to banned_function inside the event loop by integration"
|
||||
" 'hue' at homeassistant/components/hue/light.py, line 23: self.light.is_on, "
|
||||
"please create a bug report at https://github.com/home-assistant/core/issues?"
|
||||
"q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+hue%22" in caplog.text
|
||||
)
|
||||
|
||||
|
||||
async def test_check_loop_async_integration_non_strict(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test check_loop detects when called from event loop from integration context."""
|
||||
with (
|
||||
patch(
|
||||
"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 (
|
||||
"Detected blocking call to banned_function inside the event loop by integration"
|
||||
" 'hue' at homeassistant/components/hue/light.py, line 23: self.light.is_on, "
|
||||
"please create a bug report at https://github.com/home-assistant/core/issues?"
|
||||
"q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+hue%22" in caplog.text
|
||||
)
|
||||
|
||||
|
||||
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.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 (
|
||||
"Detected blocking call to banned_function inside the event loop by custom "
|
||||
"integration 'hue' at custom_components/hue/light.py, line 23: self.light.is_on"
|
||||
", please create a bug report at https://github.com/home-assistant/core/issues?"
|
||||
"q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+hue%22"
|
||||
) in caplog.text
|
||||
|
||||
|
||||
def test_check_loop_sync(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test check_loop does nothing when called from thread."""
|
||||
hasync.check_loop(banned_function)
|
||||
assert "Detected blocking call inside the event loop" not in caplog.text
|
||||
|
||||
|
||||
def test_protect_loop_sync() -> None:
|
||||
"""Test protect_loop calls check_loop."""
|
||||
func = Mock()
|
||||
with patch("homeassistant.util.async_.check_loop") as mock_check_loop:
|
||||
hasync.protect_loop(func)(1, test=2)
|
||||
mock_check_loop.assert_called_once_with(func, strict=True)
|
||||
func.assert_called_once_with(1, test=2)
|
||||
|
||||
|
||||
async def test_protect_loop_debugger_sleep(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test time.sleep injected by the debugger is not reported."""
|
||||
block_async_io.enable()
|
||||
|
||||
with patch(
|
||||
"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
|
||||
|
||||
|
||||
async def test_gather_with_limited_concurrency() -> None:
|
||||
"""Test gather_with_limited_concurrency limits the number of running tasks."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user