You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.0 KiB
152 lines
4.0 KiB
5 months ago
|
"""Pytest fixtures and other helpers for doing testing by end-users."""
|
||
|
|
||
|
from __future__ import absolute_import, division, print_function
|
||
|
__metaclass__ = type
|
||
|
|
||
|
from contextlib import closing
|
||
|
import errno
|
||
|
import socket
|
||
|
import threading
|
||
|
import time
|
||
|
|
||
|
import pytest
|
||
|
from six.moves import http_client
|
||
|
|
||
|
import cheroot.server
|
||
|
from cheroot.test import webtest
|
||
|
import cheroot.wsgi
|
||
|
|
||
|
EPHEMERAL_PORT = 0
|
||
|
NO_INTERFACE = None # Using this or '' will cause an exception
|
||
|
ANY_INTERFACE_IPV4 = '0.0.0.0'
|
||
|
ANY_INTERFACE_IPV6 = '::'
|
||
|
|
||
|
config = {
|
||
|
cheroot.wsgi.Server: {
|
||
|
'bind_addr': (NO_INTERFACE, EPHEMERAL_PORT),
|
||
|
'wsgi_app': None,
|
||
|
},
|
||
|
cheroot.server.HTTPServer: {
|
||
|
'bind_addr': (NO_INTERFACE, EPHEMERAL_PORT),
|
||
|
'gateway': cheroot.server.Gateway,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
|
||
|
def cheroot_server(server_factory):
|
||
|
"""Set up and tear down a Cheroot server instance."""
|
||
|
conf = config[server_factory].copy()
|
||
|
bind_port = conf.pop('bind_addr')[-1]
|
||
|
|
||
|
for interface in ANY_INTERFACE_IPV6, ANY_INTERFACE_IPV4:
|
||
|
try:
|
||
|
actual_bind_addr = (interface, bind_port)
|
||
|
httpserver = server_factory( # create it
|
||
|
bind_addr=actual_bind_addr,
|
||
|
**conf
|
||
|
)
|
||
|
except OSError:
|
||
|
pass
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
threading.Thread(target=httpserver.safe_start).start() # spawn it
|
||
|
while not httpserver.ready: # wait until fully initialized and bound
|
||
|
time.sleep(0.1)
|
||
|
|
||
|
yield httpserver
|
||
|
|
||
|
httpserver.stop() # destroy it
|
||
|
|
||
|
|
||
|
@pytest.fixture(scope='module')
|
||
|
def wsgi_server():
|
||
|
"""Set up and tear down a Cheroot WSGI server instance."""
|
||
|
for srv in cheroot_server(cheroot.wsgi.Server):
|
||
|
yield srv
|
||
|
|
||
|
|
||
|
@pytest.fixture(scope='module')
|
||
|
def native_server():
|
||
|
"""Set up and tear down a Cheroot HTTP server instance."""
|
||
|
for srv in cheroot_server(cheroot.server.HTTPServer):
|
||
|
yield srv
|
||
|
|
||
|
|
||
|
class _TestClient:
|
||
|
def __init__(self, server):
|
||
|
self._interface, self._host, self._port = _get_conn_data(
|
||
|
server.bind_addr,
|
||
|
)
|
||
|
self.server_instance = server
|
||
|
self._http_connection = self.get_connection()
|
||
|
|
||
|
def get_connection(self):
|
||
|
name = '{interface}:{port}'.format(
|
||
|
interface=self._interface,
|
||
|
port=self._port,
|
||
|
)
|
||
|
conn_cls = (
|
||
|
http_client.HTTPConnection
|
||
|
if self.server_instance.ssl_adapter is None else
|
||
|
http_client.HTTPSConnection
|
||
|
)
|
||
|
return conn_cls(name)
|
||
|
|
||
|
def request(
|
||
|
self, uri, method='GET', headers=None, http_conn=None,
|
||
|
protocol='HTTP/1.1',
|
||
|
):
|
||
|
return webtest.openURL(
|
||
|
uri, method=method,
|
||
|
headers=headers,
|
||
|
host=self._host, port=self._port,
|
||
|
http_conn=http_conn or self._http_connection,
|
||
|
protocol=protocol,
|
||
|
)
|
||
|
|
||
|
def __getattr__(self, attr_name):
|
||
|
def _wrapper(uri, **kwargs):
|
||
|
http_method = attr_name.upper()
|
||
|
return self.request(uri, method=http_method, **kwargs)
|
||
|
|
||
|
return _wrapper
|
||
|
|
||
|
|
||
|
def _probe_ipv6_sock(interface):
|
||
|
# Alternate way is to check IPs on interfaces using glibc, like:
|
||
|
# github.com/Gautier/minifail/blob/master/minifail/getifaddrs.py
|
||
|
try:
|
||
|
with closing(socket.socket(family=socket.AF_INET6)) as sock:
|
||
|
sock.bind((interface, 0))
|
||
|
except (OSError, socket.error) as sock_err:
|
||
|
# In Python 3 socket.error is an alias for OSError
|
||
|
# In Python 2 socket.error is a subclass of IOError
|
||
|
if sock_err.errno != errno.EADDRNOTAVAIL:
|
||
|
raise
|
||
|
else:
|
||
|
return True
|
||
|
|
||
|
return False
|
||
|
|
||
|
|
||
|
def _get_conn_data(bind_addr):
|
||
|
if isinstance(bind_addr, tuple):
|
||
|
host, port = bind_addr
|
||
|
else:
|
||
|
host, port = bind_addr, 0
|
||
|
|
||
|
interface = webtest.interface(host)
|
||
|
|
||
|
if ':' in interface and not _probe_ipv6_sock(interface):
|
||
|
interface = '127.0.0.1'
|
||
|
if ':' in host:
|
||
|
host = interface
|
||
|
|
||
|
return interface, host, port
|
||
|
|
||
|
|
||
|
def get_server_client(server):
|
||
|
"""Create and return a test client for the given server."""
|
||
|
return _TestClient(server)
|