Skip to content

test: reach 100% coverage - #289

Merged
Lancetnik merged 2 commits into
mainfrom
feat/coverage-100
Aug 13, 2026
Merged

test: reach 100% coverage#289
Lancetnik merged 2 commits into
mainfrom
feat/coverage-100

Conversation

@Lancetnik

@Lancetnik Lancetnik commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Why the 98% was not what it looked like

[tool.coverage.report].exclude_also contained 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 a def, that means the signature and the body.

'\.\.\.' therefore matched annotations such as tuple[Any, ...] and silently removed the core of the library from measurement:

excluded because of what got dropped
params: tuple[OptionItem, ...], all of CallModel.__init__
*args: tuple[Any, ...], all of CallModel._solve
*args: tuple[Any, ...], all of CallModel.solve
*args: tuple[Any, ...], all of CallModel.asolve

That is also why Provider.merge showed up as uncovered while both of its call sites did not — the call sites were inside solve/asolve.

"from .*" and "import .*" were unanchored too, and swallowed ) from err continuation lines (so e.g. the msgspec raise ValidationError(...) from er statement was excluded).

What changed

Coverage config — anchor the stub-body patterns (^\s*pass$, (^|:)\s*\.\.\.$), replace the unanchored import patterns with if 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-level dependency_provider= on solve/asolve (tests/test_provider.py)
  • ExceptionGroup unwrapping for async field=True custom 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.encode and Serializer.get_aliases defaults, plus a minimal custom serializer end to end (tests/library/test_serializer.py)
  • msgspec use_fastdepends_errors=False and msgspec.field(name=...) aliases (tests/serializers/msgspec/test_serializer.py)
  • pydantic non-class response types and the pre-2.11 model_fields fallback (tests/serializers/pydantic/)
  • inject() with a prebuilt CallModel — the path integrations such as FastStream use (tests/test_inject.py)

The JSON backend branches, without new CI jobsfast_depends.pydantic._compat picks orjson/ujson/stdlib at import time, and pydantic v1 routes dump_json through whichever won (v2 uses pydantic_core.to_json). Rather than a CI run per backend, tests/serializers/pydantic/test_json_backend.py re-executes the real module from disk against stubbed sys.modules entries. 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: drop if SerializerCls is None: immediately after SerializerCls = None; the guard could never be false, so its second branch was unreachable.
  • core/model.py: # pragma: no branch on for ex in exgr.exceptions: — the loop always raises on the first item and never exits normally.

CI — one line: coverage report in coverage-combine runs with --fail-under=100 so this cannot silently regress (plus if: 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.12 no-serializer, py3.10+pydantic-v2, py3.13+pydantic-v2 — all green, combined to 100%:

TOTAL   2846   0   320   0   100%

ruff check, mypy and codespell are unchanged from main (each still reports the same pre-existing findings; none are introduced or fixed here).

🤖 Generated with Claude Code

Lancetnik and others added 2 commits August 13, 2026 00:15
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>
@Lancetnik
Lancetnik merged commit 058298c into main Aug 13, 2026
40 of 42 checks passed
@Lancetnik
Lancetnik deleted the feat/coverage-100 branch August 13, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant