mutmut 3.0.1__tar.gz → 3.0.3__tar.gz

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.
@@ -1,6 +1,21 @@
1
1
  Changelog
2
2
  ---------
3
3
 
4
+ 3.0.3
5
+ ~~~~~
6
+
7
+ * Fixed missing requirement in install package
8
+
9
+ * Fixed missing file from the install package
10
+
11
+ 3.0.2
12
+ ~~~~~
13
+
14
+ * Fixed bad entrypoint definition
15
+
16
+ * Ignore files that can't be parsed by `parso`
17
+
18
+
4
19
  3.0.1
5
20
  ~~~~~
6
21
 
@@ -2,5 +2,5 @@ include HISTORY.rst
2
2
  include LICENSE
3
3
  include README.rst
4
4
  include requirements.txt
5
- include test_requirements.txt
6
- include mutmut/result_browser_layout.tcss
5
+ recursive-include mutmut *.tcss
6
+ exclude tests/*.*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mutmut
3
- Version: 3.0.1
3
+ Version: 3.0.3
4
4
  Summary: mutation testing for Python 3
5
5
  Home-page: https://github.com/boxed/mutmut
6
6
  Author: Anders Hovmöller
@@ -19,6 +19,7 @@ Requires-Dist: parso
19
19
  Requires-Dist: click
20
20
  Requires-Dist: junit-xml==1.8
21
21
  Requires-Dist: setproctitle
22
+ Requires-Dist: textual
22
23
  Provides-Extra: pytest
23
24
  Requires-Dist: pytest; extra == "pytest"
24
25
  Provides-Extra: coverage
@@ -4,7 +4,7 @@ from parso.python.tree import (
4
4
  Number,
5
5
  )
6
6
 
7
- __version__ = '3.0.1'
7
+ __version__ = '3.0.3'
8
8
 
9
9
 
10
10
  # We have a global whitelist for constants of the pattern __all__, __version__, etc
@@ -43,7 +43,10 @@ from typing import (
43
43
  )
44
44
 
45
45
  import click
46
- from parso import parse
46
+ from parso import (
47
+ parse,
48
+ ParserSyntaxError,
49
+ )
47
50
  from setproctitle import setproctitle
48
51
 
49
52
  import mutmut
@@ -181,10 +184,10 @@ def _mutmut_trampoline(orig, mutants, *args, **kwargs):
181
184
  import os
182
185
  mutant_under_test = os.environ['MUTANT_UNDER_TEST']
183
186
  if mutant_under_test == 'fail':
184
- from __main__ import MutmutProgrammaticFailException
187
+ from mutmut.__main__ import MutmutProgrammaticFailException
185
188
  raise MutmutProgrammaticFailException('Failed programmatically')
186
189
  elif mutant_under_test == 'stats':
187
- from __main__ import record_trampoline_hit
190
+ from mutmut.__main__ import record_trampoline_hit
188
191
  record_trampoline_hit(orig.__module__ + '.' + orig.__name__)
189
192
  return orig(*args, **kwargs)
190
193
  prefix = orig.__module__ + '.' + orig.__name__ + '__mutmut_'
@@ -239,7 +242,7 @@ def create_mutants_for_file(filename, output_path):
239
242
  source = f.read()
240
243
 
241
244
  with open(output_path, 'w') as out:
242
- mutant_names, hash_by_function_name = write_all_mutants_to_file(out=out, source=source)
245
+ mutant_names, hash_by_function_name = write_all_mutants_to_file(out=out, source=source, filename=filename)
243
246
 
244
247
  # validate no syntax errors of mutants
245
248
  with open(output_path) as f:
@@ -269,13 +272,20 @@ def ensure_ends_with_newline(source):
269
272
  return source
270
273
 
271
274
 
272
- def write_all_mutants_to_file(*, out, source):
275
+ def write_all_mutants_to_file(*, out, source, filename):
273
276
  no_mutate_lines = pragma_no_mutate_lines(source)
274
277
 
275
278
  hash_by_function_name = {}
276
279
  mutant_names = []
277
280
 
278
- for type_, x, name_and_hash, mutant_name in yield_mutants_for_module(parse(ensure_ends_with_newline(source), error_recovery=False), no_mutate_lines):
281
+ try:
282
+ ast = parse(ensure_ends_with_newline(source), error_recovery=False)
283
+ except ParserSyntaxError:
284
+ print(f'Warning: unsupported syntax in {filename}, skipping')
285
+ out.write(source)
286
+ return [], {}
287
+
288
+ for type_, x, name_and_hash, mutant_name in yield_mutants_for_module(ast, no_mutate_lines):
279
289
  out.write(x)
280
290
  if mutant_name:
281
291
  mutant_names.append(mutant_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mutmut
3
- Version: 3.0.1
3
+ Version: 3.0.3
4
4
  Summary: mutation testing for Python 3
5
5
  Home-page: https://github.com/boxed/mutmut
6
6
  Author: Anders Hovmöller
@@ -19,6 +19,7 @@ Requires-Dist: parso
19
19
  Requires-Dist: click
20
20
  Requires-Dist: junit-xml==1.8
21
21
  Requires-Dist: setproctitle
22
+ Requires-Dist: textual
22
23
  Provides-Extra: pytest
23
24
  Requires-Dist: pytest; extra == "pytest"
24
25
  Provides-Extra: coverage
@@ -6,11 +6,8 @@ README.rst
6
6
  requirements.txt
7
7
  setup.cfg
8
8
  setup.py
9
- test_requirements.txt
10
9
  ./mutmut/__init__.py
11
10
  ./mutmut/__main__.py
12
- mutmut/__init__.py
13
- mutmut/__main__.py
14
11
  mutmut/result_browser_layout.tcss
15
12
  mutmut.egg-info/PKG-INFO
16
13
  mutmut.egg-info/SOURCES.txt
@@ -18,6 +15,4 @@ mutmut.egg-info/dependency_links.txt
18
15
  mutmut.egg-info/entry_points.txt
19
16
  mutmut.egg-info/not-zip-safe
20
17
  mutmut.egg-info/requires.txt
21
- mutmut.egg-info/top_level.txt
22
- tests/test_mutation.py
23
- tests/test_mutmut3.py
18
+ mutmut.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mutmut = mutmut.__main__:cli
@@ -2,6 +2,7 @@ parso
2
2
  click
3
3
  junit-xml==1.8
4
4
  setproctitle
5
+ textual
5
6
 
6
7
  [coverage]
7
8
  coverage
@@ -2,3 +2,4 @@ parso
2
2
  click
3
3
  junit-xml==1.8
4
4
  setproctitle
5
+ textual
@@ -117,6 +117,6 @@ setup(
117
117
  'mutmut = mutmut.pytestplugin',
118
118
  ],
119
119
  } if running_inside_tests else {
120
- 'console_scripts': ["mutmut = mutmut.__main__:climain"],
120
+ 'console_scripts': ["mutmut = mutmut.__main__:cli"],
121
121
  },
122
122
  )
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- mutmut = mutmut.__main__:climain
@@ -1,4 +0,0 @@
1
- pytest
2
- mock>=2.0.0
3
- coverage
4
- whatthepatch==0.0.6
@@ -1,339 +0,0 @@
1
- from io import StringIO
2
-
3
- import pytest
4
- from parso import parse
5
-
6
- from mutmut.__main__ import (
7
- CLASS_NAME_SEPARATOR,
8
- FuncContext,
9
- get_diff_for_mutant,
10
- mangle_function_name,
11
- orig_function_and_class_names_from_key,
12
- pragma_no_mutate_lines,
13
- write_all_mutants_to_file,
14
- yield_mutants_for_module,
15
- yield_mutants_for_node,
16
- )
17
-
18
-
19
- @pytest.mark.parametrize(
20
- 'original, expected', [
21
- ('foo(a, *args, **kwargs)', [
22
- 'foo( *args, **kwargs)',
23
- 'foo(None, *args, **kwargs)',
24
- 'foo(a, **kwargs)',
25
- 'foo(a, *args,)',
26
- ]),
27
- # TODO: Fix these
28
- # ('break', 'continue'), # probably a bad idea. Can introduce infinite loops.
29
- ('a(b)', 'a(None)'),
30
- # ("x if a else b", "x if a else b"),
31
- ("dict(a=None)", ["dict(aXX=None)"]),
32
- ("dict(a=b)", ["dict(aXX=b)", 'dict(a=None)']),
33
- ('lambda **kwargs: Variable.integer(**setdefaults(kwargs, dict(show=False)))', [
34
- 'lambda **kwargs: Variable.integer(**setdefaults(kwargs, dict(show=True)))',
35
- 'lambda **kwargs: Variable.integer(**setdefaults(kwargs, dict(showXX=False)))',
36
- 'lambda **kwargs: Variable.integer(**setdefaults(None, dict(show=False)))',
37
- 'lambda **kwargs: Variable.integer(**setdefaults( dict(show=False)))',
38
- # TODO: this mutant should exist... I guess we need to handle method calls separately from function calls?
39
- # 'lambda **kwargs: Variable.integer()',
40
- 'lambda **kwargs: None',
41
- ]),
42
- ('a: Optional[int] = None', 'a: Optional[int] = ""'),
43
- ('a: int = 1', ['a: int = 2', 'a: int = None']),
44
- ('lambda: 0', ['lambda: 1', 'lambda: None']),
45
- ("1 in (1, 2)", ['2 in (1, 2)', '1 not in (1, 2)', '1 in (2, 2)', '1 in (1, 3)']),
46
- ('1+1', ['2+1', '1-1', '1+2']),
47
- ('1', '2'),
48
- ('1-1', ['2-1', '1+1', '1-2']),
49
- ('1*1', ['2*1', '1/1', '1*2']),
50
- ('1/1', ['2/1', '1*1', '1/2']),
51
- # ('1.0', '1.0000000000000002'), # using numpy features
52
- ('1.0', '2.0'),
53
- ('0.1', '1.1'),
54
- ('1e-3', '1.001'),
55
- ('True', 'False'),
56
- ('False', 'True'),
57
- ('"foo"', '"XXfooXX"'),
58
- ("'foo'", "'XXfooXX'"),
59
- ("u'foo'", "u'XXfooXX'"),
60
- ("0", "1"),
61
- ("0o0", "1"),
62
- ("0.", "1.0"),
63
- ("0x0", "1"),
64
- ("0b0", "1"),
65
- ("1<2", ['2<2', '1<=2', '1<3']),
66
- ('(1, 2)', ['(2, 2)', '(1, 3)']),
67
- ("1 not in (1, 2)", ['2 not in (1, 2)', '1 in (1, 2)', '1 not in (2, 2)', '1 not in (1, 3)']), # two spaces here because "not in" is two words
68
- ("foo is foo", "foo is not foo"),
69
- ("foo is not foo", "foo is foo"),
70
- ('a or b', 'a and b'),
71
- ('a and b', 'a or b'),
72
- ('a = b', 'a = None'),
73
- ('a = b = c = x', 'a = b = c = None'),
74
-
75
- # subscript
76
- ('a[None]', []),
77
- ('a[b]', 'a[None]'),
78
- ('s[0]', ['s[1]', 's[None]']),
79
- ('s[0] = a', ['s[1] = a', 's[None] = a', 's[0] = None']),
80
- ('s[1:]', ['s[2:]', 's[None]']),
81
-
82
- ('1j', '2j'),
83
- ('1.0j', '2.0j'),
84
- ('0o1', '2'),
85
- ('1.0e10', '10000000001.0'),
86
- ('a = {x for x in y}', 'a = None'),
87
- ('x+=1', ['x=1', 'x-=1', 'x+=2']),
88
- ('x-=1', ['x=1', 'x+=1', 'x-=2']),
89
- ('x*=1', ['x=1', 'x/=1', 'x*=2']),
90
- ('x/=1', ['x=1', 'x*=1', 'x/=2']),
91
- ('x//=1', ['x=1', 'x/=1', 'x//=2']),
92
- ('x%=1', ['x=1', 'x/=1', 'x%=2']),
93
- ('x<<=1', ['x=1', 'x>>=1', 'x<<=2']),
94
- ('x>>=1', ['x=1', 'x<<=1', 'x>>=2']),
95
- ('x&=1', ['x=1', 'x|=1', 'x&=2']),
96
- ('x|=1', ['x=1', 'x&=1', 'x|=2']),
97
- ('x^=1', ['x=1', 'x&=1', 'x^=2']),
98
- ('x**=1', ['x=1', 'x*=1', 'x**=2']),
99
- ('def foo(s: Int = 1): pass', 'def foo(s: Int = 2): pass'),
100
- ('a = None', 'a = ""'),
101
- ('lambda **kwargs: None', 'lambda **kwargs: 0'),
102
- ('lambda: None', 'lambda: 0'),
103
- ('def foo(s: str): pass', []),
104
- ('def foo(a, *, b): pass', []),
105
- ('a(None)', []),
106
- ("'''foo'''", []), # don't mutate things we assume to be docstrings
107
- ("r'''foo'''", []), # don't mutate things we assume to be docstrings
108
- ('(x for x in [])', []), # don't mutate 'in' in generators
109
- ("DictSynonym(a=b)", ["DictSynonym(aXX=b)", 'DictSynonym(a=None)']),
110
- ("NotADictSynonym(a=b)", "NotADictSynonym(a=None)"),
111
- ('from foo import *', []),
112
- ('from .foo import *', []),
113
- ('import foo', []),
114
- ('import foo as bar', []),
115
- ('foo.bar', []),
116
- ('for x in y: pass', []),
117
- ('def foo(a, *args, **kwargs): pass', []),
118
- ('import foo', []),
119
- ('isinstance(a, b)', []),
120
- ('len(a)', []),
121
- ]
122
- )
123
- def test_basic_mutations(original, expected):
124
- if isinstance(expected, str):
125
- expected = [expected]
126
- func_node = parse(f'def fake():\n {original}').children[0]
127
- node = func_node.children[-1]
128
- assert node.get_code().strip() == original.strip()
129
- mutants = list(yield_mutants_for_node(func_node=func_node, context=FuncContext(dict_synonyms={'DictSynonym'}), node=node))
130
- actual = sorted([
131
- parse(mutant).children[0].children[-1].get_code().strip()
132
- for (type_, mutant, _, _) in mutants
133
- if type_ == 'mutant'
134
- ])
135
- expected = sorted(expected)
136
- assert actual == expected
137
-
138
-
139
- def test_do_not_mutate_annotations():
140
- source = """
141
- def foo() -> int:
142
- bar: Optional[int]
143
- return
144
- """.strip()
145
-
146
- mutants = [
147
- mutant
148
- for type_, mutant, _, _ in yield_mutants_for_module(parse(source), {})
149
- if type_ == 'mutant'
150
- ]
151
- for m in mutants:
152
- print(m) # pragma: no cover
153
-
154
- assert not mutants
155
-
156
-
157
- def test_basic_class():
158
- source = """
159
- class Foo:
160
- def member(self):
161
- return 1
162
- """.strip()
163
-
164
- mutants = [
165
- mutant
166
- for type_, mutant, _, _ in yield_mutants_for_module(parse(source), {})
167
- if type_ == 'mutant'
168
- ]
169
- for m in mutants:
170
- print(m)
171
-
172
- assert len(mutants) == 1
173
- assert mutants[0] == f' def x{CLASS_NAME_SEPARATOR}Foo{CLASS_NAME_SEPARATOR}member__mutmut_1(self): \n return 2'
174
-
175
-
176
- def mutants_for_source(source):
177
- no_mutate_lines = pragma_no_mutate_lines(source)
178
- r = []
179
- for type_, x, name_and_hash, mutant_name in yield_mutants_for_module(parse(source, error_recovery=False), no_mutate_lines):
180
- if type_ == 'mutant':
181
- r.append(x)
182
- return r
183
-
184
-
185
- def test_function_with_annotation():
186
- source = "def capitalize(s : str):\n return s[0].upper() + s[1:] if s else s\n".strip()
187
- mutants = mutants_for_source(source)
188
- assert mutants == [
189
- 'def capitalize__mutmut_1(s : str):\n return s[1].upper() + s[1:] if s else s',
190
- 'def capitalize__mutmut_2(s : str):\n return s[None].upper() + s[1:] if s else s',
191
- 'def capitalize__mutmut_3(s : str):\n return s[0].upper() - s[1:] if s else s',
192
- 'def capitalize__mutmut_4(s : str):\n return s[0].upper() + s[2:] if s else s',
193
- 'def capitalize__mutmut_5(s : str):\n return s[0].upper() + s[None] if s else s'
194
- ]
195
-
196
-
197
- def test_pragma_no_mutate():
198
- source = """def foo():\n return 1+1 # pragma: no mutate\n""".strip()
199
- mutants = mutants_for_source(source)
200
- assert not mutants
201
-
202
-
203
- def test_pragma_no_mutate_and_no_cover():
204
- source = """def foo():\n return 1+1 # pragma: no cover, no mutate\n""".strip()
205
- mutants = mutants_for_source(source)
206
- assert not mutants
207
-
208
-
209
- def test_mutate_dict():
210
- source = '''
211
- def foo():
212
- dict(a=b, c=d)
213
- '''
214
- mutants = mutants_for_source(source)
215
- assert mutants == [
216
- '\ndef foo__mutmut_1(): \n dict(a=None, c=d)\n',
217
- '\ndef foo__mutmut_2(): \n dict(aXX=b, c=d)\n',
218
- '\ndef foo__mutmut_3(): \n dict(a=b, c=None)\n',
219
- '\ndef foo__mutmut_4(): \n dict(a=b, cXX=d)\n',
220
- '\ndef foo__mutmut_5(): \n dict( c=d)\n',
221
- '\ndef foo__mutmut_6(): \n dict(a=b,)\n',
222
- ]
223
-
224
-
225
- def test_syntax_error():
226
- with pytest.raises(Exception):
227
- mutants_for_source(':!')
228
-
229
-
230
- def test_bug_github_issue_18():
231
- source = """
232
- @register.simple_tag(name='icon')
233
- def icon(name):
234
- if name is None:
235
- return ''
236
- tpl = '<span class="glyphicon glyphicon-{}"></span>'
237
- return format_html(tpl, name)
238
- """.strip()
239
- mutants_for_source(source)
240
-
241
-
242
- def test_bug_github_issue_19_argument_mutation_crash():
243
- source = """key = lambda a: "foo"
244
- filters = dict((key(field), False) for field in fields)"""
245
- mutants_for_source(source)
246
-
247
-
248
- def test_bug_github_issue_26():
249
- source = """
250
- def wrapper():
251
- class ConfigurationOptions(Protocol):
252
- min_name_length: int
253
- """.strip()
254
- assert mutants_for_source(source) == []
255
-
256
-
257
- def test_bug_github_issue_30():
258
- source = """
259
- def wrapper():
260
- def from_checker(cls: Type['BaseVisitor'], checker) -> 'BaseVisitor':
261
- pass
262
- """.strip()
263
- assert mutants_for_source(source) == []
264
-
265
-
266
- def test_bug_github_issue_77():
267
- # Don't crash on this
268
- assert mutants_for_source('') == []
269
-
270
-
271
- def test_multiline_dunder_whitelist():
272
- source = """
273
- __all__ = [
274
- 1,
275
- 2,
276
- 'foo',
277
- 'bar',
278
- ]
279
- """.strip()
280
- mutants = mutants_for_source(source)
281
- assert not mutants
282
-
283
-
284
- def test_bad_mutation_str_type_definition():
285
- source = """
286
- def foo():
287
- foo: 'SomeType'
288
- """.strip()
289
- mutants = mutants_for_source(source)
290
- assert not mutants
291
-
292
-
293
- def test_orig_function_name_from_key():
294
- assert orig_function_and_class_names_from_key(f'_{CLASS_NAME_SEPARATOR}Foo{CLASS_NAME_SEPARATOR}bar__mutmut_1') == ('bar', 'Foo')
295
- assert orig_function_and_class_names_from_key('bar__mutmut_1') == ('bar', None)
296
-
297
-
298
- def test_mangle_function_name():
299
- assert mangle_function_name(name='bar', class_name=None) == 'bar'
300
- assert mangle_function_name(name='bar', class_name='Foo') == f'x{CLASS_NAME_SEPARATOR}Foo{CLASS_NAME_SEPARATOR}bar'
301
-
302
-
303
- def test_diff_ops():
304
- source = """
305
- def foo():
306
- return 1
307
-
308
-
309
- class Foo:
310
- def member(self):
311
- return 3
312
-
313
- """.strip()
314
-
315
- out = StringIO()
316
- mutant_names, hash_by_function_name = write_all_mutants_to_file(out=out, source=source)
317
- assert len(mutant_names) == 2
318
- mutants_source = out.getvalue()
319
-
320
- diff1 = get_diff_for_mutant(mutant_name=mutant_names[0], source=mutants_source, path='test.py').strip()
321
- diff2 = get_diff_for_mutant(mutant_name=mutant_names[1], source=mutants_source, path='test.py').strip()
322
-
323
- assert diff1 == '''
324
- --- test.py
325
- +++ test.py
326
- @@ -1,2 +1,2 @@
327
- def foo():
328
- - return 1
329
- + return 2
330
- '''.strip()
331
-
332
- assert diff2 == '''
333
- --- test.py
334
- +++ test.py
335
- @@ -1,2 +1,2 @@
336
- def member(self):
337
- - return 3
338
- + return 4
339
- '''.strip()
@@ -1,80 +0,0 @@
1
- from parso import parse
2
-
3
- from mutmut.__main__ import (
4
- trampoline_impl,
5
- yield_mutants_for_module,
6
- )
7
-
8
-
9
- def test_yield_mutants_for_module():
10
- source = """
11
- a + 1
12
-
13
- def foo(a, b, c):
14
- return a + b * c
15
- """
16
-
17
- expected = trampoline_impl + """
18
-
19
- a + 1
20
-
21
- def foo__mutmut_orig(a, b, c):
22
- return a + b * c
23
-
24
- def foo__mutmut_1(a, b, c):
25
- return a - b * c
26
-
27
- def foo__mutmut_2(a, b, c):
28
- return a + b / c
29
-
30
- foo__mutmut_mutants = {
31
- 'foo__mutmut_1': foo__mutmut_1,
32
- 'foo__mutmut_2': foo__mutmut_2
33
- }
34
-
35
- def foo(*args, **kwargs):
36
- return _mutmut_trampoline(foo__mutmut_orig, foo__mutmut_mutants, *args, **kwargs)
37
-
38
- foo.__signature__ = _mutmut_signature(foo__mutmut_orig)
39
- foo__mutmut_orig.__name__ = 'foo'
40
-
41
-
42
- """
43
-
44
- node = parse(source)
45
- result = ''.join([x[1] for x in yield_mutants_for_module(node, no_mutate_lines=[])])
46
-
47
- assert result == expected
48
-
49
-
50
- def test_avoid_annotations():
51
- source = """
52
- def foo(a: List[int]) -> int:
53
- return 1
54
- """
55
-
56
- expected = trampoline_impl + """
57
-
58
- def foo__mutmut_orig(a: List[int]) -> int:
59
- return 1
60
-
61
- def foo__mutmut_1(a: List[int]) -> int:
62
- return 2
63
-
64
- foo__mutmut_mutants = {
65
- 'foo__mutmut_1': foo__mutmut_1
66
- }
67
-
68
- def foo(*args, **kwargs):
69
- return _mutmut_trampoline(foo__mutmut_orig, foo__mutmut_mutants, *args, **kwargs)
70
-
71
- foo.__signature__ = _mutmut_signature(foo__mutmut_orig)
72
- foo__mutmut_orig.__name__ = 'foo'
73
-
74
-
75
- """
76
-
77
- node = parse(source)
78
- result = ''.join([x[1] for x in yield_mutants_for_module(node, no_mutate_lines=[])])
79
-
80
- assert result == expected
File without changes
File without changes
File without changes
File without changes