test_jupyterlab.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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.mime_renderer_dir = pjoin(here, 'mock-mimeextension')
  67. self.patches = []
  68. p = patch.dict('os.environ', {
  69. 'JUPYTER_CONFIG_DIR': self.config_dir,
  70. 'JUPYTER_DATA_DIR': self.data_dir,
  71. })
  72. self.patches.append(p)
  73. for mod in (paths, commands):
  74. if hasattr(mod, 'ENV_JUPYTER_PATH'):
  75. p = patch.object(mod, 'ENV_JUPYTER_PATH', [self.data_dir])
  76. self.patches.append(p)
  77. if hasattr(mod, 'ENV_CONFIG_PATH'):
  78. p = patch.object(mod, 'ENV_CONFIG_PATH', [self.config_dir])
  79. self.patches.append(p)
  80. if hasattr(mod, 'CONFIG_PATH'):
  81. p = patch.object(mod, 'CONFIG_PATH', self.config_dir)
  82. self.patches.append(p)
  83. if hasattr(mod, 'BUILD_PATH'):
  84. p = patch.object(mod, 'BUILD_PATH', self.data_dir)
  85. self.patches.append(p)
  86. for p in self.patches:
  87. p.start()
  88. self.addCleanup(p.stop)
  89. # verify our patches
  90. self.assertEqual(paths.ENV_CONFIG_PATH, [self.config_dir])
  91. self.assertEqual(paths.ENV_JUPYTER_PATH, [self.data_dir])
  92. self.assertEqual(commands.ENV_JUPYTER_PATH, [self.data_dir])
  93. self.assertEqual(commands.get_app_dir(), os.path.realpath(pjoin(self.data_dir, 'lab')))
  94. self.app_dir = commands.get_app_dir()
  95. def tearDown(self):
  96. for modulename in self._mock_extensions:
  97. sys.modules.pop(modulename)
  98. def test_install_extension(self):
  99. install_extension(self.source_dir)
  100. path = pjoin(self.app_dir, 'extensions', '*python-tests*.tgz')
  101. assert glob.glob(path)
  102. assert '@jupyterlab/python-tests' in _get_extensions(self.app_dir)
  103. def test_install_twice(self):
  104. install_extension(self.source_dir)
  105. path = pjoin(commands.get_app_dir(), 'extensions', '*python-tests*.tgz')
  106. install_extension(self.source_dir)
  107. assert glob.glob(path)
  108. assert '@jupyterlab/python-tests' in _get_extensions(self.app_dir)
  109. def test_install_mime_renderer(self):
  110. install_extension(self.mime_renderer_dir)
  111. assert '@jupyterlab/mime-extension-test' in _get_extensions(self.app_dir)
  112. uninstall_extension('@jupyterlab/mime-extension-test')
  113. assert '@jupyterlab/mime-extension-test' not in _get_extensions(self.app_dir)
  114. def test_install_incompatible(self):
  115. with pytest.raises(ValueError) as excinfo:
  116. install_extension(self.incompat_dir)
  117. assert 'Conflicting Dependencies' in str(excinfo.value)
  118. def test_install_failed(self):
  119. path = self.mock_package
  120. with pytest.raises(ValueError):
  121. install_extension(path)
  122. with open(pjoin(path, 'package.json')) as fid:
  123. data = json.load(fid)
  124. assert not data['name'] in _get_extensions(self.app_dir)
  125. def test_uninstall_extension(self):
  126. install_extension(self.source_dir)
  127. uninstall_extension('@jupyterlab/python-tests')
  128. path = pjoin(self.app_dir, 'extensions', '*python_tests*.tgz')
  129. assert not glob.glob(path)
  130. assert '@jupyterlab/python-tests' not in _get_extensions(self.app_dir)
  131. def test_uninstall_core_extension(self):
  132. uninstall_extension('@jupyterlab/console-extension')
  133. app_dir = self.app_dir
  134. _ensure_package(app_dir)
  135. with open(pjoin(app_dir, 'staging', 'package.json')) as fid:
  136. data = json.load(fid)
  137. extensions = data['jupyterlab']['extensions']
  138. assert '@jupyterlab/console-extension' not in extensions
  139. install_extension('@jupyterlab/console-extension')
  140. _ensure_package(app_dir)
  141. with open(pjoin(app_dir, 'staging', 'package.json')) as fid:
  142. data = json.load(fid)
  143. extensions = data['jupyterlab']['extensions']
  144. assert '@jupyterlab/console-extension' in extensions
  145. def test_link_extension(self):
  146. link_package(self.source_dir)
  147. linked = _get_linked_packages().keys()
  148. assert '@jupyterlab/python-tests' in linked
  149. assert '@jupyterlab/python-tests' in _get_extensions(self.app_dir)
  150. def test_link_mime_renderer(self):
  151. link_package(self.mime_renderer_dir)
  152. linked = _get_linked_packages().keys()
  153. assert '@jupyterlab/mime-extension-test' in linked
  154. assert '@jupyterlab/mime-extension-test' in _get_extensions(self.app_dir)
  155. unlink_package('@jupyterlab/mime-extension-test')
  156. linked = _get_linked_packages().keys()
  157. assert '@jupyterlab/mime-extension-test' not in linked
  158. assert '@jupyterlab/mime-extension-test' not in _get_extensions(self.app_dir)
  159. def test_link_package(self):
  160. path = self.mock_package
  161. link_package(path)
  162. linked = _get_linked_packages().keys()
  163. with open(pjoin(path, 'package.json')) as fid:
  164. data = json.load(fid)
  165. assert data['name'] in linked
  166. assert not data['name'] in _get_extensions(self.app_dir)
  167. unlink_package(path)
  168. linked = _get_linked_packages().keys()
  169. assert not data['name'] in linked
  170. def test_link_incompatible(self):
  171. with pytest.raises(ValueError) as excinfo:
  172. install_extension(self.incompat_dir)
  173. assert 'Conflicting Dependencies' in str(excinfo.value)
  174. def test_unlink_package(self):
  175. target = self.source_dir
  176. link_package(target)
  177. unlink_package(target)
  178. linked = _get_linked_packages().keys()
  179. assert '@jupyterlab/python-tests' not in linked
  180. assert '@jupyterlab/python-tests' not in _get_extensions(self.app_dir)
  181. def test_list_extensions(self):
  182. install_extension(self.source_dir)
  183. list_extensions()
  184. def test_app_dir(self):
  185. app_dir = self.tempdir()
  186. install_extension(self.source_dir, app_dir)
  187. path = pjoin(app_dir, 'extensions', '*python-tests*.tgz')
  188. assert glob.glob(path)
  189. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  190. uninstall_extension('@jupyterlab/python-tests', app_dir)
  191. path = pjoin(app_dir, 'extensions', '*python-tests*.tgz')
  192. assert not glob.glob(path)
  193. assert '@jupyterlab/python-tests' not in _get_extensions(app_dir)
  194. link_package(self.source_dir, app_dir)
  195. linked = _get_linked_packages(app_dir).keys()
  196. assert '@jupyterlab/python-tests' in linked
  197. unlink_package(self.source_dir, app_dir)
  198. linked = _get_linked_packages(app_dir).keys()
  199. assert '@jupyterlab/python-tests' not in linked
  200. def test_app_dir_use_sys_prefix(self):
  201. app_dir = self.tempdir()
  202. if os.path.exists(self.app_dir):
  203. os.removedirs(self.app_dir)
  204. install_extension(self.source_dir)
  205. path = pjoin(app_dir, 'extensions', '*python-tests*.tgz')
  206. assert not glob.glob(path)
  207. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  208. def test_app_dir_shadowing(self):
  209. app_dir = self.tempdir()
  210. sys_dir = self.app_dir
  211. if os.path.exists(sys_dir):
  212. os.removedirs(sys_dir)
  213. install_extension(self.source_dir)
  214. sys_path = pjoin(sys_dir, 'extensions', '*python-tests*.tgz')
  215. assert glob.glob(sys_path)
  216. app_path = pjoin(app_dir, 'extensions', '*python-tests*.tgz')
  217. assert not glob.glob(app_path)
  218. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  219. install_extension(self.source_dir, app_dir)
  220. assert glob.glob(app_path)
  221. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  222. uninstall_extension('@jupyterlab/python-tests', app_dir)
  223. assert not glob.glob(app_path)
  224. assert glob.glob(sys_path)
  225. assert '@jupyterlab/python-tests' in _get_extensions(app_dir)
  226. uninstall_extension('@jupyterlab/python-tests', app_dir)
  227. assert not glob.glob(app_path)
  228. assert not glob.glob(sys_path)
  229. assert '@jupyterlab/python-tests' not in _get_extensions(app_dir)
  230. def test_build(self):
  231. install_extension(self.source_dir)
  232. build()
  233. # check staging directory.
  234. entry = pjoin(self.app_dir, 'staging', 'build', 'index.out.js')
  235. with open(entry) as fid:
  236. data = fid.read()
  237. assert '@jupyterlab/python-tests' in data
  238. # check static directory.
  239. entry = pjoin(self.app_dir, 'static', 'index.out.js')
  240. with open(entry) as fid:
  241. data = fid.read()
  242. assert '@jupyterlab/python-tests' in data
  243. def test_build_custom(self):
  244. install_extension(self.source_dir)
  245. build(name='foo', version='1.0')
  246. # check static directory.
  247. entry = pjoin(self.app_dir, 'static', 'index.out.js')
  248. with open(entry) as fid:
  249. data = fid.read()
  250. assert '@jupyterlab/python-tests' in data
  251. pkg = pjoin(self.app_dir, 'static', 'package.json')
  252. with open(pkg) as fid:
  253. data = json.load(fid)
  254. assert data['jupyterlab']['name'] == 'foo'
  255. assert data['jupyterlab']['version'] == '1.0'
  256. def test_load_extension(self):
  257. app = NotebookApp()
  258. stderr = sys.stderr
  259. sys.stderr = self.devnull
  260. app.initialize()
  261. sys.stderr = stderr
  262. load_jupyter_server_extension(app)
  263. def test_disable_extension(self):
  264. app_dir = self.tempdir()
  265. install_extension(self.source_dir, app_dir)
  266. disable_extension('@jupyterlab/python-tests', app_dir)
  267. disabled = _get_disabled(app_dir)
  268. assert '@jupyterlab/python-tests' in disabled
  269. disable_extension('@jupyterlab/notebook-extension', app_dir)
  270. disabled = _get_disabled(app_dir)
  271. assert '@jupyterlab/notebook-extension' in disabled
  272. def test_enable_extension(self):
  273. app_dir = self.tempdir()
  274. install_extension(self.source_dir, app_dir)
  275. disable_extension('@jupyterlab/python-tests', app_dir)
  276. enable_extension('@jupyterlab/python-tests', app_dir)
  277. disabled = _get_disabled(app_dir)
  278. assert '@jupyterlab/python-tests' not in disabled
  279. def test_should_build(self):
  280. assert not should_build()[0]
  281. install_extension(self.source_dir)
  282. assert should_build()[0]
  283. build()
  284. assert not should_build()[0]
  285. uninstall_extension('@jupyterlab/python-tests')
  286. assert should_build()[0]
  287. def test_compatibility(self):
  288. assert _test_overlap('^0.6.0', '^0.6.1')
  289. assert _test_overlap('>0.1', '0.6')
  290. assert _test_overlap('~0.5.0', '~0.5.2')
  291. assert _test_overlap('0.5.2', '^0.5.0')
  292. assert not _test_overlap('^0.5.0', '^0.6.0')
  293. assert not _test_overlap('~1.5.0', '^1.6.0')
  294. assert _test_overlap('*', '0.6') is None
  295. assert _test_overlap('<0.6', '0.1') is None