-
Notifications
You must be signed in to change notification settings - Fork 1
/
test_conventions.py
137 lines (91 loc) · 3.58 KB
/
test_conventions.py
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
from pathlib import Path
from unittest.mock import Mock
from boltons import iterutils
from pyastgrep.api import Match, search_python_files
SRC_ROOT = Path(__file__).parent.parent.parent.resolve()
def assert_expected_pyastgrep_matches(xpath_expr: str, *, expected_count: int, message: str):
"""
Asserts that the pyastgrep XPath expression matches only `expected_count` times,
each of which must be marked with `pyastgrep_exception`
`message` is a message to be printed on failure.
"""
xpath_expr = xpath_expr.strip()
matches: list[Match] = [item for item in search_python_files([SRC_ROOT], xpath_expr) if isinstance(item, Match)]
expected_matches, other_matches = iterutils.partition(
matches, key=lambda match: "pyastgrep: expected" in match.matching_line
)
if len(expected_matches) < expected_count:
assert False, f"Expected {expected_count} matches but found {len(expected_matches)} for {xpath_expr}"
assert not other_matches, (
message
+ "\n Failing examples:\n"
+ "\n".join(
f" {match.path}:{match.position.lineno}:{match.position.col_offset}:{match.matching_line}"
for match in other_matches
)
)
def test_inclusion_tag_names():
"""
Check that all @inclusion_tag usages have a function name matching the file name.
"""
assert_expected_pyastgrep_matches(
"""
//FunctionDef[
decorator_list/Call/func/Attribute[@attr="inclusion_tag"] and not(
contains(decorator_list/Call/args/Constant/@value,
concat("/", @name, ".html")) or
contains(decorator_list/Call/keywords/keyword[@arg="filename"]/value/Constant/@value,
concat("/", @name, ".html"))
)
]
""",
message="The @inclusion_tag function name should match the template file name",
expected_count=3,
)
# Examples of what test_inclusion_tag_names is looking for:
register = Mock()
@register.inclusion_tag(filename="something/not_bad.html", takes_context=True)
def bad(context): # pyastgrep: expected
pass
@register.inclusion_tag(filename="something/not_bad2.html")
def bad2(): # pyastgrep: expected
pass
# positional arg
@register.inclusion_tag("something/not_bad3.html")
def bad3(): # pyastgrep: expected
pass
# Good examples that don't need `pyastgrep: expected`, for debugging:
@register.inclusion_tag(filename="something/good.html")
def good():
pass
@register.inclusion_tag("something/good2.html")
def good2():
pass
def test_boolean_arguments_are_keyword_only():
"""
Check that any function arguments with type hint of `bool` should be keyword argument only.
This is because in calls like `do_thing(123, True)`, the `True` argument is almost always
difficult to decipher, while `do_thing(123, dry_run=True)` is much better.
"""
assert_expected_pyastgrep_matches(
"""
.//FunctionDef/args/arguments/args/arg/annotation/Name[@id="bool"]
""",
message="Function arguments with type `bool` should be keyword-only",
expected_count=3,
)
# Examples:
def good_boolean_arg(*, foo: bool):
pass
def good_boolean_arg_2(*, foo: bool = True):
pass
def good_boolean_arg_3(*, x: int, foo: bool = True):
pass
def good_boolean_arg_4(x: int, *, foo: bool = True):
pass
def bad_boolean_arg(foo: bool): # pyastgrep: expected
pass
def bad_boolean_arg_2(foo: bool = True): # pyastgrep: expected
pass
def bad_boolean_arg_3(x: int, foo: bool = True): # pyastgrep: expected
pass