Ensure test async_create_task eager start behavior matches production (#115517)

This commit is contained in:
J. Nick Koston 2024-04-13 10:58:52 -10:00 committed by GitHub
parent d9617a8e3a
commit ee535ee611
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 125 additions and 82 deletions

View File

@ -94,7 +94,9 @@ class LegacySubscriptionRepairFlow(RepairsFlow):
)
if not self.wait_task:
self.wait_task = self.hass.async_create_task(_async_wait_for_plan_change())
self.wait_task = self.hass.async_create_task(
_async_wait_for_plan_change(), eager_start=False
)
migration = await async_migrate_paypal_agreement(cloud)
return self.async_external_step(
step_id="change_plan",

View File

@ -148,7 +148,9 @@ class GitHubConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="could_not_register")
if self.login_task is None:
self.login_task = self.hass.async_create_task(_wait_for_login())
self.login_task = self.hass.async_create_task(
_wait_for_login(), eager_start=False
)
if self.login_task.done():
if self.login_task.exception():

View File

@ -381,7 +381,7 @@ class AddonManager:
self._logger.error(err)
break
return self._hass.async_create_task(addon_operation())
return self._hass.async_create_task(addon_operation(), eager_start=False)
class AddonError(HomeAssistantError):

View File

@ -417,6 +417,7 @@ class OptionsFlowHandler(OptionsFlow, ABC):
self.install_task = self.hass.async_create_task(
multipan_manager.async_install_addon_waiting(),
"SiLabs Multiprotocol addon install",
eager_start=False,
)
if not self.install_task.done():
@ -524,7 +525,7 @@ class OptionsFlowHandler(OptionsFlow, ABC):
if not self.start_task:
self.start_task = self.hass.async_create_task(
multipan_manager.async_start_addon_waiting()
multipan_manager.async_start_addon_waiting(), eager_start=False
)
if not self.start_task.done():
@ -561,7 +562,8 @@ class OptionsFlowHandler(OptionsFlow, ABC):
"""Prepare info needed to complete the config entry update."""
# Always reload entry after installing the addon.
self.hass.async_create_task(
self.hass.config_entries.async_reload(self.config_entry.entry_id)
self.hass.config_entries.async_reload(self.config_entry.entry_id),
eager_start=False,
)
# Finish ZHA migration if needed
@ -721,6 +723,7 @@ class OptionsFlowHandler(OptionsFlow, ABC):
self.install_task = self.hass.async_create_task(
flasher_manager.async_install_addon_waiting(),
"SiLabs Flasher addon install",
eager_start=False,
)
if not self.install_task.done():
@ -811,6 +814,7 @@ class OptionsFlowHandler(OptionsFlow, ABC):
self.stop_task = self.hass.async_create_task(
multipan_manager.async_uninstall_addon_waiting(),
"SiLabs Multiprotocol addon uninstall",
eager_start=False,
)
if not self.stop_task.done():
@ -843,7 +847,9 @@ class OptionsFlowHandler(OptionsFlow, ABC):
AddonState.NOT_RUNNING
)
self.start_task = self.hass.async_create_task(start_and_wait_until_done())
self.start_task = self.hass.async_create_task(
start_and_wait_until_done(), eager_start=False
)
if not self.start_task.done():
return self.async_show_progress(

View File

@ -176,7 +176,9 @@ class HomematicipGenericEntity(Entity):
"""Handle hmip device removal."""
# Set marker showing that the HmIP device hase been removed.
self.hmip_device_removed = True
self.hass.async_create_task(self.async_remove(force_remove=True))
self.hass.async_create_task(
self.async_remove(force_remove=True), eager_start=False
)
@property
def name(self) -> str:

View File

@ -344,7 +344,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
# UI to approve the request for a new token).
assert self._auth_id is not None
self._request_token_task = self.hass.async_create_task(
self._request_token_task_func(self._auth_id)
self._request_token_task_func(self._auth_id), eager_start=False
)
return self.async_external_step(
step_id="create_token_external", url=self._get_hyperion_url()

View File

@ -75,7 +75,7 @@ class IdasenDeskCoordinator(DataUpdateCoordinator[int | None]): # pylint: disab
if not self.desk.is_connected:
_LOGGER.debug("Desk disconnected. Reconnecting")
self._connection_lost = True
self.hass.async_create_task(self.async_connect())
self.hass.async_create_task(self.async_connect(), eager_start=False)
elif self._connection_lost:
_LOGGER.info("Reconnected to desk")
self._connection_lost = False

View File

@ -326,7 +326,9 @@ class ImprovBLEConfigFlow(ConfigFlow, domain=DOMAIN):
return
if not self._provision_task:
self._provision_task = self.hass.async_create_task(_do_provision())
self._provision_task = self.hass.async_create_task(
_do_provision(), eager_start=False
)
if not self._provision_task.done():
return self.async_show_progress(
@ -372,7 +374,9 @@ class ImprovBLEConfigFlow(ConfigFlow, domain=DOMAIN):
except AbortFlow as err:
return self.async_abort(reason=err.reason)
self._authorize_task = self.hass.async_create_task(authorized_event.wait())
self._authorize_task = self.hass.async_create_task(
authorized_event.wait(), eager_start=False
)
if not self._authorize_task.done():
return self.async_show_progress(

View File

@ -308,7 +308,9 @@ class MatrixBot:
async def _resolve_room_aliases(self, listening_rooms: list[RoomAnyID]) -> None:
"""Resolve any RoomAliases into RoomIDs for the purpose of client interactions."""
resolved_rooms = [
self.hass.async_create_task(self._resolve_room_alias(room_alias_or_id))
self.hass.async_create_task(
self._resolve_room_alias(room_alias_or_id), eager_start=False
)
for room_alias_or_id in listening_rooms
]
for resolved_room in asyncio.as_completed(resolved_rooms):
@ -330,7 +332,9 @@ class MatrixBot:
async def _join_rooms(self) -> None:
"""Join the Matrix rooms that we listen for commands in."""
rooms = [
self.hass.async_create_task(self._join_room(room_id, room_alias_or_id))
self.hass.async_create_task(
self._join_room(room_id, room_alias_or_id), eager_start=False
)
for room_alias_or_id, room_id in self._listening_rooms.items()
]
await asyncio.wait(rooms)
@ -438,7 +442,8 @@ class MatrixBot:
target_room=target_room,
message_type=message_type,
content=content,
)
),
eager_start=False,
)
for target_room in target_rooms
)
@ -514,7 +519,9 @@ class MatrixBot:
and len(target_rooms) > 0
):
image_tasks = [
self.hass.async_create_task(self._send_image(image_path, target_rooms))
self.hass.async_create_task(
self._send_image(image_path, target_rooms), eager_start=False
)
for image_path in image_paths
]
await asyncio.wait(image_tasks)

