Merge PR #293: fix: eliminate shell noise from terminal output and fix test failures
Authored by 0xbyt4. Wraps commands with unique fence markers to isolate real output from shell init/exit noise (oh-my-zsh, macOS session restore, etc.). Falls back to expanded pattern-based cleaning. Also fixes BSD find fallback and test module shadowing.
This commit is contained in:
commit
ad9c26afb8
4 changed files with 168 additions and 21 deletions
|
|
@ -17,7 +17,13 @@ import pytest
|
|||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
|
||||
from tools.environments.local import LocalEnvironment, _clean_shell_noise, _SHELL_NOISE_SUBSTRINGS
|
||||
from tools.environments.local import (
|
||||
LocalEnvironment,
|
||||
_clean_shell_noise,
|
||||
_extract_fenced_output,
|
||||
_OUTPUT_FENCE,
|
||||
_SHELL_NOISE_SUBSTRINGS,
|
||||
)
|
||||
from tools.file_operations import ShellFileOperations
|
||||
|
||||
|
||||
|
|
@ -28,6 +34,7 @@ from tools.file_operations import ShellFileOperations
|
|||
_ALL_NOISE_PATTERNS = list(_SHELL_NOISE_SUBSTRINGS) + [
|
||||
"bash: ",
|
||||
"Inappropriate ioctl",
|
||||
"Auto-suggestions:",
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -125,11 +132,84 @@ class TestCleanShellNoise:
|
|||
_assert_clean(result)
|
||||
|
||||
def test_noise_in_middle_not_stripped(self):
|
||||
"""Only LEADING noise is stripped -- noise in the middle is real output."""
|
||||
"""Noise in the middle is real output and should be preserved."""
|
||||
output = "real\nbash: no job control in this shell\nmore real\n"
|
||||
result = _clean_shell_noise(output)
|
||||
assert result == output
|
||||
|
||||
def test_zsh_restored_session(self):
|
||||
output = "Restored session: Mon Mar 2 22:16:54 +03 2026\nhello\n"
|
||||
result = _clean_shell_noise(output)
|
||||
assert result == "hello\n"
|
||||
|
||||
def test_zsh_saving_session_trailing(self):
|
||||
output = "hello\nSaving session...completed.\n"
|
||||
result = _clean_shell_noise(output)
|
||||
assert result == "hello\n"
|
||||
|
||||
def test_zsh_oh_my_zsh_banner(self):
|
||||
output = "Oh My Zsh on! | Auto-suggestions: press right\nhello\n"
|
||||
result = _clean_shell_noise(output)
|
||||
assert result == "hello\n"
|
||||
|
||||
def test_zsh_full_noise_sandwich(self):
|
||||
"""Both leading and trailing zsh noise stripped."""
|
||||
output = (
|
||||
"Restored session: Mon Mar 2\n"
|
||||
"command not found: docker\n"
|
||||
"Oh My Zsh on!\n"
|
||||
"actual output\n"
|
||||
"Saving session...completed.\n"
|
||||
)
|
||||
result = _clean_shell_noise(output)
|
||||
assert result == "actual output\n"
|
||||
|
||||
def test_last_login_stripped(self):
|
||||
output = "Last login: Mon Mar 2 22:00:00 on ttys001\nhello\n"
|
||||
result = _clean_shell_noise(output)
|
||||
assert result == "hello\n"
|
||||
|
||||
|
||||
# ── _extract_fenced_output unit tests ────────────────────────────────────
|
||||
|
||||
class TestExtractFencedOutput:
|
||||
def test_normal_fenced_output(self):
|
||||
raw = f"noise\n{_OUTPUT_FENCE}hello world\n{_OUTPUT_FENCE}more noise\n"
|
||||
assert _extract_fenced_output(raw) == "hello world\n"
|
||||
|
||||
def test_no_trailing_newline(self):
|
||||
"""printf output with no trailing newline is preserved."""
|
||||
raw = f"noise{_OUTPUT_FENCE}exact{_OUTPUT_FENCE}noise"
|
||||
assert _extract_fenced_output(raw) == "exact"
|
||||
|
||||
def test_no_fences_falls_back(self):
|
||||
"""Without fences, falls back to pattern-based cleaning."""
|
||||
raw = "bash: no job control in this shell\nhello\n"
|
||||
result = _extract_fenced_output(raw)
|
||||
assert result == "hello\n"
|
||||
|
||||
def test_only_start_fence(self):
|
||||
"""Only start fence (e.g. user command called exit)."""
|
||||
raw = f"noise{_OUTPUT_FENCE}hello\nSaving session...\n"
|
||||
result = _extract_fenced_output(raw)
|
||||
assert result == "hello\n"
|
||||
|
||||
def test_user_outputs_fence_string(self):
|
||||
"""If user command outputs the fence marker, it is preserved."""
|
||||
raw = f"noise{_OUTPUT_FENCE}{_OUTPUT_FENCE}real\n{_OUTPUT_FENCE}noise"
|
||||
result = _extract_fenced_output(raw)
|
||||
# first fence -> last fence captures the middle including user's fence
|
||||
assert _OUTPUT_FENCE in result
|
||||
assert "real\n" in result
|
||||
|
||||
def test_empty_command_output(self):
|
||||
raw = f"noise{_OUTPUT_FENCE}{_OUTPUT_FENCE}noise"
|
||||
assert _extract_fenced_output(raw) == ""
|
||||
|
||||
def test_multiline_output(self):
|
||||
raw = f"noise\n{_OUTPUT_FENCE}line1\nline2\nline3\n{_OUTPUT_FENCE}noise\n"
|
||||
assert _extract_fenced_output(raw) == "line1\nline2\nline3\n"
|
||||
|
||||
|
||||
# ── LocalEnvironment.execute() ───────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ from unittest.mock import patch, MagicMock
|
|||
|
||||
import pytest
|
||||
|
||||
# tools/__init__.py re-exports a *function* called ``terminal_tool`` which
|
||||
# shadows the module of the same name. Use sys.modules to get the real module
|
||||
# so patch.object works correctly.
|
||||
import sys
|
||||
import tools.terminal_tool # noqa: F401 -- ensure module is loaded
|
||||
_tt_mod = sys.modules["tools.terminal_tool"]
|
||||
from tools.terminal_tool import get_active_environments_info
|
||||
|
||||
# 1 MiB of data so the rounded MB value is clearly distinguishable
|
||||
|
|
@ -34,10 +40,8 @@ class TestDiskUsageGlob:
|
|||
"aaaaaaaa-1111-2222-3333-444444444444": MagicMock(),
|
||||
}
|
||||
|
||||
with (
|
||||
patch("tools.terminal_tool._active_environments", fake_envs),
|
||||
patch("tools.terminal_tool._get_scratch_dir", return_value=fake_scratch),
|
||||
):
|
||||
with patch.object(_tt_mod, "_active_environments", fake_envs), \
|
||||
patch.object(_tt_mod, "_get_scratch_dir", return_value=fake_scratch):
|
||||
info = get_active_environments_info()
|
||||
|
||||
# Task A only: ~1.0 MB. With the bug (hardcoded hermes-*),
|
||||
|
|
@ -51,10 +55,8 @@ class TestDiskUsageGlob:
|
|||
"bbbbbbbb-5555-6666-7777-888888888888": MagicMock(),
|
||||
}
|
||||
|
||||
with (
|
||||
patch("tools.terminal_tool._active_environments", fake_envs),
|
||||
patch("tools.terminal_tool._get_scratch_dir", return_value=fake_scratch),
|
||||
):
|
||||
with patch.object(_tt_mod, "_active_environments", fake_envs), \
|
||||
patch.object(_tt_mod, "_get_scratch_dir", return_value=fake_scratch):
|
||||
info = get_active_environments_info()
|
||||
|
||||
# Should be ~2.0 MB total (1 MB per task).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue