Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting up testing #42

Merged
merged 3 commits into from
Jan 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ os:
env:
matrix:
- PYTHON_VERSION=2.7
- PYTHON_VERSION=3.5
- PYTHON_VERSION=3.6
- PYTHON_VERSION=3.7
before_install:
- if [[ $TRAVIS_OS_NAME == linux ]]; then sudo apt-get update; fi
- if [[ $TRAVIS_OS_NAME == linux ]]; then wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; fi
Expand All @@ -17,7 +17,9 @@ before_install:
- conda config --set always_yes yes --set changeps1 no
- conda update -q conda
- conda info -a
- conda create -q -n test-environment python=$PYTHON_VERSION jupyter_server pytest pytest-cov nodejs
- conda create -q -n test-environment -c conda-forge python=$PYTHON_VERSION jupyter_server pytest==3.10.1 pytest-cov nodejs
- source activate test-environment
install:
- pip install .
- pip install ".[test]"
script:
- py.test tests/
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ def run(self):
'jupyter_server>=0.0.3',
'nbconvert>=5.4,<6'
],
'extras_require': {
'test': ['mock', 'pytest<4', 'pytest-tornado5']
},
'author': 'QuantStack',
'author_email': 'info@quantstack.net',
'keywords': [
Expand Down
34 changes: 34 additions & 0 deletions tests/notebooks/print.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('Hi Voila!')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
65 changes: 65 additions & 0 deletions tests/voila_single_notebook_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import pytest
import tornado.web
import tornado.gen
import voila.app
import os
import re
import json
import logging
from traitlets.config import Application
try:
from unittest import mock
except:
import mock

BASE_DIR = os.path.dirname(__file__)

class VoilaTest(voila.app.Voila):
def listen(self):
pass # the ioloop is taken care of by the pytest-tornado framework

@pytest.fixture
def voila_app():
voila_app = VoilaTest.instance()
voila_app.initialize([os.path.join(BASE_DIR, 'notebooks/print.ipynb'), '--VoilaApp.log_level=DEBUG'])
voila_app.start()
return voila_app

@pytest.fixture
def app(voila_app):
return voila_app.app

@pytest.mark.gen_test
def test_hello_world(http_client, base_url):
response = yield http_client.fetch(base_url)
assert response.code == 200
assert 'Hi Voila' in response.body.decode('utf-8')

@pytest.mark.gen_test
def test_no_execute_allowed(voila_app, app, http_client, base_url):
assert voila_app.app is app
response = (yield http_client.fetch(base_url)).body.decode('utf-8')
pattern = r"""kernelId": ["']([0-9a-zA-Z-]+)["']"""
groups = re.findall(pattern, response)
kernel_id = groups[0]
print(kernel_id, base_url)
session_id = '445edd75-c6f5-45d2-8b58-5fe8f84a7123'
url = '{base_url}/api/kernels/{kernel_id}/channels?session_id={session_id}'.format(
kernel_id=kernel_id, base_url=base_url, session_id=session_id
).replace('http://', 'ws://')
conn = yield tornado.websocket.websocket_connect(url)

msg = {
"header": {"msg_id":"8573fb401ac848aab63c3bf0081e9b65","username":"username","session":"7a7d94334ea745f888d9c479fa738d63","msg_type":"execute_request","version":"5.2"},
"metadata":{},
"content":{"code":"print('la')","silent":False,"store_history":False,"user_expressions":{},"allow_stdin":False,"stop_on_error":False},
"buffers":[],
"parent_header":{},
"channel":"shell"
}
with mock.patch.object(voila_app.log, 'warning') as mock_warning:
yield conn.write_message(json.dumps(msg))
# make sure the warning method is called
while not mock_warning.called:
yield tornado.gen.sleep(0.1)
mock_warning.assert_called_with('Received message of type "execute_request", which is not allowed. Ignoring.')
35 changes: 25 additions & 10 deletions voila/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from jupyter_server.services.config import ConfigManager
from jupyter_server.base.handlers import FileFindHandler
from jupyter_core.paths import jupyter_config_path, jupyter_path
from ipython_genutils.py3compat import getcwd

from .paths import ROOT, STATIC_ROOT, collect_template_paths
from .handler import VoilaHandler
Expand Down Expand Up @@ -71,6 +72,9 @@ class Voila(Application):
config=True,
help='Will autoreload to server and the page when a template, js file or Python code changes'
)
root_dir = Unicode(config=True,
help="The directory to use for notebooks."
)
static_root = Unicode(
STATIC_ROOT,
config=True,
Expand Down Expand Up @@ -126,6 +130,13 @@ def nbextensions_path(self):
path.append(os.path.join(get_ipython_dir(), 'nbextensions'))
return path

@default('root_dir')
def _default_root_dir(self):
if self.notebook_path:
return os.path.dirname(os.path.abspath(self.notebook_path))
else:
return getcwd()

def parse_command_line(self, argv=None):
super(Voila, self).parse_command_line(argv)
self.notebook_path = self.extra_args[0] if len(self.extra_args) == 1 else None
Expand All @@ -142,17 +153,19 @@ def parse_command_line(self, argv=None):
self.log.debug('nbconvert template paths: %s', self.nbconvert_template_paths)
self.log.debug('template paths: %s', self.template_paths)
self.log.debug('static paths: %s', self.static_paths)
if self.notebook_path and not os.path.exists(self.notebook_path):
raise ValueError('Notebook not found: %s' % self.notebook_path)

def start(self):
connection_dir = tempfile.mkdtemp(
self.connection_dir = tempfile.mkdtemp(
prefix='voila_',
dir=self.connection_dir_root
)
self.log.info('Storing connection files in %s.' % connection_dir)
self.log.info('Storing connection files in %s.' % self.connection_dir)
self.log.info('Serving static files from %s.' % self.static_root)

kernel_manager = MappingKernelManager(
connection_dir=connection_dir,
connection_dir=self.connection_dir,
allowed_message_types=[
'comm_msg',
'comm_info_request',
Expand All @@ -165,14 +178,14 @@ def start(self):
env = jinja2.Environment(loader=jinja2.FileSystemLoader(self.template_paths), extensions=['jinja2.ext.i18n'], **jenv_opt)
nbui = gettext.translation('nbui', localedir=os.path.join(ROOT, 'i18n'), fallback=True)
env.install_gettext_translations(nbui, newstyle=False)
contents_manager = LargeFileManager() # TODO: make this configurable like notebook
contents_manager = LargeFileManager(parent=self) # TODO: make this configurable like notebook

# we create a config manager that load both the serverconfig and nbconfig (classical notebook)
read_config_path = [os.path.join(p, 'serverconfig') for p in jupyter_config_path()]
read_config_path += [os.path.join(p, 'nbconfig') for p in jupyter_config_path()]
self.config_manager = ConfigManager(parent=self, read_config_path=read_config_path)

webapp = tornado.web.Application(
self.app = tornado.web.Application(
kernel_manager=kernel_manager,
allow_remote_access=True,
autoreload=self.autoreload,
Expand All @@ -184,7 +197,7 @@ def start(self):
config_manager=self.config_manager
)

base_url = webapp.settings.get('base_url', '/')
base_url = self.app.settings.get('base_url', '/')

handlers = []

Expand Down Expand Up @@ -218,7 +231,7 @@ def start(self):
url_path_join(base_url, r'/'),
VoilaHandler,
{
'notebook_path': self.notebook_path,
'notebook_path': os.path.relpath(self.notebook_path, self.root_dir),
'strip_sources': self.strip_sources,
'nbconvert_template_paths': self.nbconvert_template_paths,
'config': self.config
Expand All @@ -231,14 +244,16 @@ def start(self):
(url_path_join(base_url, r'/voila/render' + path_regex), VoilaHandler, {'strip_sources': self.strip_sources}),
])

webapp.add_handlers('.*$', handlers)
self.app.add_handlers('.*$', handlers)
self.listen()

webapp.listen(self.port)
def listen(self):
self.app.listen(self.port)
self.log.info('Voila listening on port %s.' % self.port)

try:
tornado.ioloop.IOLoop.current().start()
finally:
shutil.rmtree(connection_dir)
shutil.rmtree(self.connection_dir)

main = Voila.launch_instance
9 changes: 6 additions & 3 deletions voila/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ def get(self, path=None):
# a template can use that to load classical notebook extensions, but does not have to
notebook_config = self.config_manager.get('notebook')
# except for the widget extension itself, since voila has its own
if "jupyter-js-widgets/extension" in notebook_config['load_extensions']:
notebook_config['load_extensions']["jupyter-js-widgets/extension"] = False
nbextensions = [name for name, enabled in notebook_config['load_extensions'].items() if enabled]
load_extensions = notebook_config.get('load_extensions', {})
if "jupyter-js-widgets/extension" in load_extensions:
load_extensions["jupyter-js-widgets/extension"] = False
nbextensions = [name for name, enabled in load_extensions.items() if enabled]
else:
nbextensions = []

model = self.contents_manager.get(path=notebook_path)
if 'content' in model:
Expand Down