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
|
"""Deprecation-stub contract harness.
Every deprecated public symbol must fire exactly one :class:`DeprecationWarning`
whose message matches the policy format::
<name> is deprecated since <version>; use <replacement>. See <docs-url>
with ``stacklevel=2`` so the warning points at the caller. Edify 1.0 ships
stub-free, so the registry below is empty; this harness parametrizes over it, so
the moment a stub lands its row here pins the category, message shape, single-fire
behavior, and documentation URL.
"""
import importlib
import re
import warnings
import pytest
_MESSAGE_PATTERN = re.compile(
r"^(?P<name>\S+) is deprecated since (?P<version>\S+); "
r"use (?P<replacement>.+)\. See (?P<url>https://\S+#\S+)$"
)
# One row per deprecated stub: (import_path, symbol_name, call_args).
# Empty in 1.0 (stub-free). Each future deprecation adds its row here.
_DEPRECATED_STUBS: list[tuple[str, str, tuple[object, ...]]] = []
@pytest.mark.parametrize(
("import_path", "symbol_name", "call_args"),
_DEPRECATED_STUBS,
ids=[f"{path}.{name}" for path, name, _ in _DEPRECATED_STUBS],
)
def test_deprecated_stub_fires_one_well_formed_warning(
import_path: str, symbol_name: str, call_args: tuple[object, ...]
) -> None:
module = importlib.import_module(import_path)
stub = getattr(module, symbol_name)
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
stub(*call_args)
deprecation_warnings = [w for w in caught if issubclass(w.category, DeprecationWarning)]
assert len(deprecation_warnings) == 1
message_text = str(deprecation_warnings[0].message)
assert _MESSAGE_PATTERN.match(message_text), (
f"{import_path}.{symbol_name} warning does not match the policy format: {message_text!r}"
)
def test_message_pattern_accepts_a_policy_conformant_message():
conformant = (
"old_name is deprecated since 1.1; use new_name. "
"See https://edify.readthedocs.io/en/latest/upgrading/1.0-to-1.1.html#new-name"
)
assert _MESSAGE_PATTERN.match(conformant)
def test_message_pattern_rejects_a_message_missing_the_docs_url():
non_conformant = "old_name is deprecated since 1.1; use new_name."
assert _MESSAGE_PATTERN.match(non_conformant) is None
def test_message_pattern_rejects_a_url_without_an_anchor():
non_conformant = (
"old_name is deprecated since 1.1; use new_name. "
"See https://edify.readthedocs.io/en/latest/upgrading/1.0-to-1.1.html"
)
assert _MESSAGE_PATTERN.match(non_conformant) is None
def test_registry_is_empty_or_every_row_has_three_fields():
for row in _DEPRECATED_STUBS:
assert len(row) == 3
|