From 875d930ac70589a337a3b30c2434da30a5be14d2 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 28 May 2026 15:17:41 +1000 Subject: [PATCH] test(docker-update): stub subprocess.run in git-install regression guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The regression-guard test `test_cmd_update_on_git_install_does_not_print_docker_message` mocked `is_managed` and `detect_install_method` but not `subprocess.run`, so once `cmd_update(check=True)` decided this was a git install it shelled out to a real `git fetch upstream` / `git fetch origin`. On CI runners the worktree has no `upstream` remote configured and the fetch hung past the 30s pytest-timeout — test (4) slice failed in #33659 CI. Fix: stub `subprocess.run` with a successful CompletedProcess-shaped object whose stdout is `"0\n"`, so: - no real git command is ever invoked - the rev-list parsing later in the flow (`int(stdout.strip())`) succeeds rather than `ValueError`-ing through the test's SystemExit catch - the flow proceeds far enough to confirm the docker banner is absent (the actual assertion) Also broaden the except clause to `(SystemExit, Exception)`: the only assertion in this test is the negative-banner check on captured stdout; any further failure in the rest of the update flow is irrelevant to that contract. Verified locally: all 7 tests in `tests/hermes_cli/test_cmd_update_docker.py` pass in 0.39s (previously the regression-guard test alone consumed 30s+ and got SIGTERM'd). --- tests/hermes_cli/test_cmd_update_docker.py | 30 +++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tests/hermes_cli/test_cmd_update_docker.py b/tests/hermes_cli/test_cmd_update_docker.py index 7bccfe89782..c56a3ffcfda 100644 --- a/tests/hermes_cli/test_cmd_update_docker.py +++ b/tests/hermes_cli/test_cmd_update_docker.py @@ -109,23 +109,35 @@ def test_cmd_update_check_direct_in_docker(mock_run, _mock_method, capsys): @patch("hermes_cli.config.is_managed", return_value=False) @patch("hermes_cli.config.detect_install_method", return_value="git") +@patch( + "subprocess.run", + return_value=SimpleNamespace(returncode=0, stdout="0\n", stderr=""), +) def test_cmd_update_on_git_install_does_not_print_docker_message( - _mock_method, _mock_managed, capsys + _mock_run, _mock_method, _mock_managed, capsys ): """Source/git installs MUST NOT hit the Docker branch. Regression guard: an over-eager detection refactor could accidentally - route git users through the docker-pull message. We swallow the - SystemExit / errors from the rest of the update flow — those don't - matter for this assertion; what matters is that the docker text is - absent. + route git users through the docker-pull message. We swallow + SystemExit / unrelated errors from the rest of the update flow — + those don't matter for this assertion; what matters is that the + docker text is absent. + + ``subprocess.run`` is mocked because the git path will otherwise shell + out to ``git fetch upstream`` / ``git fetch origin`` — on CI runners + with no ``upstream`` remote configured this can hang past the 30s + pytest-timeout depending on git's network behaviour. The stub + returns a successful CompletedProcess-shaped object with ``"0\\n"`` + stdout, which both keeps the flow shell-free AND parses cleanly as + the "0 commits behind" rev-list output the check path later parses + via ``int(rev_result.stdout.strip())``. """ try: cmd_update(SimpleNamespace(check=True, branch=None)) - except SystemExit: - # Update flow may exit for unrelated reasons in a test env (no - # network, no real .git) — that's fine; we only care about the - # banner not appearing. + except (SystemExit, Exception): + # Update flow may exit for unrelated reasons in a stubbed env — + # that's fine; we only care about the banner not appearing. pass assert "doesn't apply inside the Docker container" not in capsys.readouterr().out