test_jupyterlab.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. # coding: utf-8
  2. """Test installation of JupyterLab extensions"""
  3. # Copyright (c) Jupyter Development Team.
  4. # Distributed under the terms of the Modified BSD License.
  5. import glob
  6. import json
  7. import os
  8. import sys
  9. from os.path import join as pjoin
  10. from unittest import TestCase
  11. import pytest
  12. try:
  13. from unittest.mock import patch
  14. except ImportError:
  15. from mock import patch # py2
  16. from ipython_genutils import py3compat
  17. from ipython_genutils.tempdir import TemporaryDirectory
  18. from notebook.notebookapp import NotebookApp
  19. from jupyter_core import paths
  20. from jupyterlab import commands
  21. from jupyterlab.extension import (
  22. load_jupyter_server_extension
  23. )
  24. from jupyterlab.commands import (
  25. install_extension, uninstall_extension, list_extensions,
  26. build, link_package, unlink_package, should_build,
  27. disable_extension, enable_extension, _get_extensions,
  28. _get_linked_packages, _ensure_package, _get_disabled,
  29. _test_overlap
  30. )
  31. here = os.path.dirname(os.path.abspath(__file__))
  32. def touch(file, mtime=None):
  33. """ensure a file exists, and set its modification time
  34. returns the modification time of the file
  35. """
  36. dirname = os.path.dirname(file)
  37. if not os.path.exists(dirname):
  38. os.makedirs(dirname)
  39. open(file, 'a').close()
  40. # set explicit mtime
  41. if mtime:
  42. atime = os.stat(file).st_atime
  43. os.utime(file, (atime, mtime))
  44. return os.stat(file).st_mtime
  45. class TestExtension(TestCase):
  46. def tempdir(self):
  47. td = TemporaryDirectory()
  48. self.tempdirs.append(td)
  49. return py3compat.cast_unicode(td.name)
  50. def setUp(self):
  51. # Any TemporaryDirectory objects appended to this list will be cleaned
  52. # up at the end of the test run.
  53. self.tempdirs = []
  54. self._mock_extensions = []
  55. self.devnull = open(os.devnull, 'w')
  56. @self.addCleanup
  57. def cleanup_tempdirs():
  58. for d in self.tempdirs:
  59. d.cleanup()
  60. self.test_dir = self.tempdir()
  61. self.data_dir = pjoin(self.test_dir, 'data')
  62. self.config_dir = pjoin(self.test_dir, 'config')
  63. self.source_dir = pjoin(here, 'mockextension')
  64. self.incompat_dir = pjoin(here, 'mockextension-incompat')
  65. self.mock_package = pjoin(here, 'mockpackage')
  66. self.patches = []
  67. p = patch.dict('os.environ', {
  68. 'JUPYTER_CONFIG_DIR': self.config_dir,
  69. 'JUPYTER_DATA_DIR': self.data_dir,
  70. })
  71. self.patches.append(p)
  72. for mod in (paths, commands):
  73. if hasattr(mod, 'ENV_JUPYTER_PATH'):
  74. p = patch.object(mod, 'ENV_JUPYTER_PATH', [self.data_dir])
  75. self.patches.append(p)
  76. if hasattr(mod, 'ENV_CONFIG_PATH'):
  77. p = patch.object(mod, 'ENV_CONFIG_PATH', [self.config_dir])
  78. self.patches.append(p)
  79. if hasattr(mod, 'CONFIG_PATH'):
  80. p = patch.object(mod, 'CONFIG_PATH', self.config_dir)
  81. self.patches.append(p)
  82. if hasattr(mod, 'BUILD_PATH'):
  83. p = patch.object(mod, 'BUILD_PATH', self.data_dir)
  84. self.patches.append(p)
  85. for p in self.patches:
  86. p.start()
  87. self.addCleanup(p.stop)
  88. # verify our patches
  89. self.assertEqual(paths.ENV_CONFIG_PATH, [self.config_dir])
  90. self.assertEqual(paths.ENV_JUPYTER_PATH, [self.data_dir])
  91. self.assertEqual(commands.ENV_JUPYTER_PATH, [self.data_dir])
  92. self.assertEqual(commands.get_app_dir(), os.path.realpath(pjoin(self.data_dir, 'lab')))
  93. self.app_dir = commands.get_app_dir()
  94. def tearDown(self):
  95. for modulename in self._mock_extensions:
  96. sys.modules.pop(modulename)
  97. def test_install_extension(self):
  98. install_extension(self.source_dir)
  99. path = pjoin(self.app_dir, 'extensions', '*python-tests*.tgz')
  100. assert glob.glob(path)
  101. assert '@jupyterlab/python-tests' in _get_extensions(self.app_dir)
  102. def test_install_twice(self):
  103. install_extension(self.source_dir)
  104. path = pjoin(commands.get_app_dir(), 'extensions', '*python-tests*.tgz')
  105. install_extension(self.source_dir)
  106. assert glob.glob(path)
  107. assert '@jupyterlab/python-tests' in _get_extensions(self.app_dir)
  108. def test_install_incompatible(self):
  109. with pytest.raises(ValueError):
  110. install_extension(self.incompat_dir)
  111. def test_install_failed(self):
  112. path = self.mock_package
  113. with pytest.raises(ValueError):
  114. install_extension(path)
  115. with open(pjoin(path, 'package.json')) as fid:
  116. data = json.load(fid)
  117. assert not data['name'] in _get_extensions(self.app_dir)
  118. def test_uninstall_extension(self):
  119. install_extension(self.source_dir)
  120. uninstall_extension('@jupyterlab/python-tests')
  121. path = pjoin(self.app_dir, 'extensions', '*python_tests*.tgz')
  122. assert not glob.glob(path)
  123. assert '@jupyterlab/python-tests' not in _get_extensions(self.app_dir)
  124. def test_uninstall_core_extension(self):
  125. uninstall_extension('@jupyterlab/console-extension')
  126. app_dir = self.app_dir
  127. _ensure_package(app_dir)
  128. with open(pjoin(app_dir, 'staging', 'package.json')) as fid:
  129. data = json.load(fid)
  130. extensions = data['jupyterlab']['extensions']
  131. assert '@jupyterlab/console-extension' not in extensions
  132. install_extension('@jupyterlab/console-extension')
  133. _ensure_package(app_dir)
  134. with open(pjoin(app_dir, 'staging', 'package.json')) as fid:
  135. data = json.load(fid)
  136. extensions = data['jupyterlab']['extensions']
  137. assert '@jupyterlab/console-extension' in extensions
  138. def test_link_extension(self):
  139. link_package(self.source_dir)
  140. linked = _get_linked_packages().keys()
  141. assert '@jupyterlab/python-tests' in linked
  142. assert '@jupyterlab/python-tests' in _get_extensions(self.app_dir)
  143. def test_link_package(self):
  144. path = self.mock_package
  145. link_package(path)
  146. linked = _get_linked_packages().keys()
  147. with open(pjoin(path, 'package.json')) as fid:
  148. data = json.load(fid)
  149. assert data['name'] in linked
  150. assert not data['name'] in _get_extensions(self.app_dir)
  151. unlink_package(path)
  152. linked = _get_linked_packages().keys()
  153. assert not data['name'] in linked
  154. def test_link_incompatible(self):
  155. with pytest.raises(ValueError):
  156. install_extension(self.incompat_dir)
  157. def test_unlink_package(self):
  158. target = self.source_dir
  159. link_package(target)
  160. unlink_package(target)
  161. linked = _get_linked_packages().keys()
  162. assert '@jupyterlab/python-tests' not in linked
  163. assert '@jupyterlab/python-tests' not in _get_extensions(self.app_dir)
  164. def test_list_extensions(self):
  165. install_extension(self.source_dir)
  166. list_extensions()
  167. def test_app_dir(self):
  168. app_dir = self.tempdir()
  169. install_extension(self.source_dir, app_dir)
  170. path = pjoin(app_dir, 'extensions', '*python-tests*.tgz')
  171. assert glob.glob(path)
  172. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  173. uninstall_extension('@jupyterlab/python-tests', app_dir)
  174. path = pjoin(app_dir, 'extensions', '*python-tests*.tgz')
  175. assert not glob.glob(path)
  176. assert '@jupyterlab/python-tests' not in _get_extensions(app_dir)
  177. link_package(self.source_dir, app_dir)
  178. linked = _get_linked_packages(app_dir).keys()
  179. assert '@jupyterlab/python-tests' in linked
  180. unlink_package(self.source_dir, app_dir)
  181. linked = _get_linked_packages(app_dir).keys()
  182. assert '@jupyterlab/python-tests' not in linked
  183. def test_app_dir_use_sys_prefix(self):
  184. app_dir = self.tempdir()
  185. if os.path.exists(self.app_dir):
  186. os.removedirs(self.app_dir)
  187. install_extension(self.source_dir)
  188. path = pjoin(app_dir, 'extensions', '*python-tests*.tgz')
  189. assert not glob.glob(path)
  190. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  191. def test_app_dir_shadowing(self):
  192. app_dir = self.tempdir()
  193. sys_dir = self.app_dir
  194. if os.path.exists(sys_dir):
  195. os.removedirs(sys_dir)
  196. install_extension(self.source_dir)
  197. sys_path = pjoin(sys_dir, 'extensions', '*python-tests*.tgz')
  198. assert glob.glob(sys_path)
  199. app_path = pjoin(app_dir, 'extensions', '*python-tests*.tgz')
  200. assert not glob.glob(app_path)
  201. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  202. install_extension(self.source_dir, app_dir)
  203. assert glob.glob(app_path)
  204. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  205. uninstall_extension('@jupyterlab/python-tests', app_dir)
  206. assert not glob.glob(app_path)
  207. assert glob.glob(sys_path)
  208. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  209. uninstall_extension('@jupyterlab/python-tests', app_dir)
  210. assert not glob.glob(app_path)
  211. assert not glob.glob(sys_path)
  212. assert '@jupyterlab/python-tests' not in _get_extensions(app_dir)
  213. def test_build(self):
  214. install_extension(self.source_dir)
  215. build()
  216. # check staging directory.
  217. entry = pjoin(self.app_dir, 'staging', 'build', 'index.out.js')
  218. with open(entry) as fid:
  219. data = fid.read()
  220. assert '@jupyterlab/python-tests' in data
  221. # check static directory.
  222. entry = pjoin(self.app_dir, 'static', 'index.out.js')
  223. with open(entry) as fid:
  224. data = fid.read()
  225. assert '@jupyterlab/python-tests' in data
  226. def test_build_custom(self):
  227. install_extension(self.source_dir)
  228. build(name='foo', version='1.0')
  229. # check static directory.
  230. entry = pjoin(self.app_dir, 'static', 'index.out.js')
  231. with open(entry) as fid:
  232. data = fid.read()
  233. assert '@jupyterlab/python-tests' in data
  234. pkg = pjoin(self.app_dir, 'static', 'package.json')
  235. with open(pkg) as fid:
  236. data = json.load(fid)
  237. assert data['jupyterlab']['name'] == 'foo'
  238. assert data['jupyterlab']['version'] == '1.0'
  239. def test_load_extension(self):
  240. app = NotebookApp()
  241. stderr = sys.stderr
  242. sys.stderr = self.devnull
  243. app.initialize()
  244. sys.stderr = stderr
  245. load_jupyter_server_extension(app)
  246. def test_disable_extension(self):
  247. app_dir = self.tempdir()
  248. install_extension(self.source_dir, app_dir)
  249. disable_extension('@jupyterlab/python-tests', app_dir)
  250. disabled = _get_disabled(app_dir)
  251. assert '@jupyterlab/python-tests' in disabled
  252. disable_extension('@jupyterlab/notebook-extension', app_dir)
  253. disabled = _get_disabled(app_dir)
  254. assert '@jupyterlab/notebook-extension' in disabled
  255. def test_enable_extension(self):
  256. app_dir = self.tempdir()
  257. install_extension(self.source_dir, app_dir)
  258. disable_extension('@jupyterlab/python-tests', app_dir)
  259. enable_extension('@jupyterlab/python-tests', app_dir)
  260. disabled = _get_disabled(app_dir)
  261. assert '@jupyterlab/python-tests' not in disabled
  262. def test_should_build(self):
  263. assert not should_build()[0]
  264. install_extension(self.source_dir)
  265. assert should_build()[0]
  266. build()
  267. assert not should_build()[0]
  268. uninstall_extension('@jupyterlab/python-tests')
  269. assert should_build()[0]
  270. def test_compatibility(self):
  271. assert _test_overlap('^0.6.0', '^0.6.1')
  272. assert _test_overlap('>0.1', '0.6')
  273. assert _test_overlap('~0.5.0', '~0.5.2')
  274. assert _test_overlap('0.5.2', '^0.5.0')
  275. assert not _test_overlap('^0.5.0', '^0.6.0')
  276. assert not _test_overlap('~1.5.0', '^1.6.0')
  277. assert _test_overlap('*', '0.6') is None
  278. assert _test_overlap('<0.6', '0.1') is None