-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest_util.py
More file actions
260 lines (226 loc) · 11.7 KB
/
Copy pathtest_util.py
File metadata and controls
260 lines (226 loc) · 11.7 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
# -*- coding: utf-8 -*-
"""Utilities for building macros."""
from ...syntax import macros, do, local, test, test_raises, fail, the # noqa: F401
from ...test.fixtures import session, testset
from mcpyrate.quotes import macros, q, n, h # noqa: F401, F811
from mcpyrate.metatools import macros, expandrq # noqa: F401, F811
from ...syntax.astcompat import getconstant, Num, Str
from ...syntax.util import (isec, detect_callec,
detect_lambda,
is_decorator, has_tco, has_curry, has_deco,
suggest_decorator_index,
is_lambda_decorator, is_decorated_lambda,
destructure_decorated_lambda, sort_lambda_decorators,
transform_statements, eliminate_ifones)
from ast import Call, Name, Constant, Expr
from ...ec import call_ec, throw # just so hq[] captures them, like in real code
# test data
def ec(): # the function must be defined at top level so h[] can pickle the object
pass # pragma: no cover
def runtests():
with testset("escape continuation (ec) utilities"):
known_ecs = ["ec", "brk", "throw"] # see `fallbacks` in `detect_callec`
test[isec(q[ec(42)], known_ecs)]
test[isec(q[h[ec](42)], known_ecs)]
test[isec(q[throw(42)], known_ecs)]
test[isec(q[h[throw](42)], known_ecs)]
test[not isec(q[myfunc(42)], known_ecs)] # noqa: F821
test["my_fancy_ec" in the[detect_callec(q[call_ec(lambda my_fancy_ec: None)])]]
with q as call_ec_testdata:
@call_ec
def f(my_fancy_ec):
pass
test["my_fancy_ec" in the[detect_callec(call_ec_testdata)]]
with testset("detect_lambda"):
# We expand the `do[]` to generate an implicit lambda that should be ignored by the detector
# (it specifically checks for expanded `do[]` forms).
with expandrq as detect_lambda_testdata:
a = lambda: None # noqa: F841
b = do[local[x << 21], # noqa: F821, F841
lambda y: x * y] # noqa: F821
test[len(detect_lambda(detect_lambda_testdata)) == 2]
with testset("decorator utilities"):
test[is_decorator(q[decorate], "decorate")] # noqa: F821
test[is_decorator(q[decorate_with("flowers")], "decorate_with")] # noqa: F821
with q as has_tco_testdata1:
@trampolined # noqa: F821, just quoted.
def ihavetco():
pass
with q as has_tco_testdata2:
def idonthavetco():
pass
test[has_tco(has_tco_testdata1[0])]
test[not has_tco(has_tco_testdata2[0])]
test[not has_tco(q[lambda: None])]
test[not has_tco(q[decorate(lambda: None)])] # noqa: F821
test[has_tco(q[trampolined(lambda: None)])] # noqa: F821
test[has_tco(q[decorate(trampolined(lambda: None))])] # noqa: F821
test[has_tco(q[trampolined(decorate(lambda: None))])] # noqa: F821
with q as has_curry_testdata1:
@curry # noqa: F821, just quoted.
def ihavecurry():
pass
with q as has_curry_testdata2:
def idonthavecurry():
pass
test[has_curry(has_curry_testdata1[0])]
test[not has_curry(has_curry_testdata2[0])]
test[not has_curry(q[lambda: None])]
test[not has_curry(q[decorate(lambda: None)])] # noqa: F821
test[has_curry(q[curry(lambda: None)])] # noqa: F821
test[has_curry(q[decorate(curry(lambda: None))])] # noqa: F821
test[has_curry(q[curry(decorate(lambda: None))])] # noqa: F821
test[has_deco(["decorate"], q["surprise!"]) is None] # wrong AST type, test not applicable
with q as has_deco_testdata1:
@artdeco # noqa: F821, just quoted.
def ihaveartdeco():
pass
with q as has_deco_testdata2:
def idonthaveartdeco():
pass
test[has_deco(["artdeco"], has_deco_testdata1[0])]
test[not has_deco(["artdeco"], has_deco_testdata2[0])]
test[not has_deco(["artdeco"], q[lambda: None])]
test[not has_deco(["artdeco"], q[postmodern(lambda: None)])] # noqa: F821
test[has_deco(["artdeco"], q[artdeco(lambda: None)])] # noqa: F821
test[has_deco(["artdeco"], q[postmodern(artdeco(lambda: None))])] # noqa: F821
test[has_deco(["artdeco"], q[artdeco(postmodern(lambda: None))])] # noqa: F821
# more than one option, automatically OR'd
test[has_deco(["artdeco", "neoclassical"], has_deco_testdata1[0])]
test[not has_deco(["artdeco", "neoclassical"], has_deco_testdata2[0])]
test[not has_deco(["artdeco", "neoclassical"], q[lambda: None])]
test[not has_deco(["artdeco", "neoclassical"], q[postmodern(lambda: None)])] # noqa: F821
test[has_deco(["artdeco", "neoclassical"], q[artdeco(lambda: None)])] # noqa: F821
test[has_deco(["artdeco", "neoclassical"], q[postmodern(artdeco(lambda: None))])] # noqa: F821
test[has_deco(["artdeco", "neoclassical"], q[artdeco(postmodern(lambda: None))])] # noqa: F821
# find correct insertion index of a known decorator
with q as sdi_testdata1:
# This set of decorators makes no sense, but we want to exercise
# the different branches of the analysis code.
@curry # noqa: F821
@memoize # noqa: F821
@call # noqa: F821
def purespicy(a, b, c):
pass # pragma: no cover
test[suggest_decorator_index("artdeco", sdi_testdata1[0].decorator_list) is None] # unknown decorator
test[suggest_decorator_index("namelambda", sdi_testdata1[0].decorator_list) == 0] # before any of those already specified
test[suggest_decorator_index("trampolined", sdi_testdata1[0].decorator_list) == 2] # in the middle of those already specified
test[suggest_decorator_index("passthrough_lazy_args", sdi_testdata1[0].decorator_list) == 3] # after all of those already specified
with q as sdi_testdata2:
@artdeco # noqa: F821
@neoclassical # noqa: F821
def architectural():
pass
test[suggest_decorator_index("trampolined", sdi_testdata2[0].decorator_list) is None] # known decorator, but only unknown decorators in the decorator_list
with testset("decorated lambda machinery"):
# detect if a Call could be a decorator for a lambda
test[is_lambda_decorator(q[decorate(...)])] # noqa: F821
test[is_lambda_decorator(q[decorate(...)], fname="decorate")] # noqa: F821
test[not is_lambda_decorator(q[decorate(...)], fname="artdeco")] # noqa: F821
test[not is_lambda_decorator(q[ihavenoargs()])] # noqa: F821
test[not is_lambda_decorator(q[ihavemorethanonearg(..., ...)])] # noqa: F821
# detect a chain of Call nodes terminating in a Lambda
test[not is_decorated_lambda(q[lambda: None], "any")]
test[is_decorated_lambda(q[artdeco(lambda: None)], "any")] # noqa: F821
test[not is_decorated_lambda(q[artdeco(lambda: None)], "known")] # noqa: F821
test[is_decorated_lambda(q[curry(lambda: None)], "any")] # noqa: F821
test[is_decorated_lambda(q[curry(lambda: None)], "known")] # noqa: F821
test[is_decorated_lambda(q[memoize(trampolined(curry(lambda: None)))], "known")] # noqa: F821
# mode="known" requires **all** the decorators to be recognized.
test[not is_decorated_lambda(q[memoize(artdeco(curry(lambda: None)))], "known")] # noqa: F821
# extract the "decorator list" of a decorated lambda
# (This returns the original AST nodes, for in-place transformations.)
decos, lam = destructure_decorated_lambda(q[memoize(trampolined(curry(lambda: 42)))]) # noqa: F821
test[len(decos) == 3]
test[all(type(node) is Call and type(node.func) is Name for node in decos)]
test[[node.func.id for node in decos] == ["memoize", "trampolined", "curry"]]
test[type(lam.body) in (Constant, Num)] # Python 3.8+: ast.Constant
test[getconstant(lam.body) == 42] # Python 3.8+: ast.Constant
def test_sort_lambda_decorators(testdata):
sort_lambda_decorators(testdata)
decos, _ = destructure_decorated_lambda(testdata)
# correct ordering according to unpythonic.regutil.decorator_registry
test[[node.func.id for node in decos] == ["curry", "memoize", "trampolined"]]
# input ordering correct, no effect
test_sort_lambda_decorators(q[memoize(trampolined(curry(lambda: 42)))]) # noqa: F821
# input ordering wrong, let the sorter fix it
test_sort_lambda_decorators(q[curry(memoize(trampolined(lambda: 42)))]) # noqa: F821
with testset("statement utilities"):
with q as transform_statements_testdata:
def myfunction(x):
"function body"
try:
"try"
if x:
"if body"
else:
"if else"
except ValueError:
"except"
finally:
"finally"
collected = []
def collectstrings(tree):
if type(tree) is Expr and type(tree.value) in (Constant, Str): # Python 3.8+: ast.Constant
collected.append(getconstant(tree.value))
return [tree]
transform_statements(collectstrings, transform_statements_testdata)
test[set(collected) == {"function body", "try", "if body", "if else", "finally", "except"}]
def ishello(tree):
# Python 3.8+: ast.Constant
return type(tree) is Expr and type(tree.value) in (Constant, Str) and getconstant(tree.value) == "hello"
# numeric
with q as eliminate_ifones_testdata1:
if 1:
"hello"
result = eliminate_ifones(eliminate_ifones_testdata1)
test[len(result) == 1 and ishello(result[0])]
with q as eliminate_ifones_testdata2:
if 0:
"hello"
result = eliminate_ifones(eliminate_ifones_testdata2)
test[len(result) == 0]
# boolean
with q as eliminate_ifones_testdata3:
if True:
"hello"
result = eliminate_ifones(eliminate_ifones_testdata3)
test[len(result) == 1 and ishello(result[0])]
with q as eliminate_ifones_testdata4:
if False:
"hello"
result = eliminate_ifones(eliminate_ifones_testdata4)
test[len(result) == 0]
# leave just one branch, numeric
with q as eliminate_ifones_testdata5:
if 1:
"hello"
else:
"bye"
result = eliminate_ifones(eliminate_ifones_testdata5)
test[len(result) == 1 and ishello(result[0])]
with q as eliminate_ifones_testdata6:
if 0:
"bye"
else:
"hello"
result = eliminate_ifones(eliminate_ifones_testdata6)
test[len(result) == 1 and ishello(result[0])]
# leave just one branch, boolean
with q as eliminate_ifones_testdata7:
if True:
"hello"
else:
"bye"
result = eliminate_ifones(eliminate_ifones_testdata7)
test[len(result) == 1 and ishello(result[0])]
with q as eliminate_ifones_testdata8:
if False:
"bye"
else:
"hello"
result = eliminate_ifones(eliminate_ifones_testdata8)
test[len(result) == 1 and ishello(result[0])]
if __name__ == '__main__': # pragma: no cover
with session(__file__):
runtests()