diff --git a/pm/progress.py b/pm/progress.py index c6d3fec844..72706826d4 100644 --- a/pm/progress.py +++ b/pm/progress.py @@ -21,6 +21,9 @@ import time from typing import IO, Callable, Mapping, Optional, Protocol, Sequence TAIL_LINES = 80 +# A child that never prints a newline (a bare progress stream, a binary blob) must not +# grow memory without bound; the end of an over-long line is the informative part. +MAX_LINE = 4096 _TRUE = {"1", "true", "yes", "on"} _FALSE = {"0", "false", "no", "off"} _ANSI = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]") @@ -79,7 +82,7 @@ class LiveTail: def write(self, text: str) -> int: # npm and uv redraw with bare CRs; each redraw is a line of progress. lines = (self._partial + text).replace("\r\n", "\n").replace("\r", "\n").split("\n") - self._partial = lines.pop() + self._partial = lines.pop()[-MAX_LINE:] for line in lines: self._line(line) return len(text) @@ -106,7 +109,7 @@ class LiveTail: self._emit("".join(f"{self.indent} {line}\n" for line in self.tail)) def _line(self, line: str) -> None: - line = _ANSI.sub("", line).rstrip() + line = _ANSI.sub("", line[-MAX_LINE:]).rstrip() if not line.strip(): return self.tail.append(line) diff --git a/tests/pm/test_environment_build.py b/tests/pm/test_environment_build.py index 6e1bfa419c..2af6f2ecb4 100644 --- a/tests/pm/test_environment_build.py +++ b/tests/pm/test_environment_build.py @@ -301,6 +301,7 @@ def test_build_backend_output_is_streamed_before_build_finishes(installable_proj import io from pm.environment import PythonEnvironment + monkeypatch.setenv("HERMES_VERBOSE", "1") # live backend output is the streamed (CI) view's contract source, uv, env = installable_project release = tmp_path / "release-build" stdout_marker = "construction-root: backend stdout"