mutmut 3.4.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.
Files changed (31) hide show
  1. {mutmut-3.4.0 → mutmut-3.6.0}/HISTORY.rst +57 -0
  2. {mutmut-3.4.0 → mutmut-3.6.0}/PKG-INFO +213 -15
  3. mutmut-3.6.0/README.rst +449 -0
  4. {mutmut-3.4.0 → mutmut-3.6.0}/pyproject.toml +42 -2
  5. mutmut-3.6.0/src/mutmut/__init__.py +42 -0
  6. mutmut-3.6.0/src/mutmut/__main__.py +1550 -0
  7. {mutmut-3.4.0 → mutmut-3.6.0}/src/mutmut/code_coverage.py +32 -26
  8. mutmut-3.6.0/src/mutmut/configuration.py +207 -0
  9. mutmut-3.6.0/src/mutmut/mutation/__init__.py +0 -0
  10. mutmut-3.6.0/src/mutmut/mutation/data.py +61 -0
  11. mutmut-3.6.0/src/mutmut/mutation/file_mutation.py +508 -0
  12. mutmut-3.4.0/src/mutmut/node_mutation.py → mutmut-3.6.0/src/mutmut/mutation/mutators.py +65 -71
  13. mutmut-3.6.0/src/mutmut/mutation/pragma_handling.py +229 -0
  14. mutmut-3.6.0/src/mutmut/mutation/trampoline.py +105 -0
  15. mutmut-3.6.0/src/mutmut/mutation/trampoline_templates.py +54 -0
  16. mutmut-3.6.0/src/mutmut/py.typed +0 -0
  17. mutmut-3.6.0/src/mutmut/skills/kill-survivors.md +18 -0
  18. mutmut-3.6.0/src/mutmut/threading/__init__.py +0 -0
  19. mutmut-3.6.0/src/mutmut/threading/timeout.py +57 -0
  20. mutmut-3.6.0/src/mutmut/type_checking.py +112 -0
  21. mutmut-3.6.0/src/mutmut/utils/__init__.py +0 -0
  22. mutmut-3.6.0/src/mutmut/utils/file_utils.py +14 -0
  23. mutmut-3.6.0/src/mutmut/utils/format_utils.py +55 -0
  24. mutmut-3.6.0/src/mutmut/utils/safe_setproctitle.py +24 -0
  25. mutmut-3.4.0/README.rst +0 -252
  26. mutmut-3.4.0/src/mutmut/__init__.py +0 -24
  27. mutmut-3.4.0/src/mutmut/__main__.py +0 -1527
  28. mutmut-3.4.0/src/mutmut/file_mutation.py +0 -304
  29. mutmut-3.4.0/src/mutmut/trampoline_templates.py +0 -73
  30. {mutmut-3.4.0 → mutmut-3.6.0}/LICENSE +0 -0
  31. {mutmut-3.4.0 → mutmut-3.6.0}/src/mutmut/result_browser_layout.tcss +0 -0
