mutmut 3.5.0__tar.gz → 3.6.0__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.
- {mutmut-3.5.0 → mutmut-3.6.0}/HISTORY.rst +42 -1
- {mutmut-3.5.0 → mutmut-3.6.0}/PKG-INFO +185 -16
- {mutmut-3.5.0 → mutmut-3.6.0}/README.rst +184 -15
- {mutmut-3.5.0 → mutmut-3.6.0}/pyproject.toml +36 -3
- mutmut-3.6.0/src/mutmut/__init__.py +42 -0
- mutmut-3.6.0/src/mutmut/__main__.py +1550 -0
- {mutmut-3.5.0 → mutmut-3.6.0}/src/mutmut/code_coverage.py +32 -25
- mutmut-3.6.0/src/mutmut/configuration.py +207 -0
- mutmut-3.6.0/src/mutmut/mutation/__init__.py +0 -0
- mutmut-3.6.0/src/mutmut/mutation/data.py +61 -0
- mutmut-3.6.0/src/mutmut/mutation/file_mutation.py +508 -0
- mutmut-3.5.0/src/mutmut/node_mutation.py → mutmut-3.6.0/src/mutmut/mutation/mutators.py +65 -71
- mutmut-3.6.0/src/mutmut/mutation/pragma_handling.py +229 -0
- mutmut-3.6.0/src/mutmut/mutation/trampoline.py +105 -0
- mutmut-3.6.0/src/mutmut/mutation/trampoline_templates.py +54 -0
- mutmut-3.6.0/src/mutmut/py.typed +0 -0
- mutmut-3.6.0/src/mutmut/skills/kill-survivors.md +18 -0
- mutmut-3.6.0/src/mutmut/threading/__init__.py +0 -0
- mutmut-3.6.0/src/mutmut/threading/timeout.py +57 -0
- mutmut-3.6.0/src/mutmut/type_checking.py +112 -0
- mutmut-3.6.0/src/mutmut/utils/__init__.py +0 -0
- mutmut-3.6.0/src/mutmut/utils/file_utils.py +14 -0
- mutmut-3.6.0/src/mutmut/utils/format_utils.py +55 -0
- mutmut-3.6.0/src/mutmut/utils/safe_setproctitle.py +24 -0
- mutmut-3.5.0/src/mutmut/__init__.py +0 -24
- mutmut-3.5.0/src/mutmut/__main__.py +0 -1716
- mutmut-3.5.0/src/mutmut/file_mutation.py +0 -402
- mutmut-3.5.0/src/mutmut/trampoline_templates.py +0 -56
- mutmut-3.5.0/src/mutmut/type_checking.py +0 -98
- {mutmut-3.5.0 → mutmut-3.6.0}/LICENSE +0 -0
- {mutmut-3.5.0 → mutmut-3.6.0}/src/mutmut/result_browser_layout.tcss +0 -0
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
---------
|
|
3
3
|
|
|
4
|
+
3.6.0
|
|
5
|
+
~~~~~
|
|
6
|
+
|
|
7
|
+
* Add `# pragma: no mutate block` and `# pragma: no mutate start/end` comments
|
|
8
|
+
|
|
9
|
+
* Add `do_not_mutate_patterns` to disable mutations with a regex
|
|
10
|
+
|
|
11
|
+
* Add `only_mutate` config to select which files get mutated
|
|
12
|
+
|
|
13
|
+
* Also mutate methods decorated with (only) `@staticmethod` or `@classmethod`
|
|
14
|
+
|
|
15
|
+
* Add `use_setproctitle` option to disable process renaming (automatically disabled on MacOS)
|
|
16
|
+
|
|
17
|
+
* Add (unstable) `timeout_multiplier` and `timeout_constant` options
|
|
18
|
+
|
|
19
|
+
* Rename `paths_to_mutate` to `source_paths`
|
|
20
|
+
|
|
21
|
+
* Deprecate `tests_dir` in favor of `pytest_add_cli_args_test_selection`
|
|
22
|
+
|
|
23
|
+
* Change `max_stack_depth` to only consider functions inside `source_paths`
|
|
24
|
+
|
|
25
|
+
* Disable mutation of enums and `@staticmethod`/`@classmethod` methods when `type_check_command` is set, as these mutations break type checking
|
|
26
|
+
|
|
27
|
+
* Fix mutation for enum class methods
|
|
28
|
+
|
|
29
|
+
* Fix mutate_only_covered_lines when project uses custom coverage.py config
|
|
30
|
+
|
|
31
|
+
* Fix execution when running mutmut via `python -m mutmut run`
|
|
32
|
+
|
|
33
|
+
* Fix mutation of `class _SomePrivateClass` class methods
|
|
34
|
+
|
|
35
|
+
* Fix mutation of default args
|
|
36
|
+
|
|
37
|
+
* Fix `mutmut browse` crash when no file is selected yet
|
|
38
|
+
|
|
39
|
+
* Fix timeout checker looking up the wrong mutant's expected test time, which could cause mutants to hang
|
|
40
|
+
|
|
41
|
+
* Compare stats paths after resolving symlinks
|
|
42
|
+
|
|
43
|
+
* Warn when mutmut cannot match mutants with the collected stats
|
|
44
|
+
|
|
4
45
|
3.5.0
|
|
5
46
|
~~~~~
|
|
6
47
|
|
|
7
48
|
* Filter mutants based on type annotations. This feature needs to be turned on with the `type_check_command` config.
|
|
8
49
|
|
|
9
|
-
*
|
|
50
|
+
* Show error message when running on Windows
|
|
10
51
|
|
|
11
52
|
* Save mutation stats for use with CI/CD pipeline (#460)
|
|
12
53
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mutmut
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.0
|
|
4
4
|
Summary: mutation testing for Python 3
|
|
5
5
|
Keywords: mutmut,mutant,mutation,test,testing
|
|
6
6
|
Author: Anders Hovmöller
|
|
@@ -86,8 +86,7 @@ it will try to figure out where the code to mutate is.
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
You can stop the mutation run at any time and mutmut will restart where you
|
|
89
|
-
left off.
|
|
90
|
-
modified since last run.
|
|
89
|
+
left off.
|
|
91
90
|
|
|
92
91
|
To work with the results, use `mutmut browse` where you can see the mutants,
|
|
93
92
|
retest them when you've updated your tests.
|
|
@@ -122,7 +121,7 @@ In `setup.cfg` in the root of your project you can configure mutmut if you need
|
|
|
122
121
|
.. code-block:: ini
|
|
123
122
|
|
|
124
123
|
[mutmut]
|
|
125
|
-
|
|
124
|
+
source_paths=src/
|
|
126
125
|
pytest_add_cli_args_test_selection=tests/
|
|
127
126
|
|
|
128
127
|
If you use `pyproject.toml`, you must specify the paths as array in a `tool.mutmut` section:
|
|
@@ -130,7 +129,7 @@ If you use `pyproject.toml`, you must specify the paths as array in a `tool.mutm
|
|
|
130
129
|
.. code-block:: toml
|
|
131
130
|
|
|
132
131
|
[tool.mutmut]
|
|
133
|
-
|
|
132
|
+
source_paths = [ "src/" ]
|
|
134
133
|
pytest_add_cli_args_test_selection= [ "tests/" ]
|
|
135
134
|
|
|
136
135
|
See below for more options for configuring mutmut.
|
|
@@ -162,8 +161,8 @@ introduced bug in those base functions will lead to many tests that fail which
|
|
|
162
161
|
are hard to understand how they relate to the function with the change.
|
|
163
162
|
|
|
164
163
|
You can configure mutmut to only count a test as being relevant for a function
|
|
165
|
-
if the stack depth
|
|
166
|
-
`setup.cfg` add:
|
|
164
|
+
if the stack depth is below some limit. Only stack frames from code inside `source_paths`
|
|
165
|
+
is counted towards the limit, 3rd party libraries are ignored. In your `setup.cfg` add:
|
|
167
166
|
|
|
168
167
|
.. code-block:: ini
|
|
169
168
|
|
|
@@ -177,10 +176,14 @@ caught.
|
|
|
177
176
|
Exclude files from mutation
|
|
178
177
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
179
178
|
|
|
179
|
+
By default mutmut mutates all python files in `source_paths`.
|
|
180
180
|
You can exclude files from mutation in `setup.cfg`:
|
|
181
181
|
|
|
182
182
|
.. code-block::
|
|
183
183
|
|
|
184
|
+
only_mutate=
|
|
185
|
+
src/api/*
|
|
186
|
+
src/services/*
|
|
184
187
|
do_not_mutate=
|
|
185
188
|
*__tests.py
|
|
186
189
|
|
|
@@ -221,7 +224,7 @@ To enable this filtering, configure the `type_check_command` to output json resu
|
|
|
221
224
|
# for pyrefly
|
|
222
225
|
type_check_command = ['pyrefly', 'check', '--output-format=json']
|
|
223
226
|
# for mypy
|
|
224
|
-
type_check_command = ['mypy', '
|
|
227
|
+
type_check_command = ['mypy', 'your_source_dir', '--output', 'json', '--disable-error-code', 'unused-ignore']
|
|
225
228
|
|
|
226
229
|
Currently, only `pyrefly` and `mypy` are supported.
|
|
227
230
|
With `pyright` and `ty`, mutating a class method `Foo.bar()` can break the types of all methods of `Foo`,
|
|
@@ -242,22 +245,174 @@ to failing tests.
|
|
|
242
245
|
debug=true
|
|
243
246
|
|
|
244
247
|
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
Disable setproctitle (macOS)
|
|
249
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
247
250
|
|
|
248
|
-
|
|
251
|
+
Mutmut uses ``setproctitle`` to show the current mutant name in the process
|
|
252
|
+
list, which is helpful for monitoring long runs. However, ``setproctitle``
|
|
253
|
+
uses CoreFoundation APIs on macOS that are not fork-safe, causing segfaults
|
|
254
|
+
in child processes.
|
|
249
255
|
|
|
250
|
-
|
|
256
|
+
By default, mutmut automatically disables ``setproctitle`` on macOS and
|
|
257
|
+
enables it on other platforms. If you need to override this (e.g. to enable it on
|
|
258
|
+
macOS at your own risk, or to disable it on other platforms), set ``use_setproctitle``:
|
|
259
|
+
|
|
260
|
+
.. code-block:: toml
|
|
261
|
+
|
|
262
|
+
# pyproject.toml
|
|
263
|
+
[tool.mutmut]
|
|
264
|
+
use_setproctitle = false
|
|
251
265
|
|
|
252
|
-
some_code_here() # pragma: no mutate
|
|
253
266
|
|
|
254
|
-
|
|
255
|
-
|
|
267
|
+
Disabling mutation on specific code
|
|
268
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
256
269
|
|
|
257
|
-
|
|
270
|
+
If you do not want to mutate specific parts of your code, you can disable mutation
|
|
271
|
+
via the following options. Examples where this could be relevant:
|
|
272
|
+
|
|
273
|
+
- If you don't want to test exact log messages (e.g. `logging.info("Foo")` versus the mutated `logging.info("XXFooXX")`)
|
|
258
274
|
- Optimizing break instead of continue. The code runs fine when mutating break
|
|
259
275
|
to continue, but it's slower.
|
|
260
276
|
|
|
277
|
+
Skipping via regex
|
|
278
|
+
^^^^^^^^^^^^^^^^^^
|
|
279
|
+
|
|
280
|
+
You can use a regex on the source code, to tell mutmut which expressions it should not mutate:
|
|
281
|
+
|
|
282
|
+
.. code-block:: toml
|
|
283
|
+
|
|
284
|
+
# pyproject.toml
|
|
285
|
+
[tool.mutmut]
|
|
286
|
+
do_not_mutate_patterns = [
|
|
287
|
+
# disable mutations of all logger.info/debug/... statements
|
|
288
|
+
'logger\.\w+',
|
|
289
|
+
# disable mutating exceptions
|
|
290
|
+
'raise \w+',
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
Mutmut will match the regex on the source code
|
|
294
|
+
and skip mutating any expression on the matched lines.
|
|
295
|
+
|
|
296
|
+
Skipping via code comments
|
|
297
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
298
|
+
|
|
299
|
+
You can use following comments to disable mutation:
|
|
300
|
+
|
|
301
|
+
- `# pragma: no mutate`: disable mutation on this single line
|
|
302
|
+
- `# pragma: no mutate block`: disable mutation in the whole intendation block
|
|
303
|
+
- `# pragma: no mutate start/end`: disable mutation between the start and end comments
|
|
304
|
+
|
|
305
|
+
Skipping single lines
|
|
306
|
+
^^^^^^^^^^^^^^^^^^^^^
|
|
307
|
+
|
|
308
|
+
You can mark lines like this to stop mutating them:
|
|
309
|
+
|
|
310
|
+
.. code-block:: python
|
|
311
|
+
|
|
312
|
+
some_code_here() # pragma: no mutate
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
Skipping Code Blocks
|
|
316
|
+
^^^^^^^^^^^^^^^^^^^^
|
|
317
|
+
|
|
318
|
+
You can skip an entire indentation block from mutation using
|
|
319
|
+
``# pragma: no mutate block``. This works on any compound statement --
|
|
320
|
+
functions, classes, ``if``/``elif``/``else``, loops, context managers, etc.
|
|
321
|
+
|
|
322
|
+
Both syntax styles are supported:
|
|
323
|
+
|
|
324
|
+
- ``# pragma: no mutate block``
|
|
325
|
+
- ``# pragma: no mutate: block``
|
|
326
|
+
|
|
327
|
+
**Skipping an entire function or class** -- place the pragma inline on the
|
|
328
|
+
definition line. The entire node (including all children) is skipped and no
|
|
329
|
+
trampoline is generated:
|
|
330
|
+
|
|
331
|
+
.. code-block:: python
|
|
332
|
+
|
|
333
|
+
def complex_algorithm(): # pragma: no mutate block
|
|
334
|
+
return some_complex_calculation()
|
|
335
|
+
|
|
336
|
+
class MySettings: # pragma: no mutate block
|
|
337
|
+
DEBUG = True
|
|
338
|
+
MAX_RETRIES = 3
|
|
339
|
+
|
|
340
|
+
**Skipping only the body of a function** -- place the pragma on its own line
|
|
341
|
+
inside the function. The function definition (including default arguments) is
|
|
342
|
+
still mutable, but the body is suppressed:
|
|
343
|
+
|
|
344
|
+
.. code-block:: python
|
|
345
|
+
|
|
346
|
+
def foo(val=1):
|
|
347
|
+
# pragma: no mutate block
|
|
348
|
+
x = 1
|
|
349
|
+
y = complex_calculation()
|
|
350
|
+
z = x + y
|
|
351
|
+
|
|
352
|
+
def bar():
|
|
353
|
+
# this function is still mutated normally
|
|
354
|
+
return 42
|
|
355
|
+
|
|
356
|
+
**Skipping an ``if`` branch without affecting ``elif``/``else``** -- place
|
|
357
|
+
the pragma inline on the ``if``. Only the ``if`` condition and its indented
|
|
358
|
+
body are suppressed; sibling branches (``elif``, ``else``) remain mutable
|
|
359
|
+
because they exit the original indentation scope:
|
|
360
|
+
|
|
361
|
+
.. code-block:: python
|
|
362
|
+
|
|
363
|
+
if error_condition: # pragma: no mutate block
|
|
364
|
+
log_error()
|
|
365
|
+
send_alert()
|
|
366
|
+
elif other_condition:
|
|
367
|
+
# still mutated -- this branch is outside the block scope
|
|
368
|
+
handle_other()
|
|
369
|
+
else:
|
|
370
|
+
# still mutated
|
|
371
|
+
handle_success()
|
|
372
|
+
|
|
373
|
+
The same principle applies to ``for``/``else``, ``while``/``else``,
|
|
374
|
+
``try``/``except``/``finally``, and ``match``/``case``.
|
|
375
|
+
|
|
376
|
+
This is useful for:
|
|
377
|
+
|
|
378
|
+
- Functions or classes that should be excluded from mutation entirely
|
|
379
|
+
- Error-handling branches that are hard to unit test in isolation
|
|
380
|
+
- Logging or telemetry blocks that don't affect program correctness
|
|
381
|
+
- Generated or boilerplate code within an otherwise mutable function
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
Skipping Code Regions
|
|
385
|
+
^^^^^^^^^^^^^^^^^^^^^
|
|
386
|
+
|
|
387
|
+
For suppressing mutations across a range of lines regardless of indentation,
|
|
388
|
+
use ``# pragma: no mutate start`` and ``# pragma: no mutate end``:
|
|
389
|
+
|
|
390
|
+
.. code-block:: python
|
|
391
|
+
|
|
392
|
+
a = mutate_this()
|
|
393
|
+
|
|
394
|
+
# pragma: no mutate start
|
|
395
|
+
b = skip_this()
|
|
396
|
+
c = skip_this_too()
|
|
397
|
+
# pragma: no mutate end
|
|
398
|
+
|
|
399
|
+
d = mutate_this_too()
|
|
400
|
+
|
|
401
|
+
Every line between the markers (inclusive) is suppressed. This works inside
|
|
402
|
+
functions, classes, or at module level, and ignores indentation entirely.
|
|
403
|
+
|
|
404
|
+
An unmatched ``# pragma: no mutate end`` without a preceding ``start`` raises
|
|
405
|
+
a ``PragmaParseError`` at parse time. An unclosed ``# pragma: no mutate start``
|
|
406
|
+
(no matching ``end`` before end-of-file) raises a ``PragmaParseError``.
|
|
407
|
+
Both errors include the filename and line number.
|
|
408
|
+
|
|
409
|
+
**Nesting restriction:** opening a new ``block`` or ``start`` context while
|
|
410
|
+
another context is already active is not allowed and raises a
|
|
411
|
+
``PragmaParseError``. The error message includes both the offending line and
|
|
412
|
+
the line where the existing context was opened. Close the current context
|
|
413
|
+
first (dedent for ``block``, or ``# pragma: no mutate end`` for ``start``)
|
|
414
|
+
before opening a new one.
|
|
415
|
+
|
|
261
416
|
|
|
262
417
|
Modifying pytest arguments
|
|
263
418
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
@@ -279,6 +434,20 @@ You can add and override pytest arguments:
|
|
|
279
434
|
also_copy = ["mutmut_pytest.ini"]
|
|
280
435
|
|
|
281
436
|
|
|
437
|
+
Unstable configs
|
|
438
|
+
~~~~~~~~~~~~~~~~
|
|
439
|
+
|
|
440
|
+
Following configurations exist, but may be changed in any minor version.
|
|
441
|
+
If you use them, expect that a new version could change or break this feature.
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
.. code-block:: toml
|
|
445
|
+
|
|
446
|
+
# Configure how long mutmut waits before killing a slow mutation
|
|
447
|
+
# Currently calculated as (duration_of_original_tests + timeout_constant) * timeout_multiplier seconds
|
|
448
|
+
timeout_constant = 1.0
|
|
449
|
+
timeout_multiplier = 15.0
|
|
450
|
+
|
|
282
451
|
|
|
283
452
|
Example mutations
|
|
284
453
|
-----------------
|
|
@@ -53,8 +53,7 @@ it will try to figure out where the code to mutate is.
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
You can stop the mutation run at any time and mutmut will restart where you
|
|
56
|
-
left off.
|
|
57
|
-
modified since last run.
|
|
56
|
+
left off.
|
|
58
57
|
|
|
59
58
|
To work with the results, use `mutmut browse` where you can see the mutants,
|
|
60
59
|
retest them when you've updated your tests.
|
|
@@ -89,7 +88,7 @@ In `setup.cfg` in the root of your project you can configure mutmut if you need
|
|
|
89
88
|
.. code-block:: ini
|
|
90
89
|
|
|
91
90
|
[mutmut]
|
|
92
|
-
|
|
91
|
+
source_paths=src/
|
|
93
92
|
pytest_add_cli_args_test_selection=tests/
|
|
94
93
|
|
|
95
94
|
If you use `pyproject.toml`, you must specify the paths as array in a `tool.mutmut` section:
|
|
@@ -97,7 +96,7 @@ If you use `pyproject.toml`, you must specify the paths as array in a `tool.mutm
|
|
|
97
96
|
.. code-block:: toml
|
|
98
97
|
|
|
99
98
|
[tool.mutmut]
|
|
100
|
-
|
|
99
|
+
source_paths = [ "src/" ]
|
|
101
100
|
pytest_add_cli_args_test_selection= [ "tests/" ]
|
|
102
101
|
|
|
103
102
|
See below for more options for configuring mutmut.
|
|
@@ -129,8 +128,8 @@ introduced bug in those base functions will lead to many tests that fail which
|
|
|
129
128
|
are hard to understand how they relate to the function with the change.
|
|
130
129
|
|
|
131
130
|
You can configure mutmut to only count a test as being relevant for a function
|
|
132
|
-
if the stack depth
|
|
133
|
-
`setup.cfg` add:
|
|
131
|
+
if the stack depth is below some limit. Only stack frames from code inside `source_paths`
|
|
132
|
+
is counted towards the limit, 3rd party libraries are ignored. In your `setup.cfg` add:
|
|
134
133
|
|
|
135
134
|
.. code-block:: ini
|
|
136
135
|
|
|
@@ -144,10 +143,14 @@ caught.
|
|
|
144
143
|
Exclude files from mutation
|
|
145
144
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
146
145
|
|
|
146
|
+
By default mutmut mutates all python files in `source_paths`.
|
|
147
147
|
You can exclude files from mutation in `setup.cfg`:
|
|
148
148
|
|
|
149
149
|
.. code-block::
|
|
150
150
|
|
|
151
|
+
only_mutate=
|
|
152
|
+
src/api/*
|
|
153
|
+
src/services/*
|
|
151
154
|
do_not_mutate=
|
|
152
155
|
*__tests.py
|
|
153
156
|
|
|
@@ -188,7 +191,7 @@ To enable this filtering, configure the `type_check_command` to output json resu
|
|
|
188
191
|
# for pyrefly
|
|
189
192
|
type_check_command = ['pyrefly', 'check', '--output-format=json']
|
|
190
193
|
# for mypy
|
|
191
|
-
type_check_command = ['mypy', '
|
|
194
|
+
type_check_command = ['mypy', 'your_source_dir', '--output', 'json', '--disable-error-code', 'unused-ignore']
|
|
192
195
|
|
|
193
196
|
Currently, only `pyrefly` and `mypy` are supported.
|
|
194
197
|
With `pyright` and `ty`, mutating a class method `Foo.bar()` can break the types of all methods of `Foo`,
|
|
@@ -209,22 +212,174 @@ to failing tests.
|
|
|
209
212
|
debug=true
|
|
210
213
|
|
|
211
214
|
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
Disable setproctitle (macOS)
|
|
216
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
214
217
|
|
|
215
|
-
|
|
218
|
+
Mutmut uses ``setproctitle`` to show the current mutant name in the process
|
|
219
|
+
list, which is helpful for monitoring long runs. However, ``setproctitle``
|
|
220
|
+
uses CoreFoundation APIs on macOS that are not fork-safe, causing segfaults
|
|
221
|
+
in child processes.
|
|
216
222
|
|
|
217
|
-
|
|
223
|
+
By default, mutmut automatically disables ``setproctitle`` on macOS and
|
|
224
|
+
enables it on other platforms. If you need to override this (e.g. to enable it on
|
|
225
|
+
macOS at your own risk, or to disable it on other platforms), set ``use_setproctitle``:
|
|
226
|
+
|
|
227
|
+
.. code-block:: toml
|
|
228
|
+
|
|
229
|
+
# pyproject.toml
|
|
230
|
+
[tool.mutmut]
|
|
231
|
+
use_setproctitle = false
|
|
218
232
|
|
|
219
|
-
some_code_here() # pragma: no mutate
|
|
220
233
|
|
|
221
|
-
|
|
222
|
-
|
|
234
|
+
Disabling mutation on specific code
|
|
235
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
223
236
|
|
|
224
|
-
|
|
237
|
+
If you do not want to mutate specific parts of your code, you can disable mutation
|
|
238
|
+
via the following options. Examples where this could be relevant:
|
|
239
|
+
|
|
240
|
+
- If you don't want to test exact log messages (e.g. `logging.info("Foo")` versus the mutated `logging.info("XXFooXX")`)
|
|
225
241
|
- Optimizing break instead of continue. The code runs fine when mutating break
|
|
226
242
|
to continue, but it's slower.
|
|
227
243
|
|
|
244
|
+
Skipping via regex
|
|
245
|
+
^^^^^^^^^^^^^^^^^^
|
|
246
|
+
|
|
247
|
+
You can use a regex on the source code, to tell mutmut which expressions it should not mutate:
|
|
248
|
+
|
|
249
|
+
.. code-block:: toml
|
|
250
|
+
|
|
251
|
+
# pyproject.toml
|
|
252
|
+
[tool.mutmut]
|
|
253
|
+
do_not_mutate_patterns = [
|
|
254
|
+
# disable mutations of all logger.info/debug/... statements
|
|
255
|
+
'logger\.\w+',
|
|
256
|
+
# disable mutating exceptions
|
|
257
|
+
'raise \w+',
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
Mutmut will match the regex on the source code
|
|
261
|
+
and skip mutating any expression on the matched lines.
|
|
262
|
+
|
|
263
|
+
Skipping via code comments
|
|
264
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
265
|
+
|
|
266
|
+
You can use following comments to disable mutation:
|
|
267
|
+
|
|
268
|
+
- `# pragma: no mutate`: disable mutation on this single line
|
|
269
|
+
- `# pragma: no mutate block`: disable mutation in the whole intendation block
|
|
270
|
+
- `# pragma: no mutate start/end`: disable mutation between the start and end comments
|
|
271
|
+
|
|
272
|
+
Skipping single lines
|
|
273
|
+
^^^^^^^^^^^^^^^^^^^^^
|
|
274
|
+
|
|
275
|
+
You can mark lines like this to stop mutating them:
|
|
276
|
+
|
|
277
|
+
.. code-block:: python
|
|
278
|
+
|
|
279
|
+
some_code_here() # pragma: no mutate
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
Skipping Code Blocks
|
|
283
|
+
^^^^^^^^^^^^^^^^^^^^
|
|
284
|
+
|
|
285
|
+
You can skip an entire indentation block from mutation using
|
|
286
|
+
``# pragma: no mutate block``. This works on any compound statement --
|
|
287
|
+
functions, classes, ``if``/``elif``/``else``, loops, context managers, etc.
|
|
288
|
+
|
|
289
|
+
Both syntax styles are supported:
|
|
290
|
+
|
|
291
|
+
- ``# pragma: no mutate block``
|
|
292
|
+
- ``# pragma: no mutate: block``
|
|
293
|
+
|
|
294
|
+
**Skipping an entire function or class** -- place the pragma inline on the
|
|
295
|
+
definition line. The entire node (including all children) is skipped and no
|
|
296
|
+
trampoline is generated:
|
|
297
|
+
|
|
298
|
+
.. code-block:: python
|
|
299
|
+
|
|
300
|
+
def complex_algorithm(): # pragma: no mutate block
|
|
301
|
+
return some_complex_calculation()
|
|
302
|
+
|
|
303
|
+
class MySettings: # pragma: no mutate block
|
|
304
|
+
DEBUG = True
|
|
305
|
+
MAX_RETRIES = 3
|
|
306
|
+
|
|
307
|
+
**Skipping only the body of a function** -- place the pragma on its own line
|
|
308
|
+
inside the function. The function definition (including default arguments) is
|
|
309
|
+
still mutable, but the body is suppressed:
|
|
310
|
+
|
|
311
|
+
.. code-block:: python
|
|
312
|
+
|
|
313
|
+
def foo(val=1):
|
|
314
|
+
# pragma: no mutate block
|
|
315
|
+
x = 1
|
|
316
|
+
y = complex_calculation()
|
|
317
|
+
z = x + y
|
|
318
|
+
|
|
319
|
+
def bar():
|
|
320
|
+
# this function is still mutated normally
|
|
321
|
+
return 42
|
|
322
|
+
|
|
323
|
+
**Skipping an ``if`` branch without affecting ``elif``/``else``** -- place
|
|
324
|
+
the pragma inline on the ``if``. Only the ``if`` condition and its indented
|
|
325
|
+
body are suppressed; sibling branches (``elif``, ``else``) remain mutable
|
|
326
|
+
because they exit the original indentation scope:
|
|
327
|
+
|
|
328
|
+
.. code-block:: python
|
|
329
|
+
|
|
330
|
+
if error_condition: # pragma: no mutate block
|
|
331
|
+
log_error()
|
|
332
|
+
send_alert()
|
|
333
|
+
elif other_condition:
|
|
334
|
+
# still mutated -- this branch is outside the block scope
|
|
335
|
+
handle_other()
|
|
336
|
+
else:
|
|
337
|
+
# still mutated
|
|
338
|
+
handle_success()
|
|
339
|
+
|
|
340
|
+
The same principle applies to ``for``/``else``, ``while``/``else``,
|
|
341
|
+
``try``/``except``/``finally``, and ``match``/``case``.
|
|
342
|
+
|
|
343
|
+
This is useful for:
|
|
344
|
+
|
|
345
|
+
- Functions or classes that should be excluded from mutation entirely
|
|
346
|
+
- Error-handling branches that are hard to unit test in isolation
|
|
347
|
+
- Logging or telemetry blocks that don't affect program correctness
|
|
348
|
+
- Generated or boilerplate code within an otherwise mutable function
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
Skipping Code Regions
|
|
352
|
+
^^^^^^^^^^^^^^^^^^^^^
|
|
353
|
+
|
|
354
|
+
For suppressing mutations across a range of lines regardless of indentation,
|
|
355
|
+
use ``# pragma: no mutate start`` and ``# pragma: no mutate end``:
|
|
356
|
+
|
|
357
|
+
.. code-block:: python
|
|
358
|
+
|
|
359
|
+
a = mutate_this()
|
|
360
|
+
|
|
361
|
+
# pragma: no mutate start
|
|
362
|
+
b = skip_this()
|
|
363
|
+
c = skip_this_too()
|
|
364
|
+
# pragma: no mutate end
|
|
365
|
+
|
|
366
|
+
d = mutate_this_too()
|
|
367
|
+
|
|
368
|
+
Every line between the markers (inclusive) is suppressed. This works inside
|
|
369
|
+
functions, classes, or at module level, and ignores indentation entirely.
|
|
370
|
+
|
|
371
|
+
An unmatched ``# pragma: no mutate end`` without a preceding ``start`` raises
|
|
372
|
+
a ``PragmaParseError`` at parse time. An unclosed ``# pragma: no mutate start``
|
|
373
|
+
(no matching ``end`` before end-of-file) raises a ``PragmaParseError``.
|
|
374
|
+
Both errors include the filename and line number.
|
|
375
|
+
|
|
376
|
+
**Nesting restriction:** opening a new ``block`` or ``start`` context while
|
|
377
|
+
another context is already active is not allowed and raises a
|
|
378
|
+
``PragmaParseError``. The error message includes both the offending line and
|
|
379
|
+
the line where the existing context was opened. Close the current context
|
|
380
|
+
first (dedent for ``block``, or ``# pragma: no mutate end`` for ``start``)
|
|
381
|
+
before opening a new one.
|
|
382
|
+
|
|
228
383
|
|
|
229
384
|
Modifying pytest arguments
|
|
230
385
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
@@ -246,6 +401,20 @@ You can add and override pytest arguments:
|
|
|
246
401
|
also_copy = ["mutmut_pytest.ini"]
|
|
247
402
|
|
|
248
403
|
|
|
404
|
+
Unstable configs
|
|
405
|
+
~~~~~~~~~~~~~~~~
|
|
406
|
+
|
|
407
|
+
Following configurations exist, but may be changed in any minor version.
|
|
408
|
+
If you use them, expect that a new version could change or break this feature.
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
.. code-block:: toml
|
|
412
|
+
|
|
413
|
+
# Configure how long mutmut waits before killing a slow mutation
|
|
414
|
+
# Currently calculated as (duration_of_original_tests + timeout_constant) * timeout_multiplier seconds
|
|
415
|
+
timeout_constant = 1.0
|
|
416
|
+
timeout_multiplier = 15.0
|
|
417
|
+
|
|
249
418
|
|
|
250
419
|
Example mutations
|
|
251
420
|
-----------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mutmut"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.6.0"
|
|
4
4
|
description = "mutation testing for Python 3"
|
|
5
5
|
keywords = ["mutmut", "mutant", "mutation", "test", "testing"]
|
|
6
6
|
authors = [
|
|
@@ -54,9 +54,11 @@ source-include = ["HISTORY.rst"]
|
|
|
54
54
|
[dependency-groups]
|
|
55
55
|
dev = [
|
|
56
56
|
"inline-snapshot>=0.32.0",
|
|
57
|
-
"
|
|
57
|
+
"mypy>=1.20.1",
|
|
58
|
+
"pre-commit>=4.5.1",
|
|
59
|
+
"pyrefly>=0.59.1",
|
|
58
60
|
"pytest-asyncio>=1.0.0",
|
|
59
|
-
"ruff>=0.15.
|
|
61
|
+
"ruff>=0.15.7",
|
|
60
62
|
]
|
|
61
63
|
|
|
62
64
|
[tool.pytest.ini_options]
|
|
@@ -67,3 +69,34 @@ asyncio_default_fixture_loop_scope = "function"
|
|
|
67
69
|
|
|
68
70
|
[tool.inline-snapshot]
|
|
69
71
|
format-command="ruff format --stdin-filename {filename}"
|
|
72
|
+
|
|
73
|
+
[tool.ruff]
|
|
74
|
+
line-length = 120
|
|
75
|
+
target-version = "py310"
|
|
76
|
+
extend-exclude = [
|
|
77
|
+
"docs/_themes/",
|
|
78
|
+
"docs/conf.py",
|
|
79
|
+
"e2e_projects/",
|
|
80
|
+
"tests/data/test_generation/",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint]
|
|
84
|
+
select = ["E", "F", "W", "B", "C4", "UP", "I"]
|
|
85
|
+
ignore = [
|
|
86
|
+
"E501", # line too long - handled by formatter
|
|
87
|
+
"B007", # unused loop control variable
|
|
88
|
+
"B018", # useless expression
|
|
89
|
+
"B024", # abstract base class without abstract methods
|
|
90
|
+
"B027", # empty method in abstract base class without abstract decorator
|
|
91
|
+
"B028", # no explicit stacklevel in warnings.warn
|
|
92
|
+
"B904", # raise without from in except
|
|
93
|
+
"C405", # unnecessary list literal
|
|
94
|
+
"C408", # unnecessary dict() call
|
|
95
|
+
"C416", # unnecessary list comprehension
|
|
96
|
+
"F402", # import shadowed by loop variable
|
|
97
|
+
"F841", # local variable assigned but never used
|
|
98
|
+
"UP007", # use X | Y for type annotations
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
[tool.ruff.lint.isort]
|
|
102
|
+
force-single-line = true
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import warnings
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
from mutmut.configuration import Config
|
|
8
|
+
|
|
9
|
+
__version__ = importlib.metadata.version("mutmut")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
stats_time: float | None = None
|
|
13
|
+
duration_by_test: dict[str, float] = defaultdict(float)
|
|
14
|
+
tests_by_mangled_function_name: dict[str, set[str]] = defaultdict(set)
|
|
15
|
+
|
|
16
|
+
_stats: set[str] = set()
|
|
17
|
+
_covered_lines: dict[str, set[int]] | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def __getattr__(name: str) -> object:
|
|
21
|
+
match name:
|
|
22
|
+
case "config":
|
|
23
|
+
warnings.warn(
|
|
24
|
+
"mutmut.config is deprecated as of 3.4.1, use mutmut.configuration.Config.get() instead",
|
|
25
|
+
FutureWarning,
|
|
26
|
+
stacklevel=2,
|
|
27
|
+
)
|
|
28
|
+
return Config.get()
|
|
29
|
+
case _:
|
|
30
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _reset_globals() -> None:
|
|
34
|
+
global duration_by_test, stats_time, _stats, tests_by_mangled_function_name
|
|
35
|
+
global _covered_lines
|
|
36
|
+
|
|
37
|
+
duration_by_test.clear()
|
|
38
|
+
stats_time = None
|
|
39
|
+
Config.reset()
|
|
40
|
+
_stats = set()
|
|
41
|
+
tests_by_mangled_function_name = defaultdict(set)
|
|
42
|
+
_covered_lines = None
|