mutmut 3.5.0__tar.gz → 3.7.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 (33) hide show
  1. {mutmut-3.5.0 → mutmut-3.7.0}/HISTORY.rst +69 -1
  2. {mutmut-3.5.0 → mutmut-3.7.0}/PKG-INFO +259 -19
  3. mutmut-3.7.0/README.rst +520 -0
  4. {mutmut-3.5.0 → mutmut-3.7.0}/pyproject.toml +36 -3
  5. mutmut-3.7.0/src/mutmut/__init__.py +44 -0
  6. mutmut-3.7.0/src/mutmut/__main__.py +2001 -0
  7. {mutmut-3.5.0 → mutmut-3.7.0}/src/mutmut/code_coverage.py +32 -25
  8. mutmut-3.7.0/src/mutmut/configuration.py +252 -0
  9. mutmut-3.7.0/src/mutmut/core.py +21 -0
  10. mutmut-3.7.0/src/mutmut/mutation/__init__.py +0 -0
  11. mutmut-3.7.0/src/mutmut/mutation/data.py +156 -0
  12. mutmut-3.7.0/src/mutmut/mutation/file_mutation.py +636 -0
  13. mutmut-3.5.0/src/mutmut/node_mutation.py → mutmut-3.7.0/src/mutmut/mutation/mutators.py +65 -71
  14. mutmut-3.7.0/src/mutmut/mutation/pragma_handling.py +229 -0
  15. mutmut-3.7.0/src/mutmut/mutation/trampoline.py +144 -0
  16. mutmut-3.7.0/src/mutmut/mutation/trampoline_templates.py +54 -0
  17. mutmut-3.7.0/src/mutmut/py.typed +0 -0
  18. mutmut-3.7.0/src/mutmut/state.py +36 -0
  19. mutmut-3.7.0/src/mutmut/threading/__init__.py +0 -0
  20. mutmut-3.7.0/src/mutmut/threading/timeout.py +57 -0
  21. mutmut-3.7.0/src/mutmut/type_checking.py +112 -0
  22. mutmut-3.7.0/src/mutmut/utils/__init__.py +0 -0
  23. mutmut-3.7.0/src/mutmut/utils/file_utils.py +14 -0
  24. mutmut-3.7.0/src/mutmut/utils/format_utils.py +68 -0
  25. mutmut-3.7.0/src/mutmut/utils/safe_setproctitle.py +24 -0
  26. mutmut-3.5.0/README.rst +0 -280
  27. mutmut-3.5.0/src/mutmut/__init__.py +0 -24
  28. mutmut-3.5.0/src/mutmut/__main__.py +0 -1716
  29. mutmut-3.5.0/src/mutmut/file_mutation.py +0 -402
  30. mutmut-3.5.0/src/mutmut/trampoline_templates.py +0 -56
  31. mutmut-3.5.0/src/mutmut/type_checking.py +0 -98
  32. {mutmut-3.5.0 → mutmut-3.7.0}/LICENSE +0 -0
  33. {mutmut-3.5.0 → mutmut-3.7.0}/src/mutmut/result_browser_layout.tcss +0 -0
@@ -1,12 +1,80 @@
1
1
  Changelog
2
2
  ---------
3
3
 
