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:
kgriffs 2014-01-31 13:02:50 -06:00
parent d99e975bb0
commit 626609f4cf
3 changed files with 152 additions and 13 deletions

View File

@ -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

View File

@ -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:

View File

@ -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)