core/tests/util/test_async.py

247 lines
8.4 KiB
Python
Raw Normal View History

"""Tests for async util methods from Python source."""
import asyncio
Remove asyncio.test_utils to fix tests in Docker/Python 3.7 (#12102) The module `asyncio.test_utils` has been removed from Python in the 3.7 branch, because it was intended to be a private module for internal testing of asyncio. For more information, see the upstream bug report at https://bugs.python.org/issue32273 and the upstream PR at https://github.com/python/cpython/pull/4785. For this commit, I have migrated the small amount of functionality that was being used from the `asyncio.test_utils` directly into the `RunThreadsafeTests` Class. To see the original `asyncio.test_utils.TestCase` class, which I pulled some functionality from, please see: https://github.com/python/cpython/blob/3.6/Lib/asyncio/test_utils.py#L440 Note: In addition to being broken in 3.7, this test case also seems to be broken in Python 3.6.4 when using Docker. This PR fixes the test when run in docker. To reproduce: `./script/test_docker -- tests/util/test_async.py` failing output (prior to this commit): ``` ... trimmed ... py36 runtests: PYTHONHASHSEED='3262989550' py36 runtests: commands[0] | py.test --timeout=9 --duration=10 --cov --cov-report= tests/util/test_async.py Test session starts (platform: linux, Python 3.6.4, pytest 3.3.1, pytest-sugar 0.9.0) rootdir: /usr/src/app, inifile: setup.cfg plugins: timeout-1.2.1, sugar-0.9.0, cov-2.5.1, aiohttp-0.3.0 timeout: 9.0s method: signal ―――――――――――――――――― ERROR collecting tests/util/test_async.py ―――――――――――――――――――――――― ImportError while importing test module '/usr/src/app/tests/util/test_async.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: tests/util/test_async.py:3: in <module> from asyncio import test_utils /usr/local/lib/python3.6/asyncio/test_utils.py:36: in <module> from test import support E ImportError: cannot import name 'support' ```
2018-02-02 14:59:05 +00:00
from unittest import TestCase
from unittest.mock import MagicMock, Mock, patch
import pytest
from homeassistant.util import async_ as hasync
2019-07-31 19:25:30 +00:00
@patch("asyncio.coroutines.iscoroutine")
@patch("concurrent.futures.Future")
@patch("threading.get_ident")
def test_fire_coroutine_threadsafe_from_inside_event_loop(
2019-07-31 19:25:30 +00:00
mock_ident, _, mock_iscoroutine
):
"""Testing calling fire_coroutine_threadsafe from inside an event loop."""
coro = MagicMock()
loop = MagicMock()
loop._thread_ident = None
mock_ident.return_value = 5
mock_iscoroutine.return_value = True
hasync.fire_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 5
mock_ident.return_value = 5
mock_iscoroutine.return_value = True
with pytest.raises(RuntimeError):
hasync.fire_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 1
mock_ident.return_value = 5
mock_iscoroutine.return_value = False
with pytest.raises(TypeError):
hasync.fire_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 1
mock_ident.return_value = 5
mock_iscoroutine.return_value = True
hasync.fire_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 2
2019-07-31 19:25:30 +00:00
@patch("concurrent.futures.Future")
@patch("threading.get_ident")
def test_run_callback_threadsafe_from_inside_event_loop(mock_ident, _):
"""Testing calling run_callback_threadsafe from inside an event loop."""
callback = MagicMock()
loop = MagicMock()
loop._thread_ident = None
mock_ident.return_value = 5
hasync.run_callback_threadsafe(loop, callback)
assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 5
mock_ident.return_value = 5
with pytest.raises(RuntimeError):
hasync.run_callback_threadsafe(loop, callback)
assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 1
mock_ident.return_value = 5
hasync.run_callback_threadsafe(loop, callback)
assert len(loop.call_soon_threadsafe.mock_calls) == 2
Remove asyncio.test_utils to fix tests in Docker/Python 3.7 (#12102) The module `asyncio.test_utils` has been removed from Python in the 3.7 branch, because it was intended to be a private module for internal testing of asyncio. For more information, see the upstream bug report at https://bugs.python.org/issue32273 and the upstream PR at https://github.com/python/cpython/pull/4785. For this commit, I have migrated the small amount of functionality that was being used from the `asyncio.test_utils` directly into the `RunThreadsafeTests` Class. To see the original `asyncio.test_utils.TestCase` class, which I pulled some functionality from, please see: https://github.com/python/cpython/blob/3.6/Lib/asyncio/test_utils.py#L440 Note: In addition to being broken in 3.7, this test case also seems to be broken in Python 3.6.4 when using Docker. This PR fixes the test when run in docker. To reproduce: `./script/test_docker -- tests/util/test_async.py` failing output (prior to this commit): ``` ... trimmed ... py36 runtests: PYTHONHASHSEED='3262989550' py36 runtests: commands[0] | py.test --timeout=9 --duration=10 --cov --cov-report= tests/util/test_async.py Test session starts (platform: linux, Python 3.6.4, pytest 3.3.1, pytest-sugar 0.9.0) rootdir: /usr/src/app, inifile: setup.cfg plugins: timeout-1.2.1, sugar-0.9.0, cov-2.5.1, aiohttp-0.3.0 timeout: 9.0s method: signal ―――――――――――――――――― ERROR collecting tests/util/test_async.py ―――――――――――――――――――――――― ImportError while importing test module '/usr/src/app/tests/util/test_async.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: tests/util/test_async.py:3: in <module> from asyncio import test_utils /usr/local/lib/python3.6/asyncio/test_utils.py:36: in <module> from test import support E ImportError: cannot import name 'support' ```
2018-02-02 14:59:05 +00:00
class RunThreadsafeTests(TestCase):
"""Test case for hasync.run_coroutine_threadsafe."""
def setUp(self):
2016-12-28 18:04:59 +00:00
"""Test setup method."""
self.loop = asyncio.new_event_loop()
Remove asyncio.test_utils to fix tests in Docker/Python 3.7 (#12102) The module `asyncio.test_utils` has been removed from Python in the 3.7 branch, because it was intended to be a private module for internal testing of asyncio. For more information, see the upstream bug report at https://bugs.python.org/issue32273 and the upstream PR at https://github.com/python/cpython/pull/4785. For this commit, I have migrated the small amount of functionality that was being used from the `asyncio.test_utils` directly into the `RunThreadsafeTests` Class. To see the original `asyncio.test_utils.TestCase` class, which I pulled some functionality from, please see: https://github.com/python/cpython/blob/3.6/Lib/asyncio/test_utils.py#L440 Note: In addition to being broken in 3.7, this test case also seems to be broken in Python 3.6.4 when using Docker. This PR fixes the test when run in docker. To reproduce: `./script/test_docker -- tests/util/test_async.py` failing output (prior to this commit): ``` ... trimmed ... py36 runtests: PYTHONHASHSEED='3262989550' py36 runtests: commands[0] | py.test --timeout=9 --duration=10 --cov --cov-report= tests/util/test_async.py Test session starts (platform: linux, Python 3.6.4, pytest 3.3.1, pytest-sugar 0.9.0) rootdir: /usr/src/app, inifile: setup.cfg plugins: timeout-1.2.1, sugar-0.9.0, cov-2.5.1, aiohttp-0.3.0 timeout: 9.0s method: signal ―――――――――――――――――― ERROR collecting tests/util/test_async.py ―――――――――――――――――――――――― ImportError while importing test module '/usr/src/app/tests/util/test_async.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: tests/util/test_async.py:3: in <module> from asyncio import test_utils /usr/local/lib/python3.6/asyncio/test_utils.py:36: in <module> from test import support E ImportError: cannot import name 'support' ```
2018-02-02 14:59:05 +00:00
def tearDown(self):
"""Test teardown method."""
executor = self.loop._default_executor
if executor is not None:
executor.shutdown(wait=True)
self.loop.close()
@staticmethod
def run_briefly(loop):
"""Momentarily run a coroutine on the given loop."""
2019-07-31 19:25:30 +00:00
Remove asyncio.test_utils to fix tests in Docker/Python 3.7 (#12102) The module `asyncio.test_utils` has been removed from Python in the 3.7 branch, because it was intended to be a private module for internal testing of asyncio. For more information, see the upstream bug report at https://bugs.python.org/issue32273 and the upstream PR at https://github.com/python/cpython/pull/4785. For this commit, I have migrated the small amount of functionality that was being used from the `asyncio.test_utils` directly into the `RunThreadsafeTests` Class. To see the original `asyncio.test_utils.TestCase` class, which I pulled some functionality from, please see: https://github.com/python/cpython/blob/3.6/Lib/asyncio/test_utils.py#L440 Note: In addition to being broken in 3.7, this test case also seems to be broken in Python 3.6.4 when using Docker. This PR fixes the test when run in docker. To reproduce: `./script/test_docker -- tests/util/test_async.py` failing output (prior to this commit): ``` ... trimmed ... py36 runtests: PYTHONHASHSEED='3262989550' py36 runtests: commands[0] | py.test --timeout=9 --duration=10 --cov --cov-report= tests/util/test_async.py Test session starts (platform: linux, Python 3.6.4, pytest 3.3.1, pytest-sugar 0.9.0) rootdir: /usr/src/app, inifile: setup.cfg plugins: timeout-1.2.1, sugar-0.9.0, cov-2.5.1, aiohttp-0.3.0 timeout: 9.0s method: signal ―――――――――――――――――― ERROR collecting tests/util/test_async.py ―――――――――――――――――――――――― ImportError while importing test module '/usr/src/app/tests/util/test_async.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: tests/util/test_async.py:3: in <module> from asyncio import test_utils /usr/local/lib/python3.6/asyncio/test_utils.py:36: in <module> from test import support E ImportError: cannot import name 'support' ```
2018-02-02 14:59:05 +00:00
@asyncio.coroutine
def once():
pass
2019-07-31 19:25:30 +00:00
Remove asyncio.test_utils to fix tests in Docker/Python 3.7 (#12102) The module `asyncio.test_utils` has been removed from Python in the 3.7 branch, because it was intended to be a private module for internal testing of asyncio. For more information, see the upstream bug report at https://bugs.python.org/issue32273 and the upstream PR at https://github.com/python/cpython/pull/4785. For this commit, I have migrated the small amount of functionality that was being used from the `asyncio.test_utils` directly into the `RunThreadsafeTests` Class. To see the original `asyncio.test_utils.TestCase` class, which I pulled some functionality from, please see: https://github.com/python/cpython/blob/3.6/Lib/asyncio/test_utils.py#L440 Note: In addition to being broken in 3.7, this test case also seems to be broken in Python 3.6.4 when using Docker. This PR fixes the test when run in docker. To reproduce: `./script/test_docker -- tests/util/test_async.py` failing output (prior to this commit): ``` ... trimmed ... py36 runtests: PYTHONHASHSEED='3262989550' py36 runtests: commands[0] | py.test --timeout=9 --duration=10 --cov --cov-report= tests/util/test_async.py Test session starts (platform: linux, Python 3.6.4, pytest 3.3.1, pytest-sugar 0.9.0) rootdir: /usr/src/app, inifile: setup.cfg plugins: timeout-1.2.1, sugar-0.9.0, cov-2.5.1, aiohttp-0.3.0 timeout: 9.0s method: signal ―――――――――――――――――― ERROR collecting tests/util/test_async.py ―――――――――――――――――――――――― ImportError while importing test module '/usr/src/app/tests/util/test_async.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: tests/util/test_async.py:3: in <module> from asyncio import test_utils /usr/local/lib/python3.6/asyncio/test_utils.py:36: in <module> from test import support E ImportError: cannot import name 'support' ```
2018-02-02 14:59:05 +00:00
gen = once()
t = loop.create_task(gen)
try:
loop.run_until_complete(t)
finally:
gen.close()
def add_callback(self, a, b, fail, invalid):
"""Return a + b."""
if fail:
raise RuntimeError("Fail!")
if invalid:
raise ValueError("Invalid!")
return a + b
@asyncio.coroutine
def add_coroutine(self, a, b, fail, invalid, cancel):
"""Wait 0.05 second and return a + b."""
yield from asyncio.sleep(0.05, loop=self.loop)
if cancel:
2019-12-16 06:29:19 +00:00
asyncio.current_task(self.loop).cancel()
yield
return self.add_callback(a, b, fail, invalid)
def target_callback(self, fail=False, invalid=False):
"""Run add callback in the event loop."""
future = hasync.run_callback_threadsafe(
2019-07-31 19:25:30 +00:00
self.loop, self.add_callback, 1, 2, fail, invalid
)
try:
return future.result()
finally:
future.done() or future.cancel()
2019-07-31 19:25:30 +00:00
def target_coroutine(
self, fail=False, invalid=False, cancel=False, timeout=None, advance_coro=False
):
"""Run add coroutine in the event loop."""
coro = self.add_coroutine(1, 2, fail, invalid, cancel)
future = hasync.run_coroutine_threadsafe(coro, self.loop)
if advance_coro:
# this is for test_run_coroutine_threadsafe_task_factory_exception;
# otherwise it spills errors and breaks **other** unittests, since
# 'target_coroutine' is interacting with threads.
# With this call, `coro` will be advanced, so that
# CoroWrapper.__del__ won't do anything when asyncio tests run
# in debug mode.
self.loop.call_soon_threadsafe(coro.send, None)
try:
return future.result(timeout)
finally:
future.done() or future.cancel()
def test_run_callback_threadsafe(self):
"""Test callback submission from a thread to an event loop."""
future = self.loop.run_in_executor(None, self.target_callback)
result = self.loop.run_until_complete(future)
self.assertEqual(result, 3)
def test_run_callback_threadsafe_with_exception(self):
"""Test callback submission from thread to event loop on exception."""
future = self.loop.run_in_executor(None, self.target_callback, True)
with self.assertRaises(RuntimeError) as exc_context:
self.loop.run_until_complete(future)
self.assertIn("Fail!", exc_context.exception.args)
def test_run_callback_threadsafe_with_invalid(self):
"""Test callback submission from thread to event loop on invalid."""
callback = lambda: self.target_callback(invalid=True) # noqa: E731
future = self.loop.run_in_executor(None, callback)
with self.assertRaises(ValueError) as exc_context:
self.loop.run_until_complete(future)
self.assertIn("Invalid!", exc_context.exception.args)
async def test_check_loop_async():
"""Test check_loop detects when called from event loop without integration context."""
with pytest.raises(RuntimeError):
hasync.check_loop()
async def test_check_loop_async_integration(caplog):
"""Test check_loop detects when called from event loop from integration context."""
with patch(
"homeassistant.util.async_.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()",
),
],
):
hasync.check_loop()
assert (
"Detected I/O inside the event loop. This is causing stability issues. Please report issue for hue doing I/O at homeassistant/components/hue/light.py, line 23: self.light.is_on"
in caplog.text
)
async def test_check_loop_async_custom(caplog):
"""Test check_loop detects when called from event loop with custom component context."""
with patch(
"homeassistant.util.async_.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()",
),
],
):
hasync.check_loop()
assert (
"Detected I/O inside the event loop. This is causing stability issues. Please report issue to the custom component author for hue doing I/O at custom_components/hue/light.py, line 23: self.light.is_on"
in caplog.text
)
def test_check_loop_sync(caplog):
"""Test check_loop does nothing when called from thread."""
hasync.check_loop()
assert "Detected I/O inside the event loop" not in caplog.text
def test_protect_loop_sync():
"""Test protect_loop calls check_loop."""
calls = []
with patch("homeassistant.util.async_.check_loop") as mock_loop:
hasync.protect_loop(calls.append)(1)
assert len(mock_loop.mock_calls) == 1
assert calls == [1]