test_app.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. # coding: utf-8
  2. """A lab app that runs a sub process for a demo or a test."""
  3. from os import path as osp
  4. from os.path import join as pjoin
  5. from stat import S_IRUSR, S_IRGRP, S_IROTH
  6. import argparse
  7. import atexit
  8. import glob
  9. import json
  10. import logging
  11. import os
  12. import pkg_resources
  13. import shutil
  14. import sys
  15. import tempfile
  16. from tempfile import TemporaryDirectory
  17. from unittest.mock import patch
  18. from traitlets import Unicode
  19. from ipykernel.kernelspec import write_kernel_spec
  20. import jupyter_core
  21. from jupyterlab_launcher.process_app import ProcessApp
  22. import jupyterlab_launcher
  23. HERE = osp.realpath(osp.dirname(__file__))
  24. def _create_notebook_dir():
  25. """Create a temporary directory with some file structure."""
  26. root_dir = tempfile.mkdtemp(prefix='mock_contents')
  27. os.mkdir(osp.join(root_dir, 'src'))
  28. with open(osp.join(root_dir, 'src', 'temp.txt'), 'w') as fid:
  29. fid.write('hello')
  30. readonly_filepath = osp.join(root_dir, 'src', 'readonly-temp.txt')
  31. with open(readonly_filepath, 'w') as fid:
  32. fid.write('hello from a readonly file')
  33. os.chmod(readonly_filepath, S_IRUSR | S_IRGRP | S_IROTH)
  34. atexit.register(lambda: shutil.rmtree(root_dir, True))
  35. return root_dir
  36. def _create_schemas_dir():
  37. """Create a temporary directory for schemas."""
  38. root_dir = tempfile.mkdtemp(prefix='mock_schemas')
  39. extension_dir = osp.join(root_dir, '@jupyterlab', 'apputils-extension')
  40. os.makedirs(extension_dir)
  41. # Get schema content.
  42. schema_package = jupyterlab_launcher.__name__
  43. schema_path = 'tests/schemas/@jupyterlab/apputils-extension/themes.json'
  44. themes = pkg_resources.resource_string(schema_package, schema_path)
  45. with open(osp.join(extension_dir, 'themes.json'), 'w') as fid:
  46. fid.write(themes.decode('utf-8'))
  47. atexit.register(lambda: shutil.rmtree(root_dir, True))
  48. return root_dir
  49. def _create_user_settings_dir():
  50. """Create a temporary directory for workspaces."""
  51. root_dir = tempfile.mkdtemp(prefix='mock_user_settings')
  52. atexit.register(lambda: shutil.rmtree(root_dir, True))
  53. return root_dir
  54. def _create_workspaces_dir():
  55. """Create a temporary directory for workspaces."""
  56. root_dir = tempfile.mkdtemp(prefix='mock_workspaces')
  57. atexit.register(lambda: shutil.rmtree(root_dir, True))
  58. return root_dir
  59. def _install_kernels():
  60. # Install echo and ipython kernels - should be done after env patch
  61. kernel_json = {
  62. 'argv': [
  63. sys.executable,
  64. '-m', 'jupyterlab.tests.echo_kernel',
  65. '-f', '{connection_file}'
  66. ],
  67. 'display_name': "Echo Kernel",
  68. 'language': 'echo'
  69. }
  70. paths = jupyter_core.paths
  71. kernel_dir = pjoin(paths.jupyter_data_dir(), 'kernels', 'echo')
  72. os.makedirs(kernel_dir)
  73. with open(pjoin(kernel_dir, 'kernel.json'), 'w') as f:
  74. f.write(json.dumps(kernel_json))
  75. ipykernel_dir = pjoin(paths.jupyter_data_dir(), 'kernels', 'ipython')
  76. write_kernel_spec(ipykernel_dir)
  77. class _test_env(object):
  78. """Set Jupyter path variables to a temporary directory
  79. Useful as a context manager or with explicit start/stop
  80. """
  81. def start(self):
  82. self.test_dir = td = TemporaryDirectory()
  83. self.env_patch = patch.dict(os.environ, {
  84. 'JUPYTER_CONFIG_DIR': pjoin(td.name, 'jupyter'),
  85. 'JUPYTER_DATA_DIR': pjoin(td.name, 'jupyter_data'),
  86. 'JUPYTER_RUNTIME_DIR': pjoin(td.name, 'jupyter_runtime'),
  87. 'IPYTHONDIR': pjoin(td.name, 'ipython'),
  88. })
  89. self.env_patch.start()
  90. self.path_patch = patch.multiple(
  91. jupyter_core.paths,
  92. SYSTEM_JUPYTER_PATH=[pjoin(td.name, 'share', 'jupyter')],
  93. ENV_JUPYTER_PATH=[pjoin(td.name, 'env', 'share', 'jupyter')],
  94. SYSTEM_CONFIG_PATH=[pjoin(td.name, 'etc', 'jupyter')],
  95. ENV_CONFIG_PATH=[pjoin(td.name, 'env', 'etc', 'jupyter')],
  96. )
  97. self.path_patch.start()
  98. def stop(self):
  99. self.env_patch.stop()
  100. self.path_patch.stop()
  101. try:
  102. self.test_dir.cleanup()
  103. except PermissionError as e:
  104. pass
  105. def __enter__(self):
  106. self.start()
  107. return self.test_dir.name
  108. def __exit__(self, *exc_info):
  109. self.stop()
  110. class ProcessTestApp(ProcessApp):
  111. """A process app for running tests, includes a mock contents directory.
  112. """
  113. allow_origin = Unicode('*')
  114. notebook_dir = Unicode(_create_notebook_dir())
  115. schemas_dir = Unicode(_create_schemas_dir())
  116. user_settings_dir = Unicode(_create_user_settings_dir())
  117. workspaces_dir = Unicode(_create_workspaces_dir())
  118. def __init__(self):
  119. self.env_patch = _test_env()
  120. self.env_patch.start()
  121. ProcessApp.__init__(self)
  122. def init_server_extensions(self):
  123. """Disable server extensions"""
  124. pass
  125. def start(self):
  126. _install_kernels()
  127. self.kernel_manager.default_kernel_name = 'echo'
  128. self.lab_config.schemas_dir = self.schemas_dir
  129. self.lab_config.user_settings_dir = self.user_settings_dir
  130. self.lab_config.workspaces_dir = self.workspaces_dir
  131. ProcessApp.start(self)
  132. def _process_finished(self, future):
  133. self.http_server.stop()
  134. self.io_loop.stop()
  135. self.env_patch.stop()
  136. try:
  137. os._exit(future.result())
  138. except Exception as e:
  139. self.log.error(str(e))
  140. os._exit(1)
  141. class KarmaTestApp(ProcessTestApp):
  142. """A notebook app that runs the jupyterlab karma tests.
  143. """
  144. karma_pattern = Unicode('src/*.spec.ts*')
  145. karma_base_dir = Unicode('')
  146. def get_command(self):
  147. """Get the command to run."""
  148. terminalsAvailable = self.web_app.settings['terminals_available']
  149. # Compatibility with Notebook 4.2.
  150. token = getattr(self, 'token', '')
  151. config = dict(baseUrl=self.connection_url, token=token,
  152. terminalsAvailable=str(terminalsAvailable),
  153. foo='bar')
  154. cwd = self.karma_base_dir
  155. karma_inject_file = pjoin(cwd, 'build', 'injector.js')
  156. if not os.path.exists(pjoin(cwd, 'build')):
  157. os.makedirs(pjoin(cwd, 'build'))
  158. with open(karma_inject_file, 'w') as fid:
  159. fid.write("""
  160. require('es6-promise/dist/es6-promise.js');
  161. require('@phosphor/widgets/style/index.css');
  162. var node = document.createElement('script');
  163. node.id = 'jupyter-config-data';
  164. node.type = 'application/json';
  165. node.textContent = '%s';
  166. document.body.appendChild(node);
  167. """ % json.dumps(config))
  168. # validate the pattern
  169. parser = argparse.ArgumentParser()
  170. parser.add_argument('--pattern', action='store')
  171. args, argv = parser.parse_known_args()
  172. pattern = args.pattern or self.karma_pattern
  173. files = glob.glob(pjoin(cwd, pattern))
  174. if not files:
  175. msg = 'No files matching "%s" found in "%s"'
  176. raise ValueError(msg % (pattern, cwd))
  177. # Find and validate the coverage folder
  178. with open(pjoin(cwd, 'package.json')) as fid:
  179. data = json.load(fid)
  180. name = data['name'].replace('@jupyterlab/test-', '')
  181. folder = osp.realpath(pjoin(HERE, '..', '..', 'packages', name))
  182. if not osp.exists(folder):
  183. raise ValueError(
  184. 'No source package directory found for "%s", use the pattern '
  185. '"@jupyterlab/test-<package_dir_name>"' % name
  186. )
  187. env = os.environ.copy()
  188. env['KARMA_INJECT_FILE'] = karma_inject_file
  189. env.setdefault('KARMA_FILE_PATTERN', pattern)
  190. env.setdefault('KARMA_COVER_FOLDER', folder)
  191. cwd = self.karma_base_dir
  192. cmd = ['karma', 'start'] + sys.argv[1:]
  193. return cmd, dict(env=env, cwd=cwd)
  194. def run_karma(base_dir):
  195. """Run a karma test in the given base directory.
  196. """
  197. logging.disable(logging.WARNING)
  198. app = KarmaTestApp.instance()
  199. app.karma_base_dir = base_dir
  200. app.initialize([])
  201. app.start()
  202. if __name__ == '__main__':
  203. KarmaTestApp.launch_instance()