test_api passes without threads

just put requests in a thread via `utils.async_requests`

eliminates db threads issue
This commit is contained in:
Min RK
2017-07-27 10:34:54 +02:00
parent 58f072e5af
commit f05aecf5f9
5 changed files with 165 additions and 122 deletions

View File

@@ -38,8 +38,7 @@ def db():
@fixture(scope='module')
def io_loop(request):
"""Same as pytest-tornado.gen"""
print("my io_loop fixture")
"""Same as pytest-tornado.io_loop, but re-scoped to module-level"""
io_loop = ioloop.IOLoop()
io_loop.make_current()
@@ -78,7 +77,7 @@ class MockServiceSpawner(jupyterhub.services.service._ServiceSpawner):
_mock_service_counter = 0
def _mockservice(request, app, io_loop, url=False):
def _mockservice(request, app, url=False):
global _mock_service_counter
_mock_service_counter += 1
name = 'mock-service-%i' % _mock_service_counter
@@ -90,6 +89,8 @@ def _mockservice(request, app, io_loop, url=False):
if url:
spec['url'] = 'http://127.0.0.1:%i' % random_port()
io_loop = app.io_loop
with mock.patch.object(jupyterhub.services.service, '_ServiceSpawner', MockServiceSpawner):
app.services = [spec]
app.init_services()
@@ -100,20 +101,17 @@ def _mockservice(request, app, io_loop, url=False):
# wait for proxy to be updated before starting the service
yield app.proxy.add_all_services(app._service_map)
service.start()
app.io_loop.add_callback(start)
io_loop.run_sync(start)
def cleanup():
service.stop()
app.services[:] = []
app._service_map.clear()
request.addfinalizer(cleanup)
for i in range(20):
if not getattr(service, 'proc', False):
time.sleep(0.2)
# ensure process finishes starting
with raises(TimeoutExpired):
service.proc.wait(1)
if url:
ioloop.IOLoop().run_sync(service.server.wait_up)
io_loop.run_sync(service.server.wait_up)
return service

View File

