test: reach 100% coverage - #289
Merged
Merged
Conversation
The reported 98% was measured over a reduced set of lines: the `'\.\.\.'` exclude pattern is unanchored, so it also matched annotations like `tuple[Any, ...]`. Coverage drops the whole statement a matched line belongs to - and for a `def` that means signature *and* body - so `CallModel.__init__`, `_solve`, `solve` and `asolve` were not measured at all. `"from .*"`/`"import .*"` were unanchored too and swallowed `) from err` continuation lines. Anchor those patterns (and drop the ones matching nothing), which puts 2806 statements under measurement instead of 1966, then close the gap: - `Provider.merge` via call-level `dependency_provider=` on solve/asolve - `ExceptionGroup` unwrapping for async `field=True` customs - `evaluate_forwardref` / `eval_type_backport` / `is_backport_fixable_error` - `ValidationError.__str__` - `SerializerProto.encode` and `Serializer.get_aliases` defaults - msgspec `use_fastdepends_errors=False` and `msgspec.field(name=...)` - pydantic non-class response types and the `model_fields` fallback - `inject()` with a prebuilt `CallModel` Coverage stays cumulative across CI jobs. Pydantic v1 encodes through orjson/ujson when installed, so add a `test-json-backends` job for both and make the v1 encode assertions separator-agnostic. `coverage report` now runs with `--fail-under=100`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`fast_depends.pydantic._compat` selects orjson/ujson/stdlib at import time, so those branches used to need a CI run per backend. Re-execute the real module from disk against stubbed `sys.modules` entries instead: coverage keys line data off the file, so the actual branches are measured, and nothing lands in the live module tree. Drops the `test-json-backends` job again. The v1 encode assertions stay separator-agnostic - the three backends differ only in separator whitespace, and a contributor with orjson installed locally would otherwise get spurious failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why the 98% was not what it looked like
[tool.coverage.report].exclude_alsocontained unanchored regexes. Coverage matches them against every line, and when a matched line belongs to a multi-line statement it drops the whole statement — for adef, that means the signature and the body.'\.\.\.'therefore matched annotations such astuple[Any, ...]and silently removed the core of the library from measurement:params: tuple[OptionItem, ...],CallModel.__init__*args: tuple[Any, ...],CallModel._solve*args: tuple[Any, ...],CallModel.solve*args: tuple[Any, ...],CallModel.asolveThat is also why
Provider.mergeshowed up as uncovered while both of its call sites did not — the call sites were insidesolve/asolve."from .*"and"import .*"were unanchored too, and swallowed) from errcontinuation lines (so e.g. the msgspecraise ValidationError(...) from erstatement was excluded).What changed
Coverage config — anchor the stub-body patterns (
^\s*pass$,(^|:)\s*\.\.\.$), replace the unanchored import patterns withif TYPE_CHECKING:, and drop the four patterns that matched nothing in this repo (self.logger,logger\..*,lambda: None,raise ValueError). Statements under measurement go from 1966 → 2846.Tests for the real gap:
Provider.merge— via a call-leveldependency_provider=onsolve/asolve(tests/test_provider.py)ExceptionGroupunwrapping for asyncfield=Truecustom fields (tests/library/test_custom.py)evaluate_forwardref,eval_type_backport,is_backport_fixable_error(tests/test_compat.py)ValidationError.__str__for both keyword and positional incoming options (tests/test_exceptions.py)SerializerProto.encodeandSerializer.get_aliasesdefaults, plus a minimal custom serializer end to end (tests/library/test_serializer.py)use_fastdepends_errors=Falseandmsgspec.field(name=...)aliases (tests/serializers/msgspec/test_serializer.py)model_fieldsfallback (tests/serializers/pydantic/)inject()with a prebuiltCallModel— the path integrations such as FastStream use (tests/test_inject.py)The JSON backend branches, without new CI jobs —
fast_depends.pydantic._compatpicks orjson/ujson/stdlib at import time, and pydantic v1 routesdump_jsonthrough whichever won (v2 usespydantic_core.to_json). Rather than a CI run per backend,tests/serializers/pydantic/test_json_backend.pyre-executes the real module from disk against stubbedsys.modulesentries. Coverage keys line data off the file, so the real branches are measured, and nothing is imported into the live module tree.The v1 encode assertions are now separator-agnostic — the three backends differ only in separator whitespace, so a contributor who happens to have orjson installed locally would otherwise get spurious failures.
Source — two small changes:
use.py: dropif SerializerCls is None:immediately afterSerializerCls = None; the guard could never be false, so its second branch was unreachable.core/model.py:# pragma: no branchonfor ex in exgr.exceptions:— the loop always raises on the first item and never exits normally.CI — one line:
coverage reportincoverage-combineruns with--fail-under=100so this cannot silently regress (plusif: always()so the html report is still uploaded when the gate fails). No new jobs; coverage stays cumulative across the existing ones.Verification
Ran the CI matrix locally —
py3.12+pydantic-v2,py3.12+pydantic-v1,py3.12+msgspec,py3.12no-serializer,py3.10+pydantic-v2,py3.13+pydantic-v2 — all green, combined to 100%:ruff check,mypyandcodespellare unchanged frommain(each still reports the same pre-existing findings; none are introduced or fixed here).🤖 Generated with Claude Code