@@ -1,6 +1,63 @@
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
+
45
+ 3.5.0
46
+ ~~~~~
47
+
48
+ * Filter mutants based on type annotations. This feature needs to be turned on with the `type_check_command` config.
49
+
50
+ * Show error message when running on Windows
51
+
52
+ * Save mutation stats for use with CI/CD pipeline (#460)
53
+
54
+ * Async functions fixed
55
+
56
+ * Fix for functions with positional only arguments
57
+
58
+ * Support signatures with PEP 649 deferred annotations
59
+
60
+
4
61
  3.4.0
5
62
  ~~~~~
6
63
 
@@ -1,7 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mutmut
3
- Version: 3.4.0
3
+ Version: 3.6.0
4
4
  Summary: mutation testing for Python 3
5
+ Keywords: mutmut,mutant,mutation,test,testing
5
6
  Author: Anders Hovmöller
6
7
  Author-email: Anders Hovmöller <boxed@killingar.net>
7
8
  License-Expression: BSD-3-Clause
@@ -85,8 +86,7 @@ it will try to figure out where the code to mutate is.
85
86
 
86
87
 
87
88
  You can stop the mutation run at any time and mutmut will restart where you
88
- left off. It will continue where it left off, and re-test functions that were
89
- modified since last run.
89
+ left off.
90
90
 
91
91
  To work with the results, use `mutmut browse` where you can see the mutants,
92
92
  retest them when you've updated your tests.
@@ -121,7 +121,7 @@ In `setup.cfg` in the root of your project you can configure mutmut if you need
121
121
  .. code-block:: ini
122
122
 
123
123
  [mutmut]
124
- paths_to_mutate=src/
124
+ source_paths=src/
125
125
  pytest_add_cli_args_test_selection=tests/
126
126
 
127
127
  If you use `pyproject.toml`, you must specify the paths as array in a `tool.mutmut` section:
@@ -129,7 +129,7 @@ If you use `pyproject.toml`, you must specify the paths as array in a `tool.mutm
129
129
  .. code-block:: toml
130
130
 
131
131
  [tool.mutmut]
132
- paths_to_mutate = [ "src/" ]
132
+ source_paths = [ "src/" ]
133
133
  pytest_add_cli_args_test_selection= [ "tests/" ]
134
134
 
135
135
  See below for more options for configuring mutmut.
@@ -161,8 +161,8 @@ introduced bug in those base functions will lead to many tests that fail which
161
161
  are hard to understand how they relate to the function with the change.
162
162
 
163
163
  You can configure mutmut to only count a test as being relevant for a function
164
- if the stack depth from the test to the function is below some limit. In your
165
- `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:
166
166
 
167
167
  .. code-block:: ini
168
168
 
@@ -176,10 +176,14 @@ caught.
176
176
  Exclude files from mutation
177
177
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
178
178
 
179
+ By default mutmut mutates all python files in `source_paths`.
179
180
  You can exclude files from mutation in `setup.cfg`:
180
181
 
181
182
  .. code-block::
182
183
 
184
+ only_mutate=
185
+ src/api/*
186
+ src/services/*
183
187
  do_not_mutate=
184
188
  *__tests.py
185
189
 
@@ -199,6 +203,34 @@ If you only want to mutate lines that are called (according to coverage.py), you
199
203
  mutate_only_covered_lines=true
200
204
 
201
205
 
206
+ Filter generated mutants with type checker
207
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
208
+
209
+ When your project is type checked using `mypy` or `pyrefly`, you can also use it to filter out invalid mutants.
210
+ For instance, mutmut mutates `x: str = 'foo'` to `x: str = None` which can easily caught by type checkers.
211
+
212
+ Using this filter can improve performance and reduce noise, however it can also hide a few relevant mutations:
213
+
214
+ 1. `x: str = None` may not be valid, but if your tests do not detect such a change it indicates that
215
+ the value of `x` is not properly tested (even if your type checker would catch this particular modification)
216
+ 2. In some edge cases with class properties (usually in the `__init__` method), the way `mypy` and `pyrefly` infer types does not work well
217
+ with the way mutmut mutates code. Some valid mutations like changing `self.x = 123` to `self.x = None` can
218
+ be filtered out, even though the may be valid.
219
+
220
+ To enable this filtering, configure the `type_check_command` to output json results as follows:
221
+
222
+ .. code-block::
223
+
224
+ # for pyrefly
225
+ type_check_command = ['pyrefly', 'check', '--output-format=json']
226
+ # for mypy
227
+ type_check_command = ['mypy', 'your_source_dir', '--output', 'json', '--disable-error-code', 'unused-ignore']
228
+
229
+ Currently, only `pyrefly` and `mypy` are supported.
230
+ With `pyright` and `ty`, mutating a class method `Foo.bar()` can break the types of all methods of `Foo`,
231
+ and therefore mutmut cannot match the type error with the mutant that caused the type error.
232
+
233
+
202
234
  Enable debug output (increase verbosity)
203
235
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204
236
 
@@ -213,22 +245,174 @@ to failing tests.
213
245
  debug=true
214
246
 
215
247
 
216
- Whitelisting
217
- ~~~~~~~~~~~~
248
+ Disable setproctitle (macOS)
249
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
218
250
 
219
- You can mark lines like this:
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.
220
255
 
221
- .. code-block:: python
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``:
222
259
 
223
- some_code_here() # pragma: no mutate
260
+ .. code-block:: toml
224
261
 
225
- to stop mutation on those lines. Some cases we've found where you need to
226
- whitelist lines are:
262
+ # pyproject.toml
263
+ [tool.mutmut]
264
+ use_setproctitle = false
227
265
 
228
- - The version string on your library. You really shouldn't have a test for this :P
266
+
267
+ Disabling mutation on specific code
268
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
269
+
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")`)
229
274
  - Optimizing break instead of continue. The code runs fine when mutating break
230
275
  to continue, but it's slower.
231
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
+
232
416
 
233
417
  Modifying pytest arguments
234
418
  ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -250,6 +434,20 @@ You can add and override pytest arguments:
250
434
  also_copy = ["mutmut_pytest.ini"]
251
435
 
252
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
+
253
451
 
254
452
  Example mutations
255
453
  -----------------