mirror of
https://github.com/sprockets/sprockets.http.git
synced 2024-11-14 11:19:26 +00:00
Merge pull request #32 from dave-shawley/add-testing
Add testing module.
This commit is contained in:
commit
fe4f4a7e38
12 changed files with 123 additions and 15 deletions
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2015-2018 AWeber Communications
|
||||
Copyright (c) 2015-2019 AWeber Communications
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
|
|
6
docs/_static/custom.css
vendored
6
docs/_static/custom.css
vendored
|
@ -1,5 +1,7 @@
|
|||
div.body { max-width: 85% }
|
||||
div.document { width: 90% }
|
||||
h1.logo {font-size:inherit}
|
||||
th.field-name {hyphens: manual; -webkit-hyphens: none}
|
||||
dl.class dt {padding-left: 5em; padding-right: 5em; text-indent: -5em}
|
||||
dl.function dt {padding-left: 5em; padding-right: 5em; text-indent: -5em}
|
||||
dl.class>dt {text-indent: -45px; padding-left: 45px}
|
||||
dl.function dt {text-indent: -45px; padding-left: 45px}
|
||||
div.seealso {background-color: initial; border: initial}
|
||||
|
|
12
docs/api.rst
12
docs/api.rst
|
@ -113,6 +113,17 @@ raised by the callbacks are simply logged.
|
|||
|
||||
.. seealso:: :attr:`~sprockets.http.app.CallbackManager.on_shutdown_callbacks`
|
||||
|
||||
Testing your Application
|
||||
------------------------
|
||||
The :class:`~sprockets.http.testing.SprocketsHttpTestCase` class makes
|
||||
it simple to test sprockets.http based applications. It knows how to
|
||||
call the appropriate callbacks at the appropriate time. Use this as a
|
||||
base class in place of :class:`~tornado.testing.AsyncHTTPTestCase` and
|
||||
modify your ``get_app`` method to set ``self.app``.
|
||||
|
||||
.. autoclass:: sprockets.http.testing.SprocketsHttpTestCase
|
||||
:members:
|
||||
|
||||
Response Logging
|
||||
----------------
|
||||
Version 0.5.0 introduced the :mod:`sprockets.http.mixins` module with
|
||||
|
@ -177,6 +188,7 @@ If :class:`~sprockets.mixins.mediatype.ContentMixin` is being used as well,
|
|||
:meth:`~sprockets.mixins.mediatype.ContentMixin.send_response` to send the
|
||||
document, otherwise it is sent as JSON.
|
||||
|
||||
|
||||
.. autoclass:: sprockets.http.mixins.ErrorWriter
|
||||
:members:
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ Release History
|
|||
|
||||
`Next Release`_
|
||||
---------------
|
||||
- Make shutdown timings configurable.
|
||||
- Add :class:`sprockets.http.testing.SprocketsHttpTestCase`.
|
||||
|
||||
`2.0.1`_ (5 Mar 2019)
|
||||
----------------------
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-e .
|
||||
-r testing.txt
|
||||
-r docs.txt
|
||||
flake8
|
||||
flake8==3.7.8
|
||||
|
|
|
@ -1 +1 @@
|
|||
sphinx==1.8.2
|
||||
sphinx==2.2.0
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
coverage>=4.5,<5
|
||||
nose>=1.3.7,<2
|
||||
tox>=3.5,<4
|
||||
coverage==4.5.4
|
||||
nose==1.3.7
|
||||
tox==3.13.2
|
||||
|
|
1
setup.py
1
setup.py
|
@ -45,6 +45,7 @@ setuptools.setup(
|
|||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
|
|
|
@ -8,11 +8,12 @@ from tornado import concurrent, web
|
|||
class _ShutdownHandler:
|
||||
"""Keeps track of the application state during shutdown."""
|
||||
|
||||
def __init__(self, io_loop):
|
||||
def __init__(self, io_loop, shutdown_limit, wait_timeout):
|
||||
self.io_loop = io_loop
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.pending_callbacks = 0
|
||||
self.shutdown_limit = 5
|
||||
self.shutdown_limit = shutdown_limit
|
||||
self.wait_timeout = wait_timeout
|
||||
self.__deadline = None
|
||||
|
||||
def add_future(self, future):
|
||||
|
@ -42,7 +43,7 @@ class _ShutdownHandler:
|
|||
def _maybe_stop(self):
|
||||
now = self.io_loop.time()
|
||||
if now < self.__deadline and asyncio.Task.all_tasks():
|
||||
self.io_loop.add_timeout(now + 1, self._maybe_stop)
|
||||
self.io_loop.add_timeout(now + self.wait_timeout, self._maybe_stop)
|
||||
else:
|
||||
self.io_loop.stop()
|
||||
self.logger.info('stopped IOLoop')
|
||||
|
@ -103,20 +104,24 @@ class CallbackManager:
|
|||
for callback in self.on_start_callbacks:
|
||||
io_loop.spawn_callback(callback, self.tornado_application, io_loop)
|
||||
|
||||
def stop(self, io_loop):
|
||||
def stop(self, io_loop, shutdown_limit=5.0, wait_timeout=1.0):
|
||||
"""
|
||||
Asynchronously stop the application.
|
||||
|
||||
:param tornado.ioloop.IOLoop io_loop: loop to run until all
|
||||
callbacks, timeouts, and queued calls are complete
|
||||
:param float shutdown_limit: maximum number of seconds to wait
|
||||
before terminating
|
||||
:param float wait_timeout: number of seconds to wait between checks
|
||||
for pending callbacks & timers
|
||||
|
||||
Call this method to start the application shutdown process.
|
||||
The IOLoop will be stopped once the application is completely
|
||||
shut down.
|
||||
shut down or after `shutdown_limit` seconds.
|
||||
|
||||
"""
|
||||
running_async = False
|
||||
shutdown = _ShutdownHandler(io_loop)
|
||||
shutdown = _ShutdownHandler(io_loop, shutdown_limit, wait_timeout)
|
||||
for callback in self.on_shutdown_callbacks:
|
||||
try:
|
||||
maybe_future = callback(self.tornado_application)
|
||||
|
|
|
@ -55,6 +55,8 @@ class Runner:
|
|||
app, before_run, on_start, shutdown)
|
||||
self.logger = logging.getLogger('Runner')
|
||||
self.server = None
|
||||
self.shutdown_limit = 5.0
|
||||
self.wait_timeout = 1.0
|
||||
|
||||
def start_server(self, port_number, number_of_procs=0):
|
||||
"""
|
||||
|
@ -142,7 +144,8 @@ class Runner:
|
|||
self.stop_server()
|
||||
|
||||
# Start the application shutdown process
|
||||
self.application.stop(ioloop.IOLoop.instance())
|
||||
self.application.stop(ioloop.IOLoop.instance(), self.shutdown_limit,
|
||||
self.wait_timeout)
|
||||
|
||||
|
||||
class RunCommand(cmd.Command):
|
||||
|
|
56
sprockets/http/testing.py
Normal file
56
sprockets/http/testing.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
from tornado import testing
|
||||
|
||||
|
||||
class SprocketsHttpTestCase(testing.AsyncHTTPTestCase):
|
||||
"""Test case that correctly runs a sprockets.http.app.Application.
|
||||
|
||||
This test case correctly starts and stops a sprockets.http Application
|
||||
by calling the :meth:`~sprockets.http.app.CallbackManager.start` and
|
||||
:meth:`~sprockets.http.app.CallbackManager.stop` methods during ``setUp``
|
||||
and ``tearDown``.
|
||||
|
||||
.. attribute:: app
|
||||
|
||||
You are required to set this attribute in your :meth:`.get_app`
|
||||
implementation.
|
||||
|
||||
"""
|
||||
|
||||
shutdown_limit = 0.25
|
||||
"""Maximum number of seconds to wait for the application to shut down."""
|
||||
|
||||
wait_timeout = 0.05
|
||||
"""Number of seconds to wait between checking for pending callbacks."""
|
||||
|
||||
def setUp(self):
|
||||
"""Hook method for setting up the test fixture before exercising it.
|
||||
|
||||
The sprockets.http application is started by calling the
|
||||
:meth:`~sprockets.http.app.CallbackManager.start` method after the
|
||||
application is created.
|
||||
|
||||
"""
|
||||
self.app = None
|
||||
super(SprocketsHttpTestCase, self).setUp()
|
||||
self.app.start(self.io_loop)
|
||||
|
||||
def tearDown(self):
|
||||
"""Hook method for deconstructing the test fixture after exercising it.
|
||||
|
||||
The sprockets.http application is fully stopped by calling the
|
||||
:meth:`~sprockets.http.app.CallbackManager.stop` and running the ioloop
|
||||
*before* stopping the ioloop. The shutdown timing is configured using
|
||||
the :attr:`.shutdown_limit` and :attr:`.wait_timeout` variables.
|
||||
|
||||
"""
|
||||
self.app.stop(self.io_loop, self.shutdown_limit, self.wait_timeout)
|
||||
self.io_loop.start()
|
||||
super(SprocketsHttpTestCase, self).tearDown()
|
||||
|
||||
def get_app(self):
|
||||
"""Override this method to create your application.
|
||||
|
||||
Make sure to set ``self.app`` before returning.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
27
tests.py
27
tests.py
|
@ -12,6 +12,7 @@ from tornado import concurrent, httpserver, httputil, ioloop, testing, web
|
|||
|
||||
import sprockets.http.mixins
|
||||
import sprockets.http.runner
|
||||
import sprockets.http.testing
|
||||
import examples
|
||||
|
||||
|
||||
|
@ -448,6 +449,7 @@ class RunnerTests(MockHelper, unittest.TestCase):
|
|||
self.io_loop._timeouts = [mock.Mock()]
|
||||
runner = sprockets.http.runner.Runner(self.application)
|
||||
runner.shutdown_limit = 0.25
|
||||
runner.wait_timeout = 0.05
|
||||
runner.run(8000)
|
||||
runner._shutdown()
|
||||
self.io_loop.stop.assert_called_once_with()
|
||||
|
@ -615,3 +617,28 @@ class RunCommandTests(MockHelper, unittest.TestCase):
|
|||
command.run()
|
||||
|
||||
run_function.assert_called_once_with(result_closure['result'])
|
||||
|
||||
|
||||
class TestCaseTests(unittest.TestCase):
|
||||
|
||||
class FakeTest(sprockets.http.testing.SprocketsHttpTestCase):
|
||||
def get_app(self):
|
||||
self.app = mock.Mock()
|
||||
return self.app
|
||||
|
||||
def runTest(self):
|
||||
pass
|
||||
|
||||
def test_that_setup_calls_start(self):
|
||||
test_case = self.FakeTest()
|
||||
test_case.setUp()
|
||||
test_case.app.start.assert_called_once_with(test_case.io_loop)
|
||||
|
||||
def test_that_teardown_calls_stop(self):
|
||||
test_case = self.FakeTest()
|
||||
test_case.setUp()
|
||||
test_case.io_loop = mock.Mock()
|
||||
test_case.tearDown()
|
||||
test_case.app.stop.assert_called_once_with(
|
||||
test_case.io_loop, test_case.shutdown_limit,
|
||||
test_case.wait_timeout)
|
||||
|
|
Loading…
Reference in a new issue