2016-09-13 02:16:14 +00:00
|
|
|
"""Tests for async util methods from Python source."""
|
|
|
|
import asyncio
|
2016-10-02 22:07:23 +00:00
|
|
|
from unittest.mock import MagicMock, patch
|
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
|
2016-10-02 22:07:23 +00:00
|
|
|
|
|
|
|
import pytest
|
2016-09-13 02:16:14 +00:00
|
|
|
|
|
|
|
from homeassistant.util import async as hasync
|
|
|
|
|
|
|
|
|
2017-10-03 03:25:04 +00:00
|
|
|
@patch('asyncio.coroutines.iscoroutine')
|
2016-10-02 22:07:23 +00:00
|
|
|
@patch('concurrent.futures.Future')
|
|
|
|
@patch('threading.get_ident')
|
2017-10-03 03:25:04 +00:00
|
|
|
def test_run_coroutine_threadsafe_from_inside_event_loop(
|
|
|
|
mock_ident, _, mock_iscoroutine):
|
2016-10-02 22:07:23 +00:00
|
|
|
"""Testing calling run_coroutine_threadsafe from inside an event loop."""
|
|
|
|
coro = MagicMock()
|
|
|
|
loop = MagicMock()
|
|
|
|
|
|
|
|
loop._thread_ident = None
|
|
|
|
mock_ident.return_value = 5
|
2017-10-03 03:25:04 +00:00
|
|
|
mock_iscoroutine.return_value = True
|
2016-10-02 22:07:23 +00:00
|
|
|
hasync.run_coroutine_threadsafe(coro, loop)
|
|
|
|
assert len(loop.call_soon_threadsafe.mock_calls) == 1
|
|
|
|
|
|
|
|
loop._thread_ident = 5
|
|
|
|
mock_ident.return_value = 5
|
2017-10-03 03:25:04 +00:00
|
|
|
mock_iscoroutine.return_value = True
|
2016-10-02 22:07:23 +00:00
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
hasync.run_coroutine_threadsafe(coro, loop)
|
|
|
|
assert len(loop.call_soon_threadsafe.mock_calls) == 1
|
|
|
|
|
|
|
|
loop._thread_ident = 1
|
|
|
|
mock_ident.return_value = 5
|
2017-10-03 03:25:04 +00:00
|
|
|
mock_iscoroutine.return_value = False
|
|
|
|
with pytest.raises(TypeError):
|
|
|
|
hasync.run_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
|
2016-10-02 22:07:23 +00:00
|
|
|
hasync.run_coroutine_threadsafe(coro, loop)
|
|
|
|
assert len(loop.call_soon_threadsafe.mock_calls) == 2
|
|
|
|
|
|
|
|
|
2017-10-03 03:25:04 +00:00
|
|
|
@patch('asyncio.coroutines.iscoroutine')
|
2016-10-02 22:07:23 +00:00
|
|
|
@patch('concurrent.futures.Future')
|
|
|
|
@patch('threading.get_ident')
|
2017-10-03 03:25:04 +00:00
|
|
|
def test_fire_coroutine_threadsafe_from_inside_event_loop(
|
|
|
|
mock_ident, _, mock_iscoroutine):
|
2016-10-02 22:07:23 +00:00
|
|
|
"""Testing calling fire_coroutine_threadsafe from inside an event loop."""
|
|
|
|
coro = MagicMock()
|
|
|
|
loop = MagicMock()
|
|
|
|
|
|
|
|
loop._thread_ident = None
|
|
|
|
mock_ident.return_value = 5
|
2017-10-03 03:25:04 +00:00
|
|
|
mock_iscoroutine.return_value = True
|
2016-10-02 22:07:23 +00:00
|
|
|
hasync.fire_coroutine_threadsafe(coro, loop)
|
|
|
|
assert len(loop.call_soon_threadsafe.mock_calls) == 1
|
|
|
|
|
|
|
|
loop._thread_ident = 5
|
|
|
|
mock_ident.return_value = 5
|
2017-10-03 03:25:04 +00:00
|
|
|
mock_iscoroutine.return_value = True
|
2016-10-02 22:07:23 +00:00
|
|
|
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
|
2017-10-03 03:25:04 +00:00
|
|
|
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
|
2016-10-02 22:07:23 +00:00
|
|
|
hasync.fire_coroutine_threadsafe(coro, loop)
|
|
|
|
assert len(loop.call_soon_threadsafe.mock_calls) == 2
|
|
|
|
|
|
|
|
|
|
|
|
@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."""
|
2016-09-13 02:16:14 +00:00
|
|
|
|
|
|
|
def setUp(self):
|
2016-12-28 18:04:59 +00:00
|
|
|
"""Test setup method."""
|
2016-09-13 02:16:14 +00:00
|
|
|
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."""
|
|
|
|
@asyncio.coroutine
|
|
|
|
def once():
|
|
|
|
pass
|
|
|
|
gen = once()
|
|
|
|
t = loop.create_task(gen)
|
|
|
|
try:
|
|
|
|
loop.run_until_complete(t)
|
|
|
|
finally:
|
|
|
|
gen.close()
|
2016-09-13 02:16:14 +00:00
|
|
|
|
2017-10-03 03:25:04 +00:00
|
|
|
def add_callback(self, a, b, fail, invalid):
|
|
|
|
"""Return a + b."""
|
|
|
|
if fail:
|
|
|
|
raise RuntimeError("Fail!")
|
|
|
|
if invalid:
|
|
|
|
raise ValueError("Invalid!")
|
|
|
|
return a + b
|
|
|
|
|
2016-09-13 02:16:14 +00:00
|
|
|
@asyncio.coroutine
|
2017-10-03 03:25:04 +00:00
|
|
|
def add_coroutine(self, a, b, fail, invalid, cancel):
|
2016-09-13 02:16:14 +00:00
|
|
|
"""Wait 0.05 second and return a + b."""
|
|
|
|
yield from asyncio.sleep(0.05, loop=self.loop)
|
|
|
|
if cancel:
|
|
|
|
asyncio.tasks.Task.current_task(self.loop).cancel()
|
|
|
|
yield
|
2017-10-03 03:25:04 +00:00
|
|
|
return self.add_callback(a, b, fail, invalid)
|
2016-09-13 02:16:14 +00:00
|
|
|
|
2017-10-03 03:25:04 +00:00
|
|
|
def target_callback(self, fail=False, invalid=False):
|
|
|
|
"""Run add callback in the event loop."""
|
|
|
|
future = hasync.run_callback_threadsafe(
|
|
|
|
self.loop, self.add_callback, 1, 2, fail, invalid)
|
|
|
|
try:
|
|
|
|
return future.result()
|
|
|
|
finally:
|
|
|
|
future.done() or future.cancel()
|
|
|
|
|
|
|
|
def target_coroutine(self, fail=False, invalid=False, cancel=False,
|
|
|
|
timeout=None, advance_coro=False):
|
2016-09-13 02:16:14 +00:00
|
|
|
"""Run add coroutine in the event loop."""
|
2017-10-03 03:25:04 +00:00
|
|
|
coro = self.add_coroutine(1, 2, fail, invalid, cancel)
|
2016-09-13 02:16:14 +00:00
|
|
|
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
|
2017-10-03 03:25:04 +00:00
|
|
|
# 'target_coroutine' is interacting with threads.
|
2016-09-13 02:16:14 +00:00
|
|
|
|
|
|
|
# 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_coroutine_threadsafe(self):
|
|
|
|
"""Test coroutine submission from a thread to an event loop."""
|
2017-10-03 03:25:04 +00:00
|
|
|
future = self.loop.run_in_executor(None, self.target_coroutine)
|
2016-09-13 02:16:14 +00:00
|
|
|
result = self.loop.run_until_complete(future)
|
|
|
|
self.assertEqual(result, 3)
|
|
|
|
|
|
|
|
def test_run_coroutine_threadsafe_with_exception(self):
|
2016-12-28 18:04:59 +00:00
|
|
|
"""Test coroutine submission from thread to event loop on exception."""
|
2017-10-03 03:25:04 +00:00
|
|
|
future = self.loop.run_in_executor(None, self.target_coroutine, True)
|
2016-09-13 02:16:14 +00:00
|
|
|
with self.assertRaises(RuntimeError) as exc_context:
|
|
|
|
self.loop.run_until_complete(future)
|
|
|
|
self.assertIn("Fail!", exc_context.exception.args)
|
|
|
|
|
2017-10-03 03:25:04 +00:00
|
|
|
def test_run_coroutine_threadsafe_with_invalid(self):
|
|
|
|
"""Test coroutine submission from thread to event loop on invalid."""
|
|
|
|
callback = lambda: self.target_coroutine(invalid=True) # noqa
|
|
|
|
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)
|
|
|
|
|
2016-09-13 02:16:14 +00:00
|
|
|
def test_run_coroutine_threadsafe_with_timeout(self):
|
2016-12-28 18:04:59 +00:00
|
|
|
"""Test coroutine submission from thread to event loop on timeout."""
|
2017-10-03 03:25:04 +00:00
|
|
|
callback = lambda: self.target_coroutine(timeout=0) # noqa
|
2016-09-13 02:16:14 +00:00
|
|
|
future = self.loop.run_in_executor(None, callback)
|
|
|
|
with self.assertRaises(asyncio.TimeoutError):
|
|
|
|
self.loop.run_until_complete(future)
|
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
|
|
|
self.run_briefly(self.loop)
|
2016-09-13 02:16:14 +00:00
|
|
|
# Check that there's no pending task (add has been cancelled)
|
|
|
|
for task in asyncio.Task.all_tasks(self.loop):
|
|
|
|
self.assertTrue(task.done())
|
|
|
|
|
|
|
|
def test_run_coroutine_threadsafe_task_cancelled(self):
|
2016-12-28 18:04:59 +00:00
|
|
|
"""Test coroutine submission from tread to event loop on cancel."""
|
2017-10-03 03:25:04 +00:00
|
|
|
callback = lambda: self.target_coroutine(cancel=True) # noqa
|
2016-09-13 02:16:14 +00:00
|
|
|
future = self.loop.run_in_executor(None, callback)
|
|
|
|
with self.assertRaises(asyncio.CancelledError):
|
|
|
|
self.loop.run_until_complete(future)
|
2017-10-03 03:25:04 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
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)
|