Repository navigation
Expand file tree
/
Copy pathtest_dead.py
More file actions
144 lines (112 loc) · 5.28 KB
/
Copy pathtest_dead.py
File metadata and controls
144 lines (112 loc) · 5.28 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
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2026 Mike Mol
"""Witnesses for `mikemol.pycodemod.dead`: what is dead, what is excused and why, what was unread.
⚑⚑ `ghost` AND `ghost2` ARE THE ARMS: the origin excused a def whenever its quoted name appeared
anywhere in the file — here, only in a docstring and a comment. Both must read DEAD.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from mikemol.pycodemod import dead as dd
from mikemol.pycodemod import sites
if TYPE_CHECKING:
from pathlib import Path
_CORPUS = '''"""Module doc mentioning "ghost"."""
# comment "ghost2"
def ghost():
pass
def ghost2():
pass
def live():
pass
def cb():
pass
def dispatched():
pass
def __init__():
pass
def __private():
pass
def main():
pass
def visit_Call():
pass
def on_():
pass
live()
register(callbacks=[cb])
MODES = {'dispatched': 1}
'''
def _scan(tmp_path: Path, *extra: str) -> sites.Sites:
path = tmp_path / "m.py"
path.write_text(_CORPUS, encoding="utf-8")
return sites.scan([str(path), *extra])
def test_a_prose_mention_does_not_excuse_a_dead_def(tmp_path: Path) -> None:
"""⚑⚑ A name quoted in a docstring or comment is not a dispatch: `ghost`, `ghost2` are dead."""
got = dd.dead(_scan(tmp_path))
assert [d.name for d in got.dead] == ["__private", "ghost", "ghost2", "on_"]
def test_every_exemption_is_reported_with_its_reason(tmp_path: Path) -> None:
"""⚑⚑⚑ An excused def is a visible row naming its rule — a single-quoted dispatch included."""
got = dd.dead(_scan(tmp_path))
assert [(e.name, e.why) for e in got.exempt] == [
("__init__", "dunder — invoked by the runtime"),
("dispatched", "dispatch — named by a string constant"),
("main", "entry point by convention"),
("visit_Call", "libcst/ast visitor — dispatched off the node type"),
]
def test_a_call_or_a_use_as_value_is_a_use(tmp_path: Path) -> None:
"""`live()` is a call and `callbacks=[cb]` a use as a value: neither def is dead or exempt."""
got = dd.dead(_scan(tmp_path))
named = {d.name for d in got.dead} | {e.name for e in got.exempt}
assert {"live", "cb"}.isdisjoint(named)
def test_a_narrowed_scan_is_refused(tmp_path: Path) -> None:
"""⚑⚑ "No caller in this population" over one name reads live code as dead."""
path = tmp_path / "m.py"
path.write_text(_CORPUS, encoding="utf-8")
with pytest.raises(ValueError, match="unnarrowed"):
dd.dead(sites.scan([str(path)], "ghost"))
def test_an_unread_file_is_reported_once(tmp_path: Path) -> None:
"""A file both readers refuse is one skip, not two."""
bad = tmp_path / "bad.py"
bad.write_text("def (:\n", encoding="utf-8")
got = dd.dead(_scan(tmp_path, str(bad)))
assert [(s.path, s.why) for s in got.skipped] == [(str(bad), "unparseable")]
def test_string_constants_exclude_docstrings_and_report_unread(tmp_path: Path) -> None:
"""Code strings are collected; docstrings are prose; an unreadable file is a skip."""
path = tmp_path / "s.py"
path.write_text('"""doc"""\nclass K:\n """kdoc"""\nx = "code"\n', encoding="utf-8")
missing = str(tmp_path / "absent.py")
found, skipped = dd.string_constants([str(path), missing])
assert found == {"code"}
assert [(s.path, s.why) for s in skipped] == [(missing, "unreadable")]
def test_a_framework_prefix_needs_a_name_after_it() -> None:
"""`visit_Call` satisfies the libcst contract; a bare `on_` satisfies nothing."""
assert dd.framework_dispatch("test_x") == "pytest — collected by prefix"
assert dd.framework_dispatch("on_") is None
assert dd.framework_dispatch("helper") is None
_REGISTERED = 'def q_x():\n pass\ndef q_orphan():\n pass\nrun("x")\n'
def test_a_registered_def_is_exempt_by_position_and_an_orphan_stays_dead(tmp_path: Path) -> None:
"""W656: `q_x` is invoked by the literal "x" (position given), so it is exempt, not dead.
`q_orphan` is in no literal: it stays dead, which is the case the prefix must not hide.
"""
path = tmp_path / "m.py"
path.write_text(_REGISTERED, encoding="utf-8")
got = dd.dead(sites.scan([str(path)]), frozenset({(str(path), 1)}))
assert [d.name for d in got.dead] == ["q_orphan"]
assert [(e.name, e.why.split(" — ")[0]) for e in got.exempt] == [("q_x", "registered")]
def test_without_the_registered_operand_a_prefixed_def_is_still_dead(tmp_path: Path) -> None:
"""W656: the default excuses nothing, so `dead` over the same corpus is unchanged."""
path = tmp_path / "m.py"
path.write_text(_REGISTERED, encoding="utf-8")
got = dd.dead(sites.scan([str(path)]))
assert [d.name for d in got.dead] == ["q_orphan", "q_x"]
assert got.exempt == []
def test_a_registration_excuses_only_the_position_it_names(tmp_path: Path) -> None:
"""W656: two files each define `q_x`; excusing one position leaves the other dead."""
first = tmp_path / "a.py"
second = tmp_path / "b.py"
first.write_text(_REGISTERED, encoding="utf-8")
second.write_text(_REGISTERED, encoding="utf-8")
got = dd.dead(sites.scan([str(first), str(second)]), frozenset({(str(first), 1)}))
assert [(d.path, d.name) for d in got.dead if d.name == "q_x"] == [(str(second), "q_x")]
assert [e.path for e in got.exempt] == [str(first)]