diff --git a/tests/tools/test_tts_command_providers.py b/tests/tools/test_tts_command_providers.py index 5bfe64125e78..34972f2c2d28 100644 --- a/tests/tools/test_tts_command_providers.py +++ b/tests/tools/test_tts_command_providers.py @@ -363,6 +363,39 @@ class TestRenderCommandTtsTemplate: # --------------------------------------------------------------------------- class TestRunCommandTts: + def test_reads_process_output_in_large_chunks(self): + read_sizes: dict[str, list[int]] = {"stdout": [], "stderr": []} + + class FakeStream: + def __init__(self, name: str, chunks: list[str]): + self.name = name + self.chunks = chunks + + def read(self, size: int) -> str: + read_sizes[self.name].append(size) + if self.chunks: + return self.chunks.pop(0) + return "" + + class FakeProcess: + def __init__(self): + self.pid = 12345 + self.returncode = 0 + self.stdout = FakeStream("stdout", ["done"]) + self.stderr = FakeStream("stderr", ["tick"]) + + def wait(self, timeout=None): + return self.returncode + + with patch("tools.tts_tool.subprocess.Popen", return_value=FakeProcess()): + result = _run_command_tts("fake tts", timeout=0.25) + + assert result.returncode == 0 + assert result.stdout == "done" + assert result.stderr == "tick" + assert read_sizes["stdout"][0] == 65536 + assert read_sizes["stderr"][0] == 65536 + def test_stderr_progress_extends_beyond_timeout(self, tmp_path): script = tmp_path / "progress_then_exit.py" script.write_text( @@ -403,6 +436,7 @@ class TestRunCommandTts: ) assert "starting tier 1" in (excinfo.value.stderr or "") + assert isinstance(excinfo.value.__cause__, subprocess.TimeoutExpired) # --------------------------------------------------------------------------- diff --git a/tools/tts_tool.py b/tools/tts_tool.py index 9a0c37d1393b..cb37b6b13d2a 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -1025,9 +1025,19 @@ def _run_command_tts(command: str, timeout: float) -> subprocess.CompletedProces open_streams = {"stdout", "stderr"} def read_stream(name: str, stream: Any) -> None: + encoding = getattr(stream, "encoding", None) or "utf-8" try: while True: - chunk = stream.read(1) + try: + fd = stream.fileno() + except (AttributeError, OSError): + fd = None + + if fd is None: + chunk = stream.read(65536) + else: + data = os.read(fd, 65536) + chunk = data.decode(encoding, errors="replace") if not chunk: break output_queue.put((name, chunk)) @@ -1079,12 +1089,15 @@ def _run_command_tts(command: str, timeout: float) -> subprocess.CompletedProces chunks[name].append(chunk) stdout = "".join(chunks["stdout"]) stderr = "".join(chunks["stderr"]) - raise subprocess.TimeoutExpired( - command, - timeout, - output=stdout, - stderr=stderr, - ) + try: + raise subprocess.TimeoutExpired(command, timeout) + except subprocess.TimeoutExpired as exc: + raise subprocess.TimeoutExpired( + command, + timeout, + output=stdout, + stderr=stderr, + ) from exc stdout = "".join(chunks["stdout"]) stderr = "".join(chunks["stderr"])