Skip to content

Commit 75fa259

Browse files
cowork-bot: versions command silent-green fix — _pip_version() helper distinguishes not-installed vs missing version metadata; explicit 'no version metadata'/'error checking' lines instead of silent skip; +5 tests (24 pass, ruff clean)
1 parent 7b1512a commit 75fa259

2 files changed

Lines changed: 62 additions & 14 deletions

File tree

src/devforge/cli.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,35 @@ def show_versions(
128128
for t in targets:
129129
info = TOOLS[t]
130130
try:
131-
result = subprocess.run(
132-
[sys.executable, "-m", "pip", "show", info["package"]], capture_output=True, text=True
133-
)
134-
if result.returncode == 0:
135-
for line in result.stdout.splitlines():
136-
if line.startswith("Version:"):
137-
ver = line.split(":", 1)[1].strip()
138-
console.print(f"[cyan]{t:8}[/cyan] v{ver}")
139-
break
140-
else:
141-
console.print(f"[dim]{t:8}[/dim] [red]not installed[/red]")
142-
except Exception:
143-
console.print(f"[dim]{t:8}[/dim] [red]error checking[/red]")
131+
ver = _pip_version(info["package"])
132+
except Exception as e:
133+
console.print(f"[dim]{t:8}[/dim] [red]error checking ({e})[/red]")
134+
continue
135+
if ver is None:
136+
console.print(f"[dim]{t:8}[/dim] [red]not installed[/red]")
137+
elif ver == "":
138+
# pip show succeeded but returned no Version metadata — never stay silent.
139+
console.print(f"[dim]{t:8}[/dim] [yellow]installed, no version metadata[/yellow]")
140+
else:
141+
console.print(f"[cyan]{t:8}[/cyan] v{ver}")
142+
143+
144+
def _pip_version(package: str) -> str | None:
145+
"""Return the installed version of *package*, or None if not installed.
146+
147+
Returns "" when ``pip show`` succeeds but the output carries no
148+
``Version:`` line (broken metadata) so callers can distinguish it from a
149+
clean not-installed result instead of silently printing nothing.
150+
"""
151+
result = subprocess.run(
152+
[sys.executable, "-m", "pip", "show", package], capture_output=True, text=True
153+
)
154+
if result.returncode != 0:
155+
return None
156+
for line in result.stdout.splitlines():
157+
if line.startswith("Version:"):
158+
return line.split(":", 1)[1].strip()
159+
return ""
144160

145161

146162
def _is_tool_installed(module_name: str) -> bool:

tests/test_cli.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from devforge import TOOLS, __version__
6-
from devforge.cli import _is_tool_installed, app
6+
from devforge.cli import _is_tool_installed, _pip_version, app
77
from typer.testing import CliRunner
88
from unittest import mock
99

@@ -176,3 +176,35 @@ def test_help(self):
176176
assert "tools" in result.stdout
177177
assert "versions" in result.stdout
178178
assert "guard" in result.stdout
179+
180+
181+
class TestPipVersionHelper:
182+
@mock.patch("devforge.cli.subprocess.run")
183+
def test_returns_version_line(self, mock_run):
184+
"""Parse Version: out of successful pip show output."""
185+
mock_run.return_value = mock.MagicMock(returncode=0, stdout="Name: x\nVersion: 1.2.3\n")
186+
assert _pip_version("x") == "1.2.3"
187+
188+
@mock.patch("devforge.cli.subprocess.run")
189+
def test_not_installed_returns_none(self, mock_run):
190+
mock_run.return_value = mock.MagicMock(returncode=1, stdout="", stderr="not found")
191+
assert _pip_version("x") is None
192+
193+
@mock.patch("devforge.cli.subprocess.run")
194+
def test_missing_metadata_returns_empty(self, mock_run):
195+
"""pip show success without a Version line must NOT look like not installed."""
196+
mock_run.return_value = mock.MagicMock(returncode=0, stdout="Name: x\n")
197+
assert _pip_version("x") == ""
198+
199+
@mock.patch("devforge.cli._pip_version", return_value="")
200+
def test_versions_reports_missing_metadata(self, _mock):
201+
"""Silent-green regression guard: broken metadata gets an explicit line."""
202+
result = runner.invoke(app, ["versions", "guard"])
203+
assert result.exit_code == 0
204+
assert "no version metadata" in result.stdout
205+
206+
@mock.patch("devforge.cli._pip_version", side_effect=OSError("boom"))
207+
def test_versions_reports_error(self, _mock):
208+
result = runner.invoke(app, ["versions", "guard"])
209+
assert result.exit_code == 0
210+
assert "error checking" in result.stdout

0 commit comments

Comments
 (0)