mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-14 12:10:50 +01:00
Make code_tutor OSError fingerprint test Python-version-robust
test_codebase_fingerprint_stat_oserror_raises passed on 3.14 but failed on CI's 3.11: there Path.is_file() itself calls stat() and re-raises an OSError without a recognized errno, so the injected error escaped before the loop reached the guarded path.stat() call. Force is_file() True for the target (without calling stat) so the explicit fingerprint-time stat() deterministically raises and hits the except OSError branch on every Python version. Verified passing on 3.11.11 and 3.14. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RRCdR4D8SaVrN2xraMQxM
This commit is contained in:
parent
a97bf967e8
commit
f11f9a3b5e
@ -0,0 +1,30 @@
|
||||
{
|
||||
"intent": "Fix a Python-version-fragile code_tutor test so testsAndMisc CI (Python 3.11) passes: the codebase_fingerprint OSError-branch test passed on 3.14 but failed on 3.11.",
|
||||
"scope": [
|
||||
"python_pkg/code_tutor/tests/test_analyzer.py",
|
||||
"Non-goal: any change to _analyzer.py source (behavior unchanged)."
|
||||
],
|
||||
"changes": [
|
||||
"test_codebase_fingerprint_stat_oserror_raises now patches Path.is_file to return True for the target file (without calling stat) in addition to patching Path.stat to raise, so the loop deterministically reaches the guarded path.stat() call and hits the `except OSError` branch on every Python version.",
|
||||
"Root cause: on 3.11/3.12 Path.is_file() calls stat() and re-raises OSErrors without a recognized errno, so the injected error escaped before the guarded stat() was reached; on 3.14 is_file() uses os.path.isfile and did not."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "python3.11 -m pytest test_codebase_fingerprint_stat_oserror_raises -o addopts=''",
|
||||
"result": "pass",
|
||||
"evidence": "1 passed on 3.11.11 (the CI version that previously failed)"
|
||||
},
|
||||
{
|
||||
"command": "python -m pytest test_analyzer.py::...stat_oserror_raises --cov=python_pkg.code_tutor._analyzer",
|
||||
"result": "pass",
|
||||
"evidence": "1 passed on 3.14; _analyzer.py lines 386-387 (except OSError branch) covered"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Patching is_file globally could mask a real is_file bug, but the mock delegates to the real is_file for all non-target paths."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit; the prior test still covers the branch on 3.14 only.",
|
||||
"After rollback, re-run on 3.11 to confirm the flake returns."
|
||||
]
|
||||
}
|
||||
@ -422,12 +422,15 @@ def test_code_item_defaults() -> None:
|
||||
def test_codebase_fingerprint_stat_oserror_raises(tmp_path: Path) -> None:
|
||||
"""Skip a file whose ``stat`` raises ``OSError`` (except branch).
|
||||
|
||||
On this Python ``Path.is_file`` uses ``os.path.isfile`` rather than
|
||||
``Path.stat``, so a stat that always fails for the source file reaches
|
||||
the ``except OSError`` handler that drops the unreadable entry.
|
||||
``is_file()`` is forced True for the target so the loop reaches the
|
||||
explicit ``path.stat()`` at fingerprint time; that stat then raises and
|
||||
the ``except OSError`` handler drops the unreadable entry. Patching both
|
||||
keeps this deterministic across Python versions (on some, ``is_file``
|
||||
itself calls ``stat`` and would otherwise re-raise before line reached).
|
||||
"""
|
||||
(tmp_path / "source.py").write_text("x = 1", encoding="utf-8")
|
||||
real_stat = Path.stat
|
||||
real_is_file = Path.is_file
|
||||
|
||||
def failing_stat(self: Path, **kwargs: object) -> object:
|
||||
"""Raise for the target file, delegate to the real stat otherwise."""
|
||||
@ -436,7 +439,14 @@ def test_codebase_fingerprint_stat_oserror_raises(tmp_path: Path) -> None:
|
||||
raise OSError(msg)
|
||||
return real_stat(self, **kwargs)
|
||||
|
||||
with patch.object(Path, "stat", failing_stat):
|
||||
def always_file(self: Path) -> bool:
|
||||
"""Report the target as a regular file without calling stat."""
|
||||
return str(self).endswith("source.py") or real_is_file(self)
|
||||
|
||||
with (
|
||||
patch.object(Path, "stat", failing_stat),
|
||||
patch.object(Path, "is_file", always_file),
|
||||
):
|
||||
result = codebase_fingerprint(tmp_path)
|
||||
|
||||
assert len(result) == 16
|
||||
|
||||
Loading…
Reference in New Issue
Block a user