From 470ac7a71a6d2ab480f0aab66b8f9628d1a500f3 Mon Sep 17 00:00:00 2001 From: "Stephen J. Fuhry" Date: Sun, 24 Jul 2016 08:17:49 -0400 Subject: [PATCH 1/3] change localhost to 127.0.0.1 pep8 recommendations --- pytest_flask/fixtures.py | 36 +++++++++++++++++++++--------------- tests/test_live_server.py | 8 ++++---- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/pytest_flask/fixtures.py b/pytest_flask/fixtures.py index b8539d7..7573083 100755 --- a/pytest_flask/fixtures.py +++ b/pytest_flask/fixtures.py @@ -6,9 +6,9 @@ import socket try: - from urllib2 import urlopen -except ImportError: from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen from flask import _request_ctx_stack @@ -34,13 +34,21 @@ def login(self, email, password): return self.client.post(url_for('login'), data=credentials) def test_login(self): - assert self.login('vital@example.com', 'pass').status_code == 200 + assert self.login('vital@foo.com', 'pass').status_code == 200 """ if request.cls is not None: request.cls.client = client +def _find_unused_port(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('', 0)) + port = s.getsockname()[1] + s.close() + return port + + class LiveServer(object): """The helper class uses to manage live server. Handles creation and stopping application in a separate process. @@ -56,7 +64,9 @@ def __init__(self, app, port): def start(self): """Start application in a separate process.""" - worker = lambda app, port: app.run(port=port, use_reloader=False) + def worker(app, port): + return app.run(port=port, use_reloader=False) + self._process = multiprocessing.Process( target=worker, args=(self.app, self.port) @@ -76,7 +86,7 @@ def start(self): def url(self, url=''): """Returns the complete url based on server options.""" - return 'http://localhost:%d%s' % (self.port, url) + return 'http://127.0.0.1:%d%s' % (self.port, url) def stop(self): """Stop application process.""" @@ -95,7 +105,7 @@ def _rewrite_server_name(server_name, new_port): return sep.join((server_name, new_port)) -@pytest.fixture(scope='function') +@pytest.yield_fixture(scope='function') def live_server(request, app, monkeypatch): """Run application in a separate process. @@ -104,21 +114,17 @@ def live_server(request, app, monkeypatch): def test_server_is_up_and_running(live_server): index_url = url_for('index', _external=True) - assert index_url == 'http://localhost:5000/' + assert index_url == 'http://127.0.0.1:5000/' res = urllib2.urlopen(index_url) assert res.code == 200 """ - # Bind to an open port - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(('', 0)) - port = s.getsockname()[1] - s.close() + port = _find_unused_port() # Explicitly set application ``SERVER_NAME`` for test suite # and restore original value on test teardown. - server_name = app.config['SERVER_NAME'] or 'localhost' + server_name = app.config['SERVER_NAME'] or '127.0.0.1' monkeypatch.setitem(app.config, 'SERVER_NAME', _rewrite_server_name(server_name, str(port))) @@ -126,8 +132,8 @@ def test_server_is_up_and_running(live_server): if request.config.getvalue('start_live_server'): server.start() - request.addfinalizer(server.stop) - return server + yield server + server.stop() @pytest.fixture diff --git a/tests/test_live_server.py b/tests/test_live_server.py index 1c00928..e8056d2 100755 --- a/tests/test_live_server.py +++ b/tests/test_live_server.py @@ -16,8 +16,8 @@ def test_server_is_alive(self, live_server): assert live_server._process.is_alive() def test_server_url(self, live_server): - assert live_server.url() == 'http://localhost:%d' % live_server.port - assert live_server.url('/ping') == 'http://localhost:%d/ping' % live_server.port + assert live_server.url() == 'http://127.0.0.1:%d' % live_server.port + assert live_server.url('/ping') == 'http://127.0.0.1:%d/ping' % live_server.port def test_server_listening(self, live_server): res = urlopen(live_server.url('/ping')) @@ -25,10 +25,10 @@ def test_server_listening(self, live_server): assert b'pong' in res.read() def test_url_for(self, live_server): - assert url_for('ping', _external=True) == 'http://localhost:%s/ping' % live_server.port + assert url_for('ping', _external=True) == 'http://127.0.0.1:%s/ping' % live_server.port def test_set_application_server_name(self, live_server): - assert live_server.app.config['SERVER_NAME'] == 'localhost:%d' % live_server.port + assert live_server.app.config['SERVER_NAME'] == '127.0.0.1:%d' % live_server.port @pytest.mark.options(server_name='example.com:5000') def test_rewrite_application_server_name(self, live_server): From d9c4e6efbadcd2639f2f63436554f19cf55aab6a Mon Sep 17 00:00:00 2001 From: "Stephen J. Fuhry" Date: Sun, 24 Jul 2016 08:19:14 -0400 Subject: [PATCH 2/3] use verbose standard python gitignore --- .gitignore | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 5edd0cf..72364f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,89 @@ -# Python bytecode / optimized files -*.py[co] -*.egg-info -build -dist -venv +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy # Sphinx documentation -docs/_build +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject From 738710823eee9ab4fc07757e2201d6bee38cbfee Mon Sep 17 00:00:00 2001 From: "Stephen J. Fuhry" Date: Sun, 24 Jul 2016 08:52:52 -0400 Subject: [PATCH 3/3] add LiveServerSubprocess --- Makefile | 2 +- pytest_flask/fixtures.py | 141 ++++++++++++++++++++++++++++++-------- setup.py | 3 +- tests/test_live_server.py | 8 +-- 4 files changed, 118 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index 39bef37..7b95cfc 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ test: clean: @rm -rf build dist *.egg-info - @find . -name '*.py?' -delete + @find . | grep -E '(__pycache__|\.pyc|\.pyo$$)' | xargs rm -rf docs: diff --git a/pytest_flask/fixtures.py b/pytest_flask/fixtures.py index 7573083..d2f6b30 100755 --- a/pytest_flask/fixtures.py +++ b/pytest_flask/fixtures.py @@ -7,8 +7,10 @@ try: from urllib.request import urlopen + from shutil import which except ImportError: from urllib2 import urlopen + from distutils.spawn import find_executable as which from flask import _request_ctx_stack @@ -49,30 +51,23 @@ def _find_unused_port(): return port -class LiveServer(object): - """The helper class uses to manage live server. Handles creation and - stopping application in a separate process. +class LiveServerBase(object): - :param app: The application to run. - :param port: The port to run application. - """ - - def __init__(self, app, port): + def __init__(self, app, monkeypatch, port=None): self.app = app - self.port = port + self.port = port or _find_unused_port() self._process = None + self.monkeypatch = monkeypatch def start(self): - """Start application in a separate process.""" - def worker(app, port): - return app.run(port=port, use_reloader=False) - - self._process = multiprocessing.Process( - target=worker, - args=(self.app, self.port) - ) - self._process.start() - + # Explicitly set application ``SERVER_NAME`` for test suite + # and restore original value on test teardown. + server_name = self.app.config['SERVER_NAME'] or '127.0.0.1' + self.monkeypatch.setitem( + self.app.config, 'SERVER_NAME', + _rewrite_server_name(server_name, str(self.port))) + + def _wait_for_server(self): # We must wait for the server to start listening with a maximum # timeout of 5 seconds. timeout = 5 @@ -88,13 +83,107 @@ def url(self, url=''): """Returns the complete url based on server options.""" return 'http://127.0.0.1:%d%s' % (self.port, url) + def __repr__(self): + return '<%s listening at %s>' % ( + self.__class__.__name, + self.url(), + ) + + +class LiveServerMultiprocess(LiveServerBase): + """The helper class uses this to manage live server. + Handles creation and stopping application in a separate process. + + :param app: The application to run. + :param port: The port to run application. + """ + + def start(self): + """Start application in a separate process.""" + def worker(app, port): + return app.run(port=port, use_reloader=False) + super(LiveServerMultiprocess, self).start() + + self._process = multiprocessing.Process( + target=worker, + args=(self.app, self.port) + ) + self._process.start() + self._wait_for_server() + def stop(self): """Stop application process.""" if self._process: self._process.terminate() - def __repr__(self): - return '' % self.url() +try: + import pytest_services # noqa + + class LiveServerSubprocess(LiveServerBase): + """The helper class uses this to manage live server. + Handles creation and stopping application in a subprocess + using Popen. Use this if you need more explicit separation + between processes. + + :param app: The application to run. + :param port: The port to run application. + """ + def __init__(self, app, monkeypatch, watcher_getter, port=None): + self.app = app + self.port = port or _find_unused_port() + self._process = None + self.monkeypatch = monkeypatch + self.watcher_getter = watcher_getter + + def start(self, **kwargs): + """ + Start application in a separate process. + + To add environment variables to the process, simply do: + live_server_subprocess.start( + watcher_getter_kwargs={'env': {'MYENV': '1'}}) + """ + def worker(app, port): + return app.run(port=port, use_reloader=False) + super(LiveServerSubprocess, self).start() + + self._process = self.watcher_getter( + name='flask', + arguments=['run', '--port', str(self.port)], + checker=lambda: which('flask'), + kwargs=kwargs.get('watcher_getter_kwargs', {})) + self._wait_for_server() + + def stop(self): + """Stop application process.""" + if self._process: + self._process.terminate() + + @pytest.yield_fixture(scope='function') + def live_server_subprocess(request, app, monkeypatch, watcher_getter): + """Run application in a subprocess. Use this if you need more explicit + separation of processes. Uses os.fork(). + Requires flask >= 0.11 and the pytest-services plugin. + + When the ``live_server_subprocess`` fixture is applyed, + the ``url_for`` function works as expected:: + + def test_server_is_up_and_running(live_server_subprocess): + index_url = url_for('index', _external=True) + assert index_url == 'http://127.0.0.1:5000/' + + res = urllib2.urlopen(index_url) + assert res.code == 200 + """ + + server = LiveServerSubprocess(app, monkeypatch=monkeypatch) + if request.config.getvalue('start_live_server'): + server.start() + yield server + server.stop() + +except ImportError: + pass def _rewrite_server_name(server_name, new_port): @@ -120,18 +209,10 @@ def test_server_is_up_and_running(live_server): assert res.code == 200 """ - port = _find_unused_port() - # Explicitly set application ``SERVER_NAME`` for test suite - # and restore original value on test teardown. - server_name = app.config['SERVER_NAME'] or '127.0.0.1' - monkeypatch.setitem(app.config, 'SERVER_NAME', - _rewrite_server_name(server_name, str(port))) - - server = LiveServer(app, port) + server = LiveServerMultiprocess(app, monkeypatch=monkeypatch) if request.config.getvalue('start_live_server'): server.start() - yield server server.stop() diff --git a/setup.py b/setup.py index 6e967be..c893845 100755 --- a/setup.py +++ b/setup.py @@ -132,7 +132,8 @@ def get_version(): extras_require = { 'docs': read('requirements', 'docs.txt').splitlines(), - 'tests': tests_require + 'tests': tests_require, + 'services': ['pytest-services'], } diff --git a/tests/test_live_server.py b/tests/test_live_server.py index e8056d2..6387308 100755 --- a/tests/test_live_server.py +++ b/tests/test_live_server.py @@ -2,9 +2,9 @@ # -*- coding: utf-8 -*- import pytest try: - from urllib2 import urlopen -except ImportError: from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen from flask import url_for @@ -62,9 +62,9 @@ def test_add_endpoint_to_live_server(self, appdir): appdir.create_test_module(''' import pytest try: - from urllib2 import urlopen - except ImportError: from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen from flask import url_for