@@ -6,43 +6,72 @@ Normally coverage writes the data via a pretty standard atexit handler. However,
6
6
own then the atexit handler might not run. Why that happens is best left to the adventurous to discover by waddling
7
7
through the Python bug tracker.
8
8
9
- pytest-cov supports subprocesses and multiprocessing, and works around these atexit limitations. However, there are a
10
- few pitfalls that need to be explained.
9
+ pytest-cov supports subprocesses, and works around these atexit limitations. However, there are a few pitfalls that need to be explained.
11
10
12
- If you use ``multiprocessing.Pool ``
13
- ===================================
11
+ But first, how does pytest-cov's subprocess support works?
14
12
15
- **pytest-cov ** automatically registers a multiprocessing finalizer. The finalizer will only run reliably if the pool is
16
- closed. Closing the pool basically signals the workers that there will be no more work, and they will eventually exit.
17
- Thus one also needs to call `join ` on the pool.
18
-
19
- If you use ``multiprocessing.Pool.terminate `` or the context manager API (``__exit__ ``
20
- will just call ``terminate ``) then the workers can get SIGTERM and then the finalizers won't run or complete in time.
21
- Thus you need to make sure your ``multiprocessing.Pool `` gets a nice and clean exit:
13
+ pytest-cov packaging injects a pytest-cov.pth into the installation. This file effectively runs this at *every * python startup:
22
14
23
15
.. code-block :: python
24
16
25
- from multiprocessing import Pool
17
+ if ' COV_CORE_SOURCE' in os.environ:
18
+ try :
19
+ from pytest_cov.embed import init
20
+ init()
21
+ except Exception as exc:
22
+ sys.stderr.write(
23
+ " pytest-cov: Failed to setup subprocess coverage. "
24
+ " Environ: {0!r } "
25
+ " Exception: {1!r } \n " .format(
26
+ dict ((k, v) for k, v in os.environ.items() if k.startswith(' COV_CORE' )),
27
+ exc
28
+ )
29
+ )
26
30
27
- def f ( x ):
28
- return x * x
31
+ The pytest plugin will set this `` COV_CORE_SOURCE `` environment variable thus any subprocess that inherits the environment variables
32
+ (the default behavior) will run `` pytest_cov.embed.init `` which in turn sets up coverage according to these variables:
29
33
30
- if __name__ == ' __main__' :
31
- p = Pool(5 )
32
- try :
33
- print (p.map(f, [1 , 2 , 3 ]))
34
- finally :
35
- p.close() # Marks the pool as closed.
36
- p.join() # Waits for workers to exit.
34
+ * ``COV_CORE_SOURCE ``
35
+ * ``COV_CORE_CONFIG ``
36
+ * ``COV_CORE_DATAFILE ``
37
+ * ``COV_CORE_BRANCH ``
38
+ * ``COV_CORE_CONTEXT ``
39
+
40
+ Why does it have the ``COV_CORE `` you wonder? Well, it's mostly historical reasons: long time ago pytest-cov depended on a cov-core package
41
+ that implemented common functionality for pytest-cov, nose-cov and nose2-cov. The dependency is gone but the convention is kept. It could
42
+ be changed but it would break all projects that manually set these intended-to-be-internal-but-sadly-not-in-reality environment variables.
43
+
44
+ Coverage's subprocess support
45
+ =============================
46
+
47
+ Now that you understand how pytest-cov works you can easily figure out that using
48
+ `coverage's recommended <https://coverage.readthedocs.io/en/latest/subprocess.html >`_ way of dealing with subprocesses,
49
+ by either having this in a ``.pth `` file or ``sitecustomize.py `` will break everything:
50
+
51
+ .. code-block ::
52
+
53
+ import coverage; coverage.process_startup() # this will break pytest-cov
37
54
55
+ Do not do that as that will restart coverage with the wrong options.
38
56
39
- If you must use the context manager API (e.g.: the pool is managed in third party code you can't change) then you can
40
- register a cleaning SIGTERM handler like so:
57
+ If you use `` multiprocessing ``
58
+ ==============================
41
59
42
- .. warning ::
60
+ Builtin support for multiprocessing was dropped in pytest-cov 4.0.
61
+ This support was mostly working but very broken in certain scenarios (see `issue 82408 <https://github.com/python/cpython/issues/82408 >`_)
62
+ and made the test suite very flaky and slow.
43
63
44
- **This technique cannot be used on Python 3.8 ** (registering signal handlers will cause deadlocks in the pool,
45
- see: https://bugs.python.org/issue38227).
64
+ However, there is `builtin multiprocessing support in coverage <https://coverage.readthedocs.io/en/latest/config.html#run-concurrency >`_
65
+ and you can migrate to that. All you need is this in your preferred configuration file (example: ``.coveragerc ``):
66
+
67
+ .. code-block :: ini
68
+
69
+ [run]
70
+ concurrency = multiprocessing
71
+ parallel = true
72
+ sigterm = true
73
+
74
+ Now as a side-note, it's a good idea in general to properly close your Pool by using ``Pool.join() ``:
46
75
47
76
.. code-block :: python
48
77
@@ -52,46 +81,33 @@ register a cleaning SIGTERM handler like so:
52
81
return x* x
53
82
54
83
if __name__ == ' __main__' :
84
+ p = Pool(5 )
55
85
try :
56
- from pytest_cov.embed import cleanup_on_sigterm
57
- except ImportError :
58
- pass
59
- else :
60
- cleanup_on_sigterm()
61
-
62
- with Pool(5 ) as p:
63
86
print (p.map(f, [1 , 2 , 3 ]))
87
+ finally :
88
+ p.close() # Marks the pool as closed.
89
+ p.join() # Waits for workers to exit.
64
90
65
- If you use ``multiprocessing.Process ``
66
- ======================================
67
-
68
- There's similar issue when using the ``Process `` objects. Don't forget to use ``.join() ``:
69
91
70
- .. code-block :: python
92
+ .. _ cleanup_on_sigterm :
71
93
72
- from multiprocessing import Process
94
+ Signal handlers
95
+ ===============
73
96
74
- def f ( name ):
75
- print ( ' hello ' , name)
97
+ pytest-cov provides a signal handling routines, mostly for special situations where you'd have custom signal handling that doesn't
98
+ allow atexit to properly run and the now-gone multiprocessing support:
76
99
77
- if __name__ == ' __main__' :
78
- try :
79
- from pytest_cov.embed import cleanup_on_sigterm
80
- except ImportError :
81
- pass
82
- else :
83
- cleanup_on_sigterm()
100
+ * ``pytest_cov.embed.cleanup_on_sigterm() ``
101
+ * ``pytest_cov.embed.cleanup_on_signal(signum) `` (e.g.: ``cleanup_on_signal(signal.SIGHUP) ``)
84
102
85
- p = Process(target = f, args = (' bob' ,))
86
- try :
87
- p.start()
88
- finally :
89
- p.join() # necessary so that the Process exists before the test suite exits (thus coverage is collected)
103
+ If you use multiprocessing
104
+ --------------------------
90
105
91
- .. _cleanup_on_sigterm :
106
+ It is not recommanded to use these signal handlers with multiprocessing as registering signal handlers will cause deadlocks in the pool,
107
+ see: https://bugs.python.org/issue38227).
92
108
93
109
If you got custom signal handling
94
- =================================
110
+ ---------------------------------
95
111
96
112
**pytest-cov 2.6 ** has a rudimentary ``pytest_cov.embed.cleanup_on_sigterm `` you can use to register a SIGTERM handler
97
113
that flushes the coverage data.
@@ -140,7 +156,7 @@ Alternatively you can do this:
140
156
signal.signal(signal.SIGHUP , restart_service)
141
157
142
158
If you use Windows
143
- ==================
159
+ ------------------
144
160
145
161
On Windows you can register a handler for SIGTERM but it doesn't actually work. It will work if you
146
162
`os.kill(os.getpid(), signal.SIGTERM) ` (send SIGTERM to the current process) but for most intents and purposes that's
0 commit comments