Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New server #1362

Merged
merged 14 commits into from
Nov 9, 2016
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ cov-dev: .develop
@echo "open file://`pwd`/coverage/index.html"

cov-dev-full: .develop
@echo "Run without extensions"
@AIOHTTP_NO_EXTENSIONS=1 py.test --cov=aiohttp tests
@echo "Run in debug mode"
@PYTHONASYNCIODEBUG=1 py.test --cov=aiohttp --cov-append tests
@echo "Regular run"
@py.test --cov=aiohttp --cov-report=term --cov-report=html --cov-append tests
@echo "open file://`pwd`/coverage/index.html"

Expand Down
30 changes: 29 additions & 1 deletion aiohttp/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from aiohttp.web import Application

from .test_utils import unused_port as _unused_port
from .test_utils import (TestClient, TestServer, loop_context, setup_test_loop,
from .test_utils import (RawTestServer, TestClient,
TestServer,
loop_context, setup_test_loop,
teardown_test_loop)


Expand Down Expand Up @@ -81,6 +83,27 @@ def finalize():
loop.run_until_complete(finalize())


@pytest.yield_fixture
def raw_test_server(loop):
servers = []

@asyncio.coroutine
def go(handler, **kwargs):
server = RawTestServer(handler, loop=loop)
yield from server.start_server(**kwargs)
servers.append(server)
return server

yield go

@asyncio.coroutine
def finalize():
while servers:
yield from servers.pop().close()

loop.run_until_complete(finalize())


@pytest.yield_fixture
def test_client(loop):
clients = []
Expand All @@ -97,6 +120,11 @@ def go(__param, *args, **kwargs):
assert __param.app.loop is loop, \
"TestServer is attached to other event loop"
client = TestClient(__param, **kwargs)
elif isinstance(__param, RawTestServer):
assert not args, "args should be empty"
assert __param._loop is loop, \
"TestServer is attached to other event loop"
client = TestClient(__param, **kwargs)
else:
__param = __param(loop, *args, **kwargs)
client = TestClient(__param)
Expand Down
80 changes: 66 additions & 14 deletions aiohttp/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import socket
import sys
import unittest
from abc import ABC, abstractmethod
from unittest import mock

from multidict import CIMultiDict
Expand All @@ -19,7 +20,7 @@
from .helpers import sentinel
from .protocol import HttpVersion, RawRequestMessage
from .signals import Signal
from .web import Application, Request, UrlMappingMatchInfo
from .web import Application, Request, UrlMappingMatchInfo, WebServer

PY_35 = sys.version_info >= (3, 5)

Expand All @@ -39,10 +40,8 @@ def unused_port():
return s.getsockname()[1]


class TestServer:
def __init__(self, app, *, scheme=sentinel, host='127.0.0.1'):
self.app = app
self._loop = app.loop
class BaseTestServer(ABC):
def __init__(self, *, scheme=sentinel, host='127.0.0.1'):
self.port = None
self.server = None
self.handler = None
Expand All @@ -66,17 +65,30 @@ def start_server(self, **kwargs):
self._root = URL('{}://{}:{}'.format(self.scheme,
self.host,
self.port))
yield from self.app.startup()
self.handler = self.app.make_handler(**kwargs)
self.server = yield from self._loop.create_server(self.handler,

handler = yield from self._make_factory(**kwargs)
self.server = yield from self._loop.create_server(handler,
self.host,
self.port,
ssl=self._ssl)

@abstractmethod # pragma: no cover
@asyncio.coroutine
def _make_factory(self):
pass

def make_url(self, path):
assert path.startswith('/')
return URL(str(self._root) + path)

@property
def started(self):
return self.server is not None

@property
def closed(self):
return self._closed

@asyncio.coroutine
def close(self):
"""Close all fixtures created by the test client.
Expand All @@ -90,16 +102,19 @@ def close(self):
exit when used as a context manager.

"""
if self.server is not None and not self._closed:
if self.started and not self.closed:
self.server.close()
yield from self.server.wait_closed()
yield from self.app.shutdown()
yield from self.handler.finish_connections()
yield from self.app.cleanup()
self._root = None
self.port = None
yield from self._close_hook()
self._closed = True

@abstractmethod
@asyncio.coroutine
def _close_hook(self):
pass # pragma: no cover

def __enter__(self):
self._loop.run_until_complete(self.start_server())
return self
Expand All @@ -118,6 +133,43 @@ def __aexit__(self, exc_type, exc_value, traceback):
yield from self.close()


class TestServer(BaseTestServer):
def __init__(self, app, *, scheme=sentinel, host='127.0.0.1'):
self.app = app
self._loop = app.loop
super().__init__(scheme=scheme, host=host)

@asyncio.coroutine
def _make_factory(self, **kwargs):
yield from self.app.startup()
self.handler = self.app.make_handler(**kwargs)
return self.handler

@asyncio.coroutine
def _close_hook(self):
yield from self.app.shutdown()
yield from self.handler.shutdown()
yield from self.app.cleanup()


class RawTestServer(BaseTestServer):
def __init__(self, handler,
*, loop=None, scheme=sentinel, host='127.0.0.1'):
if loop is None:
loop = asyncio.get_event_loop()
self._loop = loop
self._handler = handler
super().__init__(scheme=scheme, host=host)

@asyncio.coroutine
def _make_factory(self, **kwargs):
return WebServer(self._handler, loop=self._loop)

@asyncio.coroutine
def _close_hook(self):
return


class TestClient:
"""
A test client implementation, for a aiohttp.web.Application.
Expand All @@ -136,7 +188,7 @@ class TestClient:

def __init__(self, app_or_server, *, scheme=sentinel, host=sentinel,
cookie_jar=None, **kwargs):
if isinstance(app_or_server, TestServer):
if isinstance(app_or_server, BaseTestServer):
if scheme is not sentinel or host is not sentinel:
raise ValueError("scheme and host are mutable exclusive "
"with TestServer parameter")
Expand All @@ -149,7 +201,7 @@ def __init__(self, app_or_server, *, scheme=sentinel, host=sentinel,
else:
raise TypeError("app_or_server should be either web.Application "
"or TestServer instance")
self._loop = self._server.app.loop
self._loop = self._server._loop
if cookie_jar is None:
cookie_jar = aiohttp.CookieJar(unsafe=True,
loop=self._loop)
Expand Down
Loading