From 65358c129ab0fc3585a0de2843085c355d627a44 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 16:45:10 -1000 Subject: [PATCH] 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 --- homeassistant/bootstrap.py | 2 +- homeassistant/config_entries.py | 37 +-- homeassistant/core.py | 156 +++++------- homeassistant/helpers/entity_platform.py | 4 +- homeassistant/helpers/event.py | 8 +- homeassistant/helpers/update_coordinator.py | 4 +- tests/components/aemet/test_coordinator.py | 2 +- .../airzone_cloud/test_coordinator.py | 2 +- tests/components/broadlink/test_heartbeat.py | 7 +- tests/components/cloudflare/test_init.py | 6 +- .../command_line/test_binary_sensor.py | 4 +- tests/components/command_line/test_cover.py | 10 +- tests/components/command_line/test_init.py | 2 +- tests/components/command_line/test_sensor.py | 8 +- tests/components/command_line/test_switch.py | 6 +- tests/components/efergy/test_sensor.py | 4 +- tests/components/feedreader/test_init.py | 4 +- .../fully_kiosk/test_binary_sensor.py | 4 +- tests/components/fully_kiosk/test_number.py | 4 +- tests/components/fully_kiosk/test_sensor.py | 6 +- tests/components/hassio/test_init.py | 4 +- tests/components/hassio/test_sensor.py | 12 +- tests/components/history_stats/test_sensor.py | 32 +-- .../ign_sismologia/test_geo_location.py | 6 +- tests/components/lifx/conftest.py | 9 + tests/components/lifx/test_binary_sensor.py | 7 +- tests/components/lifx/test_init.py | 6 +- tests/components/lifx/test_light.py | 11 +- tests/components/lifx/test_migration.py | 6 +- tests/components/lifx/test_select.py | 17 +- tests/components/opensky/test_sensor.py | 2 +- tests/components/ourgroceries/test_todo.py | 2 +- .../pvpc_hourly_pricing/test_config_flow.py | 4 +- .../qld_bushfire/test_geo_location.py | 4 +- tests/components/qnap_qsw/test_coordinator.py | 6 +- tests/components/ring/test_init.py | 6 +- .../components/samsungtv/test_media_player.py | 34 +-- .../components/seventeentrack/test_sensor.py | 6 +- tests/components/sleepiq/test_light.py | 2 +- tests/components/sleepiq/test_switch.py | 2 +- tests/components/smarttub/__init__.py | 5 +- tests/components/sql/test_sensor.py | 14 +- tests/components/steamist/test_init.py | 12 +- tests/components/tplink/test_init.py | 18 +- tests/components/zha/test_sensor.py | 2 +- tests/helpers/test_entity_platform.py | 6 +- tests/test_config_entries.py | 8 - tests/test_core.py | 223 ++++++++++++++---- 48 files changed, 413 insertions(+), 333 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index da51332d93d..f1ccfcbdb89 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -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", diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b2206912fb3..150c377a6ee 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -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 diff --git a/homeassistant/core.py b/homeassistant/core.py index a386f4725fe..21a7de4311d 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -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" diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 9175d46355e..299b8c214b3 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -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, diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 15b8b083ba6..e7beddec0c7 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -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( diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 42d7232a4cb..9479c356f24 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -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, diff --git a/tests/components/aemet/test_coordinator.py b/tests/components/aemet/test_coordinator.py index 890c3476da2..e830f50c54a 100644 --- a/tests/components/aemet/test_coordinator.py +++ b/tests/components/aemet/test_coordinator.py @@ -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 diff --git a/tests/components/airzone_cloud/test_coordinator.py b/tests/components/airzone_cloud/test_coordinator.py index 40b6c937ed2..4b42ab7c329 100644 --- a/tests/components/airzone_cloud/test_coordinator.py +++ b/tests/components/airzone_cloud/test_coordinator.py @@ -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() diff --git a/tests/components/broadlink/test_heartbeat.py b/tests/components/broadlink/test_heartbeat.py index 92046000268..d6ce7104bf7 100644 --- a/tests/components/broadlink/test_heartbeat.py +++ b/tests/components/broadlink/test_heartbeat.py @@ -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) diff --git a/tests/components/cloudflare/test_init.py b/tests/components/cloudflare/test_init.py index 9136d229649..128c865e744 100644 --- a/tests/components/cloudflare/test_init.py +++ b/tests/components/cloudflare/test_init.py @@ -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 diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index adbca6124f1..fd726ab77a4 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -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 diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index 58506355ad0..8b98d8d1623 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -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 diff --git a/tests/components/command_line/test_init.py b/tests/components/command_line/test_init.py index 4f58705e7bf..3fbd0e0f898 100644 --- a/tests/components/command_line/test_init.py +++ b/tests/components/command_line/test_init.py @@ -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") diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index 86f6d4d3179..26f97e37543 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -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 diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index 53d9cc96560..c464ded34fb 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -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 diff --git a/tests/components/efergy/test_sensor.py b/tests/components/efergy/test_sensor.py index e1a893f4f86..d7ab3101900 100644 --- a/tests/components/efergy/test_sensor.py +++ b/tests/components/efergy/test_sensor.py @@ -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" diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index 25760da0028..b0f2f9e4e72 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -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 diff --git a/tests/components/fully_kiosk/test_binary_sensor.py b/tests/components/fully_kiosk/test_binary_sensor.py index 23843eef19c..70ae2d15b61 100644 --- a/tests/components/fully_kiosk/test_binary_sensor.py +++ b/tests/components/fully_kiosk/test_binary_sensor.py @@ -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 diff --git a/tests/components/fully_kiosk/test_number.py b/tests/components/fully_kiosk/test_number.py index 6bdad9af520..b4ac50cb076 100644 --- a/tests/components/fully_kiosk/test_number.py +++ b/tests/components/fully_kiosk/test_number.py @@ -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 diff --git a/tests/components/fully_kiosk/test_sensor.py b/tests/components/fully_kiosk/test_sensor.py index 6342e3216d7..ebf01f5e3d7 100644 --- a/tests/components/fully_kiosk/test_sensor.py +++ b/tests/components/fully_kiosk/test_sensor.py @@ -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 diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 462fc845c44..099b75d3686 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -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 = { diff --git a/tests/components/hassio/test_sensor.py b/tests/components/hassio/test_sensor.py index fbf9a9acc79..82ac3eccdf5 100644 --- a/tests/components/hassio/test_sensor.py +++ b/tests/components/hassio/test_sensor.py @@ -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 diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index a6422996726..e51fba9990e 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -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 diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py index d208a1d42ae..d9b268ff575 100644 --- a/tests/components/ign_sismologia/test_geo_location.py +++ b/tests/components/ign_sismologia/test_geo_location.py @@ -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 diff --git a/tests/components/lifx/conftest.py b/tests/components/lifx/conftest.py index 50296c978f0..15fe8898a5f 100644 --- a/tests/components/lifx/conftest.py +++ b/tests/components/lifx/conftest.py @@ -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.""" diff --git a/tests/components/lifx/test_binary_sensor.py b/tests/components/lifx/test_binary_sensor.py index a16bf1173b0..fa57591e305 100644 --- a/tests/components/lifx/test_binary_sensor.py +++ b/tests/components/lifx/test_binary_sensor.py @@ -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 diff --git a/tests/components/lifx/test_init.py b/tests/components/lifx/test_init.py index 31a558da052..c79e4bb3856 100644 --- a/tests/components/lifx/test_init.py +++ b/tests/components/lifx/test_init.py @@ -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 diff --git a/tests/components/lifx/test_light.py b/tests/components/lifx/test_light.py index 887e622b5cc..452112d2235 100644 --- a/tests/components/lifx/test_light.py +++ b/tests/components/lifx/test_light.py @@ -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 diff --git a/tests/components/lifx/test_migration.py b/tests/components/lifx/test_migration.py index 6f68f9e798e..3621eb165fa 100644 --- a/tests/components/lifx/test_migration.py +++ b/tests/components/lifx/test_migration.py @@ -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 diff --git a/tests/components/lifx/test_select.py b/tests/components/lifx/test_select.py index 9cc361fc1f4..c639dd441e7 100644 --- a/tests/components/lifx/test_select.py +++ b/tests/components/lifx/test_select.py @@ -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 diff --git a/tests/components/opensky/test_sensor.py b/tests/components/opensky/test_sensor.py index 80c53430ea9..df4faaa3e4a 100644 --- a/tests/components/opensky/test_sensor.py +++ b/tests/components/opensky/test_sensor.py @@ -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 diff --git a/tests/components/ourgroceries/test_todo.py b/tests/components/ourgroceries/test_todo.py index d598767409a..672e2e14447 100644 --- a/tests/components/ourgroceries/test_todo.py +++ b/tests/components/ourgroceries/test_todo.py @@ -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 diff --git a/tests/components/pvpc_hourly_pricing/test_config_flow.py b/tests/components/pvpc_hourly_pricing/test_config_flow.py index 86b0af1dd2c..2a4c5688b5f 100644 --- a/tests/components/pvpc_hourly_pricing/test_config_flow.py +++ b/tests/components/pvpc_hourly_pricing/test_config_flow.py @@ -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 diff --git a/tests/components/qld_bushfire/test_geo_location.py b/tests/components/qld_bushfire/test_geo_location.py index 916c7567b55..997d2f96aab 100644 --- a/tests/components/qld_bushfire/test_geo_location.py +++ b/tests/components/qld_bushfire/test_geo_location.py @@ -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 diff --git a/tests/components/qnap_qsw/test_coordinator.py b/tests/components/qnap_qsw/test_coordinator.py index 8a5f07e8173..893a86b262d 100644 --- a/tests/components/qnap_qsw/test_coordinator.py +++ b/tests/components/qnap_qsw/test_coordinator.py @@ -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() diff --git a/tests/components/ring/test_init.py b/tests/components/ring/test_init.py index 8d169002e38..64fca9eac2f 100644 --- a/tests/components/ring/test_init.py +++ b/tests/components/ring/test_init.py @@ -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" diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 446888a1f54..4cfb2195310 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -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 diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 995aaaaac87..2334fbd4057 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -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: diff --git a/tests/components/sleepiq/test_light.py b/tests/components/sleepiq/test_light.py index 55961ba989f..e261115c415 100644 --- a/tests/components/sleepiq/test_light.py +++ b/tests/components/sleepiq/test_light.py @@ -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 diff --git a/tests/components/sleepiq/test_switch.py b/tests/components/sleepiq/test_switch.py index 3cc9db235b6..8ab865663dc 100644 --- a/tests/components/sleepiq/test_switch.py +++ b/tests/components/sleepiq/test_switch.py @@ -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 diff --git a/tests/components/smarttub/__init__.py b/tests/components/smarttub/__init__.py index f6abd4cb5d7..747a901bbf6 100644 --- a/tests/components/smarttub/__init__.py +++ b/tests/components/smarttub/__init__.py @@ -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) diff --git a/tests/components/sql/test_sensor.py b/tests/components/sql/test_sensor.py index 701c2c363ef..9eabffd9421 100644 --- a/tests/components/sql/test_sensor.py +++ b/tests/components/sql/test_sensor.py @@ -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" diff --git a/tests/components/steamist/test_init.py b/tests/components/steamist/test_init.py index 911ac790206..a38b14ab15d 100644 --- a/tests/components/steamist/test_init.py +++ b/tests/components/steamist/test_init.py @@ -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 diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 4ad8687afe3..07d3916cc87 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -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 diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index 0ff5e91f4a9..b97ca1b8927 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -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) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 4a13e6c84d1..2486626f82f 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -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 diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 00601173e71..4ada764a9ba 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -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", ] diff --git a/tests/test_core.py b/tests/test_core.py index 9c78c670ba3..37a6251fa68 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -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)