-
-
Notifications
You must be signed in to change notification settings - Fork 36.3k
Use built-in test helpers on 3.8 #34901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d5ed02a
ad34ecb
209995d
0bd8c96
ec9c17e
4322962
c9ba04e
f62575a
6f9407b
4c899de
ce02961
3062678
008e58f
5d8435f
fb63823
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -162,7 +162,7 @@ async def run_handler(self): | |||||||||||||||||||||||||||||||||||||||
| Run inside the Home Assistant event loop. | ||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||
| state = self.hass.states.get(self.entity_id) | ||||||||||||||||||||||||||||||||||||||||
| self.hass.async_add_executor_job(self.update_state_callback, None, None, state) | ||||||||||||||||||||||||||||||||||||||||
| self.hass.async_add_job(self.update_state_callback, None, None, state) | ||||||||||||||||||||||||||||||||||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bdraco, I found this bug -> passing a callback to the executor. Does this have to run in parallel or could the method just be called too?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe make
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@MartinHjelmare Would that be safe to do since its being called from
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. It would be run with Lines 370 to 388 in d43617c
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adjusted here a04a8fa I'll cherry pick it out and make another PR after some more testing |
||||||||||||||||||||||||||||||||||||||||
| async_track_state_change(self.hass, self.entity_id, self.update_state_callback) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| battery_charging_state = None | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -90,8 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b | |
| controller.start() | ||
|
|
||
| hass.bus.async_listen_once( | ||
| EVENT_HOMEASSISTANT_STOP, | ||
| lambda event: hass.async_add_executor_job(controller.stop), | ||
| EVENT_HOMEASSISTANT_STOP, lambda event: controller.stop() | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vangorra, lambdas are not seen as an async function, regardless of what they return and so are already executed in the executor. So we were calling an async function from a sync context. Now it's fixed :) |
||
| ) | ||
|
|
||
| try: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| """Mock utilities that are async aware.""" | ||
| import sys | ||
|
|
||
| if sys.version_info[:2] < (3, 8): | ||
| from asynctest.mock import * # noqa | ||
| from asynctest.mock import CoroutineMock as AsyncMock # noqa | ||
| else: | ||
| from unittest.mock import * # noqa |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,6 @@ | |
| import uuid | ||
|
|
||
| from aiohttp.test_utils import unused_port as get_test_instance_port # noqa | ||
| from asynctest import MagicMock, Mock, patch | ||
|
|
||
| from homeassistant import auth, config_entries, core as ha, loader | ||
| from homeassistant.auth import ( | ||
|
|
@@ -60,6 +59,8 @@ | |
| from homeassistant.util.unit_system import METRIC_SYSTEM | ||
| import homeassistant.util.yaml.loader as yaml_loader | ||
|
|
||
| from tests.async_mock import AsyncMock, MagicMock, Mock, patch | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
| INSTANCES = [] | ||
| CLIENT_ID = "https://example.com/app" | ||
|
|
@@ -159,20 +160,37 @@ async def async_test_home_assistant(loop): | |
|
|
||
| def async_add_job(target, *args): | ||
| """Add job.""" | ||
| if isinstance(target, Mock): | ||
| return mock_coro(target(*args)) | ||
| check_target = target | ||
| while isinstance(check_target, ft.partial): | ||
| check_target = check_target.func | ||
|
|
||
| if isinstance(check_target, Mock) and not isinstance(target, AsyncMock): | ||
| fut = asyncio.Future() | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is how I caught the incorrect async/synv usage in homekit. Now this will raise if it is called from the executor during a test because there is no event loop! |
||
| fut.set_result(target(*args)) | ||
| return fut | ||
|
|
||
| return orig_async_add_job(target, *args) | ||
|
|
||
| def async_add_executor_job(target, *args): | ||
| """Add executor job.""" | ||
| if isinstance(target, Mock): | ||
| return mock_coro(target(*args)) | ||
| check_target = target | ||
| while isinstance(check_target, ft.partial): | ||
| check_target = check_target.func | ||
|
|
||
| if isinstance(check_target, Mock): | ||
| fut = asyncio.Future() | ||
| fut.set_result(target(*args)) | ||
| return fut | ||
|
|
||
| return orig_async_add_executor_job(target, *args) | ||
|
|
||
| def async_create_task(coroutine): | ||
| """Create task.""" | ||
| if isinstance(coroutine, Mock): | ||
| return mock_coro() | ||
| if isinstance(coroutine, Mock) and not isinstance(coroutine, AsyncMock): | ||
| fut = asyncio.Future() | ||
| fut.set_result(None) | ||
| return fut | ||
|
|
||
| return orig_async_create_task(coroutine) | ||
|
|
||
| hass.async_add_job = async_add_job | ||
|
|
@@ -311,15 +329,16 @@ async def async_mock_mqtt_component(hass, config=None): | |
| if config is None: | ||
| config = {mqtt.CONF_BROKER: "mock-broker"} | ||
|
|
||
| async def _async_fire_mqtt_message(topic, payload, qos, retain): | ||
| @ha.callback | ||
| def _async_fire_mqtt_message(topic, payload, qos, retain): | ||
| async_fire_mqtt_message(hass, topic, payload, qos, retain) | ||
|
|
||
| with patch("paho.mqtt.client.Client") as mock_client: | ||
| mock_client().connect.return_value = 0 | ||
| mock_client().subscribe.return_value = (0, 0) | ||
| mock_client().unsubscribe.return_value = (0, 0) | ||
| mock_client().publish.return_value = (0, 0) | ||
| mock_client().publish.side_effect = _async_fire_mqtt_message | ||
| mock_client = mock_client.return_value | ||
| mock_client.connect.return_value = 0 | ||
| mock_client.subscribe.return_value = (0, 0) | ||
| mock_client.unsubscribe.return_value = (0, 0) | ||
| mock_client.publish.side_effect = _async_fire_mqtt_message | ||
|
|
||
| result = await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config}) | ||
| assert result | ||
|
|
@@ -503,7 +522,7 @@ def __init__( | |
| self.async_setup = async_setup | ||
|
|
||
| if setup is None and async_setup is None: | ||
| self.async_setup = mock_coro_func(True) | ||
| self.async_setup = AsyncMock(return_value=True) | ||
|
|
||
| if async_setup_entry is not None: | ||
| self.async_setup_entry = async_setup_entry | ||
|
|
@@ -561,7 +580,7 @@ def __init__( | |
| self.async_setup_entry = async_setup_entry | ||
|
|
||
| if setup_platform is None and async_setup_platform is None: | ||
| self.async_setup_platform = mock_coro_func() | ||
| self.async_setup_platform = AsyncMock(return_value=None) | ||
|
|
||
|
|
||
| class MockEntityPlatform(entity_platform.EntityPlatform): | ||
|
|
@@ -731,14 +750,10 @@ def mock_coro(return_value=None, exception=None): | |
| def mock_coro_func(return_value=None, exception=None): | ||
| """Return a method to create a coro function that returns a value.""" | ||
|
|
||
| @asyncio.coroutine | ||
| def coro(*args, **kwargs): | ||
| """Fake coroutine.""" | ||
| if exception: | ||
| raise exception | ||
| return return_value | ||
| if exception: | ||
| return AsyncMock(side_effect=exception) | ||
|
|
||
| return coro | ||
| return AsyncMock(return_value=return_value) | ||
|
|
||
|
|
||
| @contextmanager | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bdraco, I found this bug -> calling an async function from sync context