test: replace subprocess servers with in-process threaded servers for coverage tracking#2341
Open
perhapzz wants to merge 3 commits intomodelcontextprotocol:mainfrom
Open
Conversation
perhapzz
added a commit
to perhapzz/python-sdk
that referenced
this pull request
Mar 25, 2026
- Replace multiprocessing.Process with threading.Thread for context_aware_server fixture (same pattern as PR modelcontextprotocol#2341) - Remove 5 pragma: no cover markers from context-aware server code - Remove dead run_server() function and unused imports - Remaining 5 pragmas are on genuinely unreachable defensive code
c3ef81a to
7a3fb21
Compare
522bd42 to
9c0d31f
Compare
Replace multiprocessing.Process with threading.Thread + uvicorn.Server for test server fixtures (basic_server, json_server, resumable_server) so coverage.py can track server-side code in the same process. Changes: - Add _start_server_thread() helper using uvicorn.Server in a daemon thread - Graceful shutdown via server.should_exit instead of proc.kill() - Remove 8 pragma: no cover from test fixtures (no longer needed) - Add 8 new tests covering previously-uncovered branches - Remove dead run_server() function and unused http_client fixture - Convert pragma: no cover to pragma: lax no cover in source files for non-deterministic coverage lines (thread timing dependent) - Add pragma: no branch for partial branch coverage on guard lines
9c0d31f to
f4ea245
Compare
- Add warnings.filterwarnings in _run_server thread to suppress asyncio.iscoroutinefunction deprecation (Python 3.14+) - Add ResourceWarning filter for unclosed sockets during teardown (Windows) - Add pytest filterwarnings for DeprecationWarning and PytestUnhandledThreadExceptionWarning in pyproject.toml
Add pragma annotations for 3 remaining uncovered paths: - pragma: no branch on mcp_session_id checks in _create_error_response and initialization handler (always True in stateful manager) - pragma: lax no cover on ClosedResourceError handler (non-deterministic) Add 5 integration tests for transport validation: - POST with invalid Content-Type (400) - POST/GET/DELETE with mismatched session ID (404) - PUT unsupported method (405) All 74 tests pass with 100% coverage and strict-no-cover clean.
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.
Motivation
Part of #1678
The Streamable HTTP test fixtures launch servers via
multiprocessing.Process. Sincecoverage.pycannot track code in child processes by default, server-side code paths appear uncovered despite being exercised by tests, requiring# pragma: no coverworkarounds in both test and source files.Changes
Test refactoring
Replace
multiprocessing.Processwith in-processthreading.Thread+uvicorn.Serverfor the three main server fixtures (basic_server,json_server,resumable_server).coverage.pytracks all threads in the same process by default, so these code paths are now properly covered._start_server_thread(): launchesuvicorn.Serveron a daemon thread with graceful shutdown (server.should_exit = True)run_server()function and unusedtracebackimport# pragma: no covermarkers from test server codereplay_events_after(non-existent event ID, None-message skipping)slow://resource readlong_running_with_checkpointstoolmulti_close_tool/tool_with_standalone_stream_closewith and without event storehttp_clientfixtureSource file pragma adjustment
Since the test servers now run in-process,
coverage.pynow tracks source code that was previously unreachable. This requires adjusting coverage annotations to satisfy bothcoverage --fail-under=100andstrict-no-cover:# pragma: no coverfrom lines now deterministically covered# pragma: lax no coveron non-deterministicexcepthandlers (e.g. race-condition-dependentBrokenResourceError,ClosedResourceError) — these are sometimes covered depending on thread timing;laxis excluded bycoverage.py(viaexclude_lines) but not flagged bystrict-no-cover# pragma: no branchfor branch partials that are never taken in tests (e.g.mcp_session_idchecks that are always True in the stateful manager architecture)Files modified (annotation changes only, no logic changes):
src/mcp/server/streamable_http.pysrc/mcp/server/transport_security.pysrc/mcp/server/session.pyResults
pyright0 errors,ruff check+ruff formatcleanstrict-no-cover: ✅ no wrongly marked lines