diff --git a/headroom/release_version.py b/headroom/release_version.py index 65cffbf48..643fdd68a 100644 --- a/headroom/release_version.py +++ b/headroom/release_version.py @@ -4,6 +4,7 @@ from __future__ import annotations import os import re +import subprocess from collections.abc import Sequence from dataclasses import dataclass, replace from pathlib import Path @@ -248,6 +249,8 @@ def list_release_tags(root: Path) -> list[str]: check=True, capture_output=True, text=True, + stdin=subprocess.DEVNULL, + timeout=10, ) return [tag.strip() for tag in result.stdout.splitlines() if tag.strip()] @@ -267,6 +270,8 @@ def list_release_commits(root: Path, previous_tag: str) -> list[CommitInfo]: check=True, capture_output=True, text=True, + stdin=subprocess.DEVNULL, + timeout=10, ) commits: list[CommitInfo] = [] @@ -290,6 +295,8 @@ def commit_height_since(root: Path, previous_tag: str) -> str: check=True, capture_output=True, text=True, + stdin=subprocess.DEVNULL, + timeout=10, ) return result.stdout.strip() or "0" diff --git a/tests/test_release_version.py b/tests/test_release_version.py index 059c6fe18..90edca6ad 100644 --- a/tests/test_release_version.py +++ b/tests/test_release_version.py @@ -11,11 +11,13 @@ import pytest from headroom.release_version import ( CommitInfo, classify_commit_bump, + commit_height_since, compute_release_version, determine_bump_level, find_latest_release_tag, get_canonical_version, list_release_commits, + list_release_tags, normalize_release_tag, parse_release_tag, ) @@ -156,6 +158,27 @@ def test_list_release_commits_parses_empty_body_entries( ] +def test_version_detection_git_calls_are_hang_safe( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Version-detection git subprocesses must not inherit the caller's stdin + and must be bounded — otherwise they hang ``headroom_compress``: the + compression pipeline initializes the OTel tracer, which calls + ``get_version()`` → ``_source_tree_version()`` → these git calls.""" + run = Mock() + run.return_value = Mock(stdout="") + monkeypatch.setattr("headroom.release_version.run", run) + + list_release_tags(ROOT) + list_release_commits(ROOT, "") + commit_height_since(ROOT, "v0.1.0") + + assert len(run.call_args_list) == 3 + for call in run.call_args_list: + assert call.kwargs["stdin"] == subprocess.DEVNULL + assert call.kwargs["timeout"] == 10 + + def test_release_version_script_runs_directly_without_importing_headroom_package( tmp_path: Path, ) -> None: