async def async_test_home_assistant(loop, load_registries=True): """Return a Home Assistant object pointing at test config dir.""" hass = ha.HomeAssistant() store = auth_store.AuthStore(hass) hass.auth = auth.AuthManager(hass, store, {}, {}) ensure_auth_manager_loaded(hass.auth) INSTANCES.append(hass) orig_async_add_job = hass.async_add_job orig_async_add_executor_job = hass.async_add_executor_job orig_async_create_task = hass.async_create_task def async_add_job(target, *args): """Add job.""" check_target = target while isinstance(check_target, ft.partial): check_target = check_target.func if isinstance(check_target, Mock) and not isinstance(target, AsyncMock): fut = asyncio.Future() fut.set_result(target(*args)) return fut return orig_async_add_job(target, *args) def async_add_executor_job(target, *args): """Add executor job.""" check_target = target while isinstance(check_target, ft.partial): check_target = check_target.func if isinstance(check_target, Mock): fut = asyncio.Future() fut.set_result(target(*args)) return fut return orig_async_add_executor_job(target, *args) def async_create_task(coroutine): """Create task.""" if isinstance(coroutine, Mock) and not isinstance(coroutine, AsyncMock): fut = asyncio.Future() fut.set_result(None) return fut return orig_async_create_task(coroutine) async def async_wait_for_task_count(self, max_remaining_tasks: int = 0) -> None: """Block until at most max_remaining_tasks remain. Based on HomeAssistant.async_block_till_done """ # To flush out any call_soon_threadsafe await asyncio.sleep(0) start_time: float | None = None while len(self._pending_tasks) > max_remaining_tasks: pending: Collection[Awaitable[Any]] = [ task for task in self._pending_tasks if not task.done() ] self._pending_tasks.clear() if len(pending) > max_remaining_tasks: remaining_pending = await self._await_count_and_log_pending( pending, max_remaining_tasks=max_remaining_tasks) self._pending_tasks.extend(remaining_pending) if start_time is None: # Avoid calling monotonic() until we know # we may need to start logging blocked tasks. start_time = 0 elif start_time == 0: # If we have waited twice then we set the start # time start_time = monotonic() elif monotonic() - start_time > BLOCK_LOG_TIMEOUT: # We have waited at least three loops and new tasks # continue to block. At this point we start # logging all waiting tasks. for task in pending: _LOGGER.debug("Waiting for task: %s", task) else: self._pending_tasks.extend(pending) await asyncio.sleep(0) async def _await_count_and_log_pending( self, pending: Collection[Awaitable[Any]], max_remaining_tasks: int = 0) -> Collection[Awaitable[Any]]: """Block at most max_remaining_tasks remain and log tasks that take a long time. Based on HomeAssistant._await_and_log_pending """ wait_time = 0 return_when = asyncio.ALL_COMPLETED if max_remaining_tasks: return_when = asyncio.FIRST_COMPLETED while len(pending) > max_remaining_tasks: _, pending = await asyncio.wait(pending, timeout=BLOCK_LOG_TIMEOUT, return_when=return_when) if not pending or max_remaining_tasks: return pending wait_time += BLOCK_LOG_TIMEOUT for task in pending: _LOGGER.debug("Waited %s seconds for task: %s", wait_time, task) return [] hass.async_add_job = async_add_job hass.async_add_executor_job = async_add_executor_job hass.async_create_task = async_create_task hass.async_wait_for_task_count = types.MethodType( async_wait_for_task_count, hass) hass._await_count_and_log_pending = types.MethodType( _await_count_and_log_pending, hass) hass.data[loader.DATA_CUSTOM_COMPONENTS] = {} hass.config.location_name = "test home" hass.config.config_dir = get_test_config_dir() hass.config.latitude = 32.87336 hass.config.longitude = -117.22743 hass.config.elevation = 0 hass.config.time_zone = "US/Pacific" hass.config.units = METRIC_SYSTEM hass.config.media_dirs = {"local": get_test_config_dir("media")} hass.config.skip_pip = True hass.config_entries = config_entries.ConfigEntries(hass, {}) hass.config_entries._entries = {} hass.config_entries._store._async_ensure_stop_listener = lambda: None # Load the registries if load_registries: await asyncio.gather( device_registry.async_load(hass), entity_registry.async_load(hass), area_registry.async_load(hass), ) await hass.async_block_till_done() hass.state = ha.CoreState.running # Mock async_start orig_start = hass.async_start async def mock_async_start(): """Start the mocking.""" # We only mock time during tests and we want to track tasks with patch("homeassistant.core._async_create_timer"), patch.object( hass, "async_stop_track_tasks"): await orig_start() hass.async_start = mock_async_start @ha.callback def clear_instance(event): """Clear global instance.""" INSTANCES.remove(hass) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, clear_instance) return hass
async def _async_set_up_integrations( hass: core.HomeAssistant, config: dict[str, Any] ) -> None: """Set up all the integrations.""" hass.data[DATA_SETUP_STARTED] = {} setup_time = hass.data[DATA_SETUP_TIME] = {} watch_task = asyncio.create_task(_async_watch_pending_setups(hass)) domains_to_setup = _get_domains(hass, config) # Resolve all dependencies so we know all integrations # that will have to be loaded and start rightaway integration_cache: dict[str, loader.Integration] = {} to_resolve = domains_to_setup while to_resolve: old_to_resolve = to_resolve to_resolve = set() integrations_to_process = [ int_or_exc for int_or_exc in await gather_with_concurrency( loader.MAX_LOAD_CONCURRENTLY, *( loader.async_get_integration(hass, domain) for domain in old_to_resolve ), return_exceptions=True, ) if isinstance(int_or_exc, loader.Integration) ] resolve_dependencies_tasks = [ itg.resolve_dependencies() for itg in integrations_to_process if not itg.all_dependencies_resolved ] if resolve_dependencies_tasks: await asyncio.gather(*resolve_dependencies_tasks) for itg in integrations_to_process: integration_cache[itg.domain] = itg for dep in itg.all_dependencies: if dep in domains_to_setup: continue domains_to_setup.add(dep) to_resolve.add(dep) _LOGGER.info("Domains to be set up: %s", domains_to_setup) logging_domains = domains_to_setup & LOGGING_INTEGRATIONS # Load logging as soon as possible if logging_domains: _LOGGER.info("Setting up logging: %s", logging_domains) await async_setup_multi_components(hass, logging_domains, config) # Start up debuggers. Start these first in case they want to wait. debuggers = domains_to_setup & DEBUGGER_INTEGRATIONS if debuggers: _LOGGER.debug("Setting up debuggers: %s", debuggers) await async_setup_multi_components(hass, debuggers, config) # calculate what components to setup in what stage stage_1_domains = set() # Find all dependencies of any dependency of any stage 1 integration that # we plan on loading and promote them to stage 1 deps_promotion = STAGE_1_INTEGRATIONS while deps_promotion: old_deps_promotion = deps_promotion deps_promotion = set() for domain in old_deps_promotion: if domain not in domains_to_setup or domain in stage_1_domains: continue stage_1_domains.add(domain) dep_itg = integration_cache.get(domain) if dep_itg is None: continue deps_promotion.update(dep_itg.all_dependencies) stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains # Load the registries await asyncio.gather( device_registry.async_load(hass), entity_registry.async_load(hass), area_registry.async_load(hass), ) # Start setup if stage_1_domains: _LOGGER.info("Setting up stage 1: %s", stage_1_domains) try: async with hass.timeout.async_timeout( STAGE_1_TIMEOUT, cool_down=COOLDOWN_TIME ): await async_setup_multi_components(hass, stage_1_domains, config) except asyncio.TimeoutError: _LOGGER.warning("Setup timed out for stage 1 - moving forward") # Enables after dependencies async_set_domains_to_be_loaded(hass, stage_2_domains) if stage_2_domains: _LOGGER.info("Setting up stage 2: %s", stage_2_domains) try: async with hass.timeout.async_timeout( STAGE_2_TIMEOUT, cool_down=COOLDOWN_TIME ): await async_setup_multi_components(hass, stage_2_domains, config) except asyncio.TimeoutError: _LOGGER.warning("Setup timed out for stage 2 - moving forward") watch_task.cancel() async_dispatcher_send(hass, SIGNAL_BOOTSTRAP_INTEGRATONS, {}) _LOGGER.debug( "Integration setup times: %s", { integration: timedelta.total_seconds() for integration, timedelta in sorted( setup_time.items(), key=lambda item: item[1].total_seconds() # type: ignore ) }, ) # Wrap up startup _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() except asyncio.TimeoutError: _LOGGER.warning("Setup timed out for bootstrap - moving forward")
if domain not in domains_to_setup or domain in stage_1_domains: continue stage_1_domains.add(domain) if (dep_itg := integration_cache.get(domain)) is None: continue deps_promotion.update(dep_itg.all_dependencies) stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains # Load the registries await asyncio.gather( device_registry.async_load(hass), entity_registry.async_load(hass), area_registry.async_load(hass), ) # Start setup if stage_1_domains: _LOGGER.info("Setting up stage 1: %s", stage_1_domains) try: async with hass.timeout.async_timeout(STAGE_1_TIMEOUT, cool_down=COOLDOWN_TIME): await async_setup_multi_components(hass, stage_1_domains, config) except asyncio.TimeoutError: _LOGGER.warning("Setup timed out for stage 1 - moving forward") # Enables after dependencies