`uv sync --frozen` resolved ruff 0.15.22 and the tree read clean. A loose install resolves 0.16.6, under which the SAME untouched code reports 148 findings -- 4 more than round 9 counted, because this round added four files. All of them are new rules rather than new defects: 0.16 widened the default rule set to whole families (YTT, ASYNC, PL, ISC, C4, UP, B, SIM, FURB, ...). (`[skip-docs]` is for CLAUDE.md, which a lint-configuration change does not reach. README's developer section IS updated in this commit.) THE DEFECT IS NOT THE 148, IT IS THAT NOBODY CHOSE THEM. `[tool.ruff]` set only `line-length` and `target-version`, so the acceptance was ruff's default, and the tree stayed green only as long as the lockfile froze an old ruff. `select` is now written down: `E4`, `E7`, `E9`, `F` (the historical default), `I` because this tree already keeps imports sorted, and `RUF100` so a `noqa` that has stopped meaning anything is caught rather than left as decoration. Pin `ruff>=0.9` -> `ruff>=0.16.6,<0.17`. Per rule, before -> after: RUF100 50 -> 0, I001 20 -> 0, ISC004 19, PLW1510 8, C408 8, EXE001 6, RUF007 5, PLE2515 4, UP031 3, B017 3, and fourteen more with 2 or fewer -- the families out of the declared set are 0 by selection, and 148 is the number to start from if they are adopted, which is a separate decision and not one to take inside a version-pin commit. 57 were auto-fixed; one E402 was reintroduced by the import-sorting fix merging a block away from its `noqa`, and got the directive back rather than a bare one. `S` IS MEASURED OUT, NOT ASSUMED OUT: it reports 2657 `S101` on a suite whose every assertion is an `assert`, and `S603` flags 19 subprocess calls of which one was ever marked -- selecting it buys 18 suppressions and no defect. Two `noqa` directives naming non-selected rules were dropped with that reason recorded in the configuration instead. THE TWO FILES 0.16 WOULD REFORMAT ARE MARKDOWN, NOT PYTHON: `README.md` and `docs/2026-09-08-blindsone-below-k-k2.md`. 0.16 formats fenced Python inside markdown, and both blocks are RECORDS -- the second is a quotation of `COST_VOCABULARY` as it stood when that measurement was taken. Reformatting a quotation makes it stop being one, so markdown is excluded from the formatter and `ruff format --check .` stays in the acceptance over `.py`. `tools/okf_consume_measure.py` is fenced by the order as run-not-edited, so its three findings are exempted by path with the reason and the debt named, and its bytes are untouched. THE LOCKFILE TRAP IS CLOSED, NOT AVOIDED. `uv.lock` predated the `[ocr]` extra, so any unlocked resolve wrote that extra's transitive tree back into it -- 681 insertions over 4 deletions, twice now, and round 9 recorded the cause as `uv run` OUTSIDE the project when it is `uv run` without `--frozen` INSIDE it. The relock is complete for every declared extra (703 insertions, 26 deletions), and measured after it, an unfrozen `uv run` leaves the file alone. `ruff check src tests tools`, `ruff format --check .` (0.16.6), `mypy src` over 21 files and 1535 tests, all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
147 lines
5.6 KiB
Python
147 lines
5.6 KiB
Python
"""The vendored converter is resolved explicitly, or not used at all.
|
|
|
|
`pypandoc` searches `PATH` before its own bundled binary and takes the HIGHEST
|
|
version it finds. Measured three times independently on this host: the wheel
|
|
carries pandoc 3.9, the host carries 3.10.2, and `pypandoc.get_pandoc_version()`
|
|
returns 3.10.2. So "we vendored the binary" buys nothing on its own -- the
|
|
bundle would be built by a converter nobody chose, and nothing would say so.
|
|
|
|
These tests pin the resolution, not the conversion. The seam that converts is
|
|
tested separately; what is falsifiable here is which binary a conversion would
|
|
have used, and that the answer is refused rather than guessed when it is wrong.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from llm_ingestion_okf._pandoc import (
|
|
PANDOC_VERSION,
|
|
converter_path,
|
|
resolve_pandoc,
|
|
)
|
|
from llm_ingestion_okf.errors import ExtractionError
|
|
|
|
pytest.importorskip("pypandoc", reason="the [extract] extra is not installed")
|
|
|
|
|
|
def test_the_resolved_binary_is_the_bundled_one_not_a_path_binary() -> None:
|
|
"""The whole point of the module, stated as an assertion.
|
|
|
|
The bundled binary lives inside the installed package; a PATH binary does
|
|
not. Comparing the resolved path against the package directory is what
|
|
distinguishes them -- comparing versions would not, because a host could
|
|
coincidentally carry the pinned version today and a different one tomorrow.
|
|
"""
|
|
import pypandoc
|
|
|
|
resolved = resolve_pandoc()
|
|
bundled = Path(pypandoc.__file__).parent / "files" / "pandoc"
|
|
assert resolved == bundled
|
|
assert resolved.is_file()
|
|
|
|
|
|
def test_the_resolved_binary_reports_the_pinned_version() -> None:
|
|
import subprocess
|
|
|
|
reported = subprocess.run(
|
|
[str(resolve_pandoc()), "--version"], capture_output=True, text=True
|
|
).stdout.split("\n")[0]
|
|
assert reported == f"pandoc {PANDOC_VERSION}"
|
|
|
|
|
|
def test_resolution_is_not_poisoned_by_an_earlier_probe_in_the_process() -> None:
|
|
"""The regression this file did not catch on its first pass.
|
|
|
|
`pypandoc.get_pandoc_version()` answers from a module global that
|
|
`clean_pandocpath_cache()` does not reset. Using it meant the version check
|
|
described whichever binary was probed FIRST in the process -- so the first
|
|
implementation reported the host's 3.10.2 for the bundled 3.9 binary, and
|
|
the suite stayed green because nothing in it probed the host binary first.
|
|
|
|
A test that passes only in a fresh process is not a test of the resolver.
|
|
This one poisons the caches the way real use does, then resolves.
|
|
"""
|
|
import pypandoc
|
|
|
|
pypandoc.get_pandoc_version() # caches whatever the search finds
|
|
assert resolve_pandoc().is_file() # must not raise extractor_binary_version
|
|
|
|
|
|
def test_a_version_mismatch_is_refused_and_names_both_versions(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Refused, not used with a warning.
|
|
|
|
Extraction is deterministic within a converter version and not across one,
|
|
and the byte-pinned fixtures cannot tell "different converter" from
|
|
"defect". A mismatch that proceeded would make every later measurement
|
|
unattributable.
|
|
"""
|
|
monkeypatch.setattr("llm_ingestion_okf._pandoc.PANDOC_VERSION", "0.0.0")
|
|
with pytest.raises(ExtractionError) as excinfo:
|
|
resolve_pandoc()
|
|
assert excinfo.value.code == "extractor_binary_version"
|
|
message = str(excinfo.value)
|
|
assert "0.0.0" in message, "the expected version is not named"
|
|
assert PANDOC_VERSION in message, "the found version is not named"
|
|
|
|
|
|
def test_an_absent_binary_raises_binary_missing(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
"""Distinct from the extra being absent: the wheel can be there without it."""
|
|
import pypandoc
|
|
|
|
monkeypatch.setattr(pypandoc, "__file__", str(tmp_path / "pypandoc" / "__init__.py"))
|
|
with pytest.raises(ExtractionError) as excinfo:
|
|
resolve_pandoc()
|
|
assert excinfo.value.code == "extractor_binary_missing"
|
|
|
|
|
|
def test_the_extra_being_absent_keeps_the_same_rejection(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setitem(sys.modules, "pypandoc", None)
|
|
with pytest.raises(ExtractionError) as excinfo:
|
|
resolve_pandoc()
|
|
assert excinfo.value.code == "extractor_extra_missing"
|
|
assert "extract" in str(excinfo.value)
|
|
|
|
|
|
def test_the_scoped_path_leaves_os_environ_exactly_as_it_found_it() -> None:
|
|
"""A library must not set a process-global that outlives its own call.
|
|
|
|
`pypandoc` offers no per-call path parameter -- the only override is the
|
|
`PYPANDOC_PANDOC` environment variable plus a cached module global. Both
|
|
are process-wide, so the scope is where the discipline has to live: enter,
|
|
convert, restore, whether or not the body raised.
|
|
"""
|
|
before = dict(os.environ)
|
|
with converter_path() as path:
|
|
assert os.environ["PYPANDOC_PANDOC"] == str(path)
|
|
assert dict(os.environ) == before
|
|
|
|
with pytest.raises(RuntimeError):
|
|
with converter_path():
|
|
raise RuntimeError("the body failed")
|
|
assert dict(os.environ) == before, "an exception must not leak the override"
|
|
|
|
|
|
def test_a_pre_existing_override_is_restored_not_dropped(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Restoring means putting back what was there, including a wrong value.
|
|
|
|
Deleting the key on exit would look correct in an environment that had
|
|
none, and would silently erase an operator's deliberate override in one
|
|
that did.
|
|
"""
|
|
monkeypatch.setenv("PYPANDOC_PANDOC", "/somewhere/else/pandoc")
|
|
with converter_path():
|
|
pass
|
|
assert os.environ["PYPANDOC_PANDOC"] == "/somewhere/else/pandoc"
|