You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/jupyterlab/browser_check.py

191 lines
5.6 KiB

# -*- coding: utf-8 -*-
"""
This module is meant to run JupyterLab in a headless browser, making sure
the application launches and starts up without errors.
"""
import asyncio
from concurrent.futures import ThreadPoolExecutor
import inspect
import logging
from os import path as osp
import os
import shutil
import sys
import time
from tornado.ioloop import IOLoop
from tornado.iostream import StreamClosedError
from tornado.websocket import WebSocketClosedError
from notebook.notebookapp import flags, aliases
from notebook.utils import urljoin, pathname2url
from traitlets import Bool
from .labapp import LabApp, get_app_dir
from .tests.test_app import TestEnv
here = osp.abspath(osp.dirname(__file__))
test_flags = dict(flags)
test_flags['core-mode'] = (
{'BrowserApp': {'core_mode': True}},
"Start the app in core mode."
)
test_flags['dev-mode'] = (
{'BrowserApp': {'dev_mode': True}},
"Start the app in dev mode."
)
test_flags['watch'] = (
{'BrowserApp': {'watch': True}},
"Start the app in watch mode."
)
test_aliases = dict(aliases)
test_aliases['app-dir'] = 'BrowserApp.app_dir'
class LogErrorHandler(logging.Handler):
"""A handler that exits with 1 on a logged error."""
def __init__(self):
super().__init__(level=logging.ERROR)
self.errored = False
def filter(self, record):
# Handle known StreamClosedError from Tornado
# These occur when we forcibly close Websockets or
# browser connections during the test.
# https://github.com/tornadoweb/tornado/issues/2834
if hasattr(record, 'exc_info') and not record.exc_info is None and isinstance(record.exc_info[1], (StreamClosedError, WebSocketClosedError)):
return
return super().filter(record)
def emit(self, record):
print(record.msg, file=sys.stderr)
self.errored = True
def run_test(app, func):
"""Synchronous entry point to run a test function.
func is a function that accepts an app url as a parameter and returns a result.
func can be synchronous or asynchronous. If it is synchronous, it will be run
in a thread, so asynchronous is preferred.
"""
IOLoop.current().spawn_callback(run_test_async, app, func)
async def run_test_async(app, func):
"""Run a test against the application.
func is a function that accepts an app url as a parameter and returns a result.
func can be synchronous or asynchronous. If it is synchronous, it will be run
in a thread, so asynchronous is preferred.
"""
handler = LogErrorHandler()
app.log.addHandler(handler)
env_patch = TestEnv()
env_patch.start()
# The entry URL for browser tests is different in notebook >= 6.0,
# since that uses a local HTML file to point the user at the app.
if hasattr(app, 'browser_open_file'):
url = urljoin('file:', pathname2url(app.browser_open_file))
else:
url = app.display_url
# Allow a synchronous function to be passed in.
if inspect.iscoroutinefunction(func):
test = func(url)
else:
app.log.info('Using thread pool executor to run test')
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor()
task = loop.run_in_executor(executor, func, url)
test = asyncio.wait([task])
try:
await test
except Exception as e:
app.log.critical("Caught exception during the test:")
app.log.error(str(e))
app.log.info("Test Complete")
result = 0
if handler.errored:
result = 1
app.log.critical('Exiting with 1 due to errors')
else:
app.log.info('Exiting normally')
app.log.info('Stopping server...')
try:
app.http_server.stop()
app.io_loop.stop()
env_patch.stop()
except Exception as e:
self.log.error(str(e))
result = 1
finally:
time.sleep(2)
os._exit(result)
async def run_async_process(cmd, **kwargs):
"""Run an asynchronous command"""
proc = await asyncio.create_subprocess_exec(
*cmd,
**kwargs)
stdout, stderr = await proc.communicate()
if proc.returncode != 0:
raise RuntimeError(cmd + ' exited with ' + str(proc.returncode))
return stdout, stderr
async def run_browser(url):
"""Run the browser test and return an exit code.
"""
target = osp.join(get_app_dir(), 'browser_test')
if not osp.exists(osp.join(target, 'node_modules')):
os.makedirs(target)
await run_async_process(["jlpm", "init", "-y"], cwd=target)
await run_async_process(["jlpm", "add", "puppeteer@^2"], cwd=target)
shutil.copy(osp.join(here, 'chrome-test.js'), osp.join(target, 'chrome-test.js'))
await run_async_process(["node", "chrome-test.js", url], cwd=target)
class BrowserApp(LabApp):
"""An app the launches JupyterLab and waits for it to start up, checking for
JS console errors, JS errors, and Python logged errors.
"""
open_browser = Bool(False)
base_url = '/foo/'
ip = '127.0.0.1'
flags = test_flags
aliases = test_aliases
test_browser = Bool(True)
def start(self):
web_app = self.web_app
self.kernel_manager.shutdown_wait_time = 1
web_app.settings.setdefault('page_config_data', dict())
web_app.settings['page_config_data']['browserTest'] = True
web_app.settings['page_config_data']['buildAvailable'] = False
func = run_browser if self.test_browser else lambda url: 0
run_test(self, func)
super().start()
if __name__ == '__main__':
skip_option = "--no-chrome-test"
if skip_option in sys.argv:
BrowserApp.test_browser = False
sys.argv.remove(skip_option)
BrowserApp.launch_instance()