Replace periodic tasks with background tasks (#112726)

* Phase out periodic tasks

* false by default or some tests will block forever, will need to fix each one manually

* kwarg works

* kwarg works

* kwarg works

* fixes

* fix more tests

* fix more tests

* fix lifx

* opensky

* pvpc_hourly_pricing

* adjust more

* adjust more

* smarttub

* adjust more

* adjust more

* adjust more

* adjust more

* adjust

* no eager executor

* zha

* qnap_qsw

* fix more

* fix fix

* docs

* its a wrapper now

* add more coverage

* coverage

* cover all combos

* more fixes

* more fixes

* more fixes

* remaining issues are legit bugs in tests

* make tplink test more predictable

* more fixes

* feedreader

* grind out some more

* make test race safe

* one more
This commit is contained in:
J. Nick Koston 2024-03-08 16:45:10 -10:00 committed by GitHub
parent 08416974c9
commit 65358c129a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 413 additions and 333 deletions

View File

@ -878,7 +878,7 @@ async def _async_set_up_integrations(
_LOGGER.debug("Waiting for startup to wrap up")
try:
async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
await hass.async_block_till_done(wait_periodic_tasks=False)
await hass.async_block_till_done()
except TimeoutError:
_LOGGER.warning(
"Setup timed out for bootstrap waiting on %s - moving forward",

View File

@ -18,7 +18,6 @@ from contextvars import ContextVar
from copy import deepcopy
from enum import Enum, StrEnum
import functools
from itertools import chain
import logging
from random import randint
from types import MappingProxyType
@ -379,7 +378,6 @@ class ConfigEntry:
self._tasks: set[asyncio.Future[Any]] = set()
self._background_tasks: set[asyncio.Future[Any]] = set()
self._periodic_tasks: set[asyncio.Future[Any]] = set()
self._integration_for_domain: loader.Integration | None = None
self._tries = 0
@ -857,15 +855,15 @@ class ConfigEntry:
if job := self._on_unload.pop()():
self.async_create_task(hass, job)
if not self._tasks and not self._background_tasks and not self._periodic_tasks:
if not self._tasks and not self._background_tasks:
return
cancel_message = f"Config entry {self.title} with {self.domain} unloading"
for task in chain(self._background_tasks, self._periodic_tasks):
for task in self._background_tasks:
task.cancel(cancel_message)
_, pending = await asyncio.wait(
[*self._tasks, *self._background_tasks, *self._periodic_tasks], timeout=10
[*self._tasks, *self._background_tasks], timeout=10
)
for task in pending:
@ -1044,35 +1042,6 @@ class ConfigEntry:
task.add_done_callback(self._background_tasks.remove)
return task
@callback
def async_create_periodic_task(
self,
hass: HomeAssistant,
target: Coroutine[Any, Any, _R],
name: str,
eager_start: bool = False,
) -> asyncio.Task[_R]:
"""Create a periodic task tied to the config entry lifecycle.
Periodic tasks are automatically canceled when config entry is unloaded.
This type of task is typically used for polling.
A periodic task is different from a normal task:
- Will not block startup
- Will be automatically cancelled on shutdown
- Calls to async_block_till_done will wait for completion by default
This method must be run in the event loop.
"""
task = hass.async_create_periodic_task(target, name, eager_start)
if task.done():
return task
self._periodic_tasks.add(task)
task.add_done_callback(self._periodic_tasks.remove)
return task
current_entry: ContextVar[ConfigEntry | None] = ContextVar(
"current_entry", default=None

View File

@ -375,7 +375,6 @@ class HomeAssistant:
self.loop = asyncio.get_running_loop()
self._tasks: set[asyncio.Future[Any]] = set()
self._background_tasks: set[asyncio.Future[Any]] = set()
self._periodic_tasks: set[asyncio.Future[Any]] = set()
self.bus = EventBus(self)
self.services = ServiceRegistry(self)
self.states = StateMachine(self.bus, self.loop)
@ -585,23 +584,38 @@ class HomeAssistant:
@overload
@callback
def async_add_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any
self,
hassjob: HassJob[..., Coroutine[Any, Any, _R]],
*args: Any,
eager_start: bool = False,
background: bool = False,
) -> asyncio.Future[_R] | None:
...
@overload
@callback
def async_add_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any
self,
hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R],
*args: Any,
eager_start: bool = False,
background: bool = False,
) -> asyncio.Future[_R] | None:
...
@callback
def async_add_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any
self,
hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R],
*args: Any,
eager_start: bool = False,
background: bool = False,
) -> asyncio.Future[_R] | None:
"""Add a HassJob from within the event loop.
If eager_start is True, coroutine functions will be scheduled eagerly.
If background is True, the task will created as a background task.
This method must be run in the event loop.
hassjob: HassJob to call.
args: parameters for method to call.
@ -618,7 +632,14 @@ class HomeAssistant:
)
# Use loop.create_task
# to avoid the extra function call in asyncio.create_task.
task = self.loop.create_task(hassjob.target(*args), name=hassjob.name)
if eager_start:
task = create_eager_task(
hassjob.target(*args), name=hassjob.name, loop=self.loop
)
if task.done():
return task
else:
task = self.loop.create_task(hassjob.target(*args), name=hassjob.name)
elif hassjob.job_type is HassJobType.Callback:
if TYPE_CHECKING:
hassjob.target = cast(Callable[..., _R], hassjob.target)
@ -629,58 +650,9 @@ class HomeAssistant:
hassjob.target = cast(Callable[..., _R], hassjob.target)
task = self.loop.run_in_executor(None, hassjob.target, *args)
self._tasks.add(task)
task.add_done_callback(self._tasks.remove)
return task
@overload
@callback
def async_run_periodic_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any
) -> asyncio.Future[_R] | None:
...
@overload
@callback
def async_run_periodic_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any
) -> asyncio.Future[_R] | None:
...
@callback
def async_run_periodic_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any
) -> asyncio.Future[_R] | None:
"""Add a periodic HassJob from within the event loop.
This method must be run in the event loop.
hassjob: HassJob to call.
args: parameters for method to call.
"""
task: asyncio.Future[_R]
# This code path is performance sensitive and uses
# if TYPE_CHECKING to avoid the overhead of constructing
# the type used for the cast. For history see:
# https://github.com/home-assistant/core/pull/71960
if hassjob.job_type is HassJobType.Coroutinefunction:
if TYPE_CHECKING:
hassjob.target = cast(
Callable[..., Coroutine[Any, Any, _R]], hassjob.target
)
task = create_eager_task(hassjob.target(*args), name=hassjob.name)
elif hassjob.job_type is HassJobType.Callback:
if TYPE_CHECKING:
hassjob.target = cast(Callable[..., _R], hassjob.target)
hassjob.target(*args)
return None
else:
if TYPE_CHECKING:
hassjob.target = cast(Callable[..., _R], hassjob.target)
task = self.loop.run_in_executor(None, hassjob.target, *args)
self._periodic_tasks.add(task)
task.add_done_callback(self._periodic_tasks.remove)
task_bucket = self._background_tasks if background else self._tasks
task_bucket.add(task)
task.add_done_callback(task_bucket.remove)
return task
@ -751,37 +723,6 @@ class HomeAssistant:
task.add_done_callback(self._background_tasks.remove)
return task
@callback
def async_create_periodic_task(
self, target: Coroutine[Any, Any, _R], name: str, eager_start: bool = False
) -> asyncio.Task[_R]:
"""Create a task from within the event loop.
This type of task is typically used for polling.
A periodic task is different from a normal task:
- Will not block startup
- Will be automatically cancelled on shutdown
- Calls to async_block_till_done will wait for completion by default
If you are using this in your integration, use the create task
methods on the config entry instead.
This method must be run in the event loop.
"""
if eager_start:
task = create_eager_task(target, name=name, loop=self.loop)
if task.done():
return task
else:
# Use loop.create_task
# to avoid the extra function call in asyncio.create_task.
task = self.loop.create_task(target, name=name)
self._periodic_tasks.add(task)
task.add_done_callback(self._periodic_tasks.remove)
return task
@callback
def async_add_executor_job(
self, target: Callable[..., _T], *args: Any
@ -806,25 +747,40 @@ class HomeAssistant:
@overload
@callback
def async_run_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any
self,
hassjob: HassJob[..., Coroutine[Any, Any, _R]],
*args: Any,
eager_start: bool = False,
background: bool = False,
) -> asyncio.Future[_R] | None:
...
@overload
@callback
def async_run_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any
self,
hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R],
*args: Any,
eager_start: bool = False,
background: bool = False,
) -> asyncio.Future[_R] | None:
...
@callback
def async_run_hass_job(
self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any
self,
hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R],
*args: Any,
eager_start: bool = False,
background: bool = False,
) -> asyncio.Future[_R] | None:
"""Run a HassJob from within the event loop.
This method must be run in the event loop.
If eager_start is True, coroutine functions will be scheduled eagerly.
If background is True, the task will created as a background task.
hassjob: HassJob
args: parameters for method to call.
"""
@ -838,7 +794,9 @@ class HomeAssistant:
hassjob.target(*args)
return None
return self.async_add_hass_job(hassjob, *args)
return self.async_add_hass_job(
hassjob, *args, eager_start=eager_start, background=background
)
@overload
@callback
@ -891,7 +849,7 @@ class HomeAssistant:
self.async_block_till_done(), self.loop
).result()
async def async_block_till_done(self, wait_periodic_tasks: bool = True) -> None:
async def async_block_till_done(self, wait_background_tasks: bool = False) -> None:
"""Block until all pending work is done."""
# To flush out any call_soon_threadsafe
await asyncio.sleep(0)
@ -900,8 +858,8 @@ class HomeAssistant:
while tasks := [
task
for task in (
self._tasks | self._periodic_tasks
if wait_periodic_tasks
self._tasks | self._background_tasks
if wait_background_tasks
else self._tasks
)
if task is not current_task and not cancelling(task)
@ -1034,7 +992,7 @@ class HomeAssistant:
self._tasks = set()
# Cancel all background tasks
for task in self._background_tasks | self._periodic_tasks:
for task in self._background_tasks:
self._tasks.add(task)
task.add_done_callback(self._tasks.remove)
task.cancel("Home Assistant is stopping")
@ -1046,7 +1004,7 @@ class HomeAssistant:
self.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
try:
async with self.timeout.async_timeout(STOP_STAGE_SHUTDOWN_TIMEOUT):
await self.async_block_till_done(wait_periodic_tasks=False)
await self.async_block_till_done()
except TimeoutError:
_LOGGER.warning(
"Timed out waiting for integrations to stop, the shutdown will"
@ -1059,7 +1017,7 @@ class HomeAssistant:
self.bus.async_fire(EVENT_HOMEASSISTANT_FINAL_WRITE)
try:
async with self.timeout.async_timeout(FINAL_WRITE_STAGE_SHUTDOWN_TIMEOUT):
await self.async_block_till_done(wait_periodic_tasks=False)
await self.async_block_till_done()
except TimeoutError:
_LOGGER.warning(
"Timed out waiting for final writes to complete, the shutdown will"
@ -1111,7 +1069,7 @@ class HomeAssistant:
try:
async with self.timeout.async_timeout(CLOSE_STAGE_SHUTDOWN_TIMEOUT):
await self.async_block_till_done(wait_periodic_tasks=False)
await self.async_block_till_done()
except TimeoutError:
_LOGGER.warning(
"Timed out waiting for close event to be processed, the shutdown will"

View File

@ -643,14 +643,14 @@ class EntityPlatform:
def _async_handle_interval_callback(self, now: datetime) -> None:
"""Update all the entity states in a single platform."""
if self.config_entry:
self.config_entry.async_create_periodic_task(
self.config_entry.async_create_background_task(
self.hass,
self._update_entity_states(now),
name=f"EntityPlatform poll {self.domain}.{self.platform_name}",
eager_start=True,
)
else:
self.hass.async_create_periodic_task(
self.hass.async_create_background_task(
self._update_entity_states(now),
name=f"EntityPlatform poll {self.domain}.{self.platform_name}",
eager_start=True,

View File

@ -1599,7 +1599,7 @@ class _TrackTimeInterval:
self._track_job,
hass.loop.time() + self.seconds,
)
hass.async_run_periodic_hass_job(self._run_job, now)
hass.async_run_hass_job(self._run_job, now, eager_start=True, background=True)
@callback
def async_cancel(self) -> None:
@ -1684,7 +1684,7 @@ class SunListener:
"""Handle solar event."""
self._unsub_sun = None
self._listen_next_sun_event()
self.hass.async_run_periodic_hass_job(self.job)
self.hass.async_run_hass_job(self.job, eager_start=True, background=True)
@callback
def _handle_config_event(self, _event: Any) -> None:
@ -1770,7 +1770,9 @@ class _TrackUTCTimeChange:
# time when the timer was scheduled
utc_now = time_tracker_utcnow()
localized_now = dt_util.as_local(utc_now) if self.local else utc_now
hass.async_run_periodic_hass_job(self.job, localized_now)
hass.async_run_hass_job(
self.job, localized_now, eager_start=True, background=True
)
if TYPE_CHECKING:
assert self._pattern_time_change_listener_job is not None
self._cancel_callback = async_track_point_in_utc_time(

View File

@ -258,14 +258,14 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]):
def __wrap_handle_refresh_interval(self) -> None:
"""Handle a refresh interval occurrence."""
if self.config_entry:
self.config_entry.async_create_periodic_task(
self.config_entry.async_create_background_task(
self.hass,
self._handle_refresh_interval(),
name=f"{self.name} - {self.config_entry.title} - refresh",
eager_start=True,
)
else:
self.hass.async_create_periodic_task(
self.hass.async_create_background_task(
self._handle_refresh_interval(),
name=f"{self.name} - refresh",
eager_start=True,

View File

@ -30,7 +30,7 @@ async def test_coordinator_error(
):
freezer.tick(WEATHER_UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("weather.aemet")
assert state.state == STATE_UNAVAILABLE

View File

@ -62,7 +62,7 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None:
mock_device_status.side_effect = AirzoneCloudError
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
mock_device_status.assert_called()

View File

@ -51,7 +51,7 @@ async def test_heartbeat_trigger_right_time(hass: HomeAssistant) -> None:
async_fire_time_changed(
hass, dt_util.utcnow() + BroadlinkHeartbeat.HEARTBEAT_INTERVAL
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_ping.call_count == 1
assert mock_ping.call_args == call(device.host)
@ -69,7 +69,7 @@ async def test_heartbeat_do_not_trigger_before_time(hass: HomeAssistant) -> None
hass,
dt_util.utcnow() + BroadlinkHeartbeat.HEARTBEAT_INTERVAL // 2,
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_ping.call_count == 0
@ -88,6 +88,7 @@ async def test_heartbeat_unload(hass: HomeAssistant) -> None:
async_fire_time_changed(
hass, dt_util.utcnow() + BroadlinkHeartbeat.HEARTBEAT_INTERVAL
)
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_ping.call_count == 0
@ -108,7 +109,7 @@ async def test_heartbeat_do_not_unload(hass: HomeAssistant) -> None:
async_fire_time_changed(
hass, dt_util.utcnow() + BroadlinkHeartbeat.HEARTBEAT_INTERVAL
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_ping.call_count == 1
assert mock_ping.call_args == call(device_b.host)

View File

@ -209,7 +209,7 @@ async def test_integration_update_interval(
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL)
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(instance.update_dns_record.mock_calls) == 2
assert "All target records are up to date" not in caplog.text
@ -217,12 +217,12 @@ async def test_integration_update_interval(
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL)
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(instance.update_dns_record.mock_calls) == 2
instance.list_dns_records.side_effect = pycfdns.ComunicationException()
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL)
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(instance.update_dns_record.mock_calls) == 2

View File

@ -324,7 +324,7 @@ async def test_availability(
hass.states.async_set("sensor.input1", "on")
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("binary_sensor.test")
assert entity_state
@ -335,7 +335,7 @@ async def test_availability(
with mock_asyncio_subprocess_run(b"0"):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("binary_sensor.test")
assert entity_state

View File

@ -265,7 +265,7 @@ async def test_updating_to_often(
not in caplog.text
)
async_fire_time_changed(hass, dt_util.now() + timedelta(seconds=11))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert called
called.clear()
@ -282,7 +282,7 @@ async def test_updating_to_often(
wait_till_event.set()
# Finish processing update
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert called
assert (
"Updating Command Line Cover Test took longer than the scheduled update interval"
@ -327,7 +327,7 @@ async def test_updating_manually(
await hass.async_block_till_done()
async_fire_time_changed(hass, dt_util.now() + timedelta(seconds=10))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert called
called.clear()
@ -367,7 +367,7 @@ async def test_availability(
hass.states.async_set("sensor.input1", "on")
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("cover.test")
assert entity_state
@ -378,7 +378,7 @@ async def test_availability(
with mock_asyncio_subprocess_run(b"50\n"):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("cover.test")
assert entity_state

View File

@ -20,7 +20,7 @@ async def test_setup_config(hass: HomeAssistant, load_yaml_integration: None) ->
"""Test setup from yaml."""
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state_binary_sensor = hass.states.get("binary_sensor.test")
state_sensor = hass.states.get("sensor.test")

View File

@ -108,7 +108,7 @@ async def test_template_render(
hass,
dt_util.utcnow() + timedelta(minutes=1),
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("sensor.test")
assert entity_state
@ -140,7 +140,7 @@ async def test_template_render_with_quote(hass: HomeAssistant) -> None:
hass,
dt_util.utcnow() + timedelta(minutes=1),
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(mock_subprocess_run.mock_calls) == 1
mock_subprocess_run.assert_called_with(
@ -734,7 +734,7 @@ async def test_availability(
hass.states.async_set("sensor.input1", "on")
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("sensor.test")
assert entity_state
@ -745,7 +745,7 @@ async def test_availability(
with mock_asyncio_subprocess_run(b"January 17, 2022"):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("sensor.test")
assert entity_state

View File

@ -350,7 +350,7 @@ async def test_switch_command_state_fail(
await hass.async_block_till_done()
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("switch.test")
assert entity_state
@ -734,7 +734,7 @@ async def test_availability(
hass.states.async_set("sensor.input1", "on")
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("switch.test")
assert entity_state
@ -745,7 +745,7 @@ async def test_availability(
with mock_asyncio_subprocess_run(b"50\n"):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_state = hass.states.get("switch.test")
assert entity_state

View File

@ -129,11 +129,11 @@ async def test_failed_update_and_reconnection(
await mock_responses(hass, aioclient_mock, error=True)
next_update = dt_util.utcnow() + timedelta(seconds=30)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.efergy_power_usage").state == STATE_UNAVAILABLE
aioclient_mock.clear_requests()
await mock_responses(hass, aioclient_mock)
next_update = dt_util.utcnow() + timedelta(seconds=30)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.efergy_power_usage").state == "1580"

View File

@ -370,14 +370,14 @@ async def test_feed_updates(
# Change time and fetch more entries
future = dt_util.utcnow() + timedelta(hours=1, seconds=1)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(events) == 2
# Change time but no new entries
future = dt_util.utcnow() + timedelta(hours=2, seconds=2)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(events) == 2

View File

@ -79,7 +79,7 @@ async def test_binary_sensors(
mock_fully_kiosk.getDeviceInfo.return_value = {}
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("binary_sensor.amazon_fire_plugged_in")
assert state
@ -89,7 +89,7 @@ async def test_binary_sensors(
mock_fully_kiosk.getDeviceInfo.side_effect = FullyKioskError("error", "status")
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("binary_sensor.amazon_fire_plugged_in")
assert state

View File

@ -53,7 +53,7 @@ async def test_numbers(
# Test invalid numeric data
mock_fully_kiosk.getSettings.return_value = {"screenBrightness": "invalid"}
async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("number.amazon_fire_screen_brightness")
assert state
@ -62,7 +62,7 @@ async def test_numbers(
# Test unknown/missing data
mock_fully_kiosk.getSettings.return_value = {}
async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("number.amazon_fire_screensaver_timer")
assert state

View File

@ -145,7 +145,7 @@ async def test_sensors_sensors(
mock_fully_kiosk.getDeviceInfo.return_value = {}
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("sensor.amazon_fire_internal_storage_free_space")
assert state
@ -155,7 +155,7 @@ async def test_sensors_sensors(
mock_fully_kiosk.getDeviceInfo.side_effect = FullyKioskError("error", "status")
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("sensor.amazon_fire_internal_storage_free_space")
assert state
@ -181,7 +181,7 @@ async def test_url_sensor_truncating(
"currentPage": long_url,
}
async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("sensor.amazon_fire_current_page")
assert state

View File

@ -831,11 +831,11 @@ async def test_device_registry_calls(hass: HomeAssistant) -> None:
return_value=os_mock_data,
):
async_fire_time_changed(hass, dt_util.now() + timedelta(hours=1))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(dev_reg.devices) == 5
async_fire_time_changed(hass, dt_util.now() + timedelta(hours=2))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(dev_reg.devices) == 5
supervisor_mock_data = {

View File

@ -298,7 +298,7 @@ async def test_stats_addon_sensor(
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert "Could not fetch stats" not in caplog.text
@ -308,7 +308,7 @@ async def test_stats_addon_sensor(
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert "Could not fetch stats" not in caplog.text
@ -316,7 +316,7 @@ async def test_stats_addon_sensor(
entity_registry.async_update_entity(entity_id, disabled_by=None)
freezer.tick(config_entries.RELOAD_AFTER_UPDATE_DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert config_entry.state is config_entries.ConfigEntryState.LOADED
# Verify the entity is still enabled
assert entity_registry.async_get(entity_id).disabled_by is None
@ -324,13 +324,13 @@ async def test_stats_addon_sensor(
# The config entry just reloaded, so we need to wait for the next update
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(entity_id) is not None
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
# Verify that the entity have the expected state.
state = hass.states.get(entity_id)
assert state.state == expected
@ -341,7 +341,7 @@ async def test_stats_addon_sensor(
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state.state == STATE_UNAVAILABLE

View File

@ -1003,7 +1003,7 @@ async def test_does_not_work_into_the_future(
one_hour_in = start_time + timedelta(minutes=60)
with freeze_time(one_hour_in):
async_fire_time_changed(hass, one_hour_in)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN
assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN
@ -1013,7 +1013,7 @@ async def test_does_not_work_into_the_future(
hass.states.async_set("binary_sensor.state", "off")
await hass.async_block_till_done()
async_fire_time_changed(hass, turn_off_time)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN
assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN
@ -1021,7 +1021,7 @@ async def test_does_not_work_into_the_future(
turn_back_on_time = start_time + timedelta(minutes=105)
with freeze_time(turn_back_on_time):
async_fire_time_changed(hass, turn_back_on_time)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN
assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN
@ -1036,7 +1036,7 @@ async def test_does_not_work_into_the_future(
end_time = start_time + timedelta(minutes=120)
with freeze_time(end_time):
async_fire_time_changed(hass, end_time)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN
assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN
@ -1044,7 +1044,7 @@ async def test_does_not_work_into_the_future(
in_the_window = start_time + timedelta(hours=23, minutes=5)
with freeze_time(in_the_window):
async_fire_time_changed(hass, in_the_window)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.sensor1").state == "0.08"
assert hass.states.get("sensor.sensor2").state == "0.0833333333333333"
@ -1055,7 +1055,7 @@ async def test_does_not_work_into_the_future(
return_value=[],
), freeze_time(past_the_window):
async_fire_time_changed(hass, past_the_window)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN
@ -1077,7 +1077,7 @@ async def test_does_not_work_into_the_future(
_fake_off_states,
), freeze_time(past_the_window_with_data):
async_fire_time_changed(hass, past_the_window_with_data)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN
@ -1087,7 +1087,7 @@ async def test_does_not_work_into_the_future(
_fake_off_states,
), freeze_time(at_the_next_window_with_data):
async_fire_time_changed(hass, at_the_next_window_with_data)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.sensor1").state == "0.0"
@ -1487,7 +1487,7 @@ async def test_end_time_with_microseconds_zeroed(
)
async_fire_time_changed(hass, time_200)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83"
assert (
hass.states.get("sensor.heatpump_compressor_today2").state
@ -1499,7 +1499,7 @@ async def test_end_time_with_microseconds_zeroed(
time_400 = start_of_today + timedelta(hours=4)
with freeze_time(time_400):
async_fire_time_changed(hass, time_400)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83"
assert (
hass.states.get("sensor.heatpump_compressor_today2").state
@ -1510,7 +1510,7 @@ async def test_end_time_with_microseconds_zeroed(
time_600 = start_of_today + timedelta(hours=6)
with freeze_time(time_600):
async_fire_time_changed(hass, time_600)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "3.83"
assert (
hass.states.get("sensor.heatpump_compressor_today2").state
@ -1525,7 +1525,7 @@ async def test_end_time_with_microseconds_zeroed(
with freeze_time(rolled_to_next_day):
async_fire_time_changed(hass, rolled_to_next_day)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "0.0"
assert hass.states.get("sensor.heatpump_compressor_today2").state == "0.0"
@ -1534,7 +1534,7 @@ async def test_end_time_with_microseconds_zeroed(
)
with freeze_time(rolled_to_next_day_plus_12):
async_fire_time_changed(hass, rolled_to_next_day_plus_12)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "12.0"
assert hass.states.get("sensor.heatpump_compressor_today2").state == "12.0"
@ -1543,7 +1543,7 @@ async def test_end_time_with_microseconds_zeroed(
)
with freeze_time(rolled_to_next_day_plus_14):
async_fire_time_changed(hass, rolled_to_next_day_plus_14)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "14.0"
assert hass.states.get("sensor.heatpump_compressor_today2").state == "14.0"
@ -1554,12 +1554,12 @@ async def test_end_time_with_microseconds_zeroed(
hass.states.async_set("binary_sensor.heatpump_compressor_state", "off")
await async_wait_recording_done(hass)
async_fire_time_changed(hass, rolled_to_next_day_plus_16_860000)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
rolled_to_next_day_plus_18 = start_of_today + timedelta(days=1, hours=18)
with freeze_time(rolled_to_next_day_plus_18):
async_fire_time_changed(hass, rolled_to_next_day_plus_18)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "16.0"
assert (
hass.states.get("sensor.heatpump_compressor_today2").state

View File

@ -177,7 +177,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non
[mock_entry_1, mock_entry_4, mock_entry_3],
)
async_fire_time_changed(hass, utcnow + SCAN_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
all_states = hass.states.async_all()
assert len(all_states) == 3
@ -186,7 +186,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non
# so no changes to entities.
mock_feed_update.return_value = "OK_NO_DATA", None
async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
all_states = hass.states.async_all()
assert len(all_states) == 3
@ -194,7 +194,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non
# Simulate an update - empty data, removes all entities
mock_feed_update.return_value = "ERROR", None
async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
all_states = hass.states.async_all()
assert len(all_states) == 0

View File

@ -6,9 +6,18 @@ import pytest
from homeassistant.components.lifx import config_flow, coordinator, util
from . import _patch_discovery
from tests.common import mock_device_registry, mock_registry
@pytest.fixture
def mock_discovery():
"""Mock discovery."""
with _patch_discovery():
yield
@pytest.fixture
def mock_effect_conductor():
"""Mock the effect conductor."""

View File

@ -4,6 +4,8 @@ from __future__ import annotations
from datetime import timedelta
import pytest
from homeassistant.components import lifx
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.const import (
@ -32,6 +34,7 @@ from . import (
from tests.common import MockConfigEntry, async_fire_time_changed
@pytest.mark.usefixtures("mock_discovery")
async def test_hev_cycle_state(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
@ -65,11 +68,11 @@ async def test_hev_cycle_state(
bulb.hev_cycle = {"duration": 7200, "remaining": 0, "last_power": False}
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(entity_id).state == STATE_OFF
bulb.hev_cycle = None
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(entity_id).state == STATE_UNKNOWN

View File

@ -64,15 +64,15 @@ async def test_configuring_lifx_causes_discovery(hass: HomeAssistant) -> None:
assert start_calls == 1
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 2
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 3
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 4

View File

@ -663,6 +663,7 @@ async def test_extended_multizone_messages(hass: HomeAssistant) -> None:
)
@pytest.mark.usefixtures("mock_discovery")
async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None:
"""Test the firmware flame and morph effects on a matrix device."""
config_entry = MockConfigEntry(
@ -721,7 +722,7 @@ async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None:
],
}
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state.state == STATE_ON
@ -777,7 +778,7 @@ async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None:
],
}
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state.state == STATE_ON
@ -803,6 +804,7 @@ async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None:
bulb.set_power.reset_mock()
@pytest.mark.usefixtures("mock_discovery")
async def test_lightstrip_move_effect(hass: HomeAssistant) -> None:
"""Test the firmware move effect on a light strip."""
config_entry = MockConfigEntry(
@ -859,7 +861,7 @@ async def test_lightstrip_move_effect(hass: HomeAssistant) -> None:
bulb.power_level = 65535
bulb.effect = {"name": "MOVE", "speed": 4.5, "direction": "Left"}
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state.state == STATE_ON
@ -1119,6 +1121,7 @@ async def test_white_bulb(hass: HomeAssistant) -> None:
bulb.set_color.reset_mock()
@pytest.mark.usefixtures("mock_discovery")
async def test_config_zoned_light_strip_fails(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
@ -1154,7 +1157,7 @@ async def test_config_zoned_light_strip_fails(
assert hass.states.get(entity_id).state == STATE_OFF
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE

View File

@ -144,15 +144,15 @@ async def test_discovery_is_more_frequent_during_migration(
assert start_calls == 1
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 3
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 4
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 5

View File

@ -2,6 +2,8 @@
from datetime import timedelta
import pytest
from homeassistant.components import lifx
from homeassistant.components.lifx.const import DOMAIN
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
@ -95,6 +97,7 @@ async def test_infrared_brightness(
assert state.state == "100%"
@pytest.mark.usefixtures("mock_discovery")
async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None:
"""Test getting and setting infrared brightness."""
@ -124,7 +127,7 @@ async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None:
bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=16383)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert bulb.set_infrared.calls[0][0][0] == 16383
@ -134,6 +137,7 @@ async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None:
bulb.set_infrared.reset_mock()
@pytest.mark.usefixtures("mock_discovery")
async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None:
"""Test getting and setting infrared brightness."""
@ -163,7 +167,7 @@ async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None:
bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=32767)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert bulb.set_infrared.calls[0][0][0] == 32767
@ -173,6 +177,7 @@ async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None:
bulb.set_infrared.reset_mock()
@pytest.mark.usefixtures("mock_discovery")
async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None:
"""Test getting and setting infrared brightness."""
@ -202,7 +207,7 @@ async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None:
bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=65535)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert bulb.set_infrared.calls[0][0][0] == 65535
@ -212,6 +217,7 @@ async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None:
bulb.set_infrared.reset_mock()
@pytest.mark.usefixtures("mock_discovery")
async def test_disable_infrared(hass: HomeAssistant) -> None:
"""Test getting and setting infrared brightness."""
@ -241,7 +247,7 @@ async def test_disable_infrared(hass: HomeAssistant) -> None:
bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=0)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert bulb.set_infrared.calls[0][0][0] == 0
@ -251,6 +257,7 @@ async def test_disable_infrared(hass: HomeAssistant) -> None:
bulb.set_infrared.reset_mock()
@pytest.mark.usefixtures("mock_discovery")
async def test_invalid_infrared_brightness(hass: HomeAssistant) -> None:
"""Test getting and setting infrared brightness."""
@ -273,7 +280,7 @@ async def test_invalid_infrared_brightness(hass: HomeAssistant) -> None:
bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=12345)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state.state == STATE_UNKNOWN

View File

@ -73,7 +73,7 @@ async def test_sensor_updating(
async def skip_time_and_check_events() -> None:
freezer.tick(timedelta(minutes=15))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert events == snapshot

View File

@ -275,7 +275,7 @@ async def test_coordinator_error(
ourgroceries.get_list_items.side_effect = exception
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("todo.test_list")
assert state.state == STATE_UNAVAILABLE

View File

@ -223,7 +223,7 @@ async def test_reauth(
# check reauth trigger with bad-auth responses
freezer.move_to(_MOCK_TIME_BAD_AUTH_RESPONSES)
async_fire_time_changed(hass, _MOCK_TIME_BAD_AUTH_RESPONSES)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert pvpc_aioclient_mock.call_count == 6
result = hass.config_entries.flow.async_progress_by_handler(DOMAIN)[0]
@ -252,5 +252,5 @@ async def test_reauth(
assert result["reason"] == "reauth_successful"
assert pvpc_aioclient_mock.call_count == 8
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert pvpc_aioclient_mock.call_count == 10

View File

@ -178,7 +178,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non
# so no changes to entities.
mock_feed_update.return_value = "OK_NO_DATA", None
async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
all_states = hass.states.async_all()
assert len(all_states) == 3
@ -186,7 +186,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non
# Simulate an update - empty data, removes all entities
mock_feed_update.return_value = "ERROR", None
async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
all_states = hass.states.async_all()
assert len(all_states) == 0

View File

@ -103,7 +103,7 @@ async def test_coordinator_client_connector_error(
mock_system_sensor.side_effect = QswError
freezer.tick(DATA_SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
mock_system_sensor.assert_called_once()
mock_users_verification.assert_called()
@ -115,7 +115,7 @@ async def test_coordinator_client_connector_error(
mock_firmware_update_check.side_effect = APIError
freezer.tick(FW_SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
mock_firmware_update_check.assert_called_once()
mock_firmware_update_check.reset_mock()
@ -123,7 +123,7 @@ async def test_coordinator_client_connector_error(
mock_firmware_update_check.side_effect = QswError
freezer.tick(FW_SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
mock_firmware_update_check.assert_called_once()

View File

@ -147,7 +147,7 @@ async def test_auth_failure_on_device_update(
side_effect=AuthenticationError,
):
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert "Authentication failed while fetching devices data: " in [
record.message
@ -191,7 +191,7 @@ async def test_error_on_global_update(
side_effect=error_type,
):
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert log_msg in [
record.message for record in caplog.records if record.levelname == "ERROR"
@ -232,7 +232,7 @@ async def test_error_on_device_update(
side_effect=error_type,
):
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert log_msg in [
record.message for record in caplog.records if record.levelname == "ERROR"

View File

@ -326,7 +326,7 @@ async def test_update_off_ws_with_power_state(
next_update = mock_now + timedelta(minutes=1)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
remotews.start_listening.assert_called_once()
rest_api.rest_device_info.assert_called_once()
@ -342,7 +342,7 @@ async def test_update_off_ws_with_power_state(
next_update = mock_now + timedelta(minutes=2)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
rest_api.rest_device_info.assert_called_once()
@ -355,7 +355,7 @@ async def test_update_off_ws_with_power_state(
next_update = mock_now + timedelta(minutes=3)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
rest_api.rest_device_info.assert_called_once()
@ -386,7 +386,7 @@ async def test_update_off_encryptedws(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_OFF
@ -407,12 +407,12 @@ async def test_update_access_denied(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
next_update = mock_now + timedelta(minutes=10)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert [
flow
@ -442,7 +442,7 @@ async def test_update_ws_connection_failure(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert (
"Unexpected ConnectionFailure trying to get remote for fake_host, please "
@ -470,7 +470,7 @@ async def test_update_ws_connection_closed(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_OFF
@ -492,7 +492,7 @@ async def test_update_ws_unauthorized_error(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert [
flow
@ -517,7 +517,7 @@ async def test_update_unhandled_response(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_ON
@ -537,7 +537,7 @@ async def test_connection_closed_during_update_can_recover(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_UNAVAILABLE
@ -545,7 +545,7 @@ async def test_connection_closed_during_update_can_recover(
next_update = mock_now + timedelta(minutes=10)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_ON
@ -705,7 +705,7 @@ async def test_state(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non
):
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
# Should be STATE_UNAVAILABLE since there is no way to turn it back on
@ -1444,7 +1444,7 @@ async def test_upnp_re_subscribe_events(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_OFF
@ -1454,7 +1454,7 @@ async def test_upnp_re_subscribe_events(
next_update = mock_now + timedelta(minutes=10)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_ON
@ -1490,7 +1490,7 @@ async def test_upnp_failed_re_subscribe_events(
next_update = mock_now + timedelta(minutes=5)
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_OFF
@ -1501,7 +1501,7 @@ async def test_upnp_failed_re_subscribe_events(
with patch.object(dmr_device, "async_subscribe_services", side_effect=error):
freezer.move_to(next_update)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_ON

View File

@ -139,15 +139,15 @@ async def _setup_seventeentrack(hass, config=None, summary_data=None):
await hass.async_block_till_done()
async def _goto_future(hass, future=None):
async def _goto_future(hass: HomeAssistant, future=None):
"""Move to future."""
if not future:
future = utcnow() + datetime.timedelta(minutes=10)
with patch("homeassistant.util.utcnow", return_value=future):
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
async def test_full_valid_config(hass: HomeAssistant) -> None:

View File

@ -64,7 +64,7 @@ async def test_switch_get_states(hass: HomeAssistant, mock_asyncsleepiq) -> None
mock_asyncsleepiq.beds[BED_ID].foundation.lights[0].is_on = True
async_fire_time_changed(hass, utcnow() + LONGER_UPDATE_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert (
hass.states.get(f"light.sleepnumber_{BED_NAME_LOWER}_light_1").state == STATE_ON

View File

@ -59,7 +59,7 @@ async def test_switch_get_states(hass: HomeAssistant, mock_asyncsleepiq) -> None
mock_asyncsleepiq.beds[BED_ID].paused = True
async_fire_time_changed(hass, utcnow() + LONGER_UPDATE_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert (
hass.states.get(f"switch.sleepnumber_{BED_NAME_LOWER}_pause_mode").state

View File

@ -3,13 +3,14 @@
from datetime import timedelta
from homeassistant.components.smarttub.const import SCAN_INTERVAL
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from tests.common import async_fire_time_changed
async def trigger_update(hass):
async def trigger_update(hass: HomeAssistant) -> None:
"""Trigger a polling update by moving time forward."""
new_time = dt_util.utcnow() + timedelta(seconds=SCAN_INTERVAL + 1)
async_fire_time_changed(hass, new_time)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)

View File

@ -248,7 +248,7 @@ async def test_invalid_url_on_update(
hass,
dt_util.utcnow() + timedelta(minutes=1),
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert "sqlite://****:****@homeassistant.local" in caplog.text
@ -287,7 +287,7 @@ async def test_templates_with_yaml(
hass,
dt_util.utcnow() + timedelta(minutes=1),
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("sensor.get_values_with_template")
assert state.state == "5"
@ -301,7 +301,7 @@ async def test_templates_with_yaml(
hass,
dt_util.utcnow() + timedelta(minutes=2),
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("sensor.get_values_with_template")
assert state.state == STATE_UNAVAILABLE
@ -314,7 +314,7 @@ async def test_templates_with_yaml(
hass,
dt_util.utcnow() + timedelta(minutes=3),
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("sensor.get_values_with_template")
assert state.state == "5"
@ -488,7 +488,7 @@ async def test_no_issue_when_view_has_the_text_entity_id_in_it(
hass,
dt_util.utcnow() + timedelta(minutes=1),
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert (
"Query contains entity_id but does not reference states_meta" not in caplog.text
@ -622,7 +622,7 @@ async def test_query_recover_from_rollback(
):
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert "sqlite3.OperationalError" in caplog.text
state = hass.states.get("sensor.select_value_sql_query")
@ -631,7 +631,7 @@ async def test_query_recover_from_rollback(
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("sensor.select_value_sql_query")
assert state.state == "5"

View File

@ -5,6 +5,7 @@ from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
from discovery30303 import AIODiscovery30303
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components import steamist
@ -113,7 +114,9 @@ async def test_config_entry_fills_unique_id_with_directed_discovery(
@pytest.mark.usefixtures("mock_single_broadcast_address")
async def test_discovery_happens_at_interval(hass: HomeAssistant) -> None:
async def test_discovery_happens_at_interval(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test that discovery happens at interval."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=DEFAULT_ENTRY_DATA, unique_id=FORMATTED_MAC_ADDRESS
@ -126,10 +129,11 @@ async def test_discovery_happens_at_interval(hass: HomeAssistant) -> None:
return_value=mock_aio_discovery,
), _patch_status(MOCK_ASYNC_GET_STATUS_ACTIVE):
await async_setup_component(hass, steamist.DOMAIN, {steamist.DOMAIN: {}})
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(mock_aio_discovery.async_scan.mock_calls) == 2
async_fire_time_changed(hass, utcnow() + steamist.DISCOVERY_INTERVAL)
await hass.async_block_till_done()
freezer.move_to(utcnow() + steamist.DISCOVERY_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert len(mock_aio_discovery.async_scan.mock_calls) == 3

View File

@ -6,6 +6,7 @@ import copy
from datetime import timedelta
from unittest.mock import AsyncMock, MagicMock, patch
from freezegun.api import FrozenDateTimeFactory
from kasa.exceptions import AuthenticationException
import pytest
@ -41,23 +42,28 @@ from . import (
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_configuring_tplink_causes_discovery(hass: HomeAssistant) -> None:
async def test_configuring_tplink_causes_discovery(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test that specifying empty config does discovery."""
with patch("homeassistant.components.tplink.Discover.discover") as discover, patch(
"homeassistant.components.tplink.Discover.discover_single"
):
discover.return_value = {MagicMock(): MagicMock()}
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
# call_count will differ based on number of broadcast addresses
call_count = len(discover.mock_calls)
assert discover.mock_calls
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15))
await hass.async_block_till_done()
freezer.tick(tplink.DISCOVERY_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert len(discover.mock_calls) == call_count * 2
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=30))
await hass.async_block_till_done()
freezer.tick(tplink.DISCOVERY_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert len(discover.mock_calls) == call_count * 3

View File

@ -1118,7 +1118,7 @@ async def test_elec_measurement_sensor_polling(
# let the polling happen
future = dt_util.utcnow() + timedelta(seconds=90)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
# ensure the state has been updated to 6.0
state = hass.states.get(entity_id)

View File

@ -72,7 +72,7 @@ async def test_polling_only_updates_entities_it_should_poll(
poll_ent.async_update.reset_mock()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=20))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert not no_poll_ent.async_update.called
assert poll_ent.async_update.called
@ -121,7 +121,7 @@ async def test_polling_updates_entities_with_exception(hass: HomeAssistant) -> N
update_err.clear()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=20))
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(update_ok) == 3
assert len(update_err) == 1
@ -140,7 +140,7 @@ async def test_update_state_adds_entities(hass: HomeAssistant) -> None:
ent2.update = lambda *_: component.add_entities([ent1])
async_fire_time_changed(hass, dt_util.utcnow() + DEFAULT_SCAN_INTERVAL)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert len(hass.states.async_entity_ids()) == 2

