fix(ci): stabilize main test suite regressions (#17660)
* fix: stabilize main test suite regressions * test(agent): update MiniMax normalization expectation * test: stabilize remaining CI assertions * test: harden config helper monkeypatching * test: harden CI-only assertions * fix(agent): propagate fast streaming interrupts
This commit is contained in:
committed by
GitHub
parent
e7beaaf184
commit
f73364b1c4
@@ -1470,6 +1470,8 @@ def test_credential_sources_registry_has_expected_steps():
|
||||
"~/.hermes/.anthropic_oauth.json",
|
||||
"auth.json providers.nous",
|
||||
"auth.json providers.openai-codex + ~/.codex/auth.json",
|
||||
"auth.json providers.minimax-oauth",
|
||||
"~/.qwen/oauth_creds.json",
|
||||
"Custom provider config.yaml api_key field",
|
||||
}
|
||||
missing = required - set(descriptions)
|
||||
|
||||
@@ -526,6 +526,11 @@ class TestCmdMigrate:
|
||||
class TestCmdCleanup:
|
||||
"""Test the cleanup command handler."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _mock_openclaw_running(self):
|
||||
with patch.object(claw_mod, "_detect_openclaw_processes", return_value=[]):
|
||||
yield
|
||||
|
||||
def test_no_dirs_found(self, tmp_path, capsys):
|
||||
args = Namespace(source=None, dry_run=False, yes=False)
|
||||
with patch.object(claw_mod, "_find_openclaw_dirs", return_value=[]):
|
||||
|
||||
@@ -72,7 +72,10 @@ class TestLoadConfigExpansion:
|
||||
|
||||
monkeypatch.setenv("GOOGLE_API_KEY", "gsk-test-key")
|
||||
monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "1234567:ABC-token")
|
||||
monkeypatch.setattr("hermes_cli.config.get_config_path", lambda: config_file)
|
||||
# Patch the imported function's own globals. Other tests may reload
|
||||
# hermes_cli.config, making string-target monkeypatches hit a different
|
||||
# module object than this collection-time imported load_config().
|
||||
monkeypatch.setitem(load_config.__globals__, "get_config_path", lambda: config_file)
|
||||
|
||||
config = load_config()
|
||||
|
||||
@@ -86,7 +89,7 @@ class TestLoadConfigExpansion:
|
||||
config_file.write_text(config_yaml)
|
||||
|
||||
monkeypatch.delenv("NOT_SET_XYZ_123", raising=False)
|
||||
monkeypatch.setattr("hermes_cli.config.get_config_path", lambda: config_file)
|
||||
monkeypatch.setitem(load_config.__globals__, "get_config_path", lambda: config_file)
|
||||
|
||||
config = load_config()
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_get_container_exec_info_defaults():
|
||||
)
|
||||
|
||||
with patch("hermes_constants.is_container", return_value=False), \
|
||||
patch("hermes_cli.config.get_hermes_home", return_value=hermes_home), \
|
||||
patch.dict(get_container_exec_info.__globals__, {"get_hermes_home": lambda: hermes_home}), \
|
||||
patch.dict(os.environ, {}, clear=False):
|
||||
os.environ.pop("HERMES_DEV", None)
|
||||
info = get_container_exec_info()
|
||||
|
||||
@@ -255,7 +255,8 @@ class TestLaunchdServiceRecovery:
|
||||
target = f"{domain}/{label}"
|
||||
|
||||
def fake_run(cmd, check=False, **kwargs):
|
||||
calls.append(cmd)
|
||||
if cmd and cmd[0] == "launchctl":
|
||||
calls.append(cmd)
|
||||
if cmd == ["launchctl", "kickstart", target] and calls.count(cmd) == 1:
|
||||
raise gateway_cli.subprocess.CalledProcessError(3, cmd, stderr="Could not find service")
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
@@ -282,7 +283,8 @@ class TestLaunchdServiceRecovery:
|
||||
target = f"{domain}/{label}"
|
||||
|
||||
def fake_run(cmd, check=False, **kwargs):
|
||||
calls.append(cmd)
|
||||
if cmd and cmd[0] == "launchctl":
|
||||
calls.append(cmd)
|
||||
if cmd == ["launchctl", "kickstart", target] and calls.count(cmd) == 1:
|
||||
raise gateway_cli.subprocess.CalledProcessError(113, cmd, stderr="Could not find service")
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
@@ -96,10 +96,17 @@ class TestPtyBridgeIO:
|
||||
@skip_on_windows
|
||||
class TestPtyBridgeResize:
|
||||
def test_resize_updates_child_winsize(self):
|
||||
# tput reads COLUMNS/LINES from the TTY ioctl (TIOCGWINSZ).
|
||||
# Spawn a shell, resize, then ask tput for the dimensions.
|
||||
# Query the TTY ioctl directly instead of using tput, which requires
|
||||
# TERM and fails in GitHub Actions' non-interactive environment.
|
||||
winsize_script = (
|
||||
"import fcntl, struct, termios, time; "
|
||||
"time.sleep(0.1); "
|
||||
"rows, cols, *_ = struct.unpack('HHHH', "
|
||||
"fcntl.ioctl(0, termios.TIOCGWINSZ, b'\\0' * 8)); "
|
||||
"print(cols); print(rows)"
|
||||
)
|
||||
bridge = PtyBridge.spawn(
|
||||
["/bin/sh", "-c", "sleep 0.1; tput cols; tput lines"],
|
||||
[sys.executable, "-c", winsize_script],
|
||||
cols=80,
|
||||
rows=24,
|
||||
)
|
||||
|
||||
@@ -30,6 +30,17 @@ def _clear_provider_env(monkeypatch):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
|
||||
def _clear_vercel_env(monkeypatch):
|
||||
for key in (
|
||||
"TERMINAL_VERCEL_RUNTIME",
|
||||
"VERCEL_OIDC_TOKEN",
|
||||
"VERCEL_TOKEN",
|
||||
"VERCEL_PROJECT_ID",
|
||||
"VERCEL_TEAM_ID",
|
||||
):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
|
||||
def _stub_tts(monkeypatch):
|
||||
"""Stub out TTS prompts so setup_model_provider doesn't block."""
|
||||
monkeypatch.setattr("hermes_cli.setup.prompt_choice", lambda q, c, d=0: (
|
||||
@@ -485,6 +496,7 @@ def test_modal_setup_persists_direct_mode_when_user_chooses_their_own_account(tm
|
||||
|
||||
def test_vercel_setup_configures_access_token_auth(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
_clear_vercel_env(monkeypatch)
|
||||
monkeypatch.setenv("VERCEL_OIDC_TOKEN", "old-oidc")
|
||||
monkeypatch.setitem(sys.modules, "vercel", types.ModuleType("vercel"))
|
||||
config = load_config()
|
||||
@@ -515,13 +527,7 @@ def test_vercel_setup_configures_access_token_auth(tmp_path, monkeypatch):
|
||||
|
||||
def test_vercel_setup_prefills_project_and_team_from_link_file(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
# Sibling test (test_vercel_setup_configures_access_token_auth) calls
|
||||
# save_env_value which mutates os.environ directly and never restores
|
||||
# it. When xdist schedules both tests in the same worker, VERCEL_*
|
||||
# from the earlier run masks the .vercel/project.json defaults that
|
||||
# this test exercises. Clear them before load.
|
||||
for _leaked in ("VERCEL_TOKEN", "VERCEL_PROJECT_ID", "VERCEL_TEAM_ID", "VERCEL_OIDC_TOKEN"):
|
||||
monkeypatch.delenv(_leaked, raising=False)
|
||||
_clear_vercel_env(monkeypatch)
|
||||
project_root = tmp_path / "project"
|
||||
nested = project_root / "app" / "src"
|
||||
nested.mkdir(parents=True)
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestReloadEnv:
|
||||
"""reload_env() adds vars from .env that are not in os.environ."""
|
||||
env_file = tmp_path / ".env"
|
||||
env_file.write_text("TEST_RELOAD_VAR=hello123\n")
|
||||
with patch("hermes_cli.config.get_env_path", return_value=env_file):
|
||||
with patch.dict(reload_env.__globals__, {"get_env_path": lambda: env_file}):
|
||||
os.environ.pop("TEST_RELOAD_VAR", None)
|
||||
count = reload_env()
|
||||
assert count >= 1
|
||||
@@ -40,7 +40,7 @@ class TestReloadEnv:
|
||||
"""reload_env() updates vars whose value changed on disk."""
|
||||
env_file = tmp_path / ".env"
|
||||
env_file.write_text("TEST_RELOAD_VAR=old_value\n")
|
||||
with patch("hermes_cli.config.get_env_path", return_value=env_file):
|
||||
with patch.dict(reload_env.__globals__, {"get_env_path": lambda: env_file}):
|
||||
os.environ["TEST_RELOAD_VAR"] = "old_value"
|
||||
# Now change the file
|
||||
env_file.write_text("TEST_RELOAD_VAR=new_value\n")
|
||||
@@ -55,7 +55,7 @@ class TestReloadEnv:
|
||||
env_file.write_text("") # empty .env
|
||||
# Pick a known key from OPTIONAL_ENV_VARS
|
||||
known_key = next(iter(OPTIONAL_ENV_VARS.keys()))
|
||||
with patch("hermes_cli.config.get_env_path", return_value=env_file):
|
||||
with patch.dict(reload_env.__globals__, {"get_env_path": lambda: env_file}):
|
||||
os.environ[known_key] = "stale_value"
|
||||
count = reload_env()
|
||||
assert known_key not in os.environ
|
||||
@@ -65,7 +65,7 @@ class TestReloadEnv:
|
||||
"""reload_env() preserves non-Hermes env vars even when absent from .env."""
|
||||
env_file = tmp_path / ".env"
|
||||
env_file.write_text("")
|
||||
with patch("hermes_cli.config.get_env_path", return_value=env_file):
|
||||
with patch.dict(reload_env.__globals__, {"get_env_path": lambda: env_file}):
|
||||
os.environ["MY_CUSTOM_UNRELATED_VAR"] = "keep_me"
|
||||
reload_env()
|
||||
assert os.environ.get("MY_CUSTOM_UNRELATED_VAR") == "keep_me"
|
||||
@@ -1851,14 +1851,24 @@ class TestPtyWebSocket:
|
||||
assert b"round-trip-payload" in buf
|
||||
|
||||
def test_resize_escape_is_forwarded(self, monkeypatch):
|
||||
# Resize escape gets intercepted and applied via TIOCSWINSZ,
|
||||
# then ``tput cols/lines`` reports the new dimensions back.
|
||||
# Resize escape gets intercepted and applied via TIOCSWINSZ, then the
|
||||
# child reads the TTY ioctl directly. Avoid tput because CI may not set
|
||||
# TERM for non-interactive shells.
|
||||
import sys
|
||||
|
||||
winsize_script = (
|
||||
"import fcntl, struct, termios, time; "
|
||||
"time.sleep(0.15); "
|
||||
"rows, cols, *_ = struct.unpack('HHHH', "
|
||||
"fcntl.ioctl(0, termios.TIOCGWINSZ, b'\\0' * 8)); "
|
||||
"print(cols); print(rows)"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
self.ws_module,
|
||||
"_resolve_chat_argv",
|
||||
# sleep gives the test time to push the resize before tput runs
|
||||
# sleep gives the test time to push the resize before the child reads the ioctl.
|
||||
lambda resume=None, sidecar_url=None: (
|
||||
["/bin/sh", "-c", "sleep 0.15; tput cols; tput lines"],
|
||||
[sys.executable, "-c", winsize_script],
|
||||
None,
|
||||
None,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user