mutmut 2.4.4__tar.gz → 2.5.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.
@@ -28,3 +28,12 @@ Credits
28
28
  * Daniel Versoza Alves <daniel.alves@voxy.com>
29
29
  * Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>
30
30
  * Jan Królikowski <yanekk86@gmail.com>
31
+ * Tal Amuyal <talamuyal@gmail.com>
32
+ * Eivind Jahren <eivind.jahren@gmail.com>
33
+ * WiredNerd <pbuschmail-gitlab@yahoo.com>
34
+ * Kees Hink <kees@fourdigits.nl>
35
+ * Jim Rybarski <jim@rybarski.com>
36
+ * Christopher J. Brody <chris.brody+brodybits@gmail.com>
37
+ * Century-ss <hgfc7543ggfdch@gmail.com>
38
+ * Michael Champagne <michael.champagne@elapsetech.com>
39
+ * Florian Trudel
@@ -1,6 +1,26 @@
1
1
  Changelog
2
2
  ---------
3
3
 
4
+ 2.5.0
5
+ ~~~~~
6
+
7
+ * Add cli option to specify html report directory
8
+
9
+ * Fix use patch file under Windows
10
+
11
+ * Improve documentation about cache issues
12
+
13
+ 2.4.5
14
+ ~~~~~
15
+
16
+ * Add skipped column in html index report
17
+
18
+ * Untested/skipped in show all does not show skipped
19
+
20
+ * mutmut should pick up the config before running the original tests. That allows to specify test suits you want to run
21
+
22
+ * improved some high-level help text & documentation
23
+
4
24
  2.4.4
5
25
  ~~~~~
6
26
 
