core/tests/components/wemo/entity_test_helpers.py

186 lines
6.8 KiB
Python

"""Test cases that are in common among wemo platform modules.
This is not a test module. These test methods are used by the platform test modules.
"""
import asyncio
import threading
from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN
from homeassistant.components.wemo import wemo_device
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
def _perform_registry_callback(coordinator):
"""Return a callable method to trigger a state callback from the device."""
async def async_callback():
await coordinator.hass.async_add_executor_job(
coordinator.subscription_callback, coordinator.wemo, "", ""
)
return async_callback
def _perform_async_update(coordinator):
"""Return a callable method to cause hass to update the state of the entity."""
async def async_callback():
await coordinator._async_update_data()
return async_callback
async def _async_multiple_call_helper(hass, pywemo_device, call1, call2):
"""Create two calls (call1 & call2) in parallel; verify only one polls the device.
There should only be one poll on the device at a time. Any parallel updates
# that happen at the same time should be ignored. This is verified by blocking
in the get_state method. The polling method should only be called once as a
result of calling call1 & call2 simultaneously.
"""
event = threading.Event()
waiting = asyncio.Event()
call_count = 0
def get_state(force_update=None):
if force_update is None:
return
nonlocal call_count
call_count += 1
hass.loop.call_soon_threadsafe(waiting.set)
event.wait()
# Danger! Do not use a Mock side_effect here. The test will deadlock. When
# called though hass.async_add_executor_job, Mock objects !surprisingly!
# run in the same thread as the asyncio event loop.
# https://github.com/home-assistant/core/blob/1ba5c1c9fb1e380549cb655986b5f4d3873d7352/tests/common.py#L179
pywemo_device.get_state = get_state
# One of these two calls will block on `event`. The other will return right
# away because the `_update_lock` is held.
done, pending = await asyncio.wait(
[asyncio.create_task(call1()), asyncio.create_task(call2())],
return_when=asyncio.FIRST_COMPLETED,
)
_ = [d.result() for d in done] # Allow any exceptions to be raised.
# Allow the blocked call to return.
await waiting.wait()
event.set()
if pending:
done, _ = await asyncio.wait(pending)
_ = [d.result() for d in done] # Allow any exceptions to be raised.
# Make sure the state update only happened once.
assert call_count == 1
async def test_async_update_locked_callback_and_update(
hass: HomeAssistant, pywemo_device, wemo_entity
) -> None:
"""Test that a callback and a state update request can't both happen at the same time.
When a state update is received via a callback from the device at the same time
as hass is calling `async_update`, verify that only one of the updates proceeds.
"""
coordinator = wemo_device.async_get_coordinator(hass, wemo_entity.device_id)
await async_setup_component(hass, HA_DOMAIN, {})
callback = _perform_registry_callback(coordinator)
update = _perform_async_update(coordinator)
await _async_multiple_call_helper(hass, pywemo_device, callback, update)
async def test_async_update_locked_multiple_updates(
hass: HomeAssistant, pywemo_device, wemo_entity
) -> None:
"""Test that two hass async_update state updates do not proceed at the same time."""
coordinator = wemo_device.async_get_coordinator(hass, wemo_entity.device_id)
await async_setup_component(hass, HA_DOMAIN, {})
update = _perform_async_update(coordinator)
await _async_multiple_call_helper(hass, pywemo_device, update, update)
async def test_async_update_locked_multiple_callbacks(
hass: HomeAssistant, pywemo_device, wemo_entity
) -> None:
"""Test that two device callback state updates do not proceed at the same time."""
coordinator = wemo_device.async_get_coordinator(hass, wemo_entity.device_id)
await async_setup_component(hass, HA_DOMAIN, {})
callback = _perform_registry_callback(coordinator)
await _async_multiple_call_helper(hass, pywemo_device, callback, callback)
async def test_avaliable_after_update(
hass: HomeAssistant, pywemo_registry, pywemo_device, wemo_entity, domain
) -> None:
"""Test the avaliability when an On call fails and after an update.
This test expects that the pywemo_device Mock has been setup to raise an
ActionException when the SERVICE_TURN_ON method is called and that the
state will be On after the update.
"""
await hass.services.async_call(
domain,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: [wemo_entity.entity_id]},
blocking=True,
)
assert hass.states.get(wemo_entity.entity_id).state == STATE_UNAVAILABLE
pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "")
await hass.async_block_till_done()
assert hass.states.get(wemo_entity.entity_id).state == STATE_ON
async def test_turn_off_state(hass: HomeAssistant, wemo_entity, domain) -> None:
"""Test that the device state is updated after turning off."""
await hass.services.async_call(
domain,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: [wemo_entity.entity_id]},
blocking=True,
)
assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF
class EntityTestHelpers:
"""Common state update helpers."""
async def test_async_update_locked_multiple_updates(
self, hass, pywemo_device, wemo_entity
):
"""Test that two hass async_update state updates do not proceed at the same time."""
await test_async_update_locked_multiple_updates(
hass, pywemo_device, wemo_entity
)
async def test_async_update_locked_multiple_callbacks(
self, hass, pywemo_device, wemo_entity
):
"""Test that two device callback state updates do not proceed at the same time."""
await test_async_update_locked_multiple_callbacks(
hass, pywemo_device, wemo_entity
)
async def test_async_update_locked_callback_and_update(
self, hass, pywemo_device, wemo_entity
):
"""Test that a callback and a state update request can't both happen at the same time.
When a state update is received via a callback from the device at the same time
as hass is calling `async_update`, verify that only one of the updates proceeds.
"""
await test_async_update_locked_callback_and_update(
hass, pywemo_device, wemo_entity
)