Skip to content

Commit

Permalink
New server (#1362)
Browse files Browse the repository at this point in the history
* Extract BaseRequest

* New server

* Add no branch instruction

* Work on new server

* Drop useless test

* Fix tests

* Update doc

* Fix tests

* Add echo printouts

* Add test

* Add missing test

* Improve coverage
  • Loading branch information
asvetlov authored Nov 9, 2016
1 parent bba8857 commit fce908b
Show file tree
Hide file tree
Showing 15 changed files with 384 additions and 217 deletions.
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

0 comments on commit fce908b

Please sign in to comment.