Test runserver (#214)

* Add tests for runserver and runworker management commands

* Fix flake8 and isort errors

* Refactor mocking, add comments to tests

* rm unneeded vargs
This commit is contained in:
Tim Watts 2016-06-29 20:26:21 +02:00 committed by Andrew Godwin
parent 5eb3bf848c
commit 4a09cec2d4
7 changed files with 180 additions and 6 deletions

View File

@ -1,6 +1,6 @@
[run]
branch = True
source = channels, django.http.response
source = channels
omit = channels/tests/*
[report]

View File

@ -1,14 +1,16 @@
import logging
handler = logging.StreamHandler()
def setup_logger(name, verbosity=1):
"""
Basic logger for runserver etc.
"""
formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
formatter = logging.Formatter(
fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
# Set up main logger
@ -22,7 +24,8 @@ def setup_logger(name, verbosity=1):
for module in ["daphne.ws_protocol", "daphne.http_protocol"]:
daphne_logger = logging.getLogger(module)
daphne_logger.addHandler(handler)
daphne_logger.setLevel(logging.DEBUG if verbosity > 1 else logging.INFO)
daphne_logger.setLevel(
logging.DEBUG if verbosity > 1 else logging.INFO)
logger.propagate = False
return logger

View File

@ -2,6 +2,7 @@ import datetime
import sys
import threading
from daphne.server import Server
from django.conf import settings
from django.core.management.commands.runserver import \
Command as RunserverCommand
@ -72,7 +73,6 @@ class Command(RunserverCommand):
# actually a subthread under the autoreloader.
self.logger.debug("Daphne running, listening on %s:%s", self.addr, self.port)
try:
from daphne.server import Server
Server(
channel_layer=self.channel_layer,
host=self.addr,

View File

@ -5,8 +5,8 @@ from django.core.management import BaseCommand, CommandError
from channels import DEFAULT_CHANNEL_LAYER, channel_layers
from channels.log import setup_logger
from channels.worker import Worker
from channels.staticfiles import StaticFilesConsumer
from channels.worker import Worker
class Command(BaseCommand):

View File

@ -1,5 +1,13 @@
SECRET_KEY = 'cat'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.admin',
'channels',
)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
@ -11,6 +19,10 @@ CHANNEL_LAYERS = {
'BACKEND': 'asgiref.inmemory.ChannelLayer',
'ROUTING': [],
},
'fake_channel': {
'BACKEND': 'channels.tests.test_management.FakeChannelLayer',
'ROUTING': [],
}
}
MIDDLEWARE_CLASSES = []

View File

@ -0,0 +1,158 @@
from __future__ import unicode_literals
import logging
from asgiref.inmemory import ChannelLayer
from django.core.management import CommandError, call_command
from channels.staticfiles import StaticFilesConsumer
from django.test import TestCase, mock
from six import StringIO
from channels.management.commands import runserver
class FakeChannelLayer(ChannelLayer):
'''
Dummy class to bypass the 'inmemory' string check.
'''
pass
@mock.patch('channels.management.commands.runworker.Worker')
class RunWorkerTests(TestCase):
def setUp(self):
import channels.log
self.stream = StringIO()
channels.log.handler = logging.StreamHandler(self.stream)
def test_runworker_no_local_only(self, mock_worker):
"""
Runworker should fail with the default "inmemory" worker.
"""
with self.assertRaises(CommandError):
call_command('runworker')
def test_debug(self, mock_worker):
"""
Test that the StaticFilesConsumer is used in debug mode.
"""
with self.settings(DEBUG=True, STATIC_URL='/static/'):
# Use 'fake_channel' that bypasses the 'inmemory' check
call_command('runworker', '--layer', 'fake_channel')
mock_worker.assert_called_with(
only_channels=None, exclude_channels=None, callback=None, channel_layer=mock.ANY)
channel_layer = mock_worker.call_args[1]['channel_layer']
static_consumer = channel_layer.router.root.routing[0].consumer
self.assertIsInstance(static_consumer, StaticFilesConsumer)
def test_runworker(self, mock_worker):
# Use 'fake_channel' that bypasses the 'inmemory' check
call_command('runworker', '--layer', 'fake_channel')
mock_worker.assert_called_with(callback=None,
only_channels=None,
channel_layer=mock.ANY,
exclude_channels=None)
def test_runworker_verbose(self, mocked_worker):
# Use 'fake_channel' that bypasses the 'inmemory' check
call_command('runworker', '--layer',
'fake_channel', '--verbosity', '2')
# Verify the callback is set
mocked_worker.assert_called_with(callback=mock.ANY,
only_channels=None,
channel_layer=mock.ANY,
exclude_channels=None)
class RunServerTests(TestCase):
def setUp(self):
import channels.log
self.stream = StringIO()
# Capture the logging of the channels moduel to match against the
# output.
channels.log.handler = logging.StreamHandler(self.stream)
@mock.patch('channels.management.commands.runserver.sys.stdout', new_callable=StringIO)
@mock.patch('channels.management.commands.runserver.Server')
@mock.patch('channels.management.commands.runworker.Worker')
def test_runserver_basic(self, mocked_worker, mocked_server, mock_stdout):
# Django's autoreload util uses threads and this is not needed
# in the test envirionment.
# See:
# https://github.com/django/django/blob/master/django/core/management/commands/runserver.py#L105
call_command('runserver', '--noreload')
mocked_server.assert_called_with(port=8000, signal_handlers=True, http_timeout=60,
host='127.0.0.1', action_logger=mock.ANY, channel_layer=mock.ANY)
@mock.patch('channels.management.commands.runserver.sys.stdout', new_callable=StringIO)
@mock.patch('channels.management.commands.runserver.Server')
@mock.patch('channels.management.commands.runworker.Worker')
def test_runserver_debug(self, mocked_worker, mocked_server, mock_stdout):
"""
Test that the server runs with `DEBUG=True`.
"""
# Debug requires the static url is set.
with self.settings(DEBUG=True, STATIC_URL='/static/'):
call_command('runserver', '--noreload')
mocked_server.assert_called_with(port=8000, signal_handlers=True, http_timeout=60,
host='127.0.0.1', action_logger=mock.ANY, channel_layer=mock.ANY)
call_command('runserver', '--noreload', 'localhost:8001')
mocked_server.assert_called_with(port=8001, signal_handlers=True, http_timeout=60,
host='localhost', action_logger=mock.ANY, channel_layer=mock.ANY)
self.assertFalse(mocked_worker.called,
"The worker should not be called with '--noworker'")
@mock.patch('channels.management.commands.runserver.sys.stdout', new_callable=StringIO)
@mock.patch('channels.management.commands.runserver.Server')
@mock.patch('channels.management.commands.runworker.Worker')
def test_runserver_noworker(self, mocked_worker, mocked_server, mock_stdout):
'''
Test that the Worker is not called when using the `--noworker` parameter.
'''
call_command('runserver', '--noreload', '--noworker')
mocked_server.assert_called_with(port=8000, signal_handlers=True, http_timeout=60,
host='127.0.0.1', action_logger=mock.ANY, channel_layer=mock.ANY)
self.assertFalse(mocked_worker.called,
"The worker should not be called with '--noworker'")
@mock.patch('channels.management.commands.runserver.sys.stderr', new_callable=StringIO)
def test_log_action(self, mocked_stderr):
cmd = runserver.Command()
test_actions = [
(100, 'http', 'complete',
'HTTP GET /a-path/ 100 [0.12, a-client]'),
(200, 'http', 'complete',
'HTTP GET /a-path/ 200 [0.12, a-client]'),
(300, 'http', 'complete',
'HTTP GET /a-path/ 300 [0.12, a-client]'),
(304, 'http', 'complete',
'HTTP GET /a-path/ 304 [0.12, a-client]'),
(400, 'http', 'complete',
'HTTP GET /a-path/ 400 [0.12, a-client]'),
(404, 'http', 'complete',
'HTTP GET /a-path/ 404 [0.12, a-client]'),
(500, 'http', 'complete',
'HTTP GET /a-path/ 500 [0.12, a-client]'),
(None, 'websocket', 'connected',
'WebSocket CONNECT /a-path/ [a-client]'),
(None, 'websocket', 'disconnected',
'WebSocket DISCONNECT /a-path/ [a-client]'),
(None, 'websocket', 'something', ''), # This shouldn't happen
]
for status_code, protocol, action, output in test_actions:
details = {'status': status_code,
'method': 'GET',
'path': '/a-path/',
'time_taken': 0.12345,
'client': 'a-client'}
cmd.log_action(protocol, action, details)
self.assertIn(output, mocked_stderr.getvalue())
# Clear previous output
mocked_stderr.truncate(0)

View File

@ -13,6 +13,7 @@ setenv =
deps =
autobahn
coverage
daphne
asgiref>=0.9
six
redis==2.10.5