mirror of
https://github.com/jazzband/django-ddp.git
synced 2026-03-16 22:40:24 +00:00
Merge pull request #32 from django-ddp/feature/#11_test_suite
Test suite runs tests
This commit is contained in:
commit
8ef270dbd5
18 changed files with 537 additions and 113 deletions
28
.travis.yml
28
.travis.yml
|
|
@ -5,6 +5,7 @@
|
|||
sudo: false
|
||||
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
|
|
@ -13,15 +14,24 @@ language: python
|
|||
- "pypy3"
|
||||
|
||||
env:
|
||||
- DJANGO="1.8"
|
||||
- DJANGO="1.9"
|
||||
- PGDATABASE="django_ddp_test_project"
|
||||
- PGUSER="postgres"
|
||||
global:
|
||||
- PGDATABASE="django_ddp_test_project"
|
||||
- PGUSER="postgres"
|
||||
matrix:
|
||||
- DJANGO="1.8"
|
||||
- DJANGO="1.9"
|
||||
|
||||
# Django 1.9 dropped support for Python 3.3
|
||||
matrix:
|
||||
exclude:
|
||||
- python: "3.3"
|
||||
env: DJANGO="1.9"
|
||||
allow_failures:
|
||||
- python: "3.3"
|
||||
- python: "3.4"
|
||||
- python: "3.5"
|
||||
- python: "pypy"
|
||||
- python: "pypy3"
|
||||
|
||||
services:
|
||||
- postgresql
|
||||
|
|
@ -31,19 +41,13 @@ before_install:
|
|||
|
||||
install:
|
||||
- pip install -U tox coveralls setuptools
|
||||
- [[ $TRAVIS_PYTHON_VERSION == "2.7" ]] && PYENV="py27"
|
||||
- [[ $TRAVIS_PYTHON_VERSION == "3.3" ]] && PYENV="py33"
|
||||
- [[ $TRAVIS_PYTHON_VERSION == "3.4" ]] && PYENV="py34"
|
||||
- [[ $TRAVIS_PYTHON_VERSION == "3.5" ]] && PYENV="py35"
|
||||
- [[ $TRAVIS_PYTHON_VERSION == "pypy" ]] && PYENV="pypy"
|
||||
- [[ $TRAVIS_PYTHON_VERSION == "pypy3" ]] && PYENV="pypy3"
|
||||
|
||||
before_script:
|
||||
- psql -c "create database ${PGDATABASE};"
|
||||
- env | sort
|
||||
- psql -c "create database ${PGDATABASE};" postgres
|
||||
|
||||
script:
|
||||
- PATH="$HOME/.meteor:$PATH" tox -vvvv -e ${PYENV}-django${DJANGO}
|
||||
- PATH="$HOME/.meteor:$PATH" tox -vvvv -e $( echo $TRAVIS_PYTHON_VERSION | sed -e 's/^2\./py2/' -e 's/^3\./py3/' )-django${DJANGO}
|
||||
|
||||
after_success:
|
||||
coveralls
|
||||
|
|
|
|||
3
.travis.yml.ok
Normal file
3
.travis.yml.ok
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
1.8.0
|
||||
180e6379f9c19f2fc577e42388d93b4590c6f37d .travis.yml
|
||||
valid
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#!/bin/bash
|
||||
cat<<EOF
|
||||
# .travis.yml automatically generated by "$0"
|
||||
|
||||
# Container-based builds used if "sudo: false" --> fast boot (1-6s)
|
||||
# https://docs.travis-ci.com/user/ci-environment/
|
||||
sudo: false
|
||||
|
||||
language: python
|
||||
|
||||
env:
|
||||
$( tox -l | grep '^py' | sort -n | sed -e 's/^.*$/ - TOXENV="\0"/' )
|
||||
|
||||
install:
|
||||
- pip install tox coveralls
|
||||
|
||||
script:
|
||||
- tox
|
||||
|
||||
after_success:
|
||||
coveralls
|
||||
EOF
|
||||
8
Makefile
8
Makefile
|
|
@ -8,7 +8,7 @@ WHEEL := dist/$(subst -,_,${NAME})-${VERSION}-py2.py3-none-any.whl
|
|||
|
||||
.INTERMEDIATE: dist.intermediate docs
|
||||
|
||||
all: .travis.yml docs dist
|
||||
all: .travis.yml.ok docs dist
|
||||
|
||||
test:
|
||||
tox --skip-missing-interpreters -vvv
|
||||
|
|
@ -50,5 +50,7 @@ upload-pypi: ${SDIST} ${WHEEL}
|
|||
upload-docs: docs/_build/
|
||||
python setup.py upload_sphinx --upload-dir="$<html"
|
||||
|
||||
.travis.yml: tox.ini .travis.yml.sh
|
||||
sh .travis.yml.sh > "$@"
|
||||
.travis.yml.ok: .travis.yml
|
||||
@travis --version > "$@" || { echo 'Install travis command line client?'; exit 1; }
|
||||
sha1sum "$<" >> "$@"
|
||||
travis lint --exit-code | tee -a "$@"
|
||||
|
|
|
|||
50
dddp/accounts/tests.py
Normal file
50
dddp/accounts/tests.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""Django DDP Accounts test suite."""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
from dddp import tests
|
||||
|
||||
|
||||
class AccountsTestCase(tests.DDPServerTestCase):
|
||||
|
||||
# gevent-websocket doesn't work with Python 3 yet
|
||||
@tests.expected_failure_if(sys.version_info.major == 3)
|
||||
def test_login_no_accounts(self):
|
||||
sockjs = self.server.sockjs('/sockjs/1/a/websocket')
|
||||
|
||||
resp = sockjs.websocket.recv()
|
||||
self.assertEqual(resp, 'o')
|
||||
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{'server_id': '0'},
|
||||
],
|
||||
)
|
||||
|
||||
sockjs.connect('1', 'pre2', 'pre1')
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{'msg': 'connected', 'session': msgs[0].get('session', None)},
|
||||
],
|
||||
)
|
||||
|
||||
id_ = sockjs.call(
|
||||
'login', {'user': 'invalid@example.com', 'password': 'foo'},
|
||||
)
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{
|
||||
'msg': 'result',
|
||||
'error': {
|
||||
'error': 500,
|
||||
'reason': "(403, 'Authentication failed.')",
|
||||
},
|
||||
'id': id_,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
sockjs.close()
|
||||
5
dddp/alea.py
Executable file → Normal file
5
dddp/alea.py
Executable file → Normal file
|
|
@ -160,8 +160,3 @@ class Alea(object):
|
|||
def hex_string(self, digits):
|
||||
"""Return a hex string of `digits` length."""
|
||||
return self.random_string(digits, '0123456789abcdef')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ class DjangoDDPConfig(AppConfig):
|
|||
raise ImproperlyConfigured('No databases configured.')
|
||||
for (alias, conf) in settings.DATABASES.items():
|
||||
engine = conf['ENGINE']
|
||||
if engine != 'django.db.backends.postgresql_psycopg2':
|
||||
if engine not in [
|
||||
'django.db.backends.postgresql',
|
||||
'django.db.backends.postgresql_psycopg2',
|
||||
]:
|
||||
warnings.warn(
|
||||
'Database %r uses unsupported %r engine.' % (
|
||||
alias, engine,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class PostgresGreenlet(gevent.Greenlet):
|
|||
gevent.select.select,
|
||||
[conn], [], [], timeout=None,
|
||||
)
|
||||
self.select_greenlet.join()
|
||||
self.select_greenlet.get()
|
||||
except gevent.GreenletExit:
|
||||
self._stop_event.set()
|
||||
finally:
|
||||
|
|
@ -93,6 +93,8 @@ class PostgresGreenlet(gevent.Greenlet):
|
|||
self._stop_event.set()
|
||||
if self.select_greenlet is not None:
|
||||
self.select_greenlet.kill()
|
||||
self.select_greenlet.get()
|
||||
gevent.sleep()
|
||||
|
||||
def poll(self, conn):
|
||||
"""Poll DB socket and process async tasks."""
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
# This file mainly exists to allow `python setup.py test` to work.
|
||||
import os
|
||||
import sys
|
||||
|
||||
import dddp
|
||||
import django
|
||||
from django.test.utils import get_runner
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def run_tests():
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'dddp.test.test_project.settings'
|
||||
dddp.greenify()
|
||||
django.setup()
|
||||
test_runner = get_runner(settings)()
|
||||
failures = test_runner.run_tests(['dddp', 'dddp.test.django_todos'])
|
||||
sys.exit(bool(failures))
|
||||
|
|
@ -1,3 +1,13 @@
|
|||
from django.shortcuts import render
|
||||
import os.path
|
||||
|
||||
# Create your views here.
|
||||
from dddp.views import MeteorView
|
||||
import dddp.test
|
||||
|
||||
|
||||
class MeteorTodos(MeteorView):
|
||||
"""Meteor Todos."""
|
||||
|
||||
json_path = os.path.join(
|
||||
os.path.dirname(dddp.test.__file__),
|
||||
'build', 'bundle', 'star.json'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,13 +3,27 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import dddp
|
||||
dddp.greenify()
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'dddp.test.test_project.settings'
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'dddp.test.test_project.settings'
|
||||
|
||||
from dddp import greenify
|
||||
greenify()
|
||||
|
||||
def run_tests():
|
||||
"""Run the test suite."""
|
||||
import django
|
||||
from django.test.runner import DiscoverRunner
|
||||
django.setup()
|
||||
test_runner = DiscoverRunner(verbosity=2, interactive=False)
|
||||
failures = test_runner.run_tests(['.'])
|
||||
sys.exit(bool(failures))
|
||||
|
||||
|
||||
def main(args): # pragma: no cover
|
||||
"""Execute a management command."""
|
||||
from django.core.management import execute_from_command_line
|
||||
execute_from_command_line(args)
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main(sys.argv)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
|||
SECRET_KEY = 'z@akz#7+cp9w!7%=%kqec79ltlzdn5p&__=(th8^&*t)vo4p35'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
DEBUG = False
|
||||
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
"""Django DDP test project - URL configuraiton."""
|
||||
import os.path
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
from dddp.views import MeteorView
|
||||
import dddp.test
|
||||
from dddp.test.django_todos.views import MeteorTodos
|
||||
|
||||
app = MeteorView.as_view(
|
||||
json_path=os.path.join(
|
||||
os.path.dirname(dddp.test.__file__),
|
||||
'build', 'bundle', 'star.json'
|
||||
),
|
||||
)
|
||||
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
# Examples:
|
||||
# url(r'^$', 'dddp.test.test_project.views.home', name='home'),
|
||||
# url(r'^blog/', include('blog.urls')),
|
||||
|
|
@ -29,5 +22,5 @@ urlpatterns = patterns('',
|
|||
},
|
||||
),
|
||||
# all remaining URLs routed to Meteor app.
|
||||
url(r'^(?P<path>.*)$', app),
|
||||
url(r'^(?P<path>.*)$', MeteorTodos.as_view()),
|
||||
)
|
||||
|
|
|
|||
285
dddp/tests.py
285
dddp/tests.py
|
|
@ -1,12 +1,15 @@
|
|||
"""Django DDP test suite."""
|
||||
from __future__ import unicode_literals
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import doctest
|
||||
import errno
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import unittest
|
||||
from django.test import TestCase
|
||||
import django.test
|
||||
import ejson
|
||||
import gevent
|
||||
import dddp.alea
|
||||
from dddp.main import DDPLauncher
|
||||
# pylint: disable=E0611, F0401
|
||||
|
|
@ -19,16 +22,104 @@ DOCTEST_MODULES = [
|
|||
]
|
||||
|
||||
|
||||
class DDPServerTestCase(TestCase):
|
||||
def expected_failure_if(condition):
|
||||
"""Decorator to conditionally wrap func in unittest.expectedFailure."""
|
||||
if callable(condition):
|
||||
condition = condition()
|
||||
if condition:
|
||||
# condition is True, expect failure.
|
||||
return unittest.expectedFailure
|
||||
else:
|
||||
# condition is False, expect success.
|
||||
return lambda func: func
|
||||
|
||||
"""Test case that starts a DDP server."""
|
||||
|
||||
class WebSocketClient(object):
|
||||
|
||||
"""WebSocket client."""
|
||||
|
||||
# WEBSOCKET
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Create WebSocket connection to URL."""
|
||||
import websocket
|
||||
self.websocket = websocket.create_connection(*args, **kwargs)
|
||||
self.call_seq = 0
|
||||
|
||||
def send(self, **msg):
|
||||
"""Send message."""
|
||||
self.websocket.send(ejson.dumps(msg))
|
||||
|
||||
def recv(self):
|
||||
"""Receive a message."""
|
||||
raw = self.websocket.recv()
|
||||
return ejson.loads(raw)
|
||||
|
||||
def close(self):
|
||||
"""Close the connection."""
|
||||
self.websocket.close()
|
||||
|
||||
# CONTEXT MANAGER
|
||||
|
||||
def __enter__(self):
|
||||
"""Enter context block."""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Exit context block, close connection."""
|
||||
self.websocket.close()
|
||||
|
||||
# DDP
|
||||
def next_id(self):
|
||||
"""Return next `id` from sequence."""
|
||||
self.call_seq += 1
|
||||
return self.call_seq
|
||||
|
||||
def connect(self, *versions):
|
||||
"""Connect with given versions."""
|
||||
self.send(msg='connect', version=versions[0], support=versions)
|
||||
|
||||
def ping(self, id_=None):
|
||||
"""Ping with optional id."""
|
||||
if id_:
|
||||
self.send(msg='ping', id=id_)
|
||||
else:
|
||||
self.send(msg='ping')
|
||||
|
||||
def call(self, method, *args):
|
||||
"""Make a method call."""
|
||||
id_ = self.next_id()
|
||||
self.send(msg='method', method=method, params=args, id=id_)
|
||||
return id_
|
||||
|
||||
|
||||
class SockJSClient(WebSocketClient):
|
||||
|
||||
"""SockJS wrapped WebSocketClient."""
|
||||
|
||||
def send(self, **msg):
|
||||
"""Send a SockJS wrapped msg."""
|
||||
self.websocket.send(ejson.dumps([ejson.dumps(msg)]))
|
||||
|
||||
def recv(self):
|
||||
"""Receive a SockJS wrapped msg."""
|
||||
raw = self.websocket.recv()
|
||||
if not raw.startswith('a'):
|
||||
raise ValueError('Invalid response: %r' % raw)
|
||||
wrapped = ejson.loads(raw[1:])
|
||||
return [ejson.loads(msg) for msg in wrapped]
|
||||
|
||||
|
||||
class DDPTestServer(object):
|
||||
|
||||
"""DDP server with auto start and stop."""
|
||||
|
||||
server_addr = '127.0.0.1'
|
||||
server_port_range = range(8000, 8080)
|
||||
ssl_certfile_path = None
|
||||
ssl_keyfile_path = None
|
||||
|
||||
def setUp(self):
|
||||
def __init__(self):
|
||||
"""Fire up the DDP server."""
|
||||
self.server_port = 8000
|
||||
kwargs = {}
|
||||
|
|
@ -55,10 +146,22 @@ class DDPServerTestCase(TestCase):
|
|||
continue # port in use, try next port.
|
||||
raise RuntimeError('Failed to start DDP server.')
|
||||
|
||||
def tearDown(self):
|
||||
def stop(self):
|
||||
"""Shut down the DDP server."""
|
||||
self.server.stop()
|
||||
|
||||
def websocket(self, url, *args, **kwargs):
|
||||
"""Return a WebSocketClient for the given URL."""
|
||||
return WebSocketClient(
|
||||
self.url(url).replace('http', 'ws'), *args, **kwargs
|
||||
)
|
||||
|
||||
def sockjs(self, url, *args, **kwargs):
|
||||
"""Return a SockJSClient for the given URL."""
|
||||
return SockJSClient(
|
||||
self.url(url).replace('http', 'ws'), *args, **kwargs
|
||||
)
|
||||
|
||||
def url(self, path):
|
||||
"""Return full URL for given path."""
|
||||
return urljoin(
|
||||
|
|
@ -67,10 +170,49 @@ class DDPServerTestCase(TestCase):
|
|||
)
|
||||
|
||||
|
||||
class LaunchTestCase(DDPServerTestCase):
|
||||
class DDPServerTestCase(django.test.TransactionTestCase):
|
||||
|
||||
"""Test that server launches and handles GET request."""
|
||||
_server = None
|
||||
|
||||
@property
|
||||
def server(self):
|
||||
if self._server is None:
|
||||
self._server = DDPTestServer()
|
||||
return self._server
|
||||
|
||||
def tearDown(self):
|
||||
"""Stop the DDP server, and reliably close any open DB transactions."""
|
||||
if self._server is not None:
|
||||
self._server.stop()
|
||||
self._server = None
|
||||
# OK, let me explain what I think is going on here...
|
||||
# 1. Tests run, but cursors aren't closed.
|
||||
# 2. Open cursors keeping queries running on DB.
|
||||
# 3. Django TestCase.tearDown() tries to `sqlflush`
|
||||
# 4. DB complains that queries from (2) are still running.
|
||||
# Solution is to use the open DB connection and execute a DB noop query
|
||||
# such as `SELECT pg_sleep(0)` to force another round trip to the DB.
|
||||
# If you have another idea as to what is going on, submit an issue. :)
|
||||
from django.db import connection, close_old_connections
|
||||
close_old_connections()
|
||||
cur = connection.cursor()
|
||||
cur.execute('SELECT pg_sleep(0);')
|
||||
cur.fetchall()
|
||||
cur.close()
|
||||
connection.close()
|
||||
gevent.sleep()
|
||||
super(DDPServerTestCase, self).tearDown()
|
||||
|
||||
def url(self, path):
|
||||
return self.server.url(path)
|
||||
|
||||
|
||||
class HttpTestCase(DDPServerTestCase):
|
||||
|
||||
"""Test that server launches and handles HTTP requests."""
|
||||
|
||||
# gevent-websocket doesn't work with Python 3 yet
|
||||
@expected_failure_if(sys.version_info.major == 3)
|
||||
def test_get(self):
|
||||
"""Perform HTTP GET."""
|
||||
import requests
|
||||
|
|
@ -78,6 +220,133 @@ class LaunchTestCase(DDPServerTestCase):
|
|||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
|
||||
class WebSocketTestCase(DDPServerTestCase):
|
||||
|
||||
"""Test that server launches and handles WebSocket connections."""
|
||||
|
||||
# gevent-websocket doesn't work with Python 3 yet
|
||||
@expected_failure_if(sys.version_info.major == 3)
|
||||
def test_sockjs_connect_ping(self):
|
||||
"""SockJS connect."""
|
||||
sockjs = self.server.sockjs('/sockjs/1/a/websocket')
|
||||
|
||||
resp = sockjs.websocket.recv()
|
||||
self.assertEqual(resp, 'o')
|
||||
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{'server_id': '0'},
|
||||
],
|
||||
)
|
||||
|
||||
sockjs.connect('1', 'pre2', 'pre1')
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{'msg': 'connected', 'session': msgs[0].get('session', None)},
|
||||
],
|
||||
)
|
||||
|
||||
# first without `id`
|
||||
sockjs.ping()
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(msgs, [{'msg': 'pong'}])
|
||||
|
||||
# then with `id`
|
||||
id_ = sockjs.next_id()
|
||||
sockjs.ping(id_)
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(msgs, [{'msg': 'pong', 'id': id_}])
|
||||
|
||||
sockjs.close()
|
||||
|
||||
# gevent-websocket doesn't work with Python 3 yet
|
||||
@expected_failure_if(sys.version_info.major == 3)
|
||||
def test_call_missing_arguments(self):
|
||||
"""Connect and login without any arguments."""
|
||||
sockjs = self.server.sockjs('/sockjs/1/a/websocket')
|
||||
|
||||
resp = sockjs.websocket.recv()
|
||||
self.assertEqual(resp, 'o')
|
||||
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{'server_id': '0'},
|
||||
],
|
||||
)
|
||||
|
||||
sockjs.connect('1', 'pre2', 'pre1')
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{'msg': 'connected', 'session': msgs[0].get('session', None)},
|
||||
],
|
||||
)
|
||||
|
||||
id_ = sockjs.call('login') # expects `credentials` argument
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{
|
||||
'msg': 'result',
|
||||
'error': {
|
||||
'error': 500,
|
||||
'reason':
|
||||
'login() takes exactly 2 arguments (1 given)',
|
||||
},
|
||||
'id': id_,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
sockjs.close()
|
||||
|
||||
# gevent-websocket doesn't work with Python 3 yet
|
||||
@expected_failure_if(sys.version_info.major == 3)
|
||||
def test_call_extra_arguments(self):
|
||||
"""Connect and login with extra arguments."""
|
||||
with self.server.sockjs('/sockjs/1/a/websocket') as sockjs:
|
||||
|
||||
resp = sockjs.websocket.recv()
|
||||
self.assertEqual(resp, 'o')
|
||||
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{'server_id': '0'},
|
||||
],
|
||||
)
|
||||
|
||||
sockjs.connect('1', 'pre2', 'pre1')
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{
|
||||
'msg': 'connected',
|
||||
'session': msgs[0].get('session', None),
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
id_ = sockjs.call('login', 1, 2) # takes single argument
|
||||
msgs = sockjs.recv()
|
||||
self.assertEqual(
|
||||
msgs, [
|
||||
{
|
||||
'msg': 'result',
|
||||
'error': {
|
||||
'error': 500,
|
||||
'reason':
|
||||
'login() takes exactly 2 arguments (3 given)',
|
||||
},
|
||||
'id': id_,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
"""Specify which test cases to run."""
|
||||
del pattern
|
||||
|
|
|
|||
|
|
@ -228,9 +228,9 @@ class DDPWebSocketApplication(geventwebsocket.WebSocketApplication):
|
|||
# dispatch to handler
|
||||
try:
|
||||
handler(**kwargs)
|
||||
except Exception as err: # print stack trace --> pylint: disable=W0703
|
||||
traceback.print_exc()
|
||||
except Exception as err: # re-raise err --> pylint: disable=W0703
|
||||
self.error(500, 'Internal server error', err)
|
||||
raise
|
||||
|
||||
def send(self, data, tx_id=None):
|
||||
"""Send `data` (raw string or EJSON payload) to WebSocket client."""
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
# things required to run test suite
|
||||
requests==2.9.0
|
||||
websocket_client==0.34.0
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
[bdist_wheel]
|
||||
universal=1
|
||||
161
setup.py
161
setup.py
|
|
@ -1,12 +1,18 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django/PostgreSQL implementation of the Meteor server."""
|
||||
|
||||
# stdlib
|
||||
import os.path
|
||||
import setuptools
|
||||
import posixpath # all path specs in this file are UNIX-style paths
|
||||
import shutil
|
||||
import subprocess
|
||||
from distutils import log
|
||||
from distutils.version import StrictVersion
|
||||
from distutils.command.build import build
|
||||
import setuptools.command.build_py
|
||||
import setuptools.command.build_ext
|
||||
|
||||
# pypi
|
||||
import setuptools
|
||||
|
||||
# setuptools 18.5 introduces support for the `platform_python_implementation`
|
||||
# environment marker: https://github.com/jaraco/setuptools/pull/28
|
||||
|
|
@ -15,28 +21,128 @@ __requires__ = 'setuptools>=18.5'
|
|||
assert StrictVersion(setuptools.__version__) >= StrictVersion('18.5'), \
|
||||
'Installation from source requires setuptools>=18.5.'
|
||||
|
||||
SETUP_DIR = os.path.dirname(__file__)
|
||||
|
||||
class Build(build):
|
||||
|
||||
"""Build all files of a package."""
|
||||
class build_meteor(setuptools.command.build_py.build_py):
|
||||
|
||||
"""Build a Meteor project."""
|
||||
|
||||
user_options = [
|
||||
('meteor=', None, 'path to `meteor` executable (default: meteor)'),
|
||||
('meteor-debug', None, 'meteor build with `--debug`'),
|
||||
('no-prune-npm', None, "don't prune meteor npm build directories"),
|
||||
('build-lib', 'd', 'directory to "build" (copy) to'),
|
||||
]
|
||||
|
||||
negative_opt = []
|
||||
|
||||
meteor = None
|
||||
meteor_debug = None
|
||||
build_lib = None
|
||||
package_dir = None
|
||||
meteor_builds = None
|
||||
no_prune_npm = None
|
||||
inplace = None
|
||||
|
||||
def initialize_options(self):
|
||||
"""Set command option defaults."""
|
||||
setuptools.command.build_py.build_py.initialize_options(self)
|
||||
self.meteor = 'meteor'
|
||||
self.meteor_debug = False
|
||||
self.build_lib = None
|
||||
self.package_dir = None
|
||||
self.meteor_builds = []
|
||||
self.no_prune_npm = None
|
||||
self.inplace = True
|
||||
|
||||
def finalize_options(self):
|
||||
"""Update command options."""
|
||||
# Get all the information we need to install pure Python modules
|
||||
# from the umbrella 'install' command -- build (source) directory,
|
||||
# install (target) directory, and whether to compile .py files.
|
||||
self.set_undefined_options(
|
||||
'build',
|
||||
('build_lib', 'build_lib'),
|
||||
)
|
||||
self.set_undefined_options(
|
||||
'build_py',
|
||||
('package_dir', 'package_dir'),
|
||||
)
|
||||
setuptools.command.build_py.build_py.finalize_options(self)
|
||||
|
||||
@staticmethod
|
||||
def has_meteor_builds(distribution):
|
||||
"""Returns `True` if distribution has meteor projects to be built."""
|
||||
return bool(
|
||||
distribution.command_options['build_meteor']['meteor_builds']
|
||||
)
|
||||
|
||||
def get_package_dir(self, package):
|
||||
res = setuptools.command.build_py.orig.build_py.get_package_dir(
|
||||
self, package,
|
||||
)
|
||||
if self.distribution.src_root is not None:
|
||||
return os.path.join(self.distribution.src_root, res)
|
||||
return res
|
||||
|
||||
def run(self):
|
||||
"""Build our package."""
|
||||
cmdline = [
|
||||
'meteor',
|
||||
'build',
|
||||
'--directory',
|
||||
'../build',
|
||||
]
|
||||
meteor_dir = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'dddp',
|
||||
'test',
|
||||
'meteor_todos',
|
||||
"""Peform build."""
|
||||
for (package, source, target, extra_args) in self.meteor_builds:
|
||||
src_dir = self.get_package_dir(package)
|
||||
# convert UNIX-style paths to directory names
|
||||
project_dir = self.path_to_dir(src_dir, source)
|
||||
target_dir = self.path_to_dir(src_dir, target)
|
||||
output_dir = self.path_to_dir(
|
||||
os.path.abspath(SETUP_DIR if self.inplace else self.build_lib),
|
||||
target_dir,
|
||||
)
|
||||
# construct command line.
|
||||
cmdline = [self.meteor, 'build', '--directory', output_dir]
|
||||
no_prune_npm = self.no_prune_npm
|
||||
if extra_args[:1] == ['--no-prune-npm']:
|
||||
no_prune_npm = True
|
||||
extra_args[:1] = []
|
||||
if self.meteor_debug and '--debug' not in cmdline:
|
||||
cmdline.append('--debug')
|
||||
cmdline.extend(extra_args)
|
||||
# execute command
|
||||
log.info(
|
||||
'building meteor app %r (%s)', project_dir, ' '.join(cmdline),
|
||||
)
|
||||
subprocess.check_call(cmdline, cwd=project_dir)
|
||||
if not no_prune_npm:
|
||||
# django-ddp doesn't use bundle/programs/server/npm cruft
|
||||
npm_build_dir = os.path.join(
|
||||
output_dir, 'bundle', 'programs', 'server', 'npm',
|
||||
)
|
||||
log.info('pruning meteor npm build %r', npm_build_dir)
|
||||
shutil.rmtree(npm_build_dir)
|
||||
|
||||
@staticmethod
|
||||
def path_to_dir(*path_args):
|
||||
"""Convert a UNIX-style path into platform specific directory spec."""
|
||||
return os.path.join(
|
||||
*list(path_args[:-1]) + path_args[-1].split(posixpath.sep)
|
||||
)
|
||||
log.info('Building meteor app %r (%s)', meteor_dir, ' '.join(cmdline))
|
||||
subprocess.check_call(cmdline, cwd=meteor_dir)
|
||||
return build.run(self)
|
||||
|
||||
|
||||
class build_py(setuptools.command.build_py.build_py):
|
||||
|
||||
def run(self):
|
||||
if build_meteor.has_meteor_builds(self.distribution):
|
||||
self.reinitialize_command('build_meteor', inplace=False)
|
||||
self.run_command('build_meteor')
|
||||
return setuptools.command.build_py.build_py.run(self)
|
||||
|
||||
|
||||
class build_ext(setuptools.command.build_ext.build_ext):
|
||||
|
||||
def run(self):
|
||||
if build_meteor.has_meteor_builds(self.distribution):
|
||||
self.reinitialize_command('build_meteor', inplace=True)
|
||||
self.run_command('build_meteor')
|
||||
return setuptools.command.build_ext.build_ext.run(self)
|
||||
|
||||
|
||||
CLASSIFIERS = [
|
||||
|
|
@ -149,11 +255,24 @@ setuptools.setup(
|
|||
],
|
||||
},
|
||||
classifiers=CLASSIFIERS,
|
||||
test_suite='dddp.test.run_tests',
|
||||
test_suite='dddp.test.manage.run_tests',
|
||||
tests_require=[
|
||||
'requests',
|
||||
'websocket_client',
|
||||
],
|
||||
cmdclass={
|
||||
'build': Build,
|
||||
'build_ext': build_ext,
|
||||
'build_py': build_py,
|
||||
'build_meteor': build_meteor,
|
||||
},
|
||||
options={
|
||||
'bdist_wheel': {
|
||||
'universal': '1',
|
||||
},
|
||||
'build_meteor': {
|
||||
'meteor_builds': [
|
||||
('dddp.test', 'meteor_todos', 'build', []),
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue