diff --git a/homeassistant/components/go2rtc/server.py b/homeassistant/components/go2rtc/server.py index 9be02d9a5d6..dcbe077a365 100644 --- a/homeassistant/components/go2rtc/server.py +++ b/homeassistant/components/go2rtc/server.py @@ -103,12 +103,15 @@ class Server: "-c", config_file, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.STDOUT, + stderr=asyncio.subprocess.PIPE, close_fds=False, # required for posix_spawn on CPython < 3.13 ) self._hass.async_create_background_task( - self._log_output(self._process), "Go2rtc log output" + self._log_output_stdout(self._process), "Go2rtc log stdout output" + ) + self._hass.async_create_background_task( + self._log_output_stderr(self._process), "Go2rtc log stderr output" ) try: @@ -125,7 +128,7 @@ class Server: client = Go2RtcRestClient(async_get_clientsession(self._hass), HA_MANAGED_URL) await client.validate_server_version() - async def _log_output(self, process: asyncio.subprocess.Process) -> None: + async def _log_output_stdout(self, process: asyncio.subprocess.Process) -> None: """Log the output of the process.""" assert process.stdout is not None @@ -136,6 +139,15 @@ class Server: if not self._startup_complete.is_set() and _SUCCESSFUL_BOOT_MESSAGE in msg: self._startup_complete.set() + async def _log_output_stderr(self, process: asyncio.subprocess.Process) -> None: + """Log the output of the process.""" + assert process.stderr is not None + + async for line in process.stderr: + msg = "STDERR " + line[:-1].decode().strip() + self._log_buffer.append(msg) + _LOGGER.debug(msg) + def _log_server_output(self, loglevel: int) -> None: """Log captured process output, then clear the log buffer.""" for line in list(self._log_buffer): # Copy the deque to avoid mutation error diff --git a/tests/components/go2rtc/conftest.py b/tests/components/go2rtc/conftest.py index 42b363b2324..6ec2053e44b 100644 --- a/tests/components/go2rtc/conftest.py +++ b/tests/components/go2rtc/conftest.py @@ -50,7 +50,15 @@ def server_stdout() -> list[str]: @pytest.fixture -def mock_create_subprocess(server_stdout: list[str]) -> Generator[AsyncMock]: +def server_stderr() -> list[str]: + """Server stderr lines.""" + return [] + + +@pytest.fixture +def mock_create_subprocess( + server_stdout: list[str], server_stderr: list[str] +) -> Generator[AsyncMock]: """Mock create_subprocess_exec.""" with patch(f"{GO2RTC_PATH}.server.asyncio.create_subprocess_exec") as mock_subproc: subproc = AsyncMock() @@ -61,6 +69,9 @@ def mock_create_subprocess(server_stdout: list[str]) -> Generator[AsyncMock]: subproc.stdout.__aiter__.return_value = iter( [f"{entry}\n".encode() for entry in server_stdout] ) + subproc.stderr.__aiter__.return_value = iter( + [f"{entry}\n".encode() for entry in server_stderr] + ) mock_subproc.return_value = subproc yield mock_subproc diff --git a/tests/components/go2rtc/test_server.py b/tests/components/go2rtc/test_server.py index cda05fc4f2b..d08edfe8fb3 100644 --- a/tests/components/go2rtc/test_server.py +++ b/tests/components/go2rtc/test_server.py @@ -39,39 +39,42 @@ def mock_tempfile() -> Generator[Mock]: def _assert_server_output_logged( - server_stdout: list[str], + server_output: list[str], caplog: pytest.LogCaptureFixture, loglevel: int, expect_logged: bool, + prefix: str, ) -> None: """Check server stdout was logged.""" - for entry in server_stdout: + for entry in server_output: assert ( ( "homeassistant.components.go2rtc.server", loglevel, - entry, + prefix + entry, ) in caplog.record_tuples ) is expect_logged def assert_server_output_logged( - server_stdout: list[str], + server_output: list[str], caplog: pytest.LogCaptureFixture, loglevel: int, + prefix="", ) -> None: """Check server stdout was logged.""" - _assert_server_output_logged(server_stdout, caplog, loglevel, True) + _assert_server_output_logged(server_output, caplog, loglevel, True, prefix) def assert_server_output_not_logged( - server_stdout: list[str], + server_output: list[str], caplog: pytest.LogCaptureFixture, loglevel: int, + prefix="", ) -> None: """Check server stdout was logged.""" - _assert_server_output_logged(server_stdout, caplog, loglevel, False) + _assert_server_output_logged(server_output, caplog, loglevel, False, prefix) @pytest.mark.parametrize( @@ -99,7 +102,7 @@ async def test_server_run_success( "-c", "test.yaml", stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=subprocess.PIPE, close_fds=False, ) @@ -190,16 +193,18 @@ async def test_server_failed_to_start( "-c", "test.yaml", stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=subprocess.PIPE, close_fds=False, ) @patch("homeassistant.components.go2rtc.server._RESPAWN_COOLDOWN", 0) +@pytest.mark.parametrize("server_stderr", [["exit with signal: terminated"]]) async def test_server_restart_process_exit( hass: HomeAssistant, mock_create_subprocess: AsyncMock, server_stdout: list[str], + server_stderr: list[str], rest_client: AsyncMock, server: Server, caplog: pytest.LogCaptureFixture, @@ -220,15 +225,17 @@ async def test_server_restart_process_exit( await hass.async_block_till_done() mock_create_subprocess.assert_not_awaited() - # Verify go2rtc binary stdout was not yet logged with warning level + # Verify go2rtc binary output was not yet logged with warning level assert_server_output_not_logged(server_stdout, caplog, logging.WARNING) + assert_server_output_not_logged(server_stderr, caplog, logging.WARNING, "STDERR ") evt.set() await asyncio.sleep(0.1) mock_create_subprocess.assert_awaited_once() - # Verify go2rtc binary stdout was logged with warning level + # Verify go2rtc binary output was logged with warning level assert_server_output_logged(server_stdout, caplog, logging.WARNING) + assert_server_output_logged(server_stderr, caplog, logging.WARNING, "STDERR ") await server.stop()