Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/pytest_cov/embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,26 @@ def cleanup(cov=None):

multiprocessing_finish = cleanup # in case someone dared to use this internal

_previous_handlers = {}

def _sigterm_handler(*_):

def _signal_cleanup_handler(signum, frame):
cleanup()
_previous_handler = _previous_handlers.get(signum)
if _previous_handler == signal.SIG_IGN:
return
elif _previous_handler:
_previous_handler(signum, frame)
elif signum == signal.SIGTERM:
os._exit(128 + signum)
elif signum == signal.SIGINT:
raise KeyboardInterrupt()


def cleanup_on_signal(signum):
_previous_handlers[signum] = signal.getsignal(signum)
signal.signal(signum, _signal_cleanup_handler)


def cleanup_on_sigterm():
signal.signal(signal.SIGTERM, _sigterm_handler)
cleanup_on_signal(signal.SIGTERM)
168 changes: 168 additions & 0 deletions tests/test_pytest_cov.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,174 @@ def test_run_target():
])
assert result.ret == 0


@pytest.mark.skipif('sys.platform == "win32"',
reason="fork not available on Windows")
def test_cleanup_on_sigterm(testdir):
script = testdir.makepyfile('''
import os, signal, subprocess, sys, time

def cleanup(num, frame):
print("num == signal.SIGTERM => %s" % (num == signal.SIGTERM))
raise Exception()

def test_run():
proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
time.sleep(1)
proc.terminate()
stdout, stderr = proc.communicate()
assert not stderr
assert stdout == b"""num == signal.SIGTERM => True
captured Exception()
"""
assert proc.returncode == 0

if __name__ == "__main__":
signal.signal(signal.SIGTERM, cleanup)

from pytest_cov.embed import cleanup_on_sigterm
cleanup_on_sigterm()

try:
time.sleep(10)
except BaseException as exc:
print("captured %r" % exc)
''')

result = testdir.runpytest('-vv',
'--cov=%s' % script.dirpath(),
'--cov-report=term-missing',
script)

result.stdout.fnmatch_lines([
'*- coverage: platform *, python * -*',
'test_cleanup_on_sigterm* 26-27',
'*1 passed*'
])
assert result.ret == 0


@pytest.mark.skipif('sys.platform == "win32"', reason="fork not available on Windows")
@pytest.mark.parametrize('setup', [
('signal.signal(signal.SIGTERM, signal.SIG_DFL); cleanup_on_sigterm()', '88% 18-19'),
('cleanup_on_sigterm()', '88% 18-19'),
('cleanup()', '75% 16-19'),
])
def test_cleanup_on_sigterm_sig_dfl(testdir, setup):
script = testdir.makepyfile('''
import os, signal, subprocess, sys, time

def test_run():
proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
time.sleep(1)
proc.terminate()
stdout, stderr = proc.communicate()
assert not stderr
assert stdout == b""
assert proc.returncode in [128 + signal.SIGTERM, -signal.SIGTERM]

if __name__ == "__main__":
from pytest_cov.embed import cleanup_on_sigterm, cleanup
{0}

try:
time.sleep(10)
except BaseException as exc:
print("captured %r" % exc)
'''.format(setup[0]))

result = testdir.runpytest('-vv',
'--cov=%s' % script.dirpath(),
'--cov-report=term-missing',
script)

result.stdout.fnmatch_lines([
'*- coverage: platform *, python * -*',
'test_cleanup_on_sigterm* %s' % setup[1],
'*1 passed*'
])
assert result.ret == 0


@pytest.mark.skipif('sys.platform == "win32"', reason="fork not available on Windows")
def test_cleanup_on_sigterm_sig_dfl_sigint(testdir):
script = testdir.makepyfile('''
import os, signal, subprocess, sys, time

def test_run():
proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
time.sleep(1)
proc.send_signal(signal.SIGINT)
stdout, stderr = proc.communicate()
assert not stderr
assert stdout == b"""captured KeyboardInterrupt()
"""
assert proc.returncode == 0

if __name__ == "__main__":
from pytest_cov.embed import cleanup_on_signal
cleanup_on_signal(signal.SIGINT)

try:
time.sleep(10)
except BaseException as exc:
print("captured %r" % exc)
''')

result = testdir.runpytest('-vv',
'--cov=%s' % script.dirpath(),
'--cov-report=term-missing',
script)

result.stdout.fnmatch_lines([
'*- coverage: platform *, python * -*',
'test_cleanup_on_sigterm* 88% 19-20',
'*1 passed*'
])
assert result.ret == 0

@pytest.mark.skipif('sys.platform == "win32"', reason="fork not available on Windows")
def test_cleanup_on_sigterm_sig_ign(testdir):
script = testdir.makepyfile('''
import os, signal, subprocess, sys, time

def test_run():
proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
time.sleep(1)
proc.send_signal(signal.SIGINT)
time.sleep(1)
proc.terminate()
stdout, stderr = proc.communicate()
assert not stderr
assert stdout == b""
# it appears signal handling is buggy on python 2?
if sys.version_info == 3: assert proc.returncode in [128 + signal.SIGTERM, -signal.SIGTERM]

if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_IGN)

from pytest_cov.embed import cleanup_on_signal
cleanup_on_signal(signal.SIGINT)

try:
time.sleep(10)
except BaseException as exc:
print("captured %r" % exc)
''')

result = testdir.runpytest('-vv',
'--cov=%s' % script.dirpath(),
'--cov-report=term-missing',
script)

result.stdout.fnmatch_lines([
'*- coverage: platform *, python * -*',
'test_cleanup_on_sigterm* 89% 23-24',
'*1 passed*'
])
assert result.ret == 0


MODULE = '''
def func():
return 1
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ deps =

virtualenv
hunter
process-tests==2.0.0
process-tests==2.0.2
six
fields
pip_pre = true
Expand Down