mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
First pass of test-suite reduction to address flaky CI and bloat. Removed tests that fall into these change-detector patterns: 1. Source-grep tests (tests/gateway/test_feishu.py, test_email.py): tests that call inspect.getsource() on production modules and grep for string literals. Break on any refactor/rename even when behavior is correct. 2. Platform enum tautologies (every gateway/test_X.py): assertions like `Platform.X.value == 'x'` duplicated across ~9 adapter test files. 3. Toolset/PLATFORM_HINTS/setup-wizard registry-presence checks: tests that only verify a key exists in a dict. Data-layout tests, not behavior. 4. Argparse wiring tests (test_argparse_flag_propagation, test_subparser_routing _fallback): tests that do parser.parse_args([...]) then assert args.field. Tests Python's argparse, not our code. 5. Pure dispatch tests (test_plugins_cmd.TestPluginsCommandDispatch): patch cmd_X, call plugins_command with matching action, assert mock called. Tests the if/elif chain, not behavior. 6. Kwarg-to-mock verification (test_auxiliary_client ~45 tests, test_web_tools_config, test_gemini_cloudcode, test_retaindb_plugin): tests that mock the external API client, call our function, and assert exact kwargs. Break on refactor even when behavior is preserved. 7. Schedule-internal "function-was-called" tests (acp/test_server scheduling tests): tests that patch own helper method, then assert it was called. Kept behavioral tests throughout: error paths (pytest.raises), security tests (path traversal, SSRF, redaction), message alternation invariants, provider API format conversion, streaming logic, memory contract, real config load/merge tests. Net reduction: 169 tests removed. 38 empty classes cleaned up. Collected before: 12,522 tests Collected after: 12,353 tests
66 lines
2.4 KiB
Python
66 lines
2.4 KiB
Python
"""Tests for the defensive subparser routing workaround (bpo-9338).
|
|
|
|
The main() function in hermes_cli/main.py sets subparsers.required=True
|
|
when argv contains a known subcommand name. This forces deterministic
|
|
routing on Python versions where argparse fails to match subcommand tokens
|
|
when the parent parser has nargs='?' optional arguments (--continue).
|
|
|
|
If the subcommand token is consumed as a flag value (e.g. `hermes -c model`
|
|
to resume a session named 'model'), the required=True parse raises
|
|
SystemExit and the code falls back to the default required=False behaviour.
|
|
"""
|
|
import argparse
|
|
import io
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
|
|
def _build_parser():
|
|
"""Build a minimal replica of the hermes top-level parser."""
|
|
parser = argparse.ArgumentParser(prog="hermes")
|
|
parser.add_argument("--version", "-V", action="store_true")
|
|
parser.add_argument("--resume", "-r", metavar="SESSION", default=None)
|
|
parser.add_argument(
|
|
"--continue", "-c",
|
|
dest="continue_last",
|
|
nargs="?",
|
|
const=True,
|
|
default=None,
|
|
metavar="SESSION_NAME",
|
|
)
|
|
parser.add_argument("--worktree", "-w", action="store_true", default=False)
|
|
parser.add_argument("--skills", "-s", action="append", default=None)
|
|
parser.add_argument("--yolo", action="store_true", default=False)
|
|
parser.add_argument("--pass-session-id", action="store_true", default=False)
|
|
|
|
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
chat_p = subparsers.add_parser("chat")
|
|
chat_p.add_argument("-q", "--query", default=None)
|
|
subparsers.add_parser("model")
|
|
subparsers.add_parser("gateway")
|
|
subparsers.add_parser("setup")
|
|
return parser, subparsers
|
|
|
|
|
|
def _safe_parse(parser, subparsers, argv):
|
|
"""Replica of the defensive parsing logic from main()."""
|
|
known_cmds = set(subparsers.choices.keys()) if hasattr(subparsers, "choices") else set()
|
|
has_cmd_token = any(t in known_cmds for t in argv if not t.startswith("-"))
|
|
|
|
if has_cmd_token:
|
|
subparsers.required = True
|
|
saved_stderr = sys.stderr
|
|
try:
|
|
sys.stderr = io.StringIO()
|
|
args = parser.parse_args(argv)
|
|
sys.stderr = saved_stderr
|
|
return args
|
|
except SystemExit:
|
|
sys.stderr = saved_stderr
|
|
subparsers.required = False
|
|
return parser.parse_args(argv)
|
|
else:
|
|
subparsers.required = False
|
|
return parser.parse_args(argv)
|
|
|