mirror of
https://github.com/django/daphne.git
synced 2025-07-10 16:02:18 +03:00
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:
parent
5eb3bf848c
commit
4a09cec2d4
|
@ -1,6 +1,6 @@
|
||||||
[run]
|
[run]
|
||||||
branch = True
|
branch = True
|
||||||
source = channels, django.http.response
|
source = channels
|
||||||
omit = channels/tests/*
|
omit = channels/tests/*
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
|
||||||
|
|
||||||
def setup_logger(name, verbosity=1):
|
def setup_logger(name, verbosity=1):
|
||||||
"""
|
"""
|
||||||
Basic logger for runserver etc.
|
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)
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
# Set up main logger
|
# Set up main logger
|
||||||
|
@ -22,7 +24,8 @@ def setup_logger(name, verbosity=1):
|
||||||
for module in ["daphne.ws_protocol", "daphne.http_protocol"]:
|
for module in ["daphne.ws_protocol", "daphne.http_protocol"]:
|
||||||
daphne_logger = logging.getLogger(module)
|
daphne_logger = logging.getLogger(module)
|
||||||
daphne_logger.addHandler(handler)
|
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
|
logger.propagate = False
|
||||||
return logger
|
return logger
|
||||||
|
|
|
@ -2,6 +2,7 @@ import datetime
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from daphne.server import Server
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.commands.runserver import \
|
from django.core.management.commands.runserver import \
|
||||||
Command as RunserverCommand
|
Command as RunserverCommand
|
||||||
|
@ -72,7 +73,6 @@ class Command(RunserverCommand):
|
||||||
# actually a subthread under the autoreloader.
|
# actually a subthread under the autoreloader.
|
||||||
self.logger.debug("Daphne running, listening on %s:%s", self.addr, self.port)
|
self.logger.debug("Daphne running, listening on %s:%s", self.addr, self.port)
|
||||||
try:
|
try:
|
||||||
from daphne.server import Server
|
|
||||||
Server(
|
Server(
|
||||||
channel_layer=self.channel_layer,
|
channel_layer=self.channel_layer,
|
||||||
host=self.addr,
|
host=self.addr,
|
||||||
|
|
|
@ -5,8 +5,8 @@ from django.core.management import BaseCommand, CommandError
|
||||||
|
|
||||||
from channels import DEFAULT_CHANNEL_LAYER, channel_layers
|
from channels import DEFAULT_CHANNEL_LAYER, channel_layers
|
||||||
from channels.log import setup_logger
|
from channels.log import setup_logger
|
||||||
from channels.worker import Worker
|
|
||||||
from channels.staticfiles import StaticFilesConsumer
|
from channels.staticfiles import StaticFilesConsumer
|
||||||
|
from channels.worker import Worker
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
SECRET_KEY = 'cat'
|
SECRET_KEY = 'cat'
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'channels',
|
||||||
|
)
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
@ -11,6 +19,10 @@ CHANNEL_LAYERS = {
|
||||||
'BACKEND': 'asgiref.inmemory.ChannelLayer',
|
'BACKEND': 'asgiref.inmemory.ChannelLayer',
|
||||||
'ROUTING': [],
|
'ROUTING': [],
|
||||||
},
|
},
|
||||||
|
'fake_channel': {
|
||||||
|
'BACKEND': 'channels.tests.test_management.FakeChannelLayer',
|
||||||
|
'ROUTING': [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = []
|
MIDDLEWARE_CLASSES = []
|
||||||
|
|
158
channels/tests/test_management.py
Normal file
158
channels/tests/test_management.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user