mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Systematic prune per AGENTS.md test policy, one pass over every major test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli, cron, tui_gateway, honcho/openviking, root-level): - DELETE: source-reading tests (read_text/getsource on prod files), change-detector tests (exact catalog counts, model-name snapshots, config version literals), mock-echo tests (assert a mock returns what it was told), assertion-free/trivial tests, near-duplicate parametrizations (boundaries + one representative kept), async/sync twin duplicates, cosmetic within-file variations. - KEEP (mandatory): security/redaction/approval guards, message-role alternation invariants, prompt-caching/deterministic-call-id invariants, issue-number regression tests (deduped), E2E tests. - 6 test files deleted outright (script-style/no-assert or fully redundant); conftest.py, fakes/, fixtures/ untouched. - tests/acp/conftest.py added: autouse fixture stubs the live models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server tests performed on every session create — test_server.py 147s → 3.4s, and the tests are now genuinely hermetic. - Sleep-based slowness shrunk where safe (codex_ttfb_watchdog, compression_concurrent_fork, etc.); no wall-clock assertion tightened. Verification: full hermetic suite via scripts/run_tests.sh — 2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall (baseline: 583s wall, 13,564s subprocess CPU).
100 lines
4.5 KiB
Python
100 lines
4.5 KiB
Python
"""Tests for the scrolling viewport logic in _curses_prompt_choice (issue #5755).
|
|
|
|
The "More providers" submenu has 13 entries (11 extended + custom + cancel).
|
|
Before the fix, _curses_prompt_choice rendered items starting unconditionally
|
|
from index 0 with no scroll offset. On terminals shorter than ~16 rows, items
|
|
near the bottom were never drawn. When the cursor wrapped from 0 to the last
|
|
item (Cancel) via UP-arrow, the highlight rendered off-screen, leaving the menu
|
|
looking like only "Cancel" existed.
|
|
|
|
The fix adds a scroll_offset that tracks the cursor so the highlighted item
|
|
is always within the visible window. These tests exercise that logic in
|
|
isolation without requiring a real TTY.
|
|
"""
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pure scroll-offset logic extracted from _curses_menu for unit testing
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _compute_scroll_offset(cursor: int, scroll_offset: int, visible: int, n_choices: int) -> int:
|
|
"""Mirror of the scroll adjustment block inside _curses_menu."""
|
|
if cursor < scroll_offset:
|
|
scroll_offset = cursor
|
|
elif cursor >= scroll_offset + visible:
|
|
scroll_offset = cursor - visible + 1
|
|
scroll_offset = max(0, min(scroll_offset, max(0, n_choices - visible)))
|
|
return scroll_offset
|
|
|
|
|
|
def _visible_indices(cursor: int, scroll_offset: int, visible: int, n_choices: int):
|
|
"""Return the list indices that would be rendered for the given state."""
|
|
scroll_offset = _compute_scroll_offset(cursor, scroll_offset, visible, n_choices)
|
|
return list(range(scroll_offset, min(scroll_offset + visible, n_choices)))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests: scroll offset calculation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestScrollOffsetLogic:
|
|
N = 13 # typical extended-providers list length
|
|
|
|
def test_cursor_at_zero_no_scroll(self):
|
|
"""Start position: offset stays 0, first items visible."""
|
|
assert _compute_scroll_offset(0, 0, 8, self.N) == 0
|
|
|
|
def test_cursor_within_window_unchanged(self):
|
|
"""Cursor inside the current window: offset unchanged."""
|
|
assert _compute_scroll_offset(5, 0, 8, self.N) == 0
|
|
|
|
|
|
def test_cursor_wraps_to_cancel_via_up(self):
|
|
"""UP from index 0 wraps to last item; last item must be visible."""
|
|
wrapped_cursor = (0 - 1) % self.N # == 12
|
|
indices = _visible_indices(wrapped_cursor, 0, 8, self.N)
|
|
assert wrapped_cursor in indices
|
|
|
|
|
|
def test_visible_window_never_exceeds_list(self):
|
|
"""Offset is clamped so the window never starts past the list end."""
|
|
offset = _compute_scroll_offset(12, 0, 20, self.N) # window larger than list
|
|
assert offset == 0
|
|
|
|
|
|
def test_list_fits_in_window_no_scroll_needed(self):
|
|
"""If all choices fit in the visible window, offset is always 0."""
|
|
for cursor in range(self.N):
|
|
offset = _compute_scroll_offset(cursor, 0, 20, self.N)
|
|
assert offset == 0, f"cursor={cursor} should not scroll when window > list"
|
|
|
|
def test_cursor_always_in_visible_range(self):
|
|
"""Invariant: cursor is always within the rendered window after adjustment."""
|
|
visible = 5
|
|
for cursor in range(self.N):
|
|
indices = _visible_indices(cursor, 0, visible, self.N)
|
|
assert cursor in indices, f"cursor={cursor} not in visible={indices}"
|
|
|
|
def test_full_navigation_down_cursor_always_visible(self):
|
|
"""Simulate pressing DOWN through all items; cursor always in view."""
|
|
visible = 6
|
|
scroll_offset = 0
|
|
cursor = 0
|
|
for _ in range(self.N + 2): # wrap around twice
|
|
scroll_offset = _compute_scroll_offset(cursor, scroll_offset, visible, self.N)
|
|
rendered = list(range(scroll_offset, min(scroll_offset + visible, self.N)))
|
|
assert cursor in rendered, f"cursor={cursor} not in rendered={rendered}"
|
|
cursor = (cursor + 1) % self.N
|
|
|
|
def test_full_navigation_up_cursor_always_visible(self):
|
|
"""Simulate pressing UP through all items; cursor always in view."""
|
|
visible = 6
|
|
scroll_offset = 0
|
|
cursor = 0
|
|
for _ in range(self.N + 2):
|
|
scroll_offset = _compute_scroll_offset(cursor, scroll_offset, visible, self.N)
|
|
rendered = list(range(scroll_offset, min(scroll_offset + visible, self.N)))
|
|
assert cursor in rendered, f"cursor={cursor} not in rendered={rendered}"
|
|
cursor = (cursor - 1) % self.N
|