Skip to content

Commit

Permalink
Merge pull request #872 from blink1073/labextension-tests
Browse files Browse the repository at this point in the history
Lab Extension Testing
  • Loading branch information
jasongrout committed Sep 26, 2016
2 parents 5d91733 + 7d89515 commit 3c1311a
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 2 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
recursive-include jupyterlab/build *
include jupyterlab/lab.html
include package.json
prune jupyterlab/tests
2 changes: 1 addition & 1 deletion jupyterlab/labextensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def check_labextension(files, user=False, prefix=None, labextensions_dir=None, s
return all(os.path.exists(pjoin(labext, f)) for f in files)


def install_labextension(path, name='', overwrite=False, symlink=False,
def install_labextension(path, name, overwrite=False, symlink=False,
user=False, prefix=None, labextensions_dir=None,
logger=None, sys_prefix=False
):
Expand Down
16 changes: 16 additions & 0 deletions jupyterlab/tests/build_extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

var buildExtension = require('jupyterlab-extension-builder').buildExtension;
var path = require('path');


buildExtension({
name: 'mockextension',
entryPath: './mockextension/index.js',
config: {
output: {
path: path.join(process.cwd(), 'mockextension', 'build'),
}
}
});
12 changes: 12 additions & 0 deletions jupyterlab/tests/mockextension/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

module.exports = [
{
id: 'mockextension',
autoStart: true,
activate: function(application) {
window.commands = application.commands;
}
}
]
314 changes: 314 additions & 0 deletions jupyterlab/tests/test_labextensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
# coding: utf-8
"""Test installation of JupyterLab extensions"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import glob
import os
import sys
from io import StringIO
from os.path import join as pjoin
from subprocess import check_call
from traitlets.tests.utils import check_help_all_output
from unittest import TestCase

try:
from unittest.mock import patch
except ImportError:
from mock import patch # py2

import ipython_genutils.testing.decorators as dec
from ipython_genutils import py3compat
from ipython_genutils.tempdir import TemporaryDirectory
from jupyterlab import labextensions
from jupyterlab.labextensions import (install_labextension, check_labextension,
enable_labextension, disable_labextension,
install_labextension_python, uninstall_labextension_python,
enable_labextension_python, disable_labextension_python, _get_config_dir,
validate_labextension, validate_labextension_folder
)

from traitlets.config.manager import BaseJSONConfigManager


FILENAME = 'mockextension/mockextension.bundle.js'


def touch(file, mtime=None):
"""ensure a file exists, and set its modification time
returns the modification time of the file
"""
dirname = os.path.dirname(file)
if not os.path.exists(dirname):
os.makedirs(dirname)
open(file, 'a').close()
# set explicit mtime
if mtime:
atime = os.stat(file).st_atime
os.utime(file, (atime, mtime))
return os.stat(file).st_mtime


def test_help_output():
check_help_all_output('jupyterlab.labextensions')
check_help_all_output('jupyterlab.labextensions', ['enable'])
check_help_all_output('jupyterlab.labextensions', ['disable'])
check_help_all_output('jupyterlab.labextensions', ['install'])
check_help_all_output('jupyterlab.labextensions', ['uninstall'])


def build_extension():
shell = (sys.platform == 'win32')
cwd = os.path.dirname(os.path.abspath(__file__))
cmd = 'node build_extension.js'
check_call(cmd.split(), shell=shell, cwd=cwd)


class TestInstallLabExtension(TestCase):

@classmethod
def setUpClass(cls):
# Build the extension
build_extension()

def tempdir(self):
td = TemporaryDirectory()
self.tempdirs.append(td)
return py3compat.cast_unicode(td.name)

def setUp(self):
# Any TemporaryDirectory objects appended to this list will be cleaned
# up at the end of the test run.
self.tempdirs = []

@self.addCleanup
def cleanup_tempdirs():
for d in self.tempdirs:
d.cleanup()

self.src = self.tempdir()
self.name = 'mockextension'
self.files = files = [
pjoin(u'ƒile'),
pjoin(u'∂ir', u'ƒile1'),
pjoin(u'∂ir', u'∂ir2', u'ƒile2'),
]
for file in files:
fullpath = os.path.join(self.src, file)
parent = os.path.dirname(fullpath)
if not os.path.exists(parent):
os.makedirs(parent)
touch(fullpath)

self.test_dir = self.tempdir()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
self.system_data_dir = os.path.join(self.test_dir, 'system_data')
self.system_path = [self.system_data_dir]
self.system_labext = os.path.join(self.system_data_dir, 'labextensions')

# Patch out os.environ so that tests are isolated from the real OS
# environment.
self.patch_env = patch.dict('os.environ', {
'JUPYTER_CONFIG_DIR': self.config_dir,
'JUPYTER_DATA_DIR': self.data_dir,
})
self.patch_env.start()
self.addCleanup(self.patch_env.stop)

# Patch out the system path os that we consistently use our own
# temporary directory instead.
self.patch_system_path = patch.object(
labextensions, 'SYSTEM_JUPYTER_PATH', self.system_path
)
self.patch_system_path.start()
self.addCleanup(self.patch_system_path.stop)

def assert_dir_exists(self, path):
if not os.path.exists(path):
do_exist = os.listdir(os.path.dirname(path))
self.fail(u"%s should exist (found %s)" % (path, do_exist))

def assert_not_dir_exists(self, path):
if os.path.exists(path):
self.fail(u"%s should not exist" % path)

def assert_installed(self, relative_path, user=False):
if user:
labext = pjoin(self.data_dir, u'labextensions')
else:
labext = self.system_labext
self.assert_dir_exists(
pjoin(labext, relative_path)
)

def assert_not_installed(self, relative_path, user=False):
if user:
labext = pjoin(self.data_dir, u'labextensions')
else:
labext = self.system_labext
self.assert_not_dir_exists(
pjoin(labext, relative_path)
)

def test_create_labextensions_user(self):
install_labextension(self.src, self.name, user=True)
self.assert_installed(self.name, user=True)

def test_create_labextensions_system(self):
with TemporaryDirectory() as td:
with patch.object(labextensions, 'SYSTEM_JUPYTER_PATH', [td]):
self.system_labext = pjoin(td, u'labextensions')
install_labextension(self.src, self.name, user=False)
self.assert_installed(self.name, user=False)

def test_install_labextension(self):
with self.assertRaises(TypeError):
install_labextension(glob.glob(pjoin(self.src, '*')), self.name)

def test_quiet(self):
stdout = StringIO()
stderr = StringIO()
with patch.object(sys, 'stdout', stdout), \
patch.object(sys, 'stderr', stderr):
install_labextension(self.src, self.name)
self.assertEqual(stdout.getvalue(), '')
self.assertEqual(stderr.getvalue(), '')

def test_check_labextension(self):
with TemporaryDirectory() as d:
f = u'ƒ.js'
src = pjoin(d, self.name, 'build')
touch(pjoin(src, f))
install_labextension(src, self.name, user=True)

f = pjoin(self.name, f)
assert check_labextension(f, user=True)
assert check_labextension([f], user=True)
assert not check_labextension([f, pjoin('dne', f)], user=True)

@dec.skip_win32
def test_install_symlink(self):
with TemporaryDirectory() as d:
f = u'ƒ.js'
src = pjoin(d, f)
touch(src)
install_labextension(d, self.name, symlink=True)
dest = pjoin(self.system_labext, self.name)
assert os.path.islink(dest)
link = os.readlink(dest)
self.assertEqual(link, d)

def test_labextension_enable(self):
with TemporaryDirectory() as d:
f = u'ƒ.js'
src = pjoin(d, f)
touch(src)
install_labextension(src, self.name, user=True)
enable_labextension(self.name)

config_dir = os.path.join(_get_config_dir(user=True), 'labconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('jupyterlab_config').get('LabApp', {}).get('labextensions', {}).get(self.name, False)
assert enabled

def test_labextension_disable(self):
self.test_labextension_enable()
disable_labextension(self.name)

config_dir = os.path.join(_get_config_dir(user=True), 'labconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('jupyterlab_config').get('LabApp', {}).get('labextensions', {}).get(self.name, False)
assert not enabled


def _mock_extension_spec_meta(self):
return {
'name': 'mockextension',
'src': 'mockextension/build',
}

def _inject_mock_extension(self):
outer_file = __file__

meta = self._mock_extension_spec_meta()

class mock():
__file__ = outer_file

@staticmethod
def _jupyter_labextension_paths():
return [meta]

import sys
sys.modules['mockextension'] = mock

def test_labextensionpy_files(self):
self._inject_mock_extension()
install_labextension_python('mockextension')

assert check_labextension(FILENAME)
assert check_labextension([FILENAME])

def test_labextensionpy_user_files(self):
self._inject_mock_extension()
install_labextension_python('mockextension', user=True)

assert check_labextension(FILENAME, user=True)
assert check_labextension([FILENAME], user=True)

def test_labextensionpy_uninstall_files(self):
self._inject_mock_extension()
install_labextension_python('mockextension', user=True)
uninstall_labextension_python('mockextension', user=True)

assert not check_labextension(FILENAME)
assert not check_labextension([FILENAME])

def test_labextensionpy_enable(self):
self._inject_mock_extension()
install_labextension_python('mockextension', user=True)
enable_labextension_python('mockextension')

config_dir = os.path.join(_get_config_dir(user=True), 'labconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('jupyterlab_config').get('LabApp', {}).get('labextensions', {}).get('mockextension', False)
assert enabled

def test_labextensionpy_disable(self):
self._inject_mock_extension()
install_labextension_python('mockextension', user=True)
enable_labextension_python('mockextension')
disable_labextension_python('mockextension', user=True)

config_dir = os.path.join(_get_config_dir(user=True), 'labconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('jupyterlab_config').get('LabApp', {}).get('labextensions', {}).get('mockextension', False)
assert not enabled

def test_labextensionpy_validate(self):
self._inject_mock_extension()

paths = install_labextension_python('mockextension', user=True)
enable_labextension_python('mockextension')

meta = self._mock_extension_spec_meta()
warnings = validate_labextension_folder(meta['name'], paths[0])
self.assertEqual([], warnings, warnings)

def test_labextension_validate(self):
# Break the metadata (correct file will still be copied)
self._inject_mock_extension()

install_labextension_python('mockextension', user=True)
enable_labextension_python('mockextension')

warnings = validate_labextension("mockextension")
self.assertEqual([], warnings, warnings)

def test_labextension_validate_bad(self):
warnings = validate_labextension("this-doesn't-exist")
self.assertNotEqual([], warnings, warnings)

2 changes: 1 addition & 1 deletion scripts/travis_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ hash -r
conda config --set always_yes yes --set changeps1 no
conda update -q conda
conda info -a
conda install jupyter
conda install jupyter nose

# create jupyter base dir (needed for config retreival)
mkdir ~/.jupyter
4 changes: 4 additions & 0 deletions scripts/travis_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ npm run build
npm test
npm run test:coverage

# Run the python tests
npm run build:serverextension
pushd jupyterlab && nosetests && popd

npm run build:examples
npm run docs
cp jupyter-plugins-demo.gif docs
Expand Down

0 comments on commit 3c1311a

Please sign in to comment.