View File

@ -1015,7 +1015,8 @@ class MqttDiscoveryUpdate(Entity):
self.hass.async_create_task(
_async_process_discovery_update_and_remove(
payload, self._discovery_data
)
),
eager_start=False,
)
elif self._discovery_update:
if old_payload != self._discovery_data[ATTR_DISCOVERY_PAYLOAD]:
@ -1024,7 +1025,8 @@ class MqttDiscoveryUpdate(Entity):
self.hass.async_create_task(
_async_process_discovery_update(
payload, self._discovery_update, self._discovery_data
)
),
eager_start=False,
)
else:
# Non-empty, unchanged payload: Ignore to avoid changing states

View File

@ -105,7 +105,9 @@ class OctoPrintConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_get_api_key(self, user_input=None):
"""Get an Application Api Key."""
if not self.api_key_task:
self.api_key_task = self.hass.async_create_task(self._async_get_auth_key())
self.api_key_task = self.hass.async_create_task(
self._async_get_auth_key(), eager_start=False
)
if not self.api_key_task.done():
return self.async_show_progress(
step_id="get_api_key",
@ -133,7 +135,7 @@ class OctoPrintConfigFlow(ConfigFlow, domain=DOMAIN):
self.hass.config_entries.async_update_entry(existing_entry, data=user_input)
# Reload the config entry otherwise devices will remain unavailable
self.hass.async_create_task(
self.hass.config_entries.async_reload(existing_entry.entry_id)
self.hass.config_entries.async_reload(existing_entry.entry_id),
)
return self.async_abort(reason="reauth_successful")

View File

@ -209,7 +209,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
TMP_ENTITY.format(event_id)
)
hass.async_create_task(
hass.data[DATA_DEVICE_REGISTER][event_type](event)
hass.data[DATA_DEVICE_REGISTER][event_type](event),
eager_start=False,
)
else:
_LOGGER.debug("device_id not known and automatic add disabled")
@ -257,7 +258,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
# If HA is not stopping, initiate new connection
if hass.state is not CoreState.stopping:
_LOGGER.warning("Disconnected from Rflink, reconnecting")
hass.async_create_task(connect())
hass.async_create_task(connect(), eager_start=False)
_reconnect_job = HassJob(reconnect, "Rflink reconnect", cancel_on_shutdown=True)
@ -312,7 +313,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_LOGGER.info("Connected to Rflink")
hass.async_create_task(connect())
hass.async_create_task(connect(), eager_start=False)
async_dispatcher_connect(hass, SIGNAL_EVENT, event_callback)
return True
@ -580,7 +581,7 @@ class RflinkCommand(RflinkDevice):
if repetitions > 1:
self._repetition_task = self.hass.async_create_task(
self._async_send_command(cmd, repetitions - 1)
self._async_send_command(cmd, repetitions - 1), eager_start=False
)

View File

@ -132,7 +132,7 @@ class SnoozConfigFlow(ConfigFlow, domain=DOMAIN):
"""Wait for device to enter pairing mode."""
if not self._pairing_task:
self._pairing_task = self.hass.async_create_task(
self._async_wait_for_pairing_mode()
self._async_wait_for_pairing_mode(), eager_start=False
)
if not self._pairing_task.done():

View File

@ -174,7 +174,9 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
else:
self._discovered_device = device
await set_credentials(self.hass, username, password)
self.hass.async_create_task(self._async_reload_requires_auth_entries())
self.hass.async_create_task(
self._async_reload_requires_auth_entries(), eager_start=False
)
return self._async_create_entry_from_device(self._discovered_device)
self.context["title_placeholders"] = placeholders
@ -267,7 +269,9 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
placeholders["error"] = str(ex)
else:
await set_credentials(self.hass, username, password)
self.hass.async_create_task(self._async_reload_requires_auth_entries())
self.hass.async_create_task(
self._async_reload_requires_auth_entries(), eager_start=False
)
return self._async_create_entry_from_device(device)
return self.async_show_form(
@ -446,7 +450,9 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
placeholders["error"] = str(ex)
else:
await set_credentials(self.hass, username, password)
self.hass.async_create_task(self._async_reload_requires_auth_entries())
self.hass.async_create_task(
self._async_reload_requires_auth_entries(), eager_start=False
)
return self.async_abort(reason="reauth_successful")
# Old config entries will not have these values.

View File

@ -782,7 +782,7 @@ class SpeechManager:
return filename
audio_task = self.hass.async_create_task(get_tts_data())
audio_task = self.hass.async_create_task(get_tts_data(), eager_start=False)
def handle_error(_future: asyncio.Future) -> None:
"""Handle error."""

View File

@ -307,7 +307,8 @@ class DriverEvents:
controller.on(
"node added",
lambda event: self.hass.async_create_task(
self.controller_events.async_on_node_added(event["node"])
self.controller_events.async_on_node_added(event["node"]),
eager_start=False,
),
)
)
@ -415,7 +416,8 @@ class ControllerEvents:
node.on(
"ready",
lambda event: self.hass.async_create_task(
self.node_events.async_on_node_ready(event["node"])
self.node_events.async_on_node_ready(event["node"]),
eager_start=False,
),
)
)

View File

@ -971,6 +971,7 @@ class ConfigEntry:
hass.async_create_task(
self._async_init_reauth(hass, context, data),
f"config entry reauth {self.title} {self.domain} {self.entry_id}",
eager_start=False,
)
async def _async_init_reauth(

View File

@ -262,7 +262,7 @@ async def async_test_home_assistant(
return orig_async_add_executor_job(target, *args)
def async_create_task(coroutine, name=None, eager_start=False):
def async_create_task(coroutine, name=None, eager_start=True):
"""Create task."""
if isinstance(coroutine, Mock) and not isinstance(coroutine, AsyncMock):
fut = asyncio.Future()

View File

@ -35,12 +35,6 @@ async def test_setup_with_config(hass: HomeAssistant) -> None:
{"platform": DOMAIN, CONF_HOST: HOST, CONF_PORT: 888},
],
}
assert await async_setup_component(hass, SENSOR_DOMAIN, config) is True
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
next_update = dt_util.utcnow() + timedelta(seconds=20)
async_fire_time_changed(hass, next_update)
with (
patch(
@ -51,7 +45,13 @@ async def test_setup_with_config(hass: HomeAssistant) -> None:
return_value=future_timestamp(1),
),
):
assert await async_setup_component(hass, SENSOR_DOMAIN, config) is True
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
next_update = dt_util.utcnow() + timedelta(seconds=20)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done(wait_background_tasks=True)
assert len(hass.config_entries.async_entries(DOMAIN)) == 2

View File

