diff --git a/appdaemon/plugin_management.py b/appdaemon/plugin_management.py index 739cec1b6..c2700be14 100644 --- a/appdaemon/plugin_management.py +++ b/appdaemon/plugin_management.py @@ -469,23 +469,23 @@ async def wait_for_plugins(self, timeout: float | None = None): self.AD.loop.create_task(event.wait(), name=f"waiting for {plugin_name} to be ready") for plugin_name, event in self._ready_events() ] - readiness = self.AD.loop.create_task( - asyncio.wait(wait_tasks, timeout=timeout, return_when=asyncio.ALL_COMPLETED), - name="waiting for all plugins to be ready", - ) + if wait_tasks: + readiness = self.AD.loop.create_task( + asyncio.wait(wait_tasks, timeout=timeout, return_when=asyncio.ALL_COMPLETED), + name="waiting for all plugins to be ready", + ) - early_stop = self.AD.loop.create_task(self.AD.stop_event.wait(), name="waiting for appdaemon to stop") - await self.AD.loop.create_task( - asyncio.wait((readiness, early_stop), timeout=timeout, return_when=asyncio.FIRST_COMPLETED), - name="waiting for plugins or stop event", - ) - if readiness.done(): - # The readiness wait completed - self.logger.info("All plugins ready") - elif self.AD.stopping: - self.logger.info("AppDaemon stopping before all plugins ready, cancelling readiness waits") - for task in wait_tasks: - task.cancel() + early_stop = self.AD.loop.create_task(self.AD.stop_event.wait(), name="waiting for appdaemon to stop") + await self.AD.loop.create_task( + asyncio.wait((readiness, early_stop), timeout=timeout, return_when=asyncio.FIRST_COMPLETED), + name="waiting for plugins or stop event", + ) + if self.AD.stopping: + self.logger.info("AppDaemon stopping before all plugins ready, cancelling readiness waits") + for task in wait_tasks: + task.cancel() + return + self.logger.info("All plugins ready") def get_config_for_namespace(self, namespace: str) -> PluginConfig: plugin_name = self.get_plugin_from_namespace(namespace) diff --git a/tests/functional/test_startup.py b/tests/functional/test_startup.py index 7a0d282e8..9748d70dd 100644 --- a/tests/functional/test_startup.py +++ b/tests/functional/test_startup.py @@ -23,3 +23,20 @@ async def test_hello_world(ad: AppDaemon, caplog: pytest.LogCaptureFixture, app_ assert "Hello from AppDaemon" in caplog.text assert "You are now ready to run Apps!" in caplog.text + + +@pytest.mark.ci +@pytest.mark.functional +@pytest.mark.asyncio(loop_scope="session") +async def test_no_plugins(ad_obj: AppDaemon, caplog: pytest.LogCaptureFixture) -> None: + """Ensure that apps start correctly when there are no plugins configured.""" + ad_obj.config.plugins = {} + ad_obj.app_dir = ad_obj.config_dir / "apps/hello_world" + + ad_obj.start() + with caplog.at_level(logging.INFO, logger="AppDaemon"): + async with ad_obj.app_management.app_run_context("hello_world"): + await ad_obj.utility.app_update_event.wait() + + await ad_obj.stop() + assert not any(r.levelname == "ERROR" for r in caplog.records)