mutmut 3.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mutmut/__init__.py +317 -0
- mutmut/__main__.py +1695 -0
- mutmut/result_browser_layout.tcss +34 -0
- mutmut-3.2.3.dist-info/AUTHORS.rst +23 -0
- mutmut-3.2.3.dist-info/LICENSE +12 -0
- mutmut-3.2.3.dist-info/METADATA +215 -0
- mutmut-3.2.3.dist-info/RECORD +13 -0
- mutmut-3.2.3.dist-info/WHEEL +5 -0
- mutmut-3.2.3.dist-info/entry_points.txt +2 -0
- mutmut-3.2.3.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_mutation.py +409 -0
- tests/test_mutmut3.py +83 -0
mutmut/__init__.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
3
|
+
from parso.python.tree import (
|
|
4
|
+
Keyword,
|
|
5
|
+
Name,
|
|
6
|
+
Number,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__version__ = '3.2.3'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# We have a global whitelist for constants of the pattern __all__, __version__, etc
|
|
13
|
+
|
|
14
|
+
dunder_whitelist = [
|
|
15
|
+
'all',
|
|
16
|
+
'version',
|
|
17
|
+
'title',
|
|
18
|
+
'package_name',
|
|
19
|
+
'author',
|
|
20
|
+
'description',
|
|
21
|
+
'email',
|
|
22
|
+
'version',
|
|
23
|
+
'license',
|
|
24
|
+
'copyright',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
duration_by_test = {}
|
|
28
|
+
stats_time = None
|
|
29
|
+
config = None
|
|
30
|
+
|
|
31
|
+
_stats = set()
|
|
32
|
+
tests_by_mangled_function_name = defaultdict(set)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SkipException(Exception):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
UNTESTED = 'untested'
|
|
40
|
+
OK_KILLED = 'ok_killed'
|
|
41
|
+
OK_SUSPICIOUS = 'ok_suspicious'
|
|
42
|
+
BAD_TIMEOUT = 'bad_timeout'
|
|
43
|
+
BAD_SURVIVED = 'bad_survived'
|
|
44
|
+
SKIPPED = 'skipped'
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
mutant_statuses = [
|
|
48
|
+
UNTESTED,
|
|
49
|
+
OK_KILLED,
|
|
50
|
+
OK_SUSPICIOUS,
|
|
51
|
+
BAD_TIMEOUT,
|
|
52
|
+
BAD_SURVIVED,
|
|
53
|
+
SKIPPED,
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def number_mutation(value, **_):
|
|
58
|
+
suffix = ''
|
|
59
|
+
if value.upper().endswith('L'): # pragma: no cover (python 2 specific)
|
|
60
|
+
suffix = value[-1]
|
|
61
|
+
value = value[:-1]
|
|
62
|
+
|
|
63
|
+
if value.upper().endswith('J'):
|
|
64
|
+
suffix = value[-1]
|
|
65
|
+
value = value[:-1]
|
|
66
|
+
|
|
67
|
+
if value.startswith('0o'):
|
|
68
|
+
base = 8
|
|
69
|
+
value = value[2:]
|
|
70
|
+
elif value.startswith('0x'):
|
|
71
|
+
base = 16
|
|
72
|
+
value = value[2:]
|
|
73
|
+
elif value.startswith('0b'):
|
|
74
|
+
base = 2
|
|
75
|
+
value = value[2:]
|
|
76
|
+
elif value.startswith('0') and len(value) > 1 and value[1] != '.': # pragma: no cover (python 2 specific)
|
|
77
|
+
base = 8
|
|
78
|
+
value = value[1:]
|
|
79
|
+
else:
|
|
80
|
+
base = 10
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
parsed = int(value, base=base)
|
|
84
|
+
except ValueError:
|
|
85
|
+
# Since it wasn't an int, it must be a float
|
|
86
|
+
parsed = float(value)
|
|
87
|
+
|
|
88
|
+
result = repr(parsed + 1)
|
|
89
|
+
if not result.endswith(suffix):
|
|
90
|
+
result += suffix
|
|
91
|
+
yield dict(value=result)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def string_mutation(value, context, **_):
|
|
95
|
+
if context.is_inside_annassign():
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
prefix = value[:min([x for x in [value.find('"'), value.find("'")] if x != -1])]
|
|
99
|
+
value = value[len(prefix):]
|
|
100
|
+
|
|
101
|
+
if value.startswith('"""') or value.startswith("'''"):
|
|
102
|
+
# We assume here that triple-quoted stuff are docs or other things
|
|
103
|
+
# that mutation is meaningless for
|
|
104
|
+
return prefix + value
|
|
105
|
+
yield dict(value=prefix + value[0] + 'XX' + value[1:-1] + 'XX' + value[-1])
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def partition_node_list(nodes, value):
|
|
109
|
+
for i, n in enumerate(nodes):
|
|
110
|
+
if hasattr(n, 'value') and n.value == value:
|
|
111
|
+
return nodes[:i], n, nodes[i + 1:]
|
|
112
|
+
|
|
113
|
+
assert False, "didn't find node to split on"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def lambda_mutation(children, **_):
|
|
117
|
+
pre, op, post = partition_node_list(children, value=':')
|
|
118
|
+
|
|
119
|
+
if len(post) == 1 and getattr(post[0], 'value', None) == 'None':
|
|
120
|
+
yield dict(children=pre + [op] + [Number(value=' 0', start_pos=post[0].start_pos)])
|
|
121
|
+
else:
|
|
122
|
+
yield dict(children=pre + [op] + [Keyword(value=' None', start_pos=post[0].start_pos)])
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
NEWLINE = {'formatting': [], 'indent': '', 'type': 'endl', 'value': ''}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def argument_mutation(children, context, **_):
|
|
129
|
+
if len(context.stack) >= 3 and context.stack[-3].type in ('power', 'atom_expr'):
|
|
130
|
+
stack_pos_of_power_node = -3
|
|
131
|
+
elif len(context.stack) >= 4 and context.stack[-4].type in ('power', 'atom_expr'):
|
|
132
|
+
stack_pos_of_power_node = -4
|
|
133
|
+
else:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
power_node = context.stack[stack_pos_of_power_node]
|
|
137
|
+
|
|
138
|
+
# `dict(a=1)` -> `dict(aXX=1)`
|
|
139
|
+
if power_node.children[0].type == 'name' and power_node.children[0].value in context.dict_synonyms:
|
|
140
|
+
c = children[0]
|
|
141
|
+
if c.type == 'name':
|
|
142
|
+
children = children[:]
|
|
143
|
+
children[0] = Name(c.value + 'XX', start_pos=c.start_pos, prefix=c.prefix)
|
|
144
|
+
yield dict(children=children)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def arglist_mutation(children, node, **_):
|
|
148
|
+
for i, child_node in enumerate(children):
|
|
149
|
+
if child_node.type in ('name', 'argument'):
|
|
150
|
+
offset = 1
|
|
151
|
+
if len(children) > i+1:
|
|
152
|
+
if children[i+1].type == 'operator' and children[i+1].value == ',':
|
|
153
|
+
offset = 2
|
|
154
|
+
yield dict(children=children[:i] + children[i + offset:])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def keyword_mutation(value, context, **_):
|
|
158
|
+
if len(context.stack) > 2 and context.stack[-2].type in ('comp_op', 'sync_comp_for') and value in ('in', 'is'):
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
if len(context.stack) > 1 and context.stack[-2].type == 'for_stmt':
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
target = {
|
|
165
|
+
'not': '',
|
|
166
|
+
'is': 'is not', # this will cause "is not not" sometimes, so there's a hack to fix that later
|
|
167
|
+
'in': 'not in',
|
|
168
|
+
'break': 'return',
|
|
169
|
+
'continue': 'break',
|
|
170
|
+
'True': 'False',
|
|
171
|
+
'False': 'True',
|
|
172
|
+
}.get(value)
|
|
173
|
+
|
|
174
|
+
if target is not None:
|
|
175
|
+
yield dict(value=target)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def operator_mutation(value, node, **_):
|
|
179
|
+
if value in ('*', '**') and node.parent.type in ('param', 'argument'):
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
if value == '*' and node.parent.type == 'parameters':
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
for op in {
|
|
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
|
+
}.get(value, []):
|
|
222
|
+
yield dict(value=op)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def and_or_test_mutation(children, node, **_):
|
|
226
|
+
children = children[:]
|
|
227
|
+
children[1] = Keyword(
|
|
228
|
+
value={'and': ' or', 'or': ' and'}[children[1].value],
|
|
229
|
+
start_pos=node.start_pos,
|
|
230
|
+
)
|
|
231
|
+
yield dict(children=children)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def expression_mutation(children, **_):
|
|
235
|
+
def handle_assignment(children):
|
|
236
|
+
mutation_index = -1 # we mutate the last value to handle multiple assignment
|
|
237
|
+
if getattr(children[mutation_index], 'value', '---') != 'None':
|
|
238
|
+
x = ' None'
|
|
239
|
+
else:
|
|
240
|
+
x = ' ""'
|
|
241
|
+
children = children[:]
|
|
242
|
+
children[mutation_index] = Name(value=x, start_pos=children[mutation_index].start_pos)
|
|
243
|
+
|
|
244
|
+
return children
|
|
245
|
+
|
|
246
|
+
if children[0].type == 'operator' and children[0].value == ':':
|
|
247
|
+
if len(children) > 2 and children[2].value == '=':
|
|
248
|
+
children = children[:] # we need to copy the list here, to not get in place mutation on the next line!
|
|
249
|
+
children[1:] = handle_assignment(children[1:])
|
|
250
|
+
yield dict(children=children)
|
|
251
|
+
elif children[1].type == 'operator' and children[1].value == '=':
|
|
252
|
+
yield dict(children=handle_assignment(children))
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def decorator_mutation(children, **_):
|
|
256
|
+
assert children[-1].type == 'newline'
|
|
257
|
+
yield dict(children=children[-1:])
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def name_mutation(node, value, **_):
|
|
261
|
+
simple_mutants = {
|
|
262
|
+
'True': 'False',
|
|
263
|
+
'False': 'True',
|
|
264
|
+
'deepcopy': 'copy',
|
|
265
|
+
'None': '""',
|
|
266
|
+
# TODO: probably need to add a lot of things here... some builtins maybe, what more?
|
|
267
|
+
}
|
|
268
|
+
if value in simple_mutants:
|
|
269
|
+
yield dict(value=simple_mutants[value])
|
|
270
|
+
|
|
271
|
+
if node.parent.type == 'trailer' and node.parent.children[0].type == 'operator' and node.parent.children[0].value in ('(', ']'):
|
|
272
|
+
yield dict(value='None')
|
|
273
|
+
|
|
274
|
+
# Mutate `b` in `a=b`, but not `a`!
|
|
275
|
+
if node.parent.type == 'argument' and node.parent.children[0] != node and node.parent.children[0].type != 'operator':
|
|
276
|
+
yield dict(value='None')
|
|
277
|
+
|
|
278
|
+
# Mutate `b` in `a=b`, but not `a`!
|
|
279
|
+
if node.parent.type == 'arglist':
|
|
280
|
+
yield dict(value='None')
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def trailer_mutation(children, **kwargs):
|
|
284
|
+
if children[0].type == 'operator' and children[0].value == '[' and children[-1].type == 'operator' and children[-1].value == ']' and len(children) > 2:
|
|
285
|
+
yield from subscript_mutation(children=children, **kwargs)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def subscript_mutation(children, context, **_):
|
|
289
|
+
if len(children) == 3 and children[1].type == 'keyword' and children[1].value == 'None':
|
|
290
|
+
return
|
|
291
|
+
if context.is_inside_annassign():
|
|
292
|
+
return
|
|
293
|
+
yield dict(children=[
|
|
294
|
+
children[0],
|
|
295
|
+
Name(value='None', start_pos=children[1].start_pos),
|
|
296
|
+
children[-1],
|
|
297
|
+
])
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
mutation_by_ast_type = {
|
|
301
|
+
'operator': operator_mutation,
|
|
302
|
+
'keyword': keyword_mutation,
|
|
303
|
+
'number': number_mutation,
|
|
304
|
+
'name': name_mutation,
|
|
305
|
+
'string': string_mutation,
|
|
306
|
+
'argument': argument_mutation,
|
|
307
|
+
'arglist': arglist_mutation,
|
|
308
|
+
'or_test': and_or_test_mutation,
|
|
309
|
+
'and_test': and_or_test_mutation,
|
|
310
|
+
'lambdef': lambda_mutation,
|
|
311
|
+
'expr_stmt': expression_mutation,
|
|
312
|
+
'decorator': decorator_mutation,
|
|
313
|
+
'annassign': expression_mutation,
|
|
314
|
+
'trailer': trailer_mutation,
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# TODO: detect regexes and mutate them in nasty ways? Maybe mutate all strings as if they are regexes
|