@ -438,7 +438,7 @@ async def test_auth_create_token_approval_declined_task_canceled(
mock_task = CanceledAwaitableMock()
task_coro: Awaitable | None = None
def create_task(arg: Any) -> CanceledAwaitableMock:
def create_task(arg: Any, **kwargs: Any) -> CanceledAwaitableMock:
nonlocal task_coro
task_coro = arg
return mock_task
@ -458,6 +458,7 @@ async def test_auth_create_token_approval_declined_task_canceled(
)
assert result["step_id"] == "create_token"
# Tests should not patch the async_create_task function
with patch.object(hass, "async_create_task", side_effect=create_task):
result = await _configure_flow(hass, result)
assert result["step_id"] == "create_token_external"

View File

@ -360,6 +360,7 @@ async def test_trigger_reauth(
):
trigger_plex_update(mock_websocket)
await wait_for_debouncer(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
assert entry.state is not ConfigEntryState.LOADED

View File

@ -68,6 +68,7 @@ async def test_new_ignored_users_available(
)
trigger_plex_update(mock_websocket)
await wait_for_debouncer(hass)
await hass.async_block_till_done(wait_background_tasks=True)
server_id = mock_plex_server.machine_identifier
@ -89,6 +90,7 @@ async def test_new_ignored_users_available(
)
await wait_for_debouncer(hass)
await hass.async_block_till_done(wait_background_tasks=True)
sensor = hass.states.get("sensor.plex_server_1")
assert sensor.state == str(len(active_sessions))

View File

@ -397,7 +397,7 @@ async def test_dhcp_flow(hass: HomeAssistant, mock_setup_entry: MagicMock) -> No
None,
None,
TEST_HOST2,
[TEST_HOST, TEST_HOST2],
[TEST_HOST, TEST_HOST2, TEST_HOST2],
),
(
True,

View File

@ -332,6 +332,7 @@ async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) -
"max_media": 1000,
"allow_ea_channel": False,
}
await hass.async_block_till_done()
await hass.config_entries.async_unload(mock_config.entry_id)

View File

@ -180,6 +180,7 @@ async def test_form_reauth(hass: HomeAssistant, entry: MockConfigEntry) -> None:
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "reauth_successful"
await hass.async_block_till_done()
await hass.config_entries.async_unload(entry.entry_id)

View File

