test(update): patch isatty on real streams to fix xdist-flaky --yes tests

Two CI tests for the new `--yes` update flag (#18261) flaked under
`pytest-xdist` on Linux/Python 3.11 even though they passed every
local run on macOS/Python 3.14.4:

  FAILED tests/hermes_cli/test_update_yes_flag.py
    ::TestUpdateYesConfigMigration::test_no_yes_flag_still_prompts_in_tty
      `AssertionError: assert <MagicMock 'input'>.called is False`
  FAILED tests/hermes_cli/test_update_yes_flag.py
    ::TestUpdateYesStashRestore::test_yes_restores_stash_without_prompting
      `AssertionError: assert <MagicMock '_restore_stashed_changes'>.called is False`

Captured stdout for the first failure shows `cmd_update` taking the
"Non-interactive session \u2014 skipping config migration prompt." branch
\u2014 i.e. the `sys.stdin.isatty() and sys.stdout.isatty()` check at
`hermes_cli/main.py:7118` evaluated to `False` despite the test doing:

    with patch("hermes_cli.main.sys") as mock_sys:
        mock_sys.stdin.isatty.return_value = True
        mock_sys.stdout.isatty.return_value = True

The whole-module mock is fragile under xdist worker reuse: a sibling
test that imports `hermes_cli.main` first can leave another `sys`
reference resolved inside the function (re-import in a helper, etc.),
and the wholesale module replacement never gets consulted.

Switch to `patch.object(_sys.stdin, "isatty", return_value=True)` (and
the same for `stdout`). That patches the *attribute on the real stream
object* \u2014 every call site, no matter how it reached `sys.stdin`,
hits the patched method. Same fix applied to the stash-restore test
(it took the "non-TTY \u2192 skip restore prompt" branch for the same reason).

Validation:

    $ pytest tests/hermes_cli/test_update_yes_flag.py -q
    3 passed in 5.47s

No production code change. Fixes the two failures observed on `main`
(run 25250051126):

`tests/hermes_cli/test_update_yes_flag.py::TestUpdateYesConfigMigration::test_no_yes_flag_still_prompts_in_tty`
`tests/hermes_cli/test_update_yes_flag.py::TestUpdateYesStashRestore::test_yes_restores_stash_without_prompting`

Refs: #18261 (added the `--yes` flag + these tests).
This commit is contained in:
Sanjay Santhanam 2026-05-02 16:48:10 -07:00 committed by Teknium
parent 033e533d05
commit 595bcc89fc

View file

@ -113,11 +113,18 @@ class TestUpdateYesConfigMigration:
args = SimpleNamespace(yes=False)
with patch("builtins.input", return_value="n") as mock_input, patch(
"hermes_cli.main.sys"
) as mock_sys:
mock_sys.stdin.isatty.return_value = True
mock_sys.stdout.isatty.return_value = True
# Patch ``sys.stdin.isatty`` and ``sys.stdout.isatty`` directly on the
# real ``sys`` module instead of replacing ``hermes_cli.main.sys`` with
# a MagicMock. The MagicMock approach was flaky under ``pytest-xdist``
# — a sibling test that imported ``hermes_cli.main`` first could leave
# a different ``sys`` reference resolved inside the function and the
# mock would never be consulted, with CI then taking the
# "Non-interactive session" branch instead of prompting.
import sys as _sys
with patch("builtins.input", return_value="n") as mock_input, patch.object(
_sys.stdin, "isatty", return_value=True
), patch.object(_sys.stdout, "isatty", return_value=True):
cmd_update(args)
# The user was actually prompted.
assert mock_input.called
@ -156,7 +163,16 @@ class TestUpdateYesStashRestore:
args = SimpleNamespace(yes=True)
cmd_update(args)
# Force a TTY-shaped session so the autostash-restore branch is
# reachable in CI workers regardless of inherited stdio (matches the
# isatty patching strategy in ``test_no_yes_flag_still_prompts_in_tty``
# — ``patch.object`` on the real streams is robust under xdist).
import sys as _sys
with patch.object(_sys.stdin, "isatty", return_value=True), patch.object(
_sys.stdout, "isatty", return_value=True
):
cmd_update(args)
# _restore_stashed_changes was called, and called with prompt_user=False
# every time (so the user never sees "Restore local changes now?").