From dc9cadeedf2c375729ee274102516148a1baf887 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Fri, 28 Feb 2025 20:48:56 +0000 Subject: [PATCH] Cover read_bytes and some write_text calls as well --- supervisor/addons/addon.py | 17 +++++++++-------- supervisor/arch.py | 2 +- supervisor/homeassistant/core.py | 2 +- supervisor/homeassistant/module.py | 13 +++++++------ supervisor/host/logs.py | 4 +++- supervisor/plugins/audio.py | 9 +++++---- supervisor/plugins/dns.py | 9 +++++---- supervisor/utils/json.py | 10 ++++++++-- tests/homeassistant/test_module.py | 6 +++--- 9 files changed, 42 insertions(+), 30 deletions(-) diff --git a/supervisor/addons/addon.py b/supervisor/addons/addon.py index 6bd86a02fe7..9894dd2d707 100644 --- a/supervisor/addons/addon.py +++ b/supervisor/addons/addon.py @@ -722,7 +722,7 @@ async def write_options(self) -> None: try: options = self.schema.validate(self.options) - write_json_file(self.path_options, options) + await self.sys_run_in_executor(write_json_file, self.path_options, options) except vol.Invalid as ex: _LOGGER.error( "Add-on %s has invalid options: %s", @@ -940,19 +940,20 @@ async def rebuild(self) -> asyncio.Task | None: ) return out - def write_pulse(self) -> None: + async def write_pulse(self) -> None: """Write asound config to file and return True on success.""" pulse_config = self.sys_plugins.audio.pulse_client( input_profile=self.audio_input, output_profile=self.audio_output ) - # Cleanup wrong maps - if self.path_pulse.is_dir(): - shutil.rmtree(self.path_pulse, ignore_errors=True) + def write_pulse_config(): + # Cleanup wrong maps + if self.path_pulse.is_dir(): + shutil.rmtree(self.path_pulse, ignore_errors=True) + self.path_pulse.write_text(pulse_config, encoding="utf-8") - # Write pulse config try: - self.path_pulse.write_text(pulse_config, encoding="utf-8") + await self.sys_run_in_executor(write_pulse_config) except OSError as err: if err.errno == errno.EBADMSG: self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE @@ -1072,7 +1073,7 @@ async def start(self) -> asyncio.Task: # Sound if self.with_audio: - self.write_pulse() + await self.write_pulse() def _check_addon_config_dir(): if self.path_config.is_dir(): diff --git a/supervisor/arch.py b/supervisor/arch.py index b36e7e84fdf..dde7d9b8f74 100644 --- a/supervisor/arch.py +++ b/supervisor/arch.py @@ -50,7 +50,7 @@ def supported(self) -> list[str]: async def load(self) -> None: """Load data and initialize default arch.""" try: - arch_data = read_json_file(ARCH_JSON) + arch_data = await self.sys_run_in_executor(read_json_file, ARCH_JSON) except ConfigurationFileError: _LOGGER.warning("Can't read arch json file from %s", ARCH_JSON) return diff --git a/supervisor/homeassistant/core.py b/supervisor/homeassistant/core.py index 2137a377c1f..e8175070df1 100644 --- a/supervisor/homeassistant/core.py +++ b/supervisor/homeassistant/core.py @@ -342,7 +342,7 @@ async def start(self) -> None: await self.sys_homeassistant.save_data() # Write audio settings - self.sys_homeassistant.write_pulse() + await self.sys_homeassistant.write_pulse() try: await self.instance.run(restore_job_id=self.sys_backups.current_restore) diff --git a/supervisor/homeassistant/module.py b/supervisor/homeassistant/module.py index 3b9b76cb227..4d782df3919 100644 --- a/supervisor/homeassistant/module.py +++ b/supervisor/homeassistant/module.py @@ -313,19 +313,20 @@ async def load(self) -> None: BusEvent.HARDWARE_REMOVE_DEVICE, self._hardware_events ) - def write_pulse(self): + async def write_pulse(self): """Write asound config to file and return True on success.""" pulse_config = self.sys_plugins.audio.pulse_client( input_profile=self.audio_input, output_profile=self.audio_output ) - # Cleanup wrong maps - if self.path_pulse.is_dir(): - shutil.rmtree(self.path_pulse, ignore_errors=True) + def write_pulse_config(): + # Cleanup wrong maps + if self.path_pulse.is_dir(): + shutil.rmtree(self.path_pulse, ignore_errors=True) + self.path_pulse.write_text(pulse_config, encoding="utf-8") - # Write pulse config try: - self.path_pulse.write_text(pulse_config, encoding="utf-8") + await self.sys_run_in_executor(write_pulse_config) except OSError as err: if err.errno == errno.EBADMSG: self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE diff --git a/supervisor/host/logs.py b/supervisor/host/logs.py index 6c923db640d..e55785f4599 100644 --- a/supervisor/host/logs.py +++ b/supervisor/host/logs.py @@ -72,7 +72,9 @@ def default_identifiers(self) -> list[str]: async def load(self) -> None: """Load log control.""" try: - self._default_identifiers = read_json_file(SYSLOG_IDENTIFIERS_JSON) + self._default_identifiers = await self.sys_run_in_executor( + read_json_file, SYSLOG_IDENTIFIERS_JSON + ) except ConfigurationFileError: _LOGGER.warning( "Can't read syslog identifiers json file from %s", diff --git a/supervisor/plugins/audio.py b/supervisor/plugins/audio.py index 9cb30237992..610f49767c5 100644 --- a/supervisor/plugins/audio.py +++ b/supervisor/plugins/audio.py @@ -129,7 +129,7 @@ async def update(self, version: str | None = None) -> None: async def restart(self) -> None: """Restart Audio plugin.""" _LOGGER.info("Restarting Audio plugin") - self._write_config() + await self._write_config() try: await self.instance.restart() except DockerError as err: @@ -138,7 +138,7 @@ async def restart(self) -> None: async def start(self) -> None: """Run Audio plugin.""" _LOGGER.info("Starting Audio plugin") - self._write_config() + await self._write_config() try: await self.instance.run() except DockerError as err: @@ -183,10 +183,11 @@ def pulse_client(self, input_profile=None, output_profile=None) -> str: default_sink=output_profile, ) - def _write_config(self): + async def _write_config(self): """Write pulse audio config.""" try: - write_json_file( + await self.sys_run_in_executor( + write_json_file, self.pulse_audio_config, { "debug": self.sys_config.logging == LogLevel.DEBUG, diff --git a/supervisor/plugins/dns.py b/supervisor/plugins/dns.py index b7b726ca455..853f3193d20 100644 --- a/supervisor/plugins/dns.py +++ b/supervisor/plugins/dns.py @@ -196,7 +196,7 @@ async def update(self, version: AwesomeVersion | None = None) -> None: async def restart(self) -> None: """Restart CoreDNS plugin.""" - self._write_config() + await self._write_config() _LOGGER.info("Restarting CoreDNS plugin") try: await self.instance.restart() @@ -205,7 +205,7 @@ async def restart(self) -> None: async def start(self) -> None: """Run CoreDNS.""" - self._write_config() + await self._write_config() # Start Instance _LOGGER.info("Starting CoreDNS plugin") @@ -274,7 +274,7 @@ async def loop_detection(self) -> None: else: self._loop = False - def _write_config(self) -> None: + async def _write_config(self) -> None: """Write CoreDNS config.""" debug: bool = self.sys_config.logging == LogLevel.DEBUG dns_servers: list[str] = [] @@ -298,7 +298,8 @@ def _write_config(self) -> None: # Write config to plugin try: - write_json_file( + await self.sys_run_in_executor( + write_json_file, self.coredns_config, { "servers": dns_servers, diff --git a/supervisor/utils/json.py b/supervisor/utils/json.py index d3a6b43e432..e4712473353 100644 --- a/supervisor/utils/json.py +++ b/supervisor/utils/json.py @@ -48,7 +48,10 @@ def json_bytes(obj: Any) -> bytes: def write_json_file(jsonfile: Path, data: Any) -> None: - """Write a JSON file.""" + """Write a JSON file. + + Must be run in executor. + """ try: with atomic_write(jsonfile, overwrite=True) as fp: fp.write( @@ -67,7 +70,10 @@ def write_json_file(jsonfile: Path, data: Any) -> None: def read_json_file(jsonfile: Path) -> Any: - """Read a JSON file and return a dict.""" + """Read a JSON file and return a dict. + + Must be run in executor. + """ try: return json_loads(jsonfile.read_bytes()) except (OSError, ValueError, TypeError, UnicodeDecodeError) as err: diff --git a/tests/homeassistant/test_module.py b/tests/homeassistant/test_module.py index 70f8fe75ff9..c28ff4870b9 100644 --- a/tests/homeassistant/test_module.py +++ b/tests/homeassistant/test_module.py @@ -66,21 +66,21 @@ async def test_get_users_none(coresys: CoreSys, ha_ws_client: AsyncMock): ) -def test_write_pulse_error(coresys: CoreSys, caplog: pytest.LogCaptureFixture): +async def test_write_pulse_error(coresys: CoreSys, caplog: pytest.LogCaptureFixture): """Test errors writing pulse config.""" with patch( "supervisor.homeassistant.module.Path.write_text", side_effect=(err := OSError()), ): err.errno = errno.EBUSY - coresys.homeassistant.write_pulse() + await coresys.homeassistant.write_pulse() assert "can't write pulse/client.config" in caplog.text assert coresys.core.healthy is True caplog.clear() err.errno = errno.EBADMSG - coresys.homeassistant.write_pulse() + await coresys.homeassistant.write_pulse() assert "can't write pulse/client.config" in caplog.text assert coresys.core.healthy is False