Skip to content

Commit 8cc3ab1

Browse files
ayhammoudaclaude
andcommitted
test: complete detection fallback-chain coverage
Implement the two remaining detection branches: python3 PATH probe and the sys.version_info runtime fallback (subprocess stubbed via monkeypatch, cwd isolated so a real .python-version can't shadow the branch under test). detection.py is now fully covered — 12 tests. Mark the gap CLOSED in TEST-STRATEGY.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 25c2385 commit 8cc3ab1

2 files changed

Lines changed: 45 additions & 30 deletions

File tree

.github/TEST-STRATEGY.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ behavioral test and appear in the schema snapshot.
3232
| `list_versions` | `test_services`, `test_multi_version` | unit + integration| GOOD |
3333
| `compare_versions` | `test_compare_versions` (15), `test_services` | unit | GOOD |
3434
| `lookup_package_docs` | `test_package_docs` (8) | unit only | THIN |
35-
| `detect_python_version`| — none — | | **GAP**|
35+
| `detect_python_version`| `test_detection` (12) | unit | GOOD |
3636

3737
Cross-cutting coverage:
3838

@@ -76,17 +76,18 @@ packages, not `server.py` (intentionally thin) or `__main__.py`.
7676

7777
## 5. Known gaps
7878

79-
1. **`detection.py` is untested (HIGH).** `detect_python_version` is 1 of 6
80-
public tools and has three branches (`.python-version` file → `python3` in
81-
PATH → `sys.version_info` fallback) plus `_parse_major_minor` regex parsing
82-
and `match_to_indexed`. No `tests/test_detection.py` exists. Confirmed across
83-
prior audits. **Closing this is the top priority below.**
79+
1. **`detection.py` — CLOSED (2026-05-29).** `tests/test_detection.py` now
80+
covers all three branches of the fallback chain (`.python-version` file →
81+
`python3` in PATH → `sys.version_info`), `_parse_major_minor` parsing, and
82+
`match_to_indexed` — 12 tests. The isolation pattern (`monkeypatch.chdir` to
83+
escape a real `.python-version`, `monkeypatch.setattr` on `subprocess.run`)
84+
is the reference for testing order-dependent environment probing.
8485
2. **`lookup_package_docs` has no stdio smoke (LOW).** Covered at the service
8586
layer only; the PyPI-allowlist trust boundary is never exercised end-to-end.
8687
3. **No negative version-resolution E2E (LOW).** Unknown-version errors are
8788
unit-tested but not asserted over the stdio boundary.
8889

89-
## 6. Example test cases for the top gap (`detection.py`)
90+
## 6. Reference cases `detection.py` (now implemented in `test_detection.py`)
9091

9192
| Case | Expectation |
9293
|----------------------------------------|------------------------------------------|

tests/test_detection.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from __future__ import annotations
1717

1818
import subprocess
19+
import sys
1920

2021
import pytest
2122

@@ -86,26 +87,39 @@ def fake_run(*args, **_kwargs):
8687
assert source == "python3 in PATH"
8788

8889

89-
# TODO(you): implement the remaining two branches of the fallback chain.
90-
#
91-
# These are the cases where the test *design* matters most — you have to
92-
# neutralize the branches above the one under test. Both start from an empty
93-
# cwd so no real .python-version interferes:
94-
#
95-
# monkeypatch.chdir(tmp_path) # escape any real .python-version
96-
#
97-
# 1) test_detects_from_path_probe:
98-
# - Patch detection.subprocess.run to return a CompletedProcess with
99-
# returncode=0 and stdout="Python 3.10.9\n".
100-
# - Assert (version, source) == ("3.10", "python3 in PATH").
101-
#
102-
# 2) test_falls_back_to_runtime_when_no_python3:
103-
# - Make the PATH probe fail: patch detection.subprocess.run to raise
104-
# FileNotFoundError (python3 absent). Decide what the function should
105-
# return — it falls back to the server's own interpreter. Assert the
106-
# source is "server runtime" and the version matches
107-
# f"{sys.version_info.major}.{sys.version_info.minor}".
108-
#
109-
# Why this is the meaningful part: detection is order-dependent, so a test that
110-
# forgets to chdir() or forgets to stub subprocess will pass on your machine
111-
# and fail in CI (or vice-versa). The isolation is the assertion.
90+
def test_detects_from_path_probe(tmp_path, monkeypatch) -> None:
91+
"""Branch 2: no .python-version file, so the python3 PATH probe wins.
92+
93+
chdir to an empty tmp dir to neutralize branch 1 (any real
94+
.python-version on the host), then stub the probe deterministically.
95+
"""
96+
monkeypatch.chdir(tmp_path)
97+
98+
def fake_run(*args, **_kwargs):
99+
return subprocess.CompletedProcess(args, 0, stdout="Python 3.10.9\n", stderr="")
100+
101+
monkeypatch.setattr(detection.subprocess, "run", fake_run)
102+
103+
version, source = detect_python_version()
104+
105+
assert version == "3.10"
106+
assert source == "python3 in PATH"
107+
108+
109+
def test_falls_back_to_runtime_when_no_python3(tmp_path, monkeypatch) -> None:
110+
"""Branch 3: no file and no python3 on PATH -> server's own interpreter.
111+
112+
Neutralize branch 1 (empty cwd) and force branch 2 to fail by making the
113+
probe raise FileNotFoundError, exactly as a missing python3 would.
114+
"""
115+
monkeypatch.chdir(tmp_path)
116+
117+
def boom(*args, **_kwargs):
118+
raise FileNotFoundError("python3 not found")
119+
120+
monkeypatch.setattr(detection.subprocess, "run", boom)
121+
122+
version, source = detect_python_version()
123+
124+
assert source == "server runtime"
125+
assert version == f"{sys.version_info.major}.{sys.version_info.minor}"

0 commit comments

Comments
 (0)