test(runtimes): port-contract suite for new adapters (#114) - #219
test(runtimes): port-contract suite for new adapters (#114)#219Archita-kale wants to merge 2 commits into
Conversation
- Split contract suite into universal/lifecycle/server-bound/planned groups - Add registry backend coverage assertion (fails if a new adapter is registered without contract test coverage) - Add CachePort.path() to FakeCache and cover it in the contract suite - Add DownloaderPort structural-conformance tests - Update docs/contributing/testing.md with the new suite structure
3348643 to
21de1e7
Compare
There was a problem hiding this comment.
Nice restructuring — splitting the suite by behavioral category (universal / lifecycle / server-bound / planned) is a real improvement over one flat parametrization, and test_registry_backend_coverage is a good guardrail. A few things worth fixing before merge:
-
test_downloader_pull_signatureis a tautology and can never fail (tests/unit/test_port_contract.py):try: downloader_impl.pull(ref) except Exception as exc: # noqa: BLE001 - any typed/domain error is fine assert isinstance(exc, Exception)
except Exception as exconly catches things that are alreadyExceptioninstances, soisinstance(exc, Exception)is true by construction — this branch can never fail, and the "success" path (no exception) is also unchecked. As written this test provides no signal at all; it'll pass even ifpull()starts raisingBaseExceptionsubclasses that aren'tException, returns garbage, or a future adapter silently no-ops. If the intent is "pull() either returns without crashing the process, or raises a well-formed error," consider asserting something concrete on the exception (e.g. it's one of the domain error types, or has a non-empty message), or drop the try/except and just assert on the return value / expected exception type per-adapter. -
Minor:
tests/unit/test_port_contract.pyis missing a trailing newline at EOF (same nit as #220'stest_platform.py— might be worth a.editorconfig/pre-commit rule to catch this project-wide). -
Minor: the
FakeRuntimelifecycle factory does a somewhat awkward dynamic import —"FakeRuntime": lambda: __import__("tests.conftest", fromlist=["FakeRuntime"]).FakeRuntime(),
— when
tests.conftestis already imported directly elsewhere in the file (from tests.conftest import FakeRuntimeinside_all_runtime_implementations). Could just importFakeRuntimeonce at module level and reference it directly; simpler and avoids the__import__indirection. -
Minor style: the
CachePort.path()addition toFakeCacheintests/conftest.pyintroduces a blank line with trailing whitespace betweenstatus()andpath(). Not currently flagged by the ruff config (Wisn't inselect), but worth a quick cleanup pass.
None of these are blocking for the overall direction, but #1 should be tightened since it's presented as real DownloaderPort contract coverage and currently isn't.
- test_downloader_pull_signature now asserts pull() raises DownloadError with a non-empty message, instead of a vacuous except/isinstance check that could never fail - Replace __import__ indirection for FakeRuntime with a module-level import - Confirmed clean EOF newline and no trailing whitespace in conftest.py
|
Thanks for the detailed review — fixed all four points in 785dce0:
python Verified both implementations actually raise DownloadError with a non-empty message in this environment (HttpDownloader.pull() is intentionally unsupported and always raises it; OllamaPullDownloader.pull() delegates to the real Ollama runtime, which isn't running here, so it wraps the resulting RuntimeUnavailableError as DownloadError). I also sanity-checked the new test actually catches a broken implementation — a fake downloader that silently returns None on pull() correctly fails the test now, which it didn't before.
Re-ran the full gate after the fix: pytest tests/unit -k contract -v — 96 passed, 485 deselected |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Graphify review — findings
Restructures the port-contract suite into capability-scoped groups—universal RuntimePort properties across all seven adapters, in-process lifecycle tests for FakeRuntime/OllamaRuntime/LMStudioRuntime via fake clients, server-bound checks that LlamaCppRuntime fails informatively on pull/remove, and planned adapters (Jan/Gpt4All/Vllm) that must raise RuntimeUnavailableError. Adds test_registry_backend_coverage so any backend registered in RuntimeRegistry without an entry in _all_runtime_implementations() fails loudly instead of shipping untested. Gives FakeCache a path() returning /fake/cache and updates the testing docs to match the new structure.
Worth a look
- Unit contract test calls live Ollama pull path —
tests/unit/test_port_contract.py:477· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 146 functions depend on the 123 functions this change touches.
Health — this change adds coupling hotspots:
- new:
FakeRuntime— 23 callers, 9 callees - new:
FakeRegistry— 16 callers, 7 callees - new:
_manager()— 10 callers, 3 callees - new:
_all_runtime_implementations()— 2 callers, 7 callees
Verification — 146 functions in the blast radius were not formally verified this run (proofs are advisory here).
Health delta baseline: last indexed commit 50084a7 (diverged from this PR's base — delta is approximate).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 146 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).
|
|
||
| def _runtime_implementations() -> List[RuntimePort]: | ||
| from tests.conftest import FakeRuntime | ||
| def _all_runtime_implementations() -> List[RuntimePort]: |
There was a problem hiding this comment.
_all_runtime_implementations()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
There was a problem hiding this comment.
Thanks for the review! It flagged the coupling in _all_runtime_implementations(), but this is intentional since the helper is meant to collect all runtime implementations for the contract tests. I also ran pytest tests/unit/test_port_contract.py locally and all 96 tests passed. So I’ve left this unchanged for now, unless you’d prefer a different approach.
Code Review SuggestionsThe overall structure — universal / lifecycle / server-bound / planned tiers plus the registry-coverage sentinel — is the right design for this kind of contract suite. A few concrete issues to address: Critical1. def test_llamacpp_remove_raises_when_available() -> None:
runtime = _fake_llamacpp_runtime(model_id="model.gguf")
with pytest.raises(DownloadError):
runtime.remove(ModelRef.parse("model.gguf"))
Fix: use a ref that clearly has 2. The Fix: defer instantiation to fixture execution by using a factory approach: _ALL_RUNTIME_FACTORIES: dict[str, Callable[[], RuntimePort]] = {
"FakeRuntime": FakeRuntime,
"OllamaRuntime": OllamaRuntime,
"LMStudioRuntime": LMStudioRuntime,
"LlamaCppRuntime": LlamaCppRuntime,
"JanRuntime": JanRuntime,
"Gpt4AllRuntime": Gpt4AllRuntime,
"VllmRuntime": VllmRuntime,
}
@pytest.fixture(params=list(_ALL_RUNTIME_FACTORIES), ids=list(_ALL_RUNTIME_FACTORIES))
def any_runtime(request: pytest.FixtureRequest) -> RuntimePort:
return _ALL_RUNTIME_FACTORIES[request.param]()Update Important3. Private-attribute patching couples the fake runtimes to implementation internals runtime._client = _FakeOllamaClient() # type: ignore[attr-defined]
runtime._availability = True # type: ignore[attr-defined]
runtime._ensure_http_client = lambda: ... # type: ignore[assignment]The # Bypass HTTP probe — OllamaRuntime checks availability lazily on first use.
runtime._availability = True4. Planned-runtime tests don't cover The planned-runtime tier tests def test_planned_runtime_get_client_raises(planned_runtime: RuntimePort) -> None:
with pytest.raises(RuntimeUnavailableError):
planned_runtime.get_model_client(ModelRef.parse("anything"))5.
Two cleaner options:
The docstring on Minor6. def path(self) -> str:
return "/fake/cache"On Windows this string is a valid 7. PR title has 3 leading spaces The PR title reads Strengths
|
Closes #114
Summary
Adds comprehensive port-contract tests for all runtime adapters, split by
behavioral category so each adapter is tested the way it actually behaves
rather than forced into one generic parametrization:
identity, status(), default_tag_for(), category/capability lookups).
Runs against all 7 adapters.
with fake/mocked clients (no real daemon required).
verifying it fails informatively rather than silently.
consistent RuntimeUnavailableError behavior.
RuntimeRegistry has contract coverage, so a future adapter can't be
added without tests.
Also added
CachePort.path()toFakeCache(previously missing) andDownloaderPortstructural-conformance tests.No runtime behavior in
src/modeldock/is modified — tests and docs only.Testing
pytest tests/unit -k contract -v— 96 passed, 485 deselectedpytest tests/unit— 581 passedruff check src tests— All checks passedmypy src— Success: no issues found in 79 source filesbandit -c pyproject.toml -r src— No issues identified