test_app.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. # Copyright (c) Jupyter Development Team.
  2. # Distributed under the terms of the Modified BSD License.
  3. """A lab app that runs a sub process for a demo or a test."""
  4. import atexit
  5. import json
  6. import os
  7. import shutil
  8. import sys
  9. import tempfile
  10. from importlib.resources import files
  11. from os import path as osp
  12. from os.path import join as pjoin
  13. from stat import S_IRGRP, S_IROTH, S_IRUSR
  14. from tempfile import TemporaryDirectory
  15. from unittest.mock import patch
  16. import jupyter_core
  17. import jupyterlab_server
  18. from ipykernel.kernelspec import write_kernel_spec
  19. from jupyter_server.serverapp import ServerApp
  20. from jupyterlab_server.process_app import ProcessApp
  21. from traitlets import default
  22. HERE = osp.realpath(osp.dirname(__file__))
  23. def _create_template_dir():
  24. template_dir = tempfile.mkdtemp(prefix="mock_static")
  25. index_filepath = osp.join(template_dir, "index.html")
  26. with open(index_filepath, "w") as fid:
  27. fid.write(
  28. """
  29. <!DOCTYPE HTML>
  30. <html>
  31. <head>
  32. <meta charset="utf-8">
  33. <title>{% block title %}Jupyter Lab Test{% endblock %}</title>
  34. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  35. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  36. {% block meta %}
  37. {% endblock %}
  38. </head>
  39. <body>
  40. <h1>JupyterLab Test Application</h1>
  41. <div id="site">
  42. {% block site %}
  43. {% endblock site %}
  44. </div>
  45. {% block after_site %}
  46. {% endblock after_site %}
  47. </body>
  48. </html>"""
  49. )
  50. return template_dir
  51. def _create_static_dir():
  52. static_dir = tempfile.mkdtemp(prefix="mock_static")
  53. return static_dir
  54. def _create_schemas_dir():
  55. """Create a temporary directory for schemas."""
  56. root_dir = tempfile.mkdtemp(prefix="mock_schemas")
  57. extension_dir = osp.join(root_dir, "@jupyterlab", "apputils-extension")
  58. os.makedirs(extension_dir)
  59. # Get schema content.
  60. schema_package = jupyterlab_server.__name__
  61. schema_path = "tests/schemas/@jupyterlab/apputils-extension/themes.json"
  62. themes = files(schema_package).joinpath(schema_path).read_bytes()
  63. with open(osp.join(extension_dir, "themes.json"), "w") as fid:
  64. fid.write(themes.decode("utf-8"))
  65. atexit.register(lambda: shutil.rmtree(root_dir, True))
  66. return root_dir
  67. def _create_user_settings_dir():
  68. """Create a temporary directory for workspaces."""
  69. root_dir = tempfile.mkdtemp(prefix="mock_user_settings")
  70. atexit.register(lambda: shutil.rmtree(root_dir, True))
  71. return root_dir
  72. def _create_workspaces_dir():
  73. """Create a temporary directory for workspaces."""
  74. root_dir = tempfile.mkdtemp(prefix="mock_workspaces")
  75. atexit.register(lambda: shutil.rmtree(root_dir, True))
  76. return root_dir
  77. class TestEnv:
  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(
  84. os.environ,
  85. {
  86. "JUPYTER_CONFIG_DIR": pjoin(td.name, "jupyter"),
  87. "JUPYTER_DATA_DIR": pjoin(td.name, "jupyter_data"),
  88. "JUPYTER_RUNTIME_DIR": pjoin(td.name, "jupyter_runtime"),
  89. "IPYTHONDIR": pjoin(td.name, "ipython"),
  90. },
  91. )
  92. self.env_patch.start()
  93. self.path_patch = patch.multiple(
  94. jupyter_core.paths,
  95. SYSTEM_JUPYTER_PATH=[pjoin(td.name, "share", "jupyter")],
  96. ENV_JUPYTER_PATH=[pjoin(td.name, "env", "share", "jupyter")],
  97. SYSTEM_CONFIG_PATH=[pjoin(td.name, "etc", "jupyter")],
  98. ENV_CONFIG_PATH=[pjoin(td.name, "env", "etc", "jupyter")],
  99. )
  100. self.path_patch.start()
  101. def stop(self):
  102. self.env_patch.stop()
  103. self.path_patch.stop()
  104. try:
  105. self.test_dir.cleanup()
  106. except OSError:
  107. pass
  108. def __enter__(self):
  109. self.start()
  110. return self.test_dir.name
  111. def __exit__(self, *exc_info):
  112. self.stop()
  113. class ProcessTestApp(ProcessApp):
  114. """A process app for running tests, includes a mock contents directory."""
  115. allow_origin = "*"
  116. def initialize_templates(self):
  117. self.static_paths = [_create_static_dir()]
  118. self.template_paths = [_create_template_dir()]
  119. def initialize_settings(self):
  120. self.env_patch = TestEnv()
  121. self.env_patch.start()
  122. ProcessApp.__init__(self)
  123. self.settings["allow_origin"] = ProcessTestApp.allow_origin
  124. self.static_dir = self.static_paths[0]
  125. self.template_dir = self.template_paths[0]
  126. self.schemas_dir = _create_schemas_dir()
  127. self.user_settings_dir = _create_user_settings_dir()
  128. self.workspaces_dir = _create_workspaces_dir()
  129. self._install_default_kernels()
  130. self.settings["kernel_manager"].default_kernel_name = "echo"
  131. super().initialize_settings()
  132. def _install_kernel(self, kernel_name, kernel_spec):
  133. """Install a kernel spec to the data directory.
  134. Parameters
  135. ----------
  136. kernel_name: str
  137. Name of the kernel.
  138. kernel_spec: dict
  139. The kernel spec for the kernel
  140. """
  141. paths = jupyter_core.paths
  142. kernel_dir = pjoin(paths.jupyter_data_dir(), "kernels", kernel_name)
  143. os.makedirs(kernel_dir)
  144. with open(pjoin(kernel_dir, "kernel.json"), "w") as f:
  145. f.write(json.dumps(kernel_spec))
  146. def _install_default_kernels(self):
  147. # Install echo and ipython kernels - should be done after env patch
  148. self._install_kernel(
  149. kernel_name="echo",
  150. kernel_spec={
  151. "argv": [
  152. sys.executable,
  153. "-m",
  154. "jupyterlab.tests.echo_kernel",
  155. "-f",
  156. "{connection_file}",
  157. ],
  158. "display_name": "Echo Kernel",
  159. "language": "echo",
  160. },
  161. )
  162. paths = jupyter_core.paths
  163. ipykernel_dir = pjoin(paths.jupyter_data_dir(), "kernels", "ipython")
  164. write_kernel_spec(ipykernel_dir)
  165. def _process_finished(self, future):
  166. self.serverapp.http_server.stop()
  167. self.serverapp.io_loop.stop()
  168. self.env_patch.stop()
  169. try:
  170. os._exit(future.result())
  171. except Exception as e:
  172. self.log.error(str(e))
  173. os._exit(1)
  174. class RootedServerApp(ServerApp):
  175. @default("root_dir")
  176. def _default_root_dir(self):
  177. """Create a temporary directory with some file structure."""
  178. root_dir = tempfile.mkdtemp(prefix="mock_root")
  179. os.mkdir(osp.join(root_dir, "src"))
  180. with open(osp.join(root_dir, "src", "temp.txt"), "w") as fid:
  181. fid.write("hello")
  182. readonly_filepath = osp.join(root_dir, "src", "readonly-temp.txt")
  183. with open(readonly_filepath, "w") as fid:
  184. fid.write("hello from a readonly file")
  185. os.chmod(readonly_filepath, S_IRUSR | S_IRGRP | S_IROTH)
  186. atexit.register(lambda: shutil.rmtree(root_dir, True))
  187. return root_dir