forked from modelcontextprotocol/python-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_coverage.py
More file actions
360 lines (283 loc) · 16.1 KB
/
Copy pathtest_coverage.py
File metadata and controls
360 lines (283 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
"""Enforces the contract between the requirements manifest and the test suite.
The contract runs in both directions: every non-deferred entry in :data:`REQUIREMENTS` must be
exercised by at least one test, and every test in the suite must carry at least one
`@requirement(...)` mark referencing a manifest entry. Deferral reasons that point at coverage
elsewhere in the repo must point at paths that exist. Test modules are imported directly
(rather than relying on pytest collection) so the check holds even when only this file is run.
"""
import importlib
import re
from pathlib import Path
from types import ModuleType
from typing import cast
import pytest
from mcp_types import LATEST_PROTOCOL_VERSION
from mcp_types.version import KNOWN_PROTOCOL_VERSIONS
from tests.interaction._requirements import (
CONNECTABLE_TRANSPORTS,
REQUIREMENTS,
SPEC_2026_BASE_URL,
SPEC_BASE_URL,
SPEC_VERSIONS,
ArmExclusion,
KnownFailure,
Requirement,
SpecVersion,
Transport,
cell_id,
compute_cells,
covered_by,
requirement,
)
from tests.interaction.conftest import _FACTORIES
_SUITE_ROOT = Path(__file__).parent
_REPO_ROOT = _SUITE_ROOT.parent.parent
# Repo paths cited inside deferral reasons ("Covered by tests/... ").
_CITED_PATH = re.compile(r"(?:tests|src)/[\w./-]*\w")
# Tests that exercise the suite's own helpers rather than an interaction-model behaviour.
# Anything listed here is exempt from the every-test-has-a-requirement check.
_HARNESS_SELF_TESTS = {
"tests.interaction.lowlevel.test_wire.test_recording_read_stream_ends_iteration_when_the_sender_closes",
"tests.interaction.transports.test_bridge.test_response_chunks_arrive_as_the_application_sends_them",
"tests.interaction.transports.test_bridge.test_closing_the_response_delivers_a_disconnect_to_the_application",
"tests.interaction.transports.test_bridge.test_an_application_failure_before_the_response_starts_fails_the_request",
"tests.interaction.transports.test_bridge.test_disabling_cancel_on_close_lets_the_application_finish_after_disconnect",
"tests.interaction.auth.test_flow.test_shimmed_app_serves_overrides_404s_and_otherwise_forwards_to_the_wrapped_app",
}
def _import_all_test_modules() -> list[ModuleType]:
"""Import every other test module in the suite so their `@requirement` decorators register."""
modules: list[ModuleType] = []
for path in sorted(_SUITE_ROOT.rglob("test_*.py")):
relative = path.relative_to(_SUITE_ROOT).with_suffix("")
name = f"{__package__}.{'.'.join(relative.parts)}"
if name != __name__:
modules.append(importlib.import_module(name))
return modules
def test_every_requirement_is_exercised() -> None:
"""Each non-deferred requirement is covered by at least one test (deferred ones by none)."""
_import_all_test_modules()
uncovered = [
requirement_id
for requirement_id, spec in sorted(REQUIREMENTS.items())
if spec.deferred is None and not covered_by(requirement_id)
]
assert not uncovered, f"Requirements with no test and no deferred reason: {uncovered}"
stale_deferrals = [
requirement_id
for requirement_id, spec in sorted(REQUIREMENTS.items())
if spec.deferred is not None and covered_by(requirement_id)
]
assert not stale_deferrals, f"Deferred requirements that now have tests (remove deferred): {stale_deferrals}"
def test_every_test_exercises_a_requirement() -> None:
"""Each test in the suite carries at least one `@requirement` mark (harness self-tests excepted)."""
all_tests = {
f"{module.__name__}.{name}"
for module in _import_all_test_modules()
for name in vars(module)
if name.startswith("test_")
}
linked_tests = {test_name for requirement_id in REQUIREMENTS for test_name in covered_by(requirement_id)}
unlinked = sorted(all_tests - linked_tests - _HARNESS_SELF_TESTS)
assert not unlinked, f"Tests with no @requirement mark: {unlinked}"
stale_exemptions = sorted(_HARNESS_SELF_TESTS - all_tests)
assert not stale_exemptions, f"Harness self-test exemptions that no longer exist: {stale_exemptions}"
def test_deferral_reasons_cite_existing_paths() -> None:
"""Every repo path named in a deferral reason exists, so coverage pointers cannot rot."""
missing = sorted(
f"{requirement_id}: {cited}"
for requirement_id, spec in REQUIREMENTS.items()
if spec.deferred is not None
for cited in _CITED_PATH.findall(spec.deferred)
if not (_REPO_ROOT / cited).exists()
)
assert not missing, f"Deferral reasons citing paths that do not exist: {missing}"
def test_spec_versions_are_known_and_include_latest() -> None:
"""Every active spec version is one the SDK knows about, and the SDK's latest is on the active axis."""
assert set(SPEC_VERSIONS) <= set(KNOWN_PROTOCOL_VERSIONS)
assert LATEST_PROTOCOL_VERSION in SPEC_VERSIONS
def test_spec_base_urls_are_pinned_to_their_revision() -> None:
"""SPEC_BASE_URL constants are pinned literals, so growing SPEC_VERSIONS cannot repoint existing source links."""
assert SPEC_BASE_URL == "https://modelcontextprotocol.io/specification/2025-11-25"
assert SPEC_2026_BASE_URL == "https://modelcontextprotocol.io/specification/2026-07-28"
def test_connectable_transports_match_connect_factories() -> None:
"""CONNECTABLE_TRANSPORTS and the conftest factory map name exactly the same transports."""
assert set(CONNECTABLE_TRANSPORTS) == set(_FACTORIES)
def test_supersession_links_are_symmetric_and_versioned() -> None:
"""``supersedes``/``superseded_by`` reference real entries, agree in both directions, and carry version bounds."""
broken = [
f"{req_id} -> {target}"
for req_id, req in REQUIREMENTS.items()
for target in req.supersedes
if target not in REQUIREMENTS or REQUIREMENTS[target].superseded_by != req_id or req.added_in is None
] + [
f"{req_id} <- {req.superseded_by}"
for req_id, req in REQUIREMENTS.items()
if req.superseded_by is not None
if req.superseded_by not in REQUIREMENTS
or req_id not in REQUIREMENTS[req.superseded_by].supersedes
or req.removed_in is None
]
assert not broken, f"Broken supersession links (forward '->' or back '<-'): {broken}"
def test_removed_entry_has_disposition() -> None:
"""Every retired requirement carries either a forward link or a prose note explaining the retirement."""
undisposed = [
req_id
for req_id, req in REQUIREMENTS.items()
if req.removed_in is not None and req.superseded_by is None and req.note is None
]
assert not undisposed, f"Requirements with removed_in but no superseded_by or note: {undisposed}"
def test_transport_restriction_has_note() -> None:
"""Every transport-restricted requirement carries a note explaining why it is transport-specific."""
missing = [req_id for req_id, req in REQUIREMENTS.items() if req.transports is not None and req.note is None]
assert not missing, f"Requirements with transports= but no note: {missing}"
def test_every_arm_exclusion_targets_a_reachable_cell() -> None:
"""Every arm exclusion names a connectable transport (or wildcards).
spec_version is type-checked against the SpecVersion Literal and may reference a version not yet
on the active SPEC_VERSIONS axis, so pre-staged exclusions for an upcoming revision are permitted.
"""
unreachable = [
f"{req_id}: {exclusion}"
for req_id, req in REQUIREMENTS.items()
for exclusion in req.arm_exclusions
if exclusion.transport is not None and exclusion.transport not in CONNECTABLE_TRANSPORTS
]
assert not unreachable, f"Arm exclusions targeting unreachable cells: {unreachable}"
def test_every_known_failure_targets_a_reachable_cell() -> None:
"""Every known failure names a connectable transport (or wildcards).
spec_version is type-checked against the SpecVersion Literal and may reference a version not yet
on the active SPEC_VERSIONS axis, so pre-staged exclusions for an upcoming revision are permitted.
"""
unreachable = [
f"{req_id}: {failure}"
for req_id, req in REQUIREMENTS.items()
for failure in req.known_failures
if failure.transport is not None and failure.transport not in CONNECTABLE_TRANSPORTS
]
assert not unreachable, f"Known failures targeting unreachable cells: {unreachable}"
def test_unknown_requirement_id_is_rejected() -> None:
"""Marking a test with an ID that is not in the manifest fails at decoration time."""
with pytest.raises(KeyError, match="Unknown requirement id 'tools:call:does-not-exist'"):
requirement("tools:call:does-not-exist")
def test_invalid_requirement_source_is_rejected() -> None:
"""A requirement whose source is not a spec URL, 'sdk', or an issue reference fails at construction."""
with pytest.raises(ValueError, match="source must be a specification URL"):
Requirement(source="https://example.com/not-the-spec", behavior="Never constructed.")
def test_arm_exclusion_with_unknown_spec_version_is_rejected() -> None:
"""An arm exclusion naming a spec version outside KNOWN_PROTOCOL_VERSIONS fails at construction."""
with pytest.raises(ValueError, match="is not in KNOWN_PROTOCOL_VERSIONS"):
ArmExclusion(reason="requires-session", spec_version=cast("SpecVersion", "2099-01-01"))
def test_known_failure_with_empty_note_is_rejected() -> None:
"""A known failure with a blank note fails at construction."""
with pytest.raises(ValueError, match="note must be non-empty"):
KnownFailure(note=" ")
def test_known_failure_with_unknown_spec_version_is_rejected() -> None:
"""A known failure naming a spec version outside KNOWN_PROTOCOL_VERSIONS fails at construction."""
with pytest.raises(ValueError, match="is not in KNOWN_PROTOCOL_VERSIONS"):
KnownFailure(note="x", spec_version=cast("SpecVersion", "2099-01-01"))
def test_known_failure_with_malformed_issue_is_rejected() -> None:
"""A known failure whose issue reference is neither '#<n>' nor a GitHub URL fails at construction."""
with pytest.raises(ValueError, match="must be '#<n>' or a GitHub URL"):
KnownFailure(note="x", issue="not-a-link")
def test_requirement_with_unknown_added_in_is_rejected() -> None:
"""A requirement whose added_in is outside KNOWN_PROTOCOL_VERSIONS fails at construction."""
with pytest.raises(ValueError, match="added_in .* is not in KNOWN_PROTOCOL_VERSIONS"):
Requirement(source="sdk", behavior="x", added_in=cast("SpecVersion", "2099-01-01"))
def test_requirement_with_unknown_removed_in_is_rejected() -> None:
"""A requirement whose removed_in is outside KNOWN_PROTOCOL_VERSIONS fails at construction."""
with pytest.raises(ValueError, match="removed_in .* is not in KNOWN_PROTOCOL_VERSIONS"):
Requirement(source="sdk", behavior="x", removed_in=cast("SpecVersion", "2099-01-01"))
def test_requirement_with_empty_version_range_is_rejected() -> None:
"""A requirement whose added_in is not strictly earlier than its removed_in fails at construction."""
with pytest.raises(ValueError, match="must be earlier than"):
Requirement(source="sdk", behavior="x", added_in="2025-11-25", removed_in="2025-11-25")
def _req(
*,
added_in: SpecVersion | None = None,
removed_in: SpecVersion | None = None,
transports: tuple[Transport, ...] | None = None,
arm_exclusions: tuple[ArmExclusion, ...] = (),
known_failures: tuple[KnownFailure, ...] = (),
) -> Requirement:
"""Build a synthetic Requirement for compute_cells() unit tests."""
return Requirement(
source="sdk",
behavior="x",
added_in=added_in,
removed_in=removed_in,
transports=transports,
arm_exclusions=arm_exclusions,
known_failures=known_failures,
)
def test_compute_cells_with_no_requirements_yields_full_grid() -> None:
"""With a single-version axis, an empty requirement list yields one cell per connectable transport."""
cells = compute_cells([], spec_versions=("2025-11-25",))
assert [c.id for c in cells] == ["in-memory", "sse", "streamable-http", "streamable-http-stateless"]
assert [c.values for c in cells] == [
(("in-memory", "2025-11-25"),),
(("sse", "2025-11-25"),),
(("streamable-http", "2025-11-25"),),
(("streamable-http-stateless", "2025-11-25"),),
]
def test_compute_cells_intersects_stacked_version_ranges() -> None:
"""Stacked requirements intersect their [added_in, removed_in) windows: a cell survives only if all admit it."""
cells = compute_cells(
[_req(removed_in="2026-07-28"), _req(added_in="2025-11-25")],
spec_versions=("2025-11-25", "2026-07-28"),
)
assert [c.id for c in cells] == [
"in-memory-2025-11-25",
"sse-2025-11-25",
"streamable-http-2025-11-25",
"streamable-http-stateless-2025-11-25",
]
def test_compute_cells_drops_era_locked_transport_outside_its_versions() -> None:
"""A transport listed in TRANSPORT_SPEC_VERSIONS only appears for the spec versions it serves."""
cells = compute_cells([], spec_versions=("2025-11-25", "2026-07-28"))
assert [c.id for c in cells] == [
"in-memory-2025-11-25",
"sse-2025-11-25",
"streamable-http-2025-11-25",
"streamable-http-stateless-2025-11-25",
"in-memory-2026-07-28",
"streamable-http-2026-07-28",
]
def test_compute_cells_honours_arm_exclusion_from_any_stacked_requirement() -> None:
"""An arm exclusion on any stacked requirement drops the matching cell even when other requirements have none."""
cells = compute_cells(
[_req(), _req(arm_exclusions=(ArmExclusion(reason="requires-session", transport="sse"),))],
spec_versions=("2025-11-25",),
)
assert [c.id for c in cells] == ["in-memory", "streamable-http", "streamable-http-stateless"]
def test_compute_cells_wildcard_arm_exclusion_drops_every_cell() -> None:
"""An arm exclusion with both transport and spec_version unset matches every cell, leaving none."""
cells = compute_cells([_req(arm_exclusions=(ArmExclusion(reason="requires-session"),))])
assert cells == []
def test_compute_cells_marks_known_failure_as_strict_xfail() -> None:
"""A known failure attaches a strict xfail mark to exactly the matching cell and leaves others unmarked."""
cells = compute_cells(
[_req(known_failures=(KnownFailure(note="broken on sse", transport="sse"),))],
spec_versions=("2025-11-25",),
)
by_id = {c.id: c for c in cells}
assert set(by_id) == {"in-memory", "sse", "streamable-http", "streamable-http-stateless"}
assert by_id["sse"].marks[0].name == "xfail"
assert by_id["sse"].marks[0].kwargs == {"reason": "broken on sse", "strict": True}
assert by_id["in-memory"].marks == ()
assert by_id["streamable-http"].marks == ()
assert by_id["streamable-http-stateless"].marks == ()
def test_compute_cells_wildcard_known_failure_marks_every_cell() -> None:
"""A known failure with both transport and spec_version unset marks every emitted cell as strict xfail."""
cells = compute_cells([_req(known_failures=(KnownFailure(note="all broken"),))], spec_versions=("2025-11-25",))
assert len(cells) == 4
assert all(c.marks[0].name == "xfail" for c in cells)
assert all(c.marks[0].kwargs == {"reason": "all broken", "strict": True} for c in cells)
def test_compute_cells_ignores_transports_field() -> None:
"""Requirement.transports is descriptive metadata only and does not filter the cell grid."""
cells = compute_cells([_req(transports=("stdio",))], spec_versions=("2025-11-25",))
assert [c.id for c in cells] == list(CONNECTABLE_TRANSPORTS)
def test_cell_id_omits_version_when_single_spec_version() -> None:
"""With a single-version axis the cell id is just the transport name."""
assert cell_id("sse", "2025-11-25", spec_versions=("2025-11-25",)) == "sse"
def test_cell_id_appends_version_when_multiple_spec_versions() -> None:
"""With more than one active spec version the cell id gains a -<version> suffix."""
assert cell_id("sse", "2025-11-25", spec_versions=("2025-11-25", "2026-07-28")) == "sse-2025-11-25"