mutmut-2.5.0/PKG-INFO ADDED
@@ -0,0 +1,422 @@
1
+ Metadata-Version: 2.1
2
+ Name: mutmut
3
+ Version: 2.5.0
4
+ Summary: mutation testing for Python 3
5
+ Home-page: https://github.com/boxed/mutmut
6
+ Author: Anders Hovmöller
7
+ Author-email: boxed@killingar.net
8
+ License: BSD
9
+ Keywords: mutmut mutant mutation test testing
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.7
16
+ Requires-Python: >=3.7
17
+ License-File: LICENSE
18
+ License-File: AUTHORS.rst
19
+ Requires-Dist: glob2
20
+ Requires-Dist: parso
21
+ Requires-Dist: click
22
+ Requires-Dist: pony
23
+ Requires-Dist: junit-xml<2,>=1.8
24
+ Requires-Dist: toml
25
+ Provides-Extra: pytest
26
+ Requires-Dist: pytest; extra == "pytest"
27
+ Requires-Dist: pytest-cov; extra == "pytest"
28
+ Provides-Extra: coverage
29
+ Requires-Dist: coverage; extra == "coverage"
30
+ Provides-Extra: patch
31
+ Requires-Dist: whatthepatch==0.0.6; extra == "patch"
32
+
33
+ mutmut - python mutation tester
34
+ ===============================
35
+
36
+ .. image:: https://travis-ci.org/boxed/mutmut.svg?branch=master
37
+ :target: https://travis-ci.org/boxed/mutmut
38
+
39
+ .. image:: https://readthedocs.org/projects/mutmut/badge/?version=latest
40
+ :target: https://mutmut.readthedocs.io/en/latest/?badge=latest
41
+ :alt: Documentation Status
42
+
43
+ .. image:: https://codecov.io/gh/boxed/mutmut/branch/master/graph/badge.svg
44
+ :target: https://codecov.io/gh/boxed/mutmut
45
+
46
+ .. image:: https://img.shields.io/discord/767914934016802818.svg
47
+ :target: https://discord.gg/cwb9uNt
48
+
49
+ Mutmut is a mutation testing system for Python, with a strong focus on ease
50
+ of use. If you don't know what mutation testing is try starting with
51
+ `this article <https://hackernoon.com/mutmut-a-python-mutation-testing-system-9b9639356c78>`_.
52
+
53
+ Some highlight features:
54
+
55
+ - Found mutants can be applied on disk with a simple command making it very
56
+ easy to work with the results
57
+ - Remembers work that has been done, so you can work incrementally
58
+ - Supports all test runners (because mutmut only needs an exit code from the
59
+ test command)
60
+ - If you use the `hammett <https://github.com/boxed/hammett>`_ test runner
61
+ you can go extremely fast! There's special handling for this runner
62
+ that has some pretty dramatic results.
63
+ - Can use coverage data to only do mutation testing on covered lines
64
+ - Battle tested on real libraries by multiple companies
65
+
66
+
67
+ If you need to run mutmut on a python 2 code base use mutmut ``1.5.0``. Mutmut
68
+ ``1.9.0`` is the last version to support python ``3.4``, ``3.5`` and ``3.6``.
69
+
70
+
71
+ Install and run
72
+ ---------------
73
+
74
+ You can get started with a simple:
75
+
76
+ .. code-block:: console
77
+
78
+ pip install mutmut
79
+ mutmut run
80
+
81
+ This will by default run pytest (or unittest if pytest is unavailable)
82
+ on tests in the "tests" or "test" folder and
83
+ it will try to figure out where the code to mutate lies.
84
+
85
+ NOTE that mutmut will apply the mutations directly, one at a time;
86
+ it is **highly** recommended to add all changes to source control
87
+ before running.
88
+
89
+ Enter
90
+
91
+ .. code-block:: console
92
+
93
+ mutmut run --help
94
+
95
+ for the available flags, to use other runners, etc. The recommended way to use
96
+ mutmut if the defaults aren't working for you is to add a
97
+ block in ``setup.cfg`` or ``project.toml``.
98
+ Then when you come back to mutmut weeks later you don't have to figure out the
99
+ flags again, just run ``mutmut run`` and it works.
100
+ Like this in ``setup.cfg``:
101
+
102
+ .. code-block:: ini
103
+
104
+ [mutmut]
105
+ paths_to_mutate=src/
106
+ backup=False
107
+ runner=python -m hammett -x
108
+ tests_dir=tests/
109
+ dict_synonyms=Struct, NamedStruct
110
+
111
+ or like this in ``pyproject.toml``:
112
+
113
+ .. code-block:: ini
114
+
115
+ [tool.mutmut]
116
+ paths_to_mutate="src"
117
+ runner="python -m hammett -x"
118
+
119
+ To use multiple paths either in the ``paths_to_mutate`` or ``tests_dir`` option
120
+ use a comma or colon separated list. For example:
121
+
122
+ .. code-block:: ini
123
+
124
+ [mutmut]
125
+ paths_to_mutate=src/,src2/
126
+ tests_dir=tests/:tests2/
127
+
128
+ You can stop the mutation run at any time and mutmut will restart where you
129
+ left off. It's also smart enough to retest only the surviving mutants when the
130
+ test suite changes.
131
+
132
+ To print the results run ``mutmut show``. It will give you a list of the mutants
133
+ grouped by file. You can now look at a specific mutant diff with ``mutmut show 3``,
134
+ all mutants for a specific file with ``mutmut show path/to/file.py`` or all mutants
135
+ with ``mutmut show all``.
136
+
137
+ You can also write a mutant to disk with ``mutmut apply 3``. You should **REALLY**
138
+ have the file you mutate under source code control and committed before you apply
139
+ a mutant!
140
+
141
+ To generate a HTML report for a web browser: ``mutmut html``
142
+
143
+ Whitelisting
144
+ ------------
145
+
146
+ You can mark lines like this:
147
+
148
+ .. code-block:: python
149
+
150
+ some_code_here() # pragma: no mutate
151
+
152
+ to stop mutation on those lines. Some cases we've found where you need to
153
+ whitelist lines are:
154
+
155
+ - The version string on your library. You really shouldn't have a test for this :P
156
+ - Optimizing break instead of continue. The code runs fine when mutating break
157
+ to continue, but it's slower.
158
+
159
+ See also `Advanced whitelisting and configuration`_
160
+
161
+
162
+ Example mutations
163
+ -----------------
164
+
165
+ - Integer literals are changed by adding 1. So 0 becomes 1, 5 becomes 6, etc.
166
+ - ``<`` is changed to ``<=``
167
+ - break is changed to continue and vice versa
168
+
169
+ In general the idea is that the mutations should be as subtle as possible.
170
+ See ``__init__.py`` for the full list.
171
+
172
+
173
+ Workflow
174
+ --------
175
+
176
+ This section describes how to work with mutmut to enhance your test suite.
177
+
178
+ 1. Run mutmut with ``mutmut run``. A full run is preferred but if you're just
179
+ getting started you can exit in the middle and start working with what you
180
+ have found so far.
181
+ 2. Show the mutants with ``mutmut results``
182
+ 3. Apply a surviving mutant to disk running ``mutmut apply 3`` (replace 3 with
183
+ the relevant mutant ID from ``mutmut results``)
184
+ 4. Write a new test that fails
185
+ 5. Revert the mutant on disk
186
+ 6. Rerun the new test to see that it now passes
187
+ 7. Go back to point 2.
188
+
189
+ Mutmut keeps a result cache in ``.mutmut-cache`` so if you want to make sure you
190
+ run a full mutmut run just delete this file.
191
+
192
+ If you want to re-run all survivors after changing a lot of code or even the configuration,
193
+ you can use `for ID in $(mutmut result-ids survived); do mutmut run $ID; done` (for bash).
194
+
195
+ You can also tell mutmut to just check a single mutant:
196
+
197
+ .. code-block:: console
198
+
199
+ mutmut run 3
200
+
201
+
202
+ Advanced whitelisting and configuration
203
+ ---------------------------------------
204
+
205
+ mutmut has an advanced configuration system. You create a file called
206
+ ``mutmut_config.py``. You can define two functions there: ``init()`` and
207
+ ``pre_mutation(context)``. ``init`` gets called when mutmut starts and
208
+ ``pre_mutation`` gets called before each mutant is applied and tested. You can
209
+ mutate the ``context`` object as you need. You can modify the test command like
210
+ this:
211
+
212
+ .. code-block:: python
213
+
214
+ def pre_mutation(context):
215
+ context.config.test_command = 'python -m pytest -x ' + something_else
216
+
217
+ or skip a mutant:
218
+
219
+ .. code-block:: python
220
+
221
+ def pre_mutation(context):
222
+ if context.filename == 'foo.py':
223
+ context.skip = True
224
+
225
+ or skip logging:
226
+
227
+
228
+ .. code-block:: python
229
+
230
+ def pre_mutation(context):
231
+ line = context.current_source_line.strip()
232
+ if line.startswith('log.'):
233
+ context.skip = True
234
+
235
+ look at the code for the ``Context`` class for what you can modify. Please
236
+ open a github issue if you need help.
237
+
238
+ It is also possible to disable mutation of specific node types by passing the
239
+ ``--disable-mutation-types`` option. Multiple types can be specified by separating them
240
+ by comma:
241
+
242
+ .. code-block:: console
243
+
244
+ mutmut run --disable-mutation-types=string,decorator
245
+
246
+ Inversely, you can also only specify to only run specific mutations with ``--enable-mutation-types``.
247
+ Note that ``--disable-mutation-types`` and ``--enable-mutation-types`` are exclusive and cannot
248
+ be combined.
249
+
250
+ Changes made to ``mutmut_config.py`` does not invalidate the cache.
251
+ If you modify the configuration file, you will likely need to remove ``.mutmut-cache`` manually to ensure that the changes take effect.
252
+
253
+ Selecting tests to run
254
+ ----------------------
255
+
256
+ If you have a large test suite or long running tests, it can be beneficial to narrow the set of tests to
257
+ run for each mutant down to the tests that have a chance of killing it.
258
+ Determining the relevant subset of tests depends on your project, its structure, and the metadata that you
259
+ know about your tests.
260
+ ``mutmut`` provides information like the file to mutate and `coverage contexts <https://coverage.readthedocs.io/en/coverage-5.5/contexts.html>`_
261
+ (if used with the ``--use-coverage`` switch).
262
+ You can set the ``context.config.test_command`` in the ``pre_mutation(context)`` hook of ``mutmut_config.py``.
263
+ The ``test_command`` is reset after each mutant, so you don't have to explicitly (re)set it for each mutant.
264
+
265
+ This section gives examples to show how this could be done for some concrete use cases.
266
+ All examples use the default test runner (``python -m pytest -x --assert=plain``).
267
+
268
+ Selection based on source and test layout
269
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
270
+
271
+ If the location of the test module has a strict correlation with your source code layout, you can simply
272
+ construct the path to the corresponding test file from ``context.filename``.
273
+ Suppose your layout follows the following structure where the test file is always located right beside the
274
+ production code:
275
+
276
+ .. code-block:: console
277
+
278
+ mypackage
279
+ ├── production_module.py
280
+ ├── test_production_module.py
281
+ └── subpackage
282
+ ├── submodule.py
283
+ └── test_submodule.py
284
+
285
+ Your ``mutmut_config.py`` in this case would look like this:
286
+
287
+ .. code-block:: python
288
+
289
+ import os.path
290
+
291
+ def pre_mutation(context):
292
+ dirname, filename = os.path.split(context.filename)
293
+ testfile = "test_" + filename
294
+ context.config.test_command += ' ' + os.path.join(dirname, testfile)
295
+
296
+ Selection based on imports
297
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
298
+
299
+ If you can't rely on the directory structure or naming of the test files, you may assume that the tests most likely
300
+ to kill the mutant are located in test files that directly import the module that is affected by the mutant.
301
+ Using the ``ast`` module of the Python standard library, you can use the ``init()`` hook to build a map which test file
302
+ imports which module, and then lookup all test files importing the mutated module and only run those:
303
+
304
+ .. code-block:: python
305
+
306
+ import ast
307
+ from pathlib import Path
308
+
309
+ test_imports = {}
310
+
311
+
312
+ class ImportVisitor(ast.NodeVisitor):
313
+ """Visitor which records which modules are imported."""
314
+ def __init__(self) -> None:
315
+ super().__init__()
316
+ self.imports = []
317
+
318
+ def visit_Import(self, node: ast.Import) -> None:
319
+ for alias in node.names:
320
+ self.imports.append(alias.name)
321
+
322
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
323
+ self.imports.append(node.module)
324
+
325
+
326
+ def init():
327
+ """Find all test files located under the 'tests' directory and create an abstract syntax tree for each.
328
+ Let the ``ImportVisitor`` find out what modules they import and store the information in a global dictionary
329
+ which can be accessed by ``pre_mutation(context)``."""
330
+ test_files = (Path(__file__).parent / "tests").rglob("test*.py")
331
+ for fpath in test_files:
332
+ visitor = ImportVisitor()
333
+ visitor.visit(ast.parse(fpath.read_bytes()))
334
+ test_imports[str(fpath)] = visitor.imports
335
+
336
+
337
+ def pre_mutation(context):
338
+ """Construct the module name from the filename and run all test files which import that module."""
339
+ tests_to_run = []
340
+ for testfile, imports in test_imports.items():
341
+ module_name = context.filename.rstrip(".py").replace("/", ".")
342
+ if module_name in imports:
343
+ tests_to_run.append(testfile)
344
+ context.config.test_command += f"{' '.join(tests_to_run)}"
345
+
346
+ Selection based on coverage contexts
347
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
348
+
349
+ If you recorded `coverage contexts <https://coverage.readthedocs.io/en/coverage-5.5/contexts.html>`_ and use
350
+ the ``--use-coverage`` switch, you can access this coverage data inside the ``pre_mutation(context)`` hook
351
+ via the ``context.config.coverage_data`` attribute. This attribute is a dictionary in the form
352
+ ``{filename: {lineno: [contexts]}}``.
353
+
354
+ Let's say you have used the built-in dynamic context option of ``Coverage.py`` by adding the following to
355
+ your ``.coveragerc`` file:
356
+
357
+ .. code-block:: console
358
+
359
+ [run]
360
+ dynamic_context = test_function
361
+
362
+ ``coverage`` will create a new context for each test function that you run in the form ``module_name.function_name``.
363
+ With ``pytest``, we can use the ``-k`` switch to filter tests that match a given expression.
364
+
365
+ .. code-block:: python
366
+
367
+ import os.path
368
+
369
+ def pre_mutation(context):
370
+ """Extract the coverage contexts if possible and only run the tests matching this data."""
371
+ if not context.config.coverage_data:
372
+ # mutmut was run without ``--use-coverage``
373
+ return
374
+ fname = os.path.abspath(context.filename)
375
+ contexts_for_file = context.config.coverage_data.get(fname, {})
376
+ contexts_for_line = contexts_for_file.get(context.current_line_index, [])
377
+ test_names = [
378
+ ctx.rsplit(".", 1)[-1] # extract only the final part after the last dot, which is the test function name
379
+ for ctx in contexts_for_line
380
+ if ctx # skip empty strings
381
+ ]
382
+ if not test_names:
383
+ return
384
+ context.config.test_command += f' -k "{" or ".join(test_names)}"'
385
+
386
+ Pay attention that the format of the context name varies depending on the tool you use for creating the contexts.
387
+ For example, the ``pytest-cov`` plugin uses ``::`` as separator between module and test function.
388
+ Furthermore, not all tools are able to correctly pick up the correct contexts. ``coverage.py`` for instance is (at the time of writing)
389
+ unable to pick up tests that are inside a class when using ``pytest``.
390
+ You will have to inspect your ``.coverage`` database using the `Coverage.py API <https://coverage.readthedocs.io/en/coverage-5.5/api.html>`_
391
+ first to determine how you can extract the correct information to use with your test runner.
392
+
393
+ Making things more robust
394
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
395
+
396
+ Despite your best efforts in picking the right subset of tests, it may happen that the mutant survives because the test which is able
397
+ to kill it was not included in the test set. You can tell ``mutmut`` to re-run the full test suite in that case, to verify that this
398
+ mutant indeed survives.
399
+ You can do so by passing the ``--rerun-all`` option to ``mutmut run``. This option is disabled by default.
400
+
401
+
402
+ JUnit XML support
403
+ -----------------
404
+
405
+ In order to better integrate with CI/CD systems, ``mutmut`` supports the
406
+ generation of a JUnit XML report (using https://pypi.org/project/junit-xml/).
407
+ This option is available by calling ``mutmut junitxml``. In order to define how
408
+ to deal with suspicious and untested mutants, you can use
409
+
410
+ .. code-block:: console
411
+
412
+ mutmut junitxml --suspicious-policy=ignore --untested-policy=ignore
413
+
414
+ The possible values for these policies are:
415
+
416
+ - ``ignore``: Do not include the results on the report at all
417
+ - ``skipped``: Include the mutant on the report as "skipped"
418
+ - ``error``: Include the mutant on the report as "error"
419
+ - ``failure``: Include the mutant on the report as "failure"
420
+
421
+ If a failed mutant is included in the report, then the unified diff of the
422
+ mutant will also be included for debugging purposes.
@@ -48,16 +48,24 @@ You can get started with a simple:
48
48
 
49
49
  This will by default run pytest (or unittest if pytest is unavailable)
50
50
  on tests in the "tests" or "test" folder and
51
- it will try to figure out where the code to mutate lies. Run
51
+ it will try to figure out where the code to mutate lies.
52
+
53
+ NOTE that mutmut will apply the mutations directly, one at a time;
54
+ it is **highly** recommended to add all changes to source control
55
+ before running.
56
+
57
+ Enter
52
58
 
53
59
  .. code-block:: console
54
60
 
55
- mutmut --help
61
+ mutmut run --help
56
62
 
57
63
  for the available flags, to use other runners, etc. The recommended way to use
58
- mutmut if the defaults aren't working for you is to add a block in ``setup.cfg``.
64
+ mutmut if the defaults aren't working for you is to add a
65
+ block in ``setup.cfg`` or ``project.toml``.
59
66
  Then when you come back to mutmut weeks later you don't have to figure out the
60
- flags again, just run ``mutmut run`` and it works. Like this:
67
+ flags again, just run ``mutmut run`` and it works.
68
+ Like this in ``setup.cfg``:
61
69
 
62
70
  .. code-block:: ini
63
71
 
@@ -68,6 +76,14 @@ flags again, just run ``mutmut run`` and it works. Like this:
68
76
  tests_dir=tests/
69
77
  dict_synonyms=Struct, NamedStruct
70
78
 
79
+ or like this in ``pyproject.toml``:
80
+
81
+ .. code-block:: ini
82
+
83
+ [tool.mutmut]
84
+ paths_to_mutate="src"
85
+ runner="python -m hammett -x"
86
+
71
87
  To use multiple paths either in the ``paths_to_mutate`` or ``tests_dir`` option
72
88
  use a comma or colon separated list. For example:
73
89
 
@@ -86,11 +102,11 @@ grouped by file. You can now look at a specific mutant diff with ``mutmut show 3
86
102
  all mutants for a specific file with ``mutmut show path/to/file.py`` or all mutants
87
103
  with ``mutmut show all``.
88
104
 
89
-
90
105
  You can also write a mutant to disk with ``mutmut apply 3``. You should **REALLY**
91
106
  have the file you mutate under source code control and committed before you apply
92
107
  a mutant!
93
108
 
109
+ To generate a HTML report for a web browser: ``mutmut html``
94
110
 
95
111
  Whitelisting
96
112
  ------------
@@ -199,6 +215,8 @@ Inversely, you can also only specify to only run specific mutations with ``--ena
199
215
  Note that ``--disable-mutation-types`` and ``--enable-mutation-types`` are exclusive and cannot
200
216
  be combined.
201
217
 
218
+ Changes made to ``mutmut_config.py`` does not invalidate the cache.
219
+ If you modify the configuration file, you will likely need to remove ``.mutmut-cache`` manually to ensure that the changes take effect.
202
220
 
203
221
  Selecting tests to run
204
222
  ----------------------