@@ -22,6 +22,7 @@ from ..objects import Server
from ..spawner import LocalProcessSpawner
from ..singleuser import SingleUserNotebookApp
from ..utils import random_port, url_path_join
from .utils import async_requests
from pamela import PAMError
@@ -178,10 +179,11 @@ class MockHub(JupyterHub):
self.cleanup = lambda : None
self.db_file.close()
@gen.coroutine
def login_user(self, name):
"""Login a user by name, returning her cookies."""
base_url = public_url(self)
r = requests.post(base_url + 'hub/login',
r = yield async_requests.post(base_url + 'hub/login',
data={
'username': name,
'password': name,

View File

@@ -2,12 +2,11 @@
import json
import time
from queue import Queue
import sys
from unittest import mock
from urllib.parse import urlparse, quote
from pytest import mark, yield_fixture
from pytest import mark
import requests
from tornado import gen
@@ -18,6 +17,7 @@ from ..user import User
from ..utils import url_path_join as ujoin
from . import mocking
from .mocking import public_host, public_url
from .utils import async_requests
def check_db_locks(func):
@@ -81,6 +81,7 @@ def auth_header(db, name):
@check_db_locks
@gen.coroutine
def api_request(app, *api_path, **kwargs):
"""Make an API request"""
base_url = app.hub.url
@@ -91,8 +92,8 @@ def api_request(app, *api_path, **kwargs):
url = ujoin(base_url, 'api', *api_path)
method = kwargs.pop('method', 'get')
f = getattr(requests, method)
resp = f(url, **kwargs)
f = getattr(async_requests, method)
resp = yield f(url, **kwargs)
assert "frame-ancestors 'self'" in resp.headers['Content-Security-Policy']
assert ujoin(app.hub.base_url, "security/csp-report") in resp.headers['Content-Security-Policy']
assert 'http' not in resp.headers['Content-Security-Policy']
@@ -104,9 +105,10 @@ def api_request(app, *api_path, **kwargs):
# --------------------
@mark.gen_test
def test_auth_api(app):
db = app.db
r = api_request(app, 'authorizations', 'gobbledygook')
r = yield api_request(app, 'authorizations', 'gobbledygook')
assert r.status_code == 404
# make a new cookie token
@@ -114,36 +116,37 @@ def test_auth_api(app):
api_token = user.new_api_token()
# check success:
r = api_request(app, 'authorizations/token', api_token)
r = yield api_request(app, 'authorizations/token', api_token)
assert r.status_code == 200
reply = r.json()
assert reply['name'] == user.name
# check fail
r = api_request(app, 'authorizations/token', api_token,
r = yield api_request(app, 'authorizations/token', api_token,
headers={'Authorization': 'no sir'},
)
assert r.status_code == 403
r = api_request(app, 'authorizations/token', api_token,
r = yield api_request(app, 'authorizations/token', api_token,
headers={'Authorization': 'token: %s' % user.cookie_id},
)
assert r.status_code == 403
@mark.gen_test
def test_referer_check(app, io_loop):
url = ujoin(public_host(app), app.hub.base_url)
host = urlparse(url).netloc
user = find_user(app.db, 'admin')
if user is None:
user = add_user(app.db, name='admin', admin=True)
cookies = app.login_user('admin')
app_user = get_app_user(app, 'admin')
cookies = yield app.login_user('admin')
app_user = app.users[user]
# stop the admin's server so we don't mess up future tests
io_loop.run_sync(lambda: app.proxy.delete_user(app_user))
io_loop.run_sync(app_user.stop)
yield app.proxy.delete_user(app_user)
yield app_user.stop()
r = api_request(app, 'users',
r = yield api_request(app, 'users',
headers={
'Authorization': '',
'Referer': 'null',
@@ -151,7 +154,7 @@ def test_referer_check(app, io_loop):
)
assert r.status_code == 403
r = api_request(app, 'users',
r = yield api_request(app, 'users',
headers={
'Authorization': '',
'Referer': 'http://attack.com/csrf/vulnerability',
@@ -159,7 +162,7 @@ def test_referer_check(app, io_loop):
)
assert r.status_code == 403
r = api_request(app, 'users',
r = yield api_request(app, 'users',
headers={
'Authorization': '',
'Referer': url,
@@ -168,7 +171,7 @@ def test_referer_check(app, io_loop):
)
assert r.status_code == 200
r = api_request(app, 'users',
r = yield api_request(app, 'users',
headers={
'Authorization': '',
'Referer': ujoin(url, 'foo/bar/baz/bat'),
@@ -184,9 +187,10 @@ def test_referer_check(app, io_loop):
@mark.user
@mark.gen_test
def test_get_users(app):
db = app.db
r = api_request(app, 'users')
r = yield api_request(app, 'users')
assert r.status_code == 200
users = sorted(r.json(), key=lambda d: d['name'])
@@ -211,17 +215,18 @@ def test_get_users(app):
}
]
r = api_request(app, 'users',
r = yield api_request(app, 'users',
headers=auth_header(db, 'user'),
)
assert r.status_code == 403
@mark.user
@mark.gen_test
def test_add_user(app):
db = app.db
name = 'newuser'
r = api_request(app, 'users', name, method='post')
r = yield api_request(app, 'users', name, method='post')
assert r.status_code == 201
user = find_user(db, name)
assert user is not None
@@ -230,9 +235,10 @@ def test_add_user(app):
@mark.user
@mark.gen_test
def test_get_user(app):
name = 'user'
r = api_request(app, 'users', name)
r = yield api_request(app, 'users', name)
assert r.status_code == 200
user = r.json()
user.pop('last_activity')
@@ -247,19 +253,21 @@ def test_get_user(app):
@mark.user
@mark.gen_test
def test_add_multi_user_bad(app):
r = api_request(app, 'users', method='post')
r = yield api_request(app, 'users', method='post')
assert r.status_code == 400
r = api_request(app, 'users', method='post', data='{}')
r = yield api_request(app, 'users', method='post', data='{}')
assert r.status_code == 400
r = api_request(app, 'users', method='post', data='[]')
r = yield api_request(app, 'users', method='post', data='[]')
assert r.status_code == 400
@mark.user
@mark.gen_test
def test_add_multi_user_invalid(app):
app.authenticator.username_pattern = r'w.*'
r = api_request(app, 'users', method='post',
r = yield api_request(app, 'users', method='post',
data=json.dumps({'usernames': ['Willow', 'Andrew', 'Tara']})
)
app.authenticator.username_pattern = ''
@@ -268,10 +276,11 @@ def test_add_multi_user_invalid(app):
@mark.user
@mark.gen_test
def test_add_multi_user(app):
db = app.db
names = ['a', 'b']
r = api_request(app, 'users', method='post',
r = yield api_request(app, 'users', method='post',
data=json.dumps({'usernames': names}),
)
assert r.status_code == 201
@@ -286,7 +295,7 @@ def test_add_multi_user(app):
assert not user.admin
# try to create the same users again
r = api_request(app, 'users', method='post',
r = yield api_request(app, 'users', method='post',
data=json.dumps({'usernames': names}),
)
assert r.status_code == 400
@@ -294,7 +303,7 @@ def test_add_multi_user(app):
names = ['a', 'b', 'ab']
# try to create the same users again
r = api_request(app, 'users', method='post',
r = yield api_request(app, 'users', method='post',
data=json.dumps({'usernames': names}),
)
assert r.status_code == 201
@@ -304,10 +313,11 @@ def test_add_multi_user(app):
@mark.user
@mark.gen_test
def test_add_multi_user_admin(app):
db = app.db
names = ['c', 'd']
r = api_request(app, 'users', method='post',
r = yield api_request(app, 'users', method='post',
data=json.dumps({'usernames': names, 'admin': True}),
)
assert r.status_code == 201
@@ -323,20 +333,22 @@ def test_add_multi_user_admin(app):
@mark.user
@mark.gen_test
def test_add_user_bad(app):
db = app.db
name = 'dne_newuser'
r = api_request(app, 'users', name, method='post')
r = yield api_request(app, 'users', name, method='post')
assert r.status_code == 400
user = find_user(db, name)
assert user is None
@mark.user
@mark.gen_test
def test_add_admin(app):
db = app.db
name = 'newadmin'
r = api_request(app, 'users', name, method='post',
r = yield api_request(app, 'users', name, method='post',
data=json.dumps({'admin': True}),
)
assert r.status_code == 201
@@ -347,25 +359,27 @@ def test_add_admin(app):
@mark.user
@mark.gen_test
def test_delete_user(app):
db = app.db
mal = add_user(db, name='mal')
r = api_request(app, 'users', 'mal', method='delete')
r = yield api_request(app, 'users', 'mal', method='delete')
assert r.status_code == 204
@mark.user
@mark.gen_test
def test_make_admin(app):
db = app.db
name = 'admin2'
r = api_request(app, 'users', name, method='post')
r = yield api_request(app, 'users', name, method='post')
assert r.status_code == 201
user = find_user(db, name)
assert user is not None
assert user.name == name
assert not user.admin
r = api_request(app, 'users', name, method='patch',
r = yield api_request(app, 'users', name, method='patch',
data=json.dumps({'admin': True})
)
assert r.status_code == 200
@@ -375,23 +389,7 @@ def test_make_admin(app):
assert user.admin
def get_app_user(app, name):
"""Helper to get the User object from the main thread.
Needed for access to the Spawner during testing.
No ORM methods should be called on the result.
"""
q = Queue()
def get_user_id():
user = find_user(app.db, name)
q.put(user.id)
app.io_loop.add_callback(get_user_id)
user_id = q.get(timeout=2)
return app.users[user_id]
@mark.gen_test
def test_spawn(app, io_loop):
db = app.db
name = 'wash'
@@ -401,41 +399,41 @@ def test_spawn(app, io_loop):
'i': 5,
}
before_servers = sorted(db.query(orm.Server), key=lambda s: s.url)
r = api_request(app, 'users', name, 'server', method='post',
r = yield api_request(app, 'users', name, 'server', method='post',
data=json.dumps(options),
)
assert r.status_code == 201
assert 'pid' in user.orm_spawners[''].state
app_user = get_app_user(app, name)
app_user = app.users[name]
assert app_user.spawner is not None
spawner = app_user.spawner
assert app_user.spawner.user_options == options
assert not app_user.spawner._spawn_pending
status = io_loop.run_sync(app_user.spawner.poll)
status = yield app_user.spawner.poll()
assert status is None
assert spawner.server.base_url == ujoin(app.base_url, 'user/%s' % name) + '/'
url = public_url(app, user)
r = requests.get(url)
r = yield async_requests.get(url)
assert r.status_code == 200
assert r.text == spawner.server.base_url
r = requests.get(ujoin(url, 'args'))
r = yield async_requests.get(ujoin(url, 'args'))
assert r.status_code == 200
argv = r.json()
assert '--port' in ' '.join(argv)
r = requests.get(ujoin(url, 'env'))
r = yield async_requests.get(ujoin(url, 'env'))
env = r.json()
for expected in ['JUPYTERHUB_USER', 'JUPYTERHUB_BASE_URL', 'JUPYTERHUB_API_TOKEN']:
assert expected in env
if app.subdomain_host:
assert env['JUPYTERHUB_HOST'] == app.subdomain_host
r = api_request(app, 'users', name, 'server', method='delete')
r = yield api_request(app, 'users', name, 'server', method='delete')
assert r.status_code == 204
assert 'pid' not in user.orm_spawners[''].state
status = io_loop.run_sync(app_user.spawner.poll)
status = yield app_user.spawner.poll()
assert status == 0
# check that we cleaned up after ourselves
@@ -446,6 +444,7 @@ def test_spawn(app, io_loop):
assert tokens == []
@mark.gen_test
def test_slow_spawn(app, io_loop, no_patience, request):
patch = mock.patch.dict(app.tornado_settings, {'spawner_class': mocking.SlowSpawner})
patch.start()
@@ -453,10 +452,10 @@ def test_slow_spawn(app, io_loop, no_patience, request):
db = app.db
name = 'zoe'
user = add_user(db, app=app, name=name)
r = api_request(app, 'users', name, 'server', method='post')
r = yield api_request(app, 'users', name, 'server', method='post')
r.raise_for_status()
assert r.status_code == 202
app_user = get_app_user(app, name)
app_user = app.users[name]
assert app_user.spawner is not None
assert app_user.spawner._spawn_pending
assert not app_user.spawner._stop_pending
@@ -466,9 +465,9 @@ def test_slow_spawn(app, io_loop, no_patience, request):
while app_user.spawner._spawn_pending:
yield gen.sleep(0.1)
io_loop.run_sync(wait_spawn)
yield wait_spawn()
assert not app_user.spawner._spawn_pending
status = io_loop.run_sync(app_user.spawner.poll)
status = yield app_user.spawner.poll()
assert status is None
@gen.coroutine
@@ -476,25 +475,26 @@ def test_slow_spawn(app, io_loop, no_patience, request):
while app_user.spawner._stop_pending:
yield gen.sleep(0.1)
r = api_request(app, 'users', name, 'server', method='delete')
r = yield api_request(app, 'users', name, 'server', method='delete')
r.raise_for_status()
assert r.status_code == 202
assert app_user.spawner is not None
assert app_user.spawner._stop_pending
r = api_request(app, 'users', name, 'server', method='delete')
r = yield api_request(app, 'users', name, 'server', method='delete')
r.raise_for_status()
assert r.status_code == 202
assert app_user.spawner is not None
assert app_user.spawner._stop_pending
io_loop.run_sync(wait_stop)
yield wait_stop()
assert not app_user.spawner._stop_pending
assert app_user.spawner is not None
r = api_request(app, 'users', name, 'server', method='delete')
r = yield api_request(app, 'users', name, 'server', method='delete')
assert r.status_code == 400
@mark.gen_test
def test_never_spawn(app, io_loop, no_patience, request):
patch = mock.patch.dict(app.tornado_settings, {'spawner_class': mocking.NeverSpawner})
patch.start()
@@ -503,8 +503,8 @@ def test_never_spawn(app, io_loop, no_patience, request):
db = app.db
name = 'badger'
user = add_user(db, app=app, name=name)
r = api_request(app, 'users', name, 'server', method='post')
app_user = get_app_user(app, name)
r = yield api_request(app, 'users', name, 'server', method='post')
app_user = app.users[name]
assert app_user.spawner is not None
assert app_user.spawner._spawn_pending
@@ -513,38 +513,40 @@ def test_never_spawn(app, io_loop, no_patience, request):
while app_user.spawner._spawn_pending:
yield gen.sleep(0.1)
io_loop.run_sync(wait_pending)
yield wait_pending()
assert not app_user.spawner._spawn_pending
status = io_loop.run_sync(app_user.spawner.poll)
status = yield app_user.spawner.poll()
assert status is not None
@mark.gen_test
def test_get_proxy(app, io_loop):
r = api_request(app, 'proxy')
r = yield api_request(app, 'proxy')
r.raise_for_status()
reply = r.json()
assert list(reply.keys()) == ['/']
@mark.gen_test
def test_cookie(app):
db = app.db
name = 'patience'
user = add_user(db, app=app, name=name)
r = api_request(app, 'users', name, 'server', method='post')
r = yield api_request(app, 'users', name, 'server', method='post')
assert r.status_code == 201
assert 'pid' in user.orm_spawners[''].state
app_user = get_app_user(app, name)
app_user = app.users[name]
cookies = app.login_user(name)
cookies = yield app.login_user(name)
cookie_name = app.hub.cookie_name
# cookie jar gives '"cookie-value"', we want 'cookie-value'
cookie = cookies[cookie_name][1:-1]
r = api_request(app, 'authorizations/cookie',
r = yield api_request(app, 'authorizations/cookie',
cookie_name, "nothintoseehere",
)
assert r.status_code == 404
r = api_request(app, 'authorizations/cookie',
r = yield api_request(app, 'authorizations/cookie',
cookie_name, quote(cookie, safe=''),
)
r.raise_for_status()
@@ -552,7 +554,7 @@ def test_cookie(app):
assert reply['name'] == name
# deprecated cookie in body:
r = api_request(app, 'authorizations/cookie',
r = yield api_request(app, 'authorizations/cookie',
cookie_name, data=cookie,
)
r.raise_for_status()
@@ -560,18 +562,20 @@ def test_cookie(app):
assert reply['name'] == name
@mark.gen_test
def test_token(app):
name = 'book'
user = add_user(app.db, app=app, name=name)
token = user.new_api_token()
r = api_request(app, 'authorizations/token', token)
r = yield api_request(app, 'authorizations/token', token)
r.raise_for_status()
user_model = r.json()
assert user_model['name'] == name
r = api_request(app, 'authorizations/token', 'notauthorized')
r = yield api_request(app, 'authorizations/token', 'notauthorized')
assert r.status_code == 404
@mark.gen_test
@mark.parametrize("headers, data, status", [
({}, None, 200),
({'Authorization': ''}, None, 403),
@@ -581,13 +585,13 @@ def test_get_new_token(app, headers, data, status):
if data:
data = json.dumps(data)
# request a new token
r = api_request(app, 'authorizations', 'token', method='post', data=data, headers=headers)
r = yield api_request(app, 'authorizations', 'token', method='post', data=data, headers=headers)
assert r.status_code == status
if status != 200:
return
reply = r.json()
assert 'token' in reply
r = api_request(app, 'authorizations', 'token', reply['token'])
r = yield api_request(app, 'authorizations', 'token', reply['token'])
r.raise_for_status()
assert 'name' in r.json()
@@ -598,8 +602,9 @@ def test_get_new_token(app, headers, data, status):
@mark.group
@mark.gen_test
def test_groups_list(app):
r = api_request(app, 'groups')
r = yield api_request(app, 'groups')
r.raise_for_status()
reply = r.json()
assert reply == []
@@ -609,7 +614,7 @@ def test_groups_list(app):
app.db.add(group)
app.db.commit()
r = api_request(app, 'groups')
r = yield api_request(app, 'groups')
r.raise_for_status()
reply = r.json()
assert reply == [{
@@ -620,16 +625,17 @@ def test_groups_list(app):
@mark.group
@mark.gen_test
def test_group_get(app):
group = orm.Group.find(app.db, name='alphaflight')
user = add_user(app.db, app=app, name='sasquatch')
group.users.append(user)
app.db.commit()
r = api_request(app, 'groups/runaways')
r = yield api_request(app, 'groups/runaways')
assert r.status_code == 404
r = api_request(app, 'groups/alphaflight')
r = yield api_request(app, 'groups/alphaflight')
r.raise_for_status()
reply = r.json()
assert reply == {
@@ -640,18 +646,19 @@ def test_group_get(app):
@mark.group
@mark.gen_test
def test_group_create_delete(app):
db = app.db
r = api_request(app, 'groups/runaways', method='delete')
r = yield api_request(app, 'groups/runaways', method='delete')
assert r.status_code == 404
r = api_request(app, 'groups/new', method='post',
r = yield api_request(app, 'groups/new', method='post',
data=json.dumps({'users': ['doesntexist']}),
)
assert r.status_code == 400
assert orm.Group.find(db, name='new') is None
r = api_request(app, 'groups/omegaflight', method='post',
r = yield api_request(app, 'groups/omegaflight', method='post',
data=json.dumps({'users': ['sasquatch']}),
)
r.raise_for_status()
@@ -662,29 +669,30 @@ def test_group_create_delete(app):
assert sasquatch in omegaflight.users
# create duplicate raises 400
r = api_request(app, 'groups/omegaflight', method='post')
r = yield api_request(app, 'groups/omegaflight', method='post')
assert r.status_code == 400
r = api_request(app, 'groups/omegaflight', method='delete')
r = yield api_request(app, 'groups/omegaflight', method='delete')
assert r.status_code == 204
assert omegaflight not in sasquatch.groups
assert orm.Group.find(db, name='omegaflight') is None
# delete nonexistent gives 404
r = api_request(app, 'groups/omegaflight', method='delete')
r = yield api_request(app, 'groups/omegaflight', method='delete')
assert r.status_code == 404
@mark.group
@mark.gen_test
def test_group_add_users(app):
db = app.db
# must specify users
r = api_request(app, 'groups/alphaflight/users', method='post', data='{}')
r = yield api_request(app, 'groups/alphaflight/users', method='post', data='{}')
assert r.status_code == 400
names = ['aurora', 'guardian', 'northstar', 'sasquatch', 'shaman', 'snowbird']
users = [ find_user(db, name=name) or add_user(db, app=app, name=name) for name in names ]
r = api_request(app, 'groups/alphaflight/users', method='post', data=json.dumps({
r = yield api_request(app, 'groups/alphaflight/users', method='post', data=json.dumps({
'users': names,
}))
r.raise_for_status()
@@ -698,15 +706,16 @@ def test_group_add_users(app):
@mark.group
@mark.gen_test
def test_group_delete_users(app):
db = app.db
# must specify users
r = api_request(app, 'groups/alphaflight/users', method='delete', data='{}')
r = yield api_request(app, 'groups/alphaflight/users', method='delete', data='{}')
assert r.status_code == 400
names = ['aurora', 'guardian', 'northstar', 'sasquatch', 'shaman', 'snowbird']
users = [ find_user(db, name=name) for name in names ]
r = api_request(app, 'groups/alphaflight/users', method='delete', data=json.dumps({
r = yield api_request(app, 'groups/alphaflight/users', method='delete', data=json.dumps({
'users': names[:2],
}))
r.raise_for_status()
@@ -726,10 +735,11 @@ def test_group_delete_users(app):
@mark.services
@mark.gen_test
def test_get_services(app, mockservice_url):
mockservice = mockservice_url
db = app.db
r = api_request(app, 'services')
r = yield api_request(app, 'services')
r.raise_for_status()
assert r.status_code == 200
@@ -745,17 +755,18 @@ def test_get_services(app, mockservice_url):
}
}
r = api_request(app, 'services',
r = yield api_request(app, 'services',
headers=auth_header(db, 'user'),
)
assert r.status_code == 403
@mark.services
@mark.gen_test
def test_get_service(app, mockservice_url):
mockservice = mockservice_url
db = app.db
r = api_request(app, 'services/%s' % mockservice.name)
r = yield api_request(app, 'services/%s' % mockservice.name)
r.raise_for_status()
assert r.status_code == 200
@@ -769,22 +780,23 @@ def test_get_service(app, mockservice_url):
'url': mockservice.url,
}
r = api_request(app, 'services/%s' % mockservice.name,
r = yield api_request(app, 'services/%s' % mockservice.name,
headers={
'Authorization': 'token %s' % mockservice.api_token
}
)
r.raise_for_status()
r = api_request(app, 'services/%s' % mockservice.name,
r = yield api_request(app, 'services/%s' % mockservice.name,
headers=auth_header(db, 'user'),
)
assert r.status_code == 403
@mark.gen_test
def test_root_api(app):
base_url = app.hub.url
url = ujoin(base_url, 'api')
r = requests.get(url)
r = yield async_requests.get(url)
r.raise_for_status()
expected = {
'version': jupyterhub.__version__
@@ -792,8 +804,9 @@ def test_root_api(app):
assert r.json() == expected
@mark.gen_test
def test_info(app):
r = api_request(app, 'info')
r = yield api_request(app, 'info')
r.raise_for_status()
data = r.json()
assert data['version'] == jupyterhub.__version__
@@ -821,14 +834,16 @@ def test_info(app):
# -----------------
@mark.gen_test
def test_options(app):
r = api_request(app, 'users', method='options')
r = yield api_request(app, 'users', method='options')
r.raise_for_status()
assert 'Access-Control-Allow-Headers' in r.headers
@mark.gen_test
def test_bad_json_body(app):
r = api_request(app, 'users', method='post', data='notjson')
r = yield api_request(app, 'users', method='post', data='notjson')
assert r.status_code == 400
@@ -838,14 +853,24 @@ def test_bad_json_body(app):
def test_shutdown(app):
r = api_request(app, 'shutdown', method='post',
loop = app.io_loop
# have to do things a little funky since we are going to stop the loop,
# which makes gen_test unhappy. So we run the loop ourselves.
@gen.coroutine
def shutdown():
r = yield api_request(app, 'shutdown', method='post',
data=json.dumps({'servers': True, 'proxy': True,}),
)
return r
real_stop = loop.stop
def stop():
stop.called = True
loop.call_later(1, real_stop)
with mock.patch.object(loop, 'stop', stop):
r = loop.run_sync(shutdown, timeout=5)
r.raise_for_status()
reply = r.json()
for i in range(100):
if app.io_loop._running:
time.sleep(0.1)
else:
break
assert not app.io_loop._running
assert stop.called

18
jupyterhub/tests/utils.py Normal file
View File

@@ -0,0 +1,18 @@
from concurrent.futures import ThreadPoolExecutor
import requests
class _AsyncRequests:
"""Wrapper around requests to return a Future from request methods
A single thread is allocated to avoid blocking the IOLoop thread.
"""
def __init__(self):
self.executor = ThreadPoolExecutor(1)
def __getattr__(self, name):
requests_method = getattr(requests, name)
return lambda *args, **kwargs: self.executor.submit(requests_method, *args, **kwargs)
# async_requests.get = requests.get returning a Future, etc.
async_requests = _AsyncRequests()