Skip to content

Commit 401b458

Browse files
committed
Add new api to support more aggressive cleanup when suprocesses are being terminated. Ref #139.
1 parent 2d75403 commit 401b458

File tree

3 files changed

+76
-23
lines changed

3 files changed

+76
-23
lines changed

src/pytest_cov/embed.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@
1414
info passed via env vars.
1515
"""
1616
import os
17+
import signal
18+
19+
active_cov = None
1720

1821

1922
def multiprocessing_start(_):
2023
cov = init()
2124
if cov:
22-
multiprocessing.util.Finalize(None, multiprocessing_finish, args=(cov,), exitpriority=1000)
23-
24-
25-
def multiprocessing_finish(cov):
26-
cov.stop()
27-
cov.save()
28-
25+
multiprocessing.util.Finalize(None, cleanup, args=(cov,), exitpriority=1000)
2926

3027
try:
3128
import multiprocessing.util
@@ -38,6 +35,7 @@ def multiprocessing_finish(cov):
3835
def init():
3936
# Only continue if ancestor process has set everything needed in
4037
# the env.
38+
global active_cov
4139

4240
cov_source = os.environ.get('COV_CORE_SOURCE')
4341
cov_config = os.environ.get('COV_CORE_CONFIG')
@@ -55,7 +53,7 @@ def init():
5553
cov_config = True
5654

5755
# Activate coverage for this process.
58-
cov = coverage.coverage(
56+
cov = active_cov = coverage.coverage(
5957
source=cov_source,
6058
data_suffix=True,
6159
config_file=cov_config,
@@ -67,3 +65,25 @@ def init():
6765
cov._warn_no_data = False
6866
cov._warn_unimported_source = False
6967
return cov
68+
69+
def _cleanup(cov):
70+
cov.stop()
71+
cov.save()
72+
73+
def cleanup(cov=None):
74+
global active_cov
75+
76+
if cov:
77+
_cleanup(cov)
78+
if active_cov is not cov:
79+
_cleanup(active_cov)
80+
active_cov = None
81+
multiprocessing_finish = cleanup # in case someone dared to use this internal
82+
83+
84+
def _sigterm_handler(*_):
85+
cleanup()
86+
87+
88+
def cleanup_on_sigterm():
89+
signal.signal(signal.SIGTERM, _sigterm_handler)

src/pytest_cov/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def pytest_runtest_setup(self, item):
283283

284284
def pytest_runtest_teardown(self, item):
285285
if self.cov is not None:
286-
embed.multiprocessing_finish(self.cov)
286+
embed.cleanup(self.cov)
287287
self.cov = None
288288

289289

tests/test_pytest_cov.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,19 +135,6 @@ def test_foo(cov):
135135
assert cov is None
136136
'''
137137

138-
MULTIPROCESSING_SCRIPT = '''
139-
import multiprocessing
140-
141-
def target_fn():
142-
a = True
143-
return a
144-
145-
def test_run_target():
146-
p = multiprocessing.Process(target=target_fn)
147-
p.start()
148-
p.join()
149-
'''
150-
151138
SCRIPT_FAIL = '''
152139
def test_fail():
153140
assert False
@@ -761,7 +748,18 @@ def test_funcarg_not_active(testdir):
761748
def test_multiprocessing_subprocess(testdir):
762749
py.test.importorskip('multiprocessing.util')
763750

764-
script = testdir.makepyfile(MULTIPROCESSING_SCRIPT)
751+
script = testdir.makepyfile('''
752+
import multiprocessing
753+
754+
def target_fn():
755+
a = True
756+
return a
757+
758+
def test_run_target():
759+
p = multiprocessing.Process(target=target_fn)
760+
p.start()
761+
p.join()
762+
''')
765763

766764
result = testdir.runpytest('-v',
767765
'--cov=%s' % script.dirpath(),
@@ -776,6 +774,41 @@ def test_multiprocessing_subprocess(testdir):
776774
assert result.ret == 0
777775

778776

777+
def test_multiprocessing_subprocess_with_terminate(testdir):
778+
py.test.importorskip('multiprocessing.util')
779+
780+
script = testdir.makepyfile('''
781+
import multiprocessing
782+
import time
783+
from pytest_cov.embed import cleanup_on_sigterm
784+
cleanup_on_sigterm()
785+
786+
event = multiprocessing.Event()
787+
788+
def target_fn():
789+
a = True
790+
event.set()
791+
time.sleep(5)
792+
793+
def test_run_target():
794+
p = multiprocessing.Process(target=target_fn)
795+
p.start()
796+
event.wait(1)
797+
p.terminate()
798+
''')
799+
800+
result = testdir.runpytest('-v',
801+
'--cov=%s' % script.dirpath(),
802+
'--cov-report=term-missing',
803+
script)
804+
805+
result.stdout.fnmatch_lines([
806+
'*- coverage: platform *, python * -*',
807+
'test_multiprocessing_subprocess* 14 * 100%*',
808+
'*1 passed*'
809+
])
810+
assert result.ret == 0
811+
779812
MODULE = '''
780813
def func():
781814
return 1

0 commit comments

Comments
 (0)