more asyncio unit tests
This commit is contained in:
@@ -8,6 +8,10 @@ from microdot import Request as BaseRequest
|
|||||||
from microdot import Response as BaseResponse
|
from microdot import Response as BaseResponse
|
||||||
|
|
||||||
|
|
||||||
|
def _iscoroutine(coro):
|
||||||
|
return hasattr(coro, 'send') and hasattr(coro, 'throw')
|
||||||
|
|
||||||
|
|
||||||
class Request(BaseRequest):
|
class Request(BaseRequest):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def create(stream, client_addr):
|
async def create(stream, client_addr):
|
||||||
@@ -69,19 +73,20 @@ class Microdot(BaseMicrodot):
|
|||||||
method=req.method, path=req.path,
|
method=req.method, path=req.path,
|
||||||
status_code=res.status_code))
|
status_code=res.status_code))
|
||||||
|
|
||||||
if not hasattr(writer, 'awrite'):
|
if not hasattr(writer, 'awrite'): # pragma: no cover
|
||||||
class AWriter:
|
# CPython adds the awrite and aclose methods in 3.8
|
||||||
def __init__(self, writer):
|
async def awrite(self, data):
|
||||||
self.writer = writer
|
self.write(data)
|
||||||
|
await self.drain()
|
||||||
|
|
||||||
async def awrite(self, data):
|
async def aclose(self):
|
||||||
self.writer.write(data)
|
self.close()
|
||||||
await self.writer.drain()
|
await self.wait_closed()
|
||||||
|
|
||||||
async def aclose(self):
|
from types import MethodType
|
||||||
self.writer.close()
|
writer.awrite = MethodType(awrite, writer)
|
||||||
|
writer.aclose = MethodType(aclose, writer)
|
||||||
|
|
||||||
writer = AWriter(writer)
|
|
||||||
await res.write(writer)
|
await res.write(writer)
|
||||||
await writer.aclose()
|
await writer.aclose()
|
||||||
|
|
||||||
@@ -90,7 +95,7 @@ class Microdot(BaseMicrodot):
|
|||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
loop.run_until_complete(asyncio.start_server(serve, host, port))
|
loop.run_until_complete(asyncio.start_server(serve, host, port))
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
loop.close()
|
loop.close() # pragma: no cover
|
||||||
|
|
||||||
async def dispatch_request(self, req):
|
async def dispatch_request(self, req):
|
||||||
f = self.find_route(req)
|
f = self.find_route(req)
|
||||||
@@ -110,7 +115,7 @@ class Microdot(BaseMicrodot):
|
|||||||
for handler in self.after_request_handlers:
|
for handler in self.after_request_handlers:
|
||||||
res = await self._invoke_handler(handler, req, res) or res
|
res = await self._invoke_handler(handler, req, res) or res
|
||||||
elif 404 in self.error_handlers:
|
elif 404 in self.error_handlers:
|
||||||
res = self._invoke_handler(self.error_handlers[404], req)
|
res = await self._invoke_handler(self.error_handlers[404], req)
|
||||||
else:
|
else:
|
||||||
res = 'Not found', 404
|
res = 'Not found', 404
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -118,7 +123,7 @@ class Microdot(BaseMicrodot):
|
|||||||
res = None
|
res = None
|
||||||
if exc.__class__ in self.error_handlers:
|
if exc.__class__ in self.error_handlers:
|
||||||
try:
|
try:
|
||||||
res = self._invoke_handler(
|
res = await self._invoke_handler(
|
||||||
self.error_handlers[exc.__class__], req, exc)
|
self.error_handlers[exc.__class__], req, exc)
|
||||||
except Exception as exc2: # pragma: no cover
|
except Exception as exc2: # pragma: no cover
|
||||||
print_exception(exc2)
|
print_exception(exc2)
|
||||||
@@ -135,13 +140,10 @@ class Microdot(BaseMicrodot):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
async def _invoke_handler(self, f_or_coro, *args, **kwargs):
|
async def _invoke_handler(self, f_or_coro, *args, **kwargs):
|
||||||
r = f_or_coro(*args, **kwargs)
|
ret = f_or_coro(*args, **kwargs)
|
||||||
try:
|
if _iscoroutine(ret):
|
||||||
r = await r
|
ret = await ret
|
||||||
except TypeError:
|
return ret
|
||||||
# not a coroutine
|
|
||||||
pass
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
redirect = Response.redirect
|
redirect = Response.redirect
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ from tests.test_microdot import TestMicrodot
|
|||||||
|
|
||||||
from tests.test_request_async import TestRequestAsync
|
from tests.test_request_async import TestRequestAsync
|
||||||
from tests.test_response_async import TestResponseAsync
|
from tests.test_response_async import TestResponseAsync
|
||||||
|
from tests.test_microdot_async import TestMicrodotAsync
|
||||||
|
|||||||
42
tests/mock_asyncio.py
Normal file
42
tests/mock_asyncio.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
try:
|
||||||
|
import uasyncio as asyncio
|
||||||
|
except ImportError:
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from tests import mock_socket
|
||||||
|
|
||||||
|
_calls = []
|
||||||
|
|
||||||
|
|
||||||
|
class EventLoop:
|
||||||
|
def run_until_complete(self, coro):
|
||||||
|
_calls.append(('run_until_complete', coro))
|
||||||
|
self.coro = coro
|
||||||
|
|
||||||
|
def run_forever(self):
|
||||||
|
_calls.append(('run_forever',))
|
||||||
|
|
||||||
|
async def rf():
|
||||||
|
s = mock_socket.socket()
|
||||||
|
while True:
|
||||||
|
fd, addr = s.accept()
|
||||||
|
fd = mock_socket.FakeStreamAsync(fd)
|
||||||
|
await self.coro(fd, fd)
|
||||||
|
|
||||||
|
asyncio.get_event_loop().run_until_complete(rf())
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
loop = EventLoop()
|
||||||
|
|
||||||
|
|
||||||
|
def get_event_loop():
|
||||||
|
_calls.append(('get_event_loop',))
|
||||||
|
return loop
|
||||||
|
|
||||||
|
|
||||||
|
def start_server(cb, host, port):
|
||||||
|
_calls.append(('start_server', cb, host, port))
|
||||||
|
return cb
|
||||||
@@ -62,6 +62,12 @@ class FakeStreamAsync:
|
|||||||
async def awrite(self, data):
|
async def awrite(self, data):
|
||||||
self.stream.write(data)
|
self.stream.write(data)
|
||||||
|
|
||||||
|
async def aclose(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_extra_info(self, name, default=None):
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
def get_request_fd(method, path, headers=None, body=None):
|
def get_request_fd(method, path, headers=None, body=None):
|
||||||
if headers is None:
|
if headers is None:
|
||||||
|
|||||||
200
tests/test_microdot_async.py
Normal file
200
tests/test_microdot_async.py
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from microdot_async import Microdot, Response
|
||||||
|
from tests import mock_asyncio, mock_socket
|
||||||
|
|
||||||
|
|
||||||
|
class TestMicrodotAsync(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# mock socket module
|
||||||
|
self.original_asyncio = sys.modules['microdot_async'].asyncio
|
||||||
|
sys.modules['microdot_async'].asyncio = mock_asyncio
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# restore original socket module
|
||||||
|
sys.modules['microdot_async'].asyncio = self.original_asyncio
|
||||||
|
|
||||||
|
def test_get_request(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.route('/async')
|
||||||
|
async def index2(req):
|
||||||
|
return 'foo-async'
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('GET', '/')
|
||||||
|
fd2 = mock_socket.add_request('GET', '/async')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\nfoo'))
|
||||||
|
self.assertTrue(fd2.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 9\r\n', fd2.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd2.response)
|
||||||
|
self.assertTrue(fd2.response.endswith(b'\r\n\r\nfoo-async'))
|
||||||
|
|
||||||
|
def test_post_request(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.route('/', methods=['POST'])
|
||||||
|
def index_post(req):
|
||||||
|
return Response('bar')
|
||||||
|
|
||||||
|
@app.route('/async', methods=['POST'])
|
||||||
|
async def index_post2(req):
|
||||||
|
return Response('bar-async')
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('POST', '/')
|
||||||
|
fd2 = mock_socket.add_request('POST', '/async')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\nbar'))
|
||||||
|
self.assertTrue(fd2.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 9\r\n', fd2.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd2.response)
|
||||||
|
self.assertTrue(fd2.response.endswith(b'\r\n\r\nbar-async'))
|
||||||
|
|
||||||
|
def test_before_after_request(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def before_request(req):
|
||||||
|
if req.path == '/bar':
|
||||||
|
return 'bar', 202
|
||||||
|
req.g.message = 'baz'
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def after_request_one(req, res):
|
||||||
|
res.headers['X-One'] = '1'
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
async def after_request_two(req, res):
|
||||||
|
res.set_cookie('foo', 'bar')
|
||||||
|
return res
|
||||||
|
|
||||||
|
@app.route('/bar')
|
||||||
|
def bar(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.route('/baz')
|
||||||
|
def baz(req):
|
||||||
|
return req.g.message
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('GET', '/bar')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 202 N/A\r\n'))
|
||||||
|
self.assertIn(b'X-One: 1\r\n', fd.response)
|
||||||
|
self.assertIn(b'Set-Cookie: foo=bar\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\nbar'))
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('GET', '/baz')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
||||||
|
self.assertIn(b'X-One: 1\r\n', fd.response)
|
||||||
|
self.assertIn(b'Set-Cookie: foo=bar\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\nbaz'))
|
||||||
|
|
||||||
|
def test_404(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('GET', '/foo')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 404 N/A\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 9\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\nNot found'))
|
||||||
|
|
||||||
|
def test_404_handler(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
async def handle_404(req):
|
||||||
|
return '404'
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('GET', '/foo')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\n404'))
|
||||||
|
|
||||||
|
def test_500(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 1 / 0
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('GET', '/')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 500 N/A\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 21\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\nInternal server error'))
|
||||||
|
|
||||||
|
def test_500_handler(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 1 / 0
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def handle_500(req):
|
||||||
|
return '501', 501
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('GET', '/')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 501 N/A\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\n501'))
|
||||||
|
|
||||||
|
def test_exception_handler(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 1 / 0
|
||||||
|
|
||||||
|
@app.errorhandler(ZeroDivisionError)
|
||||||
|
async def handle_div_zero(req, exc):
|
||||||
|
return '501', 501
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.add_request('GET', '/')
|
||||||
|
self.assertRaises(IndexError, app.run)
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 501 N/A\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\n501'))
|
||||||
Reference in New Issue
Block a user