test(functional): Use direct WSGI requests in lieu of a wsgiref server
The wsgiref server is very flaky when running in the gate, so let's just pass WSGI requests directly to the app for now, at look into adding a devstack job to the gate that will use a standalone server. Change-Id: Ifb6cd0c9080ded7ab93cced6e0d40a776eb2cd50
This commit is contained in:
parent
d99e975bb0
commit
626609f4cf
@ -18,7 +18,7 @@
|
||||
This app should be used by external WSGI
|
||||
containers. For example:
|
||||
|
||||
$ gunicorn marconi.queues.transport.wsgi.public.app:app
|
||||
$ gunicorn marconi.queues.transport.wsgi.app:app
|
||||
|
||||
NOTE: As for external containers, it is necessary
|
||||
to put config files in the standard paths. There's
|
||||
|
@ -17,20 +17,28 @@
|
||||
import abc
|
||||
import jsonschema
|
||||
import multiprocessing
|
||||
|
||||
import os
|
||||
|
||||
from marconi.openstack.common import timeutils
|
||||
from marconi.queues import bootstrap
|
||||
# NOTE(flaper87): This is necessary to register,
|
||||
# TODO(flaper87): This is necessary to register,
|
||||
# wsgi configs and won't be permanent. It'll be
|
||||
# refactored as part of the work for this blueprint
|
||||
from marconi.queues.transport import validation
|
||||
from marconi.queues.transport import wsgi # noqa
|
||||
from marconi.queues.transport.wsgi import app
|
||||
from marconi import tests as testing
|
||||
from marconi.tests.functional import config
|
||||
from marconi.tests.functional import helpers
|
||||
from marconi.tests.functional import http
|
||||
|
||||
# TODO(kgriffs): Run functional tests to a devstack gate job and
|
||||
# set this using an environment variable or something.
|
||||
#
|
||||
# TODO(kgriffs): Find a more general way to do this; we seem to be
|
||||
# using this environ flag pattern over and over againg.
|
||||
_TEST_INTEGRATION = os.environ.get('MARCONI_TEST_INTEGRATION') is not None
|
||||
|
||||
|
||||
class FunctionalTestBase(testing.TestBase):
|
||||
|
||||
@ -51,19 +59,24 @@ class FunctionalTestBase(testing.TestBase):
|
||||
|
||||
self.mconf = self.load_conf(self.cfg.marconi.config)
|
||||
|
||||
# NOTE(flaper87): Use running instances.
|
||||
if self.cfg.marconi.run_server:
|
||||
if not (self.server and self.server.is_alive()):
|
||||
# pylint: disable=not-callable
|
||||
self.server = self.server_class()
|
||||
self.server.start(self.mconf)
|
||||
|
||||
validator = validation.Validator(self.mconf)
|
||||
self.limits = validator._limits_conf
|
||||
|
||||
# NOTE(flaper87): Create client
|
||||
# for this test unit.
|
||||
self.client = http.Client()
|
||||
if _TEST_INTEGRATION:
|
||||
# TODO(kgriffs): This code should be replaced to use
|
||||
# an external wsgi server instance.
|
||||
|
||||
# NOTE(flaper87): Use running instances.
|
||||
if self.cfg.marconi.run_server:
|
||||
if not (self.server and self.server.is_alive()):
|
||||
# pylint: disable=not-callable
|
||||
self.server = self.server_class()
|
||||
self.server.start(self.mconf)
|
||||
|
||||
self.client = http.Client()
|
||||
else:
|
||||
self.client = http.WSGIClient(app.app)
|
||||
|
||||
self.headers = helpers.create_marconi_headers(self.cfg)
|
||||
|
||||
if self.cfg.auth.auth_on:
|
||||
|
@ -16,7 +16,9 @@
|
||||
import functools
|
||||
import json
|
||||
|
||||
from falcon import testing as ftest
|
||||
import requests
|
||||
import six
|
||||
|
||||
|
||||
def _build_url(method):
|
||||
@ -38,6 +40,7 @@ def _build_url(method):
|
||||
class Client(object):
|
||||
|
||||
def __init__(self):
|
||||
# NOTE(kgriffs): used by @_build_url
|
||||
self.base_url = None
|
||||
self.session = requests.session()
|
||||
|
||||
@ -86,3 +89,126 @@ class Client(object):
|
||||
if "data" in kwargs:
|
||||
kwargs['data'] = json.dumps(kwargs["data"])
|
||||
return self.session.patch(url, **kwargs)
|
||||
|
||||
|
||||
class ResponseMock(object):
|
||||
"""Mocks part of the Requests library's Response object."""
|
||||
|
||||
def __init__(self, srmock, wsgi_result):
|
||||
self.status_code = int(srmock.status.partition(' ')[0])
|
||||
self._body = wsgi_result[0] if wsgi_result else ''
|
||||
self.headers = srmock.headers_dict
|
||||
|
||||
def json(self):
|
||||
return json.loads(self._body, encoding='utf-8')
|
||||
|
||||
|
||||
class WSGIClient(object):
|
||||
"""Same inteface as Client, but speaks directly to a WSGI callable."""
|
||||
|
||||
def __init__(self, app):
|
||||
# NOTE(kgriffs): used by @_build_url
|
||||
self.base_url = None
|
||||
|
||||
self.app = app
|
||||
self.headers = {}
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_headers(headers):
|
||||
# NOTE(kgriffs): Workaround for a little create_environ bug
|
||||
return dict([(key, '' if value is None else value)
|
||||
for key, value in headers.items()])
|
||||
|
||||
def _simulate_request(self, url, method='GET', data=None,
|
||||
headers=None, params=None):
|
||||
"""Simulate a request.
|
||||
|
||||
Simulates a WSGI request to the API for testing.
|
||||
|
||||
:param url: Request path for the desired resource
|
||||
:param method: (Default 'GET') The HTTP method to send
|
||||
:param data: (Default None) A dict that will be serialized
|
||||
to JSON and submitted as the body of the request. May
|
||||
also be a pre-serialized string.
|
||||
:param headers: (Default None) A dict containing
|
||||
extra HTTP headers to send.
|
||||
:param params: (Default None) A dict of parameters
|
||||
to use in the query string for the request.
|
||||
|
||||
:returns: a requests response instance
|
||||
"""
|
||||
|
||||
if headers is None:
|
||||
headers = self.headers
|
||||
|
||||
headers = self._sanitize_headers(headers)
|
||||
|
||||
if data is None:
|
||||
body = ''
|
||||
elif isinstance(data, str) or isinstance(data, six.text_type):
|
||||
body = data
|
||||
else:
|
||||
body = json.dumps(data, ensure_ascii=False)
|
||||
|
||||
parsed_url = six.moves.urllib_parse.urlparse(url)
|
||||
|
||||
query = parsed_url.query
|
||||
|
||||
if params is not None:
|
||||
extra = '&'.join([key + '=' + str(value)
|
||||
for key, value in params.items()])
|
||||
|
||||
query += '&' + extra
|
||||
|
||||
environ = ftest.create_environ(method=method,
|
||||
path=parsed_url.path,
|
||||
query_string=query,
|
||||
headers=headers,
|
||||
body=body)
|
||||
|
||||
srmock = ftest.StartResponseMock()
|
||||
wsgi_result = self.app(environ, srmock)
|
||||
|
||||
return ResponseMock(srmock, wsgi_result)
|
||||
|
||||
def set_base_url(self, base_url):
|
||||
self.base_url = base_url
|
||||
|
||||
def set_headers(self, headers):
|
||||
self.headers.update(headers)
|
||||
|
||||
@_build_url
|
||||
def get(self, url=None, **kwargs):
|
||||
"""Simulate a GET request."""
|
||||
kwargs['method'] = 'GET'
|
||||
return self._simulate_request(url=url, **kwargs)
|
||||
|
||||
@_build_url
|
||||
def head(self, url=None, **kwargs):
|
||||
"""Simulate a HEAD request."""
|
||||
kwargs['method'] = 'HEAD'
|
||||
return self._simulate_request(url=url, **kwargs)
|
||||
|
||||
@_build_url
|
||||
def post(self, url=None, **kwargs):
|
||||
"""Simulate a POST request."""
|
||||
kwargs['method'] = 'POST'
|
||||
return self._simulate_request(url=url, **kwargs)
|
||||
|
||||
@_build_url
|
||||
def put(self, url=None, **kwargs):
|
||||
"""Simulate a PUT request."""
|
||||
kwargs['method'] = 'PUT'
|
||||
return self._simulate_request(url=url, **kwargs)
|
||||
|
||||
@_build_url
|
||||
def delete(self, url=None, **kwargs):
|
||||
"""Simulate a DELETE request."""
|
||||
kwargs['method'] = 'DELETE'
|
||||
return self._simulate_request(url=url, **kwargs)
|
||||
|
||||
@_build_url
|
||||
def patch(self, url=None, **kwargs):
|
||||
"""Simulate a PATCH request."""
|
||||
kwargs['method'] = 'PATCH'
|
||||
return self._simulate_request(url=url, **kwargs)
|
||||
|
Loading…
Reference in New Issue
Block a user