View File

@ -4330,12 +4330,6 @@ async def test_task_tracking(hass: HomeAssistant) -> None:
entry.async_create_background_task(
hass, test_task(), "background-task-name", eager_start=False
)
entry.async_create_periodic_task(
hass, test_task(), "periodic-task-name", eager_start=False
)
entry.async_create_periodic_task(
hass, test_task(), "periodic-task-name", eager_start=True
)
await asyncio.sleep(0)
hass.loop.call_soon(event.set)
await entry._async_process_on_unload(hass)
@ -4343,8 +4337,6 @@ async def test_task_tracking(hass: HomeAssistant) -> None:
"on_unload",
"background",
"background",
"background",
"background",
"normal",
]

View File

@ -57,6 +57,7 @@ from homeassistant.exceptions import (
ServiceNotFound,
)
from homeassistant.helpers.json import json_dumps
from homeassistant.util.async_ import create_eager_task
import homeassistant.util.dt as dt_util
from homeassistant.util.read_only_dict import ReadOnlyDict
from homeassistant.util.unit_system import METRIC_SYSTEM
@ -97,6 +98,134 @@ async def test_async_add_hass_job_schedule_callback() -> None:
assert len(hass.add_job.mock_calls) == 0
async def test_async_add_hass_job_eager_start_coro_suspends(
hass: HomeAssistant,
) -> None:
"""Test scheduling a coro as a task that will suspend with eager_start."""
async def job_that_suspends():
await asyncio.sleep(0)
task = hass.async_add_hass_job(
ha.HassJob(ha.callback(job_that_suspends)), eager_start=True
)
assert not task.done()
assert task in hass._tasks
await task
assert task not in hass._tasks
async def test_async_run_hass_job_eager_start_coro_suspends(
hass: HomeAssistant,
) -> None:
"""Test scheduling a coro as a task that will suspend with eager_start."""
async def job_that_suspends():
await asyncio.sleep(0)
task = hass.async_run_hass_job(
ha.HassJob(ha.callback(job_that_suspends)), eager_start=True
)
assert not task.done()
assert task in hass._tasks
await task
assert task not in hass._tasks
async def test_async_add_hass_job_background(hass: HomeAssistant) -> None:
"""Test scheduling a coro as a background task with async_add_hass_job."""
async def job_that_suspends():
await asyncio.sleep(0)
task = hass.async_add_hass_job(
ha.HassJob(ha.callback(job_that_suspends)), background=True
)
assert not task.done()
assert task in hass._background_tasks
await task
assert task not in hass._background_tasks
async def test_async_run_hass_job_background(hass: HomeAssistant) -> None:
"""Test scheduling a coro as a background task with async_run_hass_job."""
async def job_that_suspends():
await asyncio.sleep(0)
task = hass.async_run_hass_job(
ha.HassJob(ha.callback(job_that_suspends)), background=True
)
assert not task.done()
assert task in hass._background_tasks
await task
assert task not in hass._background_tasks
async def test_async_add_hass_job_eager_background(hass: HomeAssistant) -> None:
"""Test scheduling a coro as an eager background task with async_add_hass_job."""
async def job_that_suspends():
await asyncio.sleep(0)
task = hass.async_add_hass_job(
ha.HassJob(ha.callback(job_that_suspends)), eager_start=True, background=True
)
assert not task.done()
assert task in hass._background_tasks
await task
assert task not in hass._background_tasks
async def test_async_run_hass_job_eager_background(hass: HomeAssistant) -> None:
"""Test scheduling a coro as an eager background task with async_run_hass_job."""
async def job_that_suspends():
await asyncio.sleep(0)
task = hass.async_run_hass_job(
ha.HassJob(ha.callback(job_that_suspends)), eager_start=True, background=True
)
assert not task.done()
assert task in hass._background_tasks
await task
assert task not in hass._background_tasks
async def test_async_run_hass_job_background_synchronous(hass: HomeAssistant) -> None:
"""Test scheduling a coro as an eager background task with async_run_hass_job."""
async def job_that_does_not_suspends():
pass
task = hass.async_run_hass_job(
ha.HassJob(ha.callback(job_that_does_not_suspends)),
eager_start=True,
background=True,
)
assert task.done()
assert task not in hass._background_tasks
assert task not in hass._tasks
await task
async def test_async_run_hass_job_synchronous(hass: HomeAssistant) -> None:
"""Test scheduling a coro as an eager task with async_run_hass_job."""
async def job_that_does_not_suspends():
pass
task = hass.async_run_hass_job(
ha.HassJob(ha.callback(job_that_does_not_suspends)),
eager_start=True,
background=False,
)
assert task.done()
assert task not in hass._background_tasks
assert task not in hass._tasks
await task
async def test_async_add_hass_job_coro_named(hass: HomeAssistant) -> None:
"""Test that we schedule coroutines and add jobs to the job pool with a name."""
@ -110,6 +239,19 @@ async def test_async_add_hass_job_coro_named(hass: HomeAssistant) -> None:
assert "named coro" in str(task)
async def test_async_add_hass_job_eager_start(hass: HomeAssistant) -> None:
"""Test eager_start with async_add_hass_job."""
async def mycoro():
pass
job = ha.HassJob(mycoro, "named coro")
assert "named coro" in str(job)
assert job.name == "named coro"
task = ha.HomeAssistant.async_add_hass_job(hass, job, eager_start=True)
assert "named coro" in str(task)
async def test_async_add_hass_job_schedule_partial_callback() -> None:
"""Test that we schedule partial coros and add jobs to the job pool."""
hass = MagicMock()
@ -135,6 +277,24 @@ async def test_async_add_hass_job_schedule_coroutinefunction() -> None:
assert len(hass.add_job.mock_calls) == 0
async def test_async_add_hass_job_schedule_corofunction_eager_start() -> None:
"""Test that we schedule coroutines and add jobs to the job pool."""
hass = MagicMock(loop=MagicMock(wraps=asyncio.get_running_loop()))
async def job():
pass
with patch(
"homeassistant.core.create_eager_task", wraps=create_eager_task
) as mock_create_eager_task:
hass_job = ha.HassJob(job)
task = ha.HomeAssistant.async_add_hass_job(hass, hass_job, eager_start=True)
assert len(hass.loop.call_soon.mock_calls) == 0
assert len(hass.add_job.mock_calls) == 0
assert mock_create_eager_task.mock_calls
await task
async def test_async_add_hass_job_schedule_partial_coroutinefunction() -> None:
"""Test that we schedule partial coros and add jobs to the job pool."""
hass = MagicMock(loop=MagicMock(wraps=asyncio.get_running_loop()))
@ -224,7 +384,7 @@ async def test_async_create_task_schedule_coroutine_with_name() -> None:
assert "named task" in str(task)
async def test_async_run_periodic_hass_job_calls_callback() -> None:
async def test_async_run_eager_hass_job_calls_callback() -> None:
"""Test that the callback annotation is respected."""
hass = MagicMock()
calls = []
@ -233,36 +393,21 @@ async def test_async_run_periodic_hass_job_calls_callback() -> None:
asyncio.get_running_loop() # ensure we are in the event loop
calls.append(1)
ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(ha.callback(job)))
ha.HomeAssistant.async_run_hass_job(
hass, ha.HassJob(ha.callback(job)), eager_start=True
)
assert len(calls) == 1
async def test_async_run_periodic_hass_job_calls_coro_function() -> None:
"""Test running coros from async_run_periodic_hass_job."""
async def test_async_run_eager_hass_job_calls_coro_function() -> None:
"""Test running coros from async_run_hass_job with eager_start."""
hass = MagicMock()
calls = []
async def job():
calls.append(1)
pass
await ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(job))
assert len(calls) == 1
async def test_async_run_periodic_hass_job_calls_executor_function() -> None:
"""Test running in the executor from async_run_periodic_hass_job."""
hass = MagicMock()
hass.loop = asyncio.get_running_loop()
calls = []
def job():
try:
asyncio.get_running_loop() # ensure we are not in the event loop
except RuntimeError:
calls.append(1)
await ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(job))
assert len(calls) == 1
ha.HomeAssistant.async_run_hass_job(hass, ha.HassJob(job), eager_start=True)
assert len(hass.async_add_hass_job.mock_calls) == 1
async def test_async_run_hass_job_calls_callback() -> None:
@ -556,7 +701,7 @@ async def test_shutdown_calls_block_till_done_after_shutdown_run_callback_thread
"""Ensure shutdown_run_callback_threadsafe is called before the final async_block_till_done."""
stop_calls = []
async def _record_block_till_done(wait_periodic_tasks: bool = True):
async def _record_block_till_done(wait_background_tasks: bool = False):
nonlocal stop_calls
stop_calls.append("async_block_till_done")
@ -2142,7 +2287,7 @@ async def test_chained_logging_hits_log_timeout(
with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.0):
hass.async_create_task(_task_chain_1())
await hass.async_block_till_done(wait_periodic_tasks=False)
await hass.async_block_till_done(wait_background_tasks=False)
assert "_task_chain_" in caplog.text
@ -2696,27 +2841,6 @@ async def test_background_task(hass: HomeAssistant, eager_start: bool) -> None:
assert result.result() == ha.CoreState.stopping
@pytest.mark.parametrize("eager_start", (True, False))
async def test_periodic_task(hass: HomeAssistant, eager_start: bool) -> None:
"""Test periodic tasks being quit."""
result = asyncio.Future()
async def test_task():
try:
await asyncio.sleep(1)
except asyncio.CancelledError:
result.set_result(hass.state)
raise
task = hass.async_create_periodic_task(
test_task(), "happy task", eager_start=eager_start
)
assert "happy task" in str(task)
await asyncio.sleep(0)
await hass.async_stop()
assert result.result() == ha.CoreState.stopping
async def test_shutdown_does_not_block_on_normal_tasks(
hass: HomeAssistant,
) -> None:
@ -2767,14 +2891,15 @@ async def test_shutdown_does_not_block_on_shielded_tasks(
sleep_task.cancel()
async def test_cancellable_hassjob(hass: HomeAssistant) -> None:
@pytest.mark.parametrize("eager_start", (True, False))
async def test_cancellable_hassjob(hass: HomeAssistant, eager_start: bool) -> None:
"""Simulate a shutdown, ensure cancellable jobs are cancelled."""
job = MagicMock()
@ha.callback
def run_job(job: HassJob) -> None:
"""Call the action."""
hass.async_run_hass_job(job)
hass.async_run_hass_job(job, eager_start=True)
timer1 = hass.loop.call_later(
60, run_job, HassJob(ha.callback(job), cancel_on_shutdown=True)