@ -58,9 +58,12 @@ async def test_caching_data(hass: HomeAssistant) -> None:
# Emulate a fresh load
hass.data.pop(DATA_RESTORE_STATE)
with patch(
"homeassistant.helpers.restore_state.Store.async_load",
side_effect=HomeAssistantError,
with (
patch(
"homeassistant.helpers.restore_state.Store.async_load",
side_effect=HomeAssistantError,
),
patch("homeassistant.helpers.restore_state.Store.async_save"),
):
# Failure to load should not be treated as fatal
await async_load(hass)
@ -68,7 +71,13 @@ async def test_caching_data(hass: HomeAssistant) -> None:
data = async_get(hass)
assert data.last_states == {}
await async_load(hass)
# Mock that only b1 is present this run
with patch(
"homeassistant.helpers.restore_state.Store.async_save"
) as mock_write_data:
await async_load(hass)
await hass.async_block_till_done()
data = async_get(hass)
entity = RestoreEntity()
@ -76,11 +85,7 @@ async def test_caching_data(hass: HomeAssistant) -> None:
entity.entity_id = "input_boolean.b1"
# Mock that only b1 is present this run
with patch(
"homeassistant.helpers.restore_state.Store.async_save"
) as mock_write_data:
state = await entity.async_get_last_state()
await hass.async_block_till_done()
state = await entity.async_get_last_state()
assert state is not None
assert state.entity_id == "input_boolean.b1"
@ -110,17 +115,17 @@ async def test_periodic_write(hass: HomeAssistant) -> None:
await data.store.async_save([])
# Emulate a fresh load
hass.data.pop(DATA_RESTORE_STATE)
await async_load(hass)
data = async_get(hass)
entity = RestoreEntity()
entity.hass = hass
entity.entity_id = "input_boolean.b1"
with patch(
"homeassistant.helpers.restore_state.Store.async_save"
) as mock_write_data:
hass.data.pop(DATA_RESTORE_STATE)
await async_load(hass)
data = async_get(hass)
entity = RestoreEntity()
entity.hass = hass
entity.entity_id = "input_boolean.b1"
await entity.async_get_last_state()
await hass.async_block_till_done()
@ -158,17 +163,17 @@ async def test_save_persistent_states(hass: HomeAssistant) -> None:
await data.store.async_save([])
# Emulate a fresh load
hass.data.pop(DATA_RESTORE_STATE)
await async_load(hass)
data = async_get(hass)
entity = RestoreEntity()
entity.hass = hass
entity.entity_id = "input_boolean.b1"
with patch(
"homeassistant.helpers.restore_state.Store.async_save"
) as mock_write_data:
hass.data.pop(DATA_RESTORE_STATE)
await async_load(hass)
data = async_get(hass)
entity = RestoreEntity()
entity.hass = hass
entity.entity_id = "input_boolean.b1"
await entity.async_get_last_state()
await hass.async_block_till_done()

View File

@ -3144,7 +3144,9 @@ async def test_multiple_runs_repeat_choose(
events = async_capture_events(hass, "abc")
for _ in range(max_runs):
hass.async_create_task(script_obj.async_run(context=Context()))
hass.async_create_task(
script_obj.async_run(context=Context()), eager_start=False
)
await hass.async_block_till_done()
assert "WARNING" not in caplog.text

View File

@ -2688,10 +2688,6 @@ async def test_unignore_step_form(
await manager.async_remove(entry.entry_id)
# Right after removal there shouldn't be an entry or active flows
assert len(hass.config_entries.async_entries("comp")) == 0
assert len(hass.config_entries.flow.async_progress_by_handler("comp")) == 0
# But after a 'tick' the unignore step has run and we can see an active flow again.
await hass.async_block_till_done()
assert len(hass.config_entries.flow.async_progress_by_handler("comp")) == 1
@ -2735,10 +2731,6 @@ async def test_unignore_create_entry(
await manager.async_remove(entry.entry_id)
# Right after removal there shouldn't be an entry or flow
assert len(hass.config_entries.flow.async_progress_by_handler("comp")) == 0
assert len(hass.config_entries.async_entries("comp")) == 0
# But after a 'tick' the unignore step has run and we can see a config entry.
await hass.async_block_till_done()
entry = hass.config_entries.async_entries("comp")[0]

View File

@ -794,7 +794,7 @@ async def test_async_create_task_pending_tasks_coro(hass: HomeAssistant) -> None
call_count.append("call")
for _ in range(2):
hass.async_create_task(test_coro())
hass.async_create_task(test_coro(), eager_start=False)
assert len(hass._tasks) == 2
await hass.async_block_till_done()
@ -2376,11 +2376,11 @@ async def test_log_blocking_events(
async def _wait_a_bit_2():
await asyncio.sleep(0.1)
hass.async_create_task(_wait_a_bit_1())
hass.async_create_task(_wait_a_bit_1(), eager_start=False)
await hass.async_block_till_done()
with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.0001):
hass.async_create_task(_wait_a_bit_2())
hass.async_create_task(_wait_a_bit_2(), eager_start=False)
await hass.async_block_till_done()
assert "_wait_a_bit_2" in caplog.text
@ -2400,14 +2400,14 @@ async def test_chained_logging_hits_log_timeout(
created += 1
if created > 1000:
return
hass.async_create_task(_task_chain_2())
hass.async_create_task(_task_chain_2(), eager_start=False)
async def _task_chain_2():
nonlocal created
created += 1
if created > 1000:
return
hass.async_create_task(_task_chain_1())
hass.async_create_task(_task_chain_1(), eager_start=False)
with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.0):
hass.async_create_task(_task_chain_1())
@ -2429,16 +2429,16 @@ async def test_chained_logging_misses_log_timeout(
created += 1
if created > 10:
return
hass.async_create_task(_task_chain_2())
hass.async_create_task(_task_chain_2(), eager_start=False)
async def _task_chain_2():
nonlocal created
created += 1
if created > 10:
return
hass.async_create_task(_task_chain_1())
hass.async_create_task(_task_chain_1(), eager_start=False)
hass.async_create_task(_task_chain_1())
hass.async_create_task(_task_chain_1(), eager_start=False)
await hass.async_block_till_done()
assert "_task_chain_" not in caplog.text

View File

@ -460,6 +460,7 @@ async def test_show_progress_error(hass: HomeAssistant, manager) -> None:
async def async_step_init(self, user_input=None):
async def long_running_task() -> None:
await asyncio.sleep(0)
raise TypeError
if not self.progress_task:
@ -518,7 +519,7 @@ async def test_show_progress_hidden_from_frontend(hass: HomeAssistant, manager)
nonlocal progress_task
async def long_running_job() -> None:
return
await asyncio.sleep(0)
if not progress_task:
progress_task = hass.async_create_task(long_running_job())