4
+ 3.7.0
5
+ ~~~~~
6
+
7
+ * Fix the trampoline dropping a generator's return value, so ``yield from`` on a mutated generator no longer yields ``None`` instead of its result
8
+
9
+ * Fix async generators, so that `aclose()`/`athrow()` are handled by the `except`/`finally` blocks of the mutated function instead of being swallowed by the trampoline
10
+
11
+ * Fix `max_stack_depth` when a test changes the working directory (for example via a tmpdir fixture)
12
+
13
+ * Fix `type_check_command` reporting type errors in copied files that have no mutants
14
+
15
+ * Per-function source hashing for incremental cache invalidation — only re-test mutants in functions that changed
16
+
17
+ * Cross-call dependency tracking — invalidate mutants in callers when a called function changes
18
+
19
+ * Use git to detect non-Python dependency file changes; falls back to a curated file list when git is unavailable
20
+
21
+ * Add `cache_invalidation_exclude` config to suppress noisy files from change detection
22
+
23
+ * Add `use_git_change_detection` config (default true) to opt out of git-based detection
24
+
25
+ * Add `uv.lock`, `poetry.lock`, `Pipfile.lock`, and `pdm.lock` to the list of files copied by default
26
+
27
+ * Invalidate cached results automatically when result-affecting config fields change
28
+
29
+ * Make `mutmut browse`, `show` and `apply` much faster and use far less memory, by storing where each mutant is in the mutated file instead of parsing the whole file to find it
30
+
31
+ 3.6.0
32
+ ~~~~~
33
+
34
+ * Add `# pragma: no mutate block` and `# pragma: no mutate start/end` comments
35
+
36
+ * Add `do_not_mutate_patterns` to disable mutations with a regex
37
+
38
+ * Add `only_mutate` config to select which files get mutated
39
+
40
+ * Also mutate methods decorated with (only) `@staticmethod` or `@classmethod`
41
+
42
+ * Add `use_setproctitle` option to disable process renaming (automatically disabled on MacOS)
43
+
44
+ * Add (unstable) `timeout_multiplier` and `timeout_constant` options
45
+
46
+ * Rename `paths_to_mutate` to `source_paths`
47
+
48
+ * Deprecate `tests_dir` in favor of `pytest_add_cli_args_test_selection`
49
+
50
+ * Change `max_stack_depth` to only consider functions inside `source_paths`
51
+
52
+ * Disable mutation of enums and `@staticmethod`/`@classmethod` methods when `type_check_command` is set, as these mutations break type checking
53
+
54
+ * Fix mutation for enum class methods
55
+
56
+ * Fix mutate_only_covered_lines when project uses custom coverage.py config
57
+
58
+ * Fix execution when running mutmut via `python -m mutmut run`
59
+
60
+ * Fix mutation of `class _SomePrivateClass` class methods
61
+
62
+ * Fix mutation of default args
63
+
64
+ * Fix `mutmut browse` crash when no file is selected yet
65
+
66
+ * Fix timeout checker looking up the wrong mutant's expected test time, which could cause mutants to hang
67
+
68
+ * Compare stats paths after resolving symlinks
69
+
70
+ * Warn when mutmut cannot match mutants with the collected stats
71
+
4
72
  3.5.0
5
73
  ~~~~~
6
74
 
7
75
  * Filter mutants based on type annotations. This feature needs to be turned on with the `type_check_command` config.
8
76
 
9
- * Support running on Windows
77
+ * Show error message when running on Windows
10
78
 
11
79
  * Save mutation stats for use with CI/CD pipeline (#460)
12
80
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mutmut
3
- Version: 3.5.0
3
+ Version: 3.7.0
4
4
  Summary: mutation testing for Python 3
5
5
  Keywords: mutmut,mutant,mutation,test,testing
6
6
  Author: Anders Hovmöller
@@ -80,14 +80,13 @@ You can get started with a simple:
80
80
  pip install mutmut
81
81
  mutmut run
82
82
 
83
- This will by run pytest on tests in the "tests" or "test" folder and
83
+ This will run pytest on tests in the "tests" or "test" folder and
84
84
  it will try to figure out where the code to mutate is.
85
85
 
86
86
 
87
87
 
88
88
  You can stop the mutation run at any time and mutmut will restart where you
89
- left off. It will continue where it left off, and re-test functions that were
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.
@@ -97,7 +96,7 @@ You can also write a mutant to disk from the `browse` interface, or via
97
96
  source code control and committed before you apply a mutant!
98
97
 
99
98
 
100
- If during the installation you get an error for the `libcst` dependency mentioning the lack of a rust compiler on your system, it is because your architecture does not have a prebuilt binary for `libcst` and it requires both `rustc` and `cargo` from the [rust toolchain](https://www.rust-lang.org/tools/install) to be built. This is known for at least the `x86_64-darwin` architecture.
99
+ If during the installation you get an error for the `libcst` dependency mentioning the lack of a rust compiler on your system, it is because your architecture does not have a prebuilt binary for `libcst` and it requires both `rustc` and `cargo` from the `rust toolchain <https://www.rust-lang.org/tools/install>`_ to be built. This is known for at least the `x86_64-darwin` architecture.
101
100
 
102
101
 
103
102
  Wildcards for testing mutants
@@ -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
- paths_to_mutate=src/
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
- paths_to_mutate = [ "src/" ]
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 from the test to the function is below some limit. In your
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', 'traces_parser', '--output', 'json']
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
- Whitelisting
246
- ~~~~~~~~~~~~
248
+ Disable setproctitle (macOS)
249
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
247
250
 
248
- 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.
249
255
 
250
- .. 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``:
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
- to stop mutation on those lines. Some cases we've found where you need to
255
- whitelist lines are:
267
+ Disabling mutation on specific code
268
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
256
269
 
257
- - The version string on your library. You really shouldn't have a test for this :P
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,91 @@ You can add and override pytest arguments:
279
434
  also_copy = ["mutmut_pytest.ini"]
280
435
 
281
436
 
437
+ Detecting dependency and config changes
438
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
439
+
440
+ Between runs, mutmut only re-tests mutants in functions whose source changed.
441
+ Changes outside your Python source — a dependency upgrade, a data file, a
442
+ config file — cannot be tied to a function, so they would otherwise be missed
443
+ and you would get cached results that no longer reflect reality.
444
+
445
+ To catch this, mutmut detects non-Python files that changed since the last full
446
+ run and warns you about them. If your project is a git repository and git is
447
+ installed, mutmut uses git (a soft dependency no extra package is required) to
448
+ find every changed non-Python file, respecting your `.gitignore`. Python files
449
+ are excluded because their changes are already tracked per function.
450
+
451
+ On a full run with git available, mutmut also records the content hashes of the
452
+ tracked non-Python files. This means a later run in an environment without git
453
+ (for example a different CI stage) can still detect changes to that known set of
454
+ files, even though it cannot discover brand-new ones.
455
+
456
+ When git is unavailable, mutmut falls back to hashing a curated set of build and
457
+ dependency files:
458
+
459
+ - `pyproject.toml`
460
+ - `setup.cfg`
461
+ - `setup.py`
462
+ - `requirements*.txt`
463
+ - `poetry.lock`
464
+ - `uv.lock`
465
+ - `Pipfile`
466
+ - `Pipfile.lock`
467
+
468
+ You can watch additional files (for example data files your tests depend on)
469
+ with the `cache_invalidation_files` config, which accepts glob patterns
470
+ resolved against the project root. These are checked even when git ignores them,
471
+ and are never dropped by the exclusions below:
472
+
473
+ .. code-block:: toml
474
+
475
+ cache_invalidation_files = [ "queries/*.sql", "config/*.yaml" ]
476
+
477
+ Git detection reports every changed non-Python file, so mutmut drops files that
478
+ practically never affect tests (markdown, `LICENSE`, `CHANGELOG`, `docs/`, git
479
+ and editor metadata, ...). Exclude additional noisy files with
480
+ `cache_invalidation_exclude` (glob patterns, `*` spans directories):
481
+
482
+ .. code-block:: toml
483
+
484
+ cache_invalidation_exclude = [ "*.json", "fixtures/snapshots/*" ]
485
+
486
+ When a watched file changes, `on_dependency_change` controls what happens:
487
+
488
+ - `warn` (default): list the changed files and keep the cache.
489
+ - `rerun`: re-test all mutants.
490
+ - `ignore`: do nothing.
491
+
492
+ .. code-block:: toml
493
+
494
+ on_dependency_change = "warn"
495
+
496
+ Git detection is on by default; disable it (forcing the curated-list fallback)
497
+ with:
498
+
499
+ .. code-block:: toml
500
+
501
+ use_git_change_detection = false
502
+
503
+ Changes to mutmut's own result-affecting config (such as `pytest_add_cli_args`,
504
+ `type_check_command`, or the timeout settings) are always detected and
505
+ invalidate the affected cached results automatically.
506
+
507
+
508
+ Unstable configs
509
+ ~~~~~~~~~~~~~~~~
510
+
511
+ Following configurations exist, but may be changed in any minor version.
512
+ If you use them, expect that a new version could change or break this feature.
513
+
514
+
515
+ .. code-block:: toml
516
+
517
+ # Configure how long mutmut waits before killing a slow mutation
518
+ # Currently calculated as (duration_of_original_tests + timeout_constant) * timeout_multiplier seconds
519
+ timeout_constant = 1.0
520
+ timeout_multiplier = 15.0
521
+
282
522
 
283
523
  Example mutations
284
524
  -----------------
@@ -304,7 +544,7 @@ This section describes how to work with mutmut to enhance your test suite.
304
544
  4. Press `r` to rerun the mutant and see if you successfully managed to kill it.
305
545
 
306
546
  Mutmut keeps the data of what it has done and the mutants in the `mutants/`
307
- directory.If you want to make sure you run a full mutmut run you can delete
547
+ directory. If you want to make sure you run a full mutmut run you can delete
308
548
  this directory to start from scratch.
309
549
 
310
550
  Contributing to Mutmut