Skip to content

Commit 121a24f

Browse files
authored
Merge pull request #53 from jadkik/fix_liveserver_process_termination
Fix liveserver process termination
2 parents 61cb484 + ae5ec79 commit 121a24f

File tree

4 files changed

+89
-3
lines changed

4 files changed

+89
-3
lines changed

docs/changelog.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ Next Release
1010
- Add new ``--live-server-port`` option to select the port the live server will use (`#82`_).
1111
Thanks `@RazerM`_ for the PR.
1212

13+
- Now ``live_server`` will try to stop the server cleanly by emitting a ``SIGINT`` signal and
14+
waiting 5 seconds for the server to shutdown. If the server is still running after 5 seconds,
15+
it will be forcefully terminated. This behavior can be changed by passing
16+
``--no-live-server-clean-stop`` in the command-line (`#49`_).
17+
Thanks `@jadkik`_ for the PR.
18+
19+
.. _@jadkik: https://github.com/jadkik
1320
.. _@RazerM: https://github.com/RazerM
21+
.. _#49: https://github.com/pytest-dev/pytest-flask/issues/49
1422
.. _#82: https://github.com/pytest-dev/pytest-flask/pull/82
1523

1624

pytest_flask/fixtures.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import multiprocessing
55
import pytest
66
import socket
7+
import signal
8+
import os
9+
import logging
710

811
try:
912
from urllib2 import URLError, urlopen
@@ -50,9 +53,10 @@ class LiveServer(object):
5053
:param port: The port to run application.
5154
"""
5255

53-
def __init__(self, app, port):
56+
def __init__(self, app, port, clean_stop=False):
5457
self.app = app
5558
self.port = port
59+
self.clean_stop = clean_stop
5660
self._process = None
5761

5862
def start(self):
@@ -83,7 +87,25 @@ def url(self, url=''):
8387
def stop(self):
8488
"""Stop application process."""
8589
if self._process:
86-
self._process.terminate()
90+
if self.clean_stop and self._stop_cleanly():
91+
return
92+
if self._process.is_alive():
93+
# If it's still alive, kill it
94+
self._process.terminate()
95+
96+
def _stop_cleanly(self, timeout=5):
97+
"""Attempts to stop the server cleanly by sending a SIGINT signal and waiting for
98+
``timeout`` seconds.
99+
100+
:return: True if the server was cleanly stopped, False otherwise.
101+
"""
102+
try:
103+
os.kill(self._process.pid, signal.SIGINT)
104+
self._process.join(timeout)
105+
return True
106+
except Exception as ex:
107+
logging.error('Failed to join the live server process: %r', ex)
108+
return False
87109

88110
def __repr__(self):
89111
return '<LiveServer listening at %s>' % self.url()
@@ -127,7 +149,8 @@ def test_server_is_up_and_running(live_server):
127149
monkeypatch.setitem(app.config, 'SERVER_NAME',
128150
_rewrite_server_name(server_name, str(port)))
129151

130-
server = LiveServer(app, port)
152+
clean_stop = request.config.getvalue('live_server_clean_stop')
153+
server = LiveServer(app, port, clean_stop)
131154
if request.config.getvalue('start_live_server'):
132155
server.start()
133156

pytest_flask/plugin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ def pytest_addoption(parser):
124124
action="store_false", dest="start_live_server",
125125
help="don't start server automatically when live_server "
126126
"fixture is applied.")
127+
group.addoption('--live-server-clean-stop',
128+
action="store_true", dest="live_server_clean_stop", default=True,
129+
help="attempt to kill the live server cleanly.")
130+
group.addoption('--no-live-server-clean-stop',
131+
action="store_false", dest="live_server_clean_stop",
132+
help="terminate the server forcefully after stop.")
127133
group.addoption('--live-server-port', action='store', default=0, type=int,
128134
help='use a fixed port for the live_server fixture.')
129135

tests/test_live_server.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,55 @@ def test_a(live_server):
6767
result.stdout.fnmatch_lines(['*PASSED*'])
6868
assert result.ret == 0
6969

70+
@pytest.mark.parametrize('clean_stop', [True, False])
71+
def test_clean_stop_live_server(self, appdir, monkeypatch, clean_stop):
72+
"""Ensure the fixture is trying to cleanly stop the server.
73+
74+
Because this is tricky to test, we are checking that the _stop_cleanly() internal
75+
function was called and reported success.
76+
"""
77+
from pytest_flask.fixtures import LiveServer
78+
79+
original_stop_cleanly_func = LiveServer._stop_cleanly
80+
81+
stop_cleanly_result = []
82+
83+
def mocked_stop_cleanly(*args, **kwargs):
84+
result = original_stop_cleanly_func(*args, **kwargs)
85+
stop_cleanly_result.append(result)
86+
return result
87+
88+
monkeypatch.setattr(LiveServer, '_stop_cleanly', mocked_stop_cleanly)
89+
90+
appdir.create_test_module('''
91+
import pytest
92+
try:
93+
from urllib2 import urlopen
94+
except ImportError:
95+
from urllib.request import urlopen
96+
97+
from flask import url_for
98+
99+
def test_a(live_server):
100+
@live_server.app.route('/')
101+
def index():
102+
return 'got it', 200
103+
104+
live_server.start()
105+
106+
res = urlopen(url_for('index', _external=True))
107+
assert res.code == 200
108+
assert b'got it' in res.read()
109+
''')
110+
args = [] if clean_stop else ['--no-live-server-clean-stop']
111+
result = appdir.runpytest_inprocess('-v', '--no-start-live-server',
112+
*args)
113+
result.stdout.fnmatch_lines('*1 passed*')
114+
if clean_stop:
115+
assert stop_cleanly_result == [True]
116+
else:
117+
assert stop_cleanly_result == []
118+
70119
def test_add_endpoint_to_live_server(self, appdir):
71120
appdir.create_test_module('''
72121
import pytest

0 commit comments

Comments
 (0)