diff --git a/appveyor.yml b/appveyor.yml index 79c6e6dddc..96226fca99 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,7 +21,7 @@ environment: platform: - x64 -build: off +build: false install: - cmd: call %CONDA_INSTALL_LOCN%\Scripts\activate.bat @@ -31,11 +31,11 @@ install: - cmd: conda config --add channels conda-forge - cmd: conda update --yes --quiet conda - cmd: conda info -a - - cmd: conda create -y -q -n test-env-%CONDA_PY% python=%CONDA_PY_SPEC% pyzmq tornado jupyter_client nbformat nbconvert ipykernel pip nose + - cmd: conda create -y -q -n test-env-%CONDA_PY% python=%CONDA_PY_SPEC% pip pyzmq tornado jupyter_client nbformat nbconvert nose - cmd: conda activate test-env-%CONDA_PY% - - cmd: pip install .[test] + - cmd: pip install -e .[test] # FIXME: Use patch for python 3.8, windows issues (https://github.com/ipython/ipykernel/pull/456) - remove once released - IF %CONDA_PY% == 38 pip install --upgrade git+https://github.com/ipython/ipykernel.git test_script: - - pytest + - pytest -s -v diff --git a/jupyter_server/pytest_plugin.py b/jupyter_server/pytest_plugin.py new file mode 100644 index 0000000000..a889c23a45 --- /dev/null +++ b/jupyter_server/pytest_plugin.py @@ -0,0 +1,216 @@ +import os +import sys +import json +import pytest +import asyncio +from binascii import hexlify + +import urllib.parse +import tornado +from tornado.escape import url_escape + +from traitlets.config import Config + +import jupyter_core.paths +from jupyter_server.extension import serverextension +from jupyter_server.serverapp import ServerApp +from jupyter_server.utils import url_path_join + +import nbformat + +# This shouldn't be needed anymore, since pytest_tornasync is found in entrypoints +pytest_plugins = "pytest_tornasync" + +# NOTE: This is a temporary fix for Windows 3.8 +# We have to override the io_loop fixture with an +# asyncio patch. This will probably be removed in +# the future. + +@pytest.fixture +def asyncio_patch(): + ServerApp()._init_asyncio_patch() + +@pytest.fixture +def io_loop(asyncio_patch): + loop = tornado.ioloop.IOLoop() + loop.make_current() + yield loop + loop.clear_current() + loop.close(all_fds=True) + + +def mkdir(tmp_path, *parts): + path = tmp_path.joinpath(*parts) + if not path.exists(): + path.mkdir(parents=True) + return path + + +config = pytest.fixture(lambda: {}) +home_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "home")) +data_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "data")) +config_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "config")) +runtime_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "runtime")) +root_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "root_dir")) +system_jupyter_path = pytest.fixture( + lambda tmp_path: mkdir(tmp_path, "share", "jupyter") +) +env_jupyter_path = pytest.fixture( + lambda tmp_path: mkdir(tmp_path, "env", "share", "jupyter") +) +system_config_path = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "etc", "jupyter")) +env_config_path = pytest.fixture( + lambda tmp_path: mkdir(tmp_path, "env", "etc", "jupyter") +) +argv = pytest.fixture(lambda: []) + + +@pytest.fixture +def environ( + monkeypatch, + tmp_path, + home_dir, + data_dir, + config_dir, + runtime_dir, + root_dir, + system_jupyter_path, + system_config_path, + env_jupyter_path, + env_config_path, +): + monkeypatch.setenv("HOME", str(home_dir)) + monkeypatch.setenv("PYTHONPATH", os.pathsep.join(sys.path)) + monkeypatch.setenv("JUPYTER_NO_CONFIG", "1") + monkeypatch.setenv("JUPYTER_CONFIG_DIR", str(config_dir)) + monkeypatch.setenv("JUPYTER_DATA_DIR", str(data_dir)) + monkeypatch.setenv("JUPYTER_RUNTIME_DIR", str(runtime_dir)) + monkeypatch.setattr( + jupyter_core.paths, "SYSTEM_JUPYTER_PATH", [str(system_jupyter_path)] + ) + monkeypatch.setattr(jupyter_core.paths, "ENV_JUPYTER_PATH", [str(env_jupyter_path)]) + monkeypatch.setattr( + jupyter_core.paths, "SYSTEM_CONFIG_PATH", [str(system_config_path)] + ) + monkeypatch.setattr(jupyter_core.paths, "ENV_CONFIG_PATH", [str(env_config_path)]) + + +@pytest.fixture +def extension_environ(env_config_path, monkeypatch): + """Monkeypatch a Jupyter Extension's config path into each test's environment variable""" + monkeypatch.setattr(serverextension, "ENV_CONFIG_PATH", [str(env_config_path)]) + monkeypatch.setattr(serverextension, "ENV_CONFIG_PATH", [str(env_config_path)]) + + +@pytest.fixture +def configurable_serverapp( + environ, http_port, tmp_path, home_dir, data_dir, config_dir, runtime_dir, root_dir, io_loop +): + def serverapp( + config={}, + argv=[], + environ=environ, + http_port=http_port, + tmp_path=tmp_path, + home_dir=home_dir, + data_dir=data_dir, + config_dir=config_dir, + runtime_dir=runtime_dir, + root_dir=root_dir, + **kwargs + ): + c = Config(config) + c.NotebookNotary.db_file = ":memory:" + token = hexlify(os.urandom(4)).decode("ascii") + url_prefix = "/" + app = ServerApp.instance( + port=http_port, + port_retries=0, + open_browser=False, + config_dir=str(config_dir), + data_dir=str(data_dir), + runtime_dir=str(runtime_dir), + root_dir=str(root_dir), + base_url=url_prefix, + config=c, + allow_root=True, + token=token, + **kwargs + ) + app.init_signal = lambda: None + app.log.propagate = True + app.log.handlers = [] + # Initialize app without httpserver + app.initialize(argv=argv, new_httpserver=False) + app.log.propagate = True + app.log.handlers = [] + # Start app without ioloop + app.start_app() + return app + + yield serverapp + ServerApp.clear_instance() + + +@pytest.fixture +def serverapp(configurable_serverapp, config, argv): + app = configurable_serverapp(config=config, argv=argv) + yield app + app.remove_server_info_file() + app.remove_browser_open_file() + app.cleanup_kernels() + + +@pytest.fixture +def app(serverapp): + return serverapp.web_app + + +@pytest.fixture +def auth_header(serverapp): + return {"Authorization": "token {token}".format(token=serverapp.token)} + + +@pytest.fixture +def http_port(http_server_port): + return http_server_port[-1] + + +@pytest.fixture +def base_url(http_server_port): + return "/" + + +@pytest.fixture +def fetch(http_server_client, auth_header, base_url): + """fetch fixture that handles auth, base_url, and path""" + def client_fetch(*parts, headers={}, params={}, **kwargs): + # Handle URL strings + path_url = url_escape(url_path_join(base_url, *parts), plus=False) + params_url = urllib.parse.urlencode(params) + url = path_url + "?" + params_url + # Add auth keys to header + headers.update(auth_header) + # Make request. + return http_server_client.fetch( + url, headers=headers, request_timeout=20, **kwargs + ) + return client_fetch + + +@pytest.fixture +def create_notebook(root_dir): + """Create a notebook in the test's home directory.""" + def inner(nbpath): + nbpath = root_dir.joinpath(nbpath) + # Check that the notebook has the correct file extension. + if nbpath.suffix != '.ipynb': + raise Exception("File extension for notebook must be .ipynb") + # If the notebook path has a parent directory, make sure it's created. + parent = nbpath.parent + parent.mkdir(parents=True, exist_ok=True) + # Create a notebook string and write to file. + nb = nbformat.v4.new_notebook() + nbtext = nbformat.writes(nb, version=4) + nbpath.write_text(nbtext) + return inner diff --git a/setup.py b/setup.py index 1e5d636f9f..a30ce3a886 100755 --- a/setup.py +++ b/setup.py @@ -100,6 +100,9 @@ 'console_scripts': [ 'jupyter-server = jupyter_server.serverapp:main', 'jupyter-bundlerextension = jupyter_server.bundler.bundlerextensions:main', + ], + 'pytest11': [ + 'pytest_jupyter_server = jupyter_server.pytest_plugin' ] }, ) diff --git a/tests/conftest.py b/tests/conftest.py index 225fc8907b..9d694425b2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,207 +1 @@ -import os -import sys -import json -import pytest -import asyncio -from binascii import hexlify - -import urllib.parse -import tornado -from tornado.escape import url_escape - -from traitlets.config import Config - -import jupyter_core.paths -import jupyter_server.extension.serverextension -from jupyter_server.serverapp import ServerApp -from jupyter_server.utils import url_path_join - - -pytest_plugins = ("pytest_tornasync") - - -# NOTE: This is a temporary fix for Windows 3.8 -# We have to override the io_loop fixture with an -# asyncio patch. This will probably be removed in -# the future. -if sys.platform.startswith("win") and sys.version_info >= (3, 8): - - @pytest.fixture - def asyncio_patch(): - ServerApp()._init_asyncio_patch() - - @pytest.fixture - def io_loop(asyncio_patch): - loop = tornado.ioloop.IOLoop() - loop.make_current() - yield loop - loop.clear_current() - loop.close(all_fds=True) - - -def mkdir(tmp_path, *parts): - path = tmp_path.joinpath(*parts) - if not path.exists(): - path.mkdir(parents=True) - return path - - -def expected_http_error(error, expected_code, expected_message=None): - """Check that the error matches the expected output error.""" - e = error.value - if isinstance(e, tornado.web.HTTPError): - if expected_code != e.status_code: - return False - if expected_message is not None and expected_message != str(e): - return False - return True - elif any([ - isinstance(e, tornado.httpclient.HTTPClientError), - isinstance(e, tornado.httpclient.HTTPError) - ]): - if expected_code != e.code: - return False - if expected_message: - message = json.loads(e.response.body.decode())['message'] - if expected_message != message: - return False - return True - - -config = pytest.fixture(lambda: {}) -home_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'home')) -data_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'data')) -config_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'config')) -runtime_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'runtime')) -root_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'root_dir')) -system_jupyter_path = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'share', 'jupyter')) -env_jupyter_path = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'env', 'share', 'jupyter')) -system_config_path = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'etc', 'jupyter')) -env_config_path = pytest.fixture(lambda tmp_path: mkdir(tmp_path, 'env', 'etc', 'jupyter')) -argv = pytest.fixture(lambda: []) - -@pytest.fixture -def environ( - monkeypatch, - tmp_path, - home_dir, - data_dir, - config_dir, - runtime_dir, - root_dir, - system_jupyter_path, - system_config_path, - env_jupyter_path, - env_config_path - ): - monkeypatch.setenv('HOME', str(home_dir)) - monkeypatch.setenv('PYTHONPATH', os.pathsep.join(sys.path)) - monkeypatch.setenv('JUPYTER_NO_CONFIG', '1') - monkeypatch.setenv('JUPYTER_CONFIG_DIR', str(config_dir)) - monkeypatch.setenv('JUPYTER_DATA_DIR', str(data_dir)) - monkeypatch.setenv('JUPYTER_RUNTIME_DIR', str(runtime_dir)) - monkeypatch.setattr(jupyter_core.paths, 'SYSTEM_JUPYTER_PATH', [str(system_jupyter_path)]) - monkeypatch.setattr(jupyter_core.paths, 'ENV_JUPYTER_PATH', [str(env_jupyter_path)]) - monkeypatch.setattr(jupyter_core.paths, 'SYSTEM_CONFIG_PATH', [str(system_config_path)]) - monkeypatch.setattr(jupyter_core.paths, 'ENV_CONFIG_PATH', [str(env_config_path)]) - - -@pytest.fixture -def configurable_serverapp( - environ, - http_port, - tmp_path, - home_dir, - data_dir, - config_dir, - runtime_dir, - root_dir - ): - - def serverapp( - config={}, - argv=[], - environ=environ, - http_port=http_port, - tmp_path=tmp_path, - home_dir=home_dir, - data_dir=data_dir, - config_dir=config_dir, - runtime_dir=runtime_dir, - root_dir=root_dir, - **kwargs): - c = Config(config) - c.NotebookNotary.db_file = ':memory:' - token = hexlify(os.urandom(4)).decode('ascii') - url_prefix = '/' - app = ServerApp.instance( - port=http_port, - port_retries=0, - open_browser=False, - config_dir=str(config_dir), - data_dir=str(data_dir), - runtime_dir=str(runtime_dir), - root_dir=str(root_dir), - base_url=url_prefix, - config=c, - allow_root=True, - token=token, - **kwargs - ) - app.init_signal = lambda : None - app.log.propagate = True - app.log.handlers = [] - # Initialize app without httpserver - app.initialize(argv=argv, new_httpserver=False) - app.log.propagate = True - app.log.handlers = [] - # Start app without ioloop - app.start_app() - return app - - yield serverapp - ServerApp.clear_instance() - - -@pytest.fixture -def serverapp(configurable_serverapp, config, argv): - app = configurable_serverapp(config=config, argv=argv) - yield app - app.remove_server_info_file() - app.remove_browser_open_file() - app.cleanup_kernels() - - -@pytest.fixture -def app(serverapp): - return serverapp.web_app - - -@pytest.fixture -def auth_header(serverapp): - return {'Authorization': 'token {token}'.format(token=serverapp.token)} - - -@pytest.fixture -def http_port(http_server_port): - return http_server_port[-1] - - -@pytest.fixture -def base_url(http_server_port): - return '/' - - -@pytest.fixture -def fetch(http_server_client, auth_header, base_url): - """fetch fixture that handles auth, base_url, and path""" - def client_fetch(*parts, headers={}, params={}, **kwargs): - # Handle URL strings - path_url = url_escape(url_path_join(base_url, *parts), plus=False) - params_url = urllib.parse.urlencode(params) - url = path_url + "?" + params_url - # Add auth keys to header - headers.update(auth_header) - # Make request. - return http_server_client.fetch(url, headers=headers, request_timeout=20, **kwargs) - return client_fetch \ No newline at end of file +pytest_plugins = ['pytest_jupyter_server'] \ No newline at end of file diff --git a/tests/extension/conftest.py b/tests/extension/conftest.py index 1dbf2170d2..8505ac0220 100644 --- a/tests/extension/conftest.py +++ b/tests/extension/conftest.py @@ -34,21 +34,17 @@ def _jupyter_server_extension_paths(): }] -@pytest.fixture -def extension_environ(env_config_path, monkeypatch): - monkeypatch.setattr(serverextension, 'ENV_CONFIG_PATH', [str(env_config_path)]) - monkeypatch.setattr(serverextension, 'ENV_CONFIG_PATH', [str(env_config_path)]) - - @pytest.fixture def config_file(config_dir): - f = config_dir.joinpath('jupyter_mockextension_config.py') + """""" + f = config_dir.joinpath("jupyter_mockextension_config.py") f.write_text("c.MockExtensionApp.mock_trait ='config from file'") return f @pytest.fixture def extended_serverapp(serverapp): + """""" m = MockExtensionApp() m.initialize(serverapp) return m @@ -56,7 +52,12 @@ def extended_serverapp(serverapp): @pytest.fixture def inject_mock_extension(environ, extension_environ): - def ext(modulename='mockextension'): + """Fixture that can be used to inject a mock Jupyter Server extension into the tests namespace. + + Usage: inject_mock_extension({'extension_name': ExtensionClass}) + """ + def ext(modulename="mockextension"): sys.modules[modulename] = e = MockExtensionApp() return e + return ext diff --git a/tests/extension/test_entrypoint.py b/tests/extension/test_entrypoint.py index e07bb00c4f..f79fe5471d 100644 --- a/tests/extension/test_entrypoint.py +++ b/tests/extension/test_entrypoint.py @@ -3,6 +3,8 @@ from jupyter_core import paths from jupyter_server.extension import serverextension +from .conftest import MockExtensionApp + # All test coroutines will be treated as marked. pytestmark = pytest.mark.script_launch_mode('subprocess') @@ -15,9 +17,10 @@ def test_server_extension_list(environ, script_runner): def test_server_extension_enable(environ, inject_mock_extension, script_runner): # 'mock' is not a valid extension The entry point should complete # but print to sterr. + extension_name = "mockextension" inject_mock_extension() - extension_name = 'mockextension' - ret = script_runner.run('jupyter', 'server', 'extension', 'enable', extension_name) + + ret = script_runner.run("jupyter", "server", "extension", "enable", extension_name) assert ret.success assert 'Enabling: {}'.format(extension_name) in ret.stderr diff --git a/tests/extension/test_serverextension.py b/tests/extension/test_serverextension.py index 3c71eb694f..3ff969d82c 100644 --- a/tests/extension/test_serverextension.py +++ b/tests/extension/test_serverextension.py @@ -6,13 +6,13 @@ from traitlets.tests.utils import check_help_all_output -from ..conftest import mkdir +from ..utils import mkdir from jupyter_server.serverapp import ServerApp from jupyter_server.extension import serverextension from jupyter_server.extension.serverextension import ( validate_server_extension, - toggle_server_extension_python, + toggle_server_extension_python, _get_config_dir ) from jupyter_server.config_manager import BaseJSONConfigManager @@ -49,8 +49,8 @@ def test_disable(inject_mock_extension): def test_merge_config( - env_config_path, - inject_mock_extension, + env_config_path, + inject_mock_extension, configurable_serverapp ): # enabled at sys level @@ -79,7 +79,7 @@ def test_merge_config( # Enable the last extension, mockext_py, using the CLI interface. app = configurable_serverapp( - config_dir=str(env_config_path), + config_dir=str(env_config_path), argv=['--ServerApp.jpserver_extensions={"mockext_py":True}'] ) # Verify that extensions are enabled and merged properly. diff --git a/tests/nbconvert/test_handlers.py b/tests/nbconvert/test_handlers.py index 12a7256d26..b5ec68c681 100644 --- a/tests/nbconvert/test_handlers.py +++ b/tests/nbconvert/test_handlers.py @@ -18,7 +18,7 @@ import pytest -from ..conftest import expected_http_error +from ..utils import expected_http_error png_green_pixel = encodebytes(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00' diff --git a/tests/services/contents/test_api.py b/tests/services/contents/test_api.py index 7dbd3fd759..d99069b9c5 100644 --- a/tests/services/contents/test_api.py +++ b/tests/services/contents/test_api.py @@ -15,7 +15,7 @@ from base64 import encodebytes, decodebytes -from ...conftest import expected_http_error +from ...utils import expected_http_error def notebooks_only(dir_model): @@ -118,7 +118,7 @@ async def test_get_nb_contents(fetch, contents, path, name): r = await fetch( 'api', 'contents', nbpath, method='GET', - params=dict(content='1') + params=dict(content='1') ) model = json.loads(r.body.decode()) assert model['name'] == nbname @@ -137,7 +137,7 @@ async def test_get_nb_no_contents(fetch, contents, path, name): r = await fetch( 'api', 'contents', nbpath, method='GET', - params=dict(content='0') + params=dict(content='0') ) model = json.loads(r.body.decode()) assert model['name'] == nbname @@ -186,7 +186,7 @@ async def test_get_text_file_contents(fetch, contents, path, name): r = await fetch( 'api', 'contents', txtpath, method='GET', - params=dict(content='1') + params=dict(content='1') ) model = json.loads(r.body.decode()) assert model['name'] == txtname @@ -223,7 +223,7 @@ async def test_get_binary_file_contents(fetch, contents, path, name): r = await fetch( 'api', 'contents', blobpath, method='GET', - params=dict(content='1') + params=dict(content='1') ) model = json.loads(r.body.decode()) assert model['name'] == blobname @@ -285,7 +285,7 @@ async def test_create_untitled(fetch, contents, contents_dir): path = 'å b' name = 'Untitled.ipynb' r = await fetch( - 'api', 'contents', path, + 'api', 'contents', path, method='POST', body=json.dumps({'ext': '.ipynb'}) ) @@ -293,7 +293,7 @@ async def test_create_untitled(fetch, contents, contents_dir): name = 'Untitled1.ipynb' r = await fetch( - 'api', 'contents', path, + 'api', 'contents', path, method='POST', body=json.dumps({'ext': '.ipynb'}) ) @@ -302,7 +302,7 @@ async def test_create_untitled(fetch, contents, contents_dir): path = 'foo/bar' name = 'Untitled.ipynb' r = await fetch( - 'api', 'contents', path, + 'api', 'contents', path, method='POST', body=json.dumps({'ext': '.ipynb'}) ) @@ -313,7 +313,7 @@ async def test_create_untitled_txt(fetch, contents, contents_dir): name = 'untitled.txt' path = 'foo/bar' r = await fetch( - 'api', 'contents', path, + 'api', 'contents', path, method='POST', body=json.dumps({'ext': '.txt'}) ) @@ -456,7 +456,7 @@ async def test_copy(fetch, contents, contents_dir): body=json.dumps({'copy_from': path+'/'+name}) ) _check_created(r, str(contents_dir), path, copy, type='notebook') - + # Copy the same file name copy2 = 'ç d-Copy2.ipynb' r = await fetch( @@ -601,14 +601,14 @@ async def test_rename(fetch, contents, contents_dir): async def test_checkpoints_follow_file(fetch, contents): path = 'foo' name = 'a.ipynb' - + # Read initial file. r = await fetch( 'api', 'contents', path, name, method='GET' ) model = json.loads(r.body.decode()) - + # Create a checkpoint of initial state r = await fetch( 'api', 'contents', path, name, 'checkpoints', @@ -616,7 +616,7 @@ async def test_checkpoints_follow_file(fetch, contents): allow_nonstandard_methods=True ) cp1 = json.loads(r.body.decode()) - + # Modify file and save. nbcontent = model['content'] nb = from_dict(nbcontent) @@ -690,7 +690,7 @@ async def test_checkpoints(fetch, contents): path = 'foo/a.ipynb' resp = await fetch( 'api', 'contents', path, - method='GET' + method='GET' ) model = json.loads(resp.body.decode()) r = await fetch( @@ -708,7 +708,7 @@ async def test_checkpoints(fetch, contents): nb = from_dict(nbcontent) hcell = new_markdown_cell('Created by test') nb.cells.append(hcell) - + # Save it. nbmodel = {'content': nb, 'type': 'notebook'} resp = await fetch( @@ -716,7 +716,7 @@ async def test_checkpoints(fetch, contents): method='PUT', body=json.dumps(nbmodel) ) - + # List checkpoints r = await fetch( 'api', 'contents', path, 'checkpoints', @@ -727,7 +727,7 @@ async def test_checkpoints(fetch, contents): r = await fetch( 'api', 'contents', path, - method='GET' + method='GET' ) nbcontent = json.loads(r.body.decode())['content'] nb = from_dict(nbcontent) @@ -768,7 +768,7 @@ async def test_file_checkpoints(fetch, contents): path = 'foo/a.txt' resp = await fetch( 'api', 'contents', path, - method='GET' + method='GET' ) orig_content = json.loads(resp.body.decode())['content'] r = await fetch( @@ -788,14 +788,14 @@ async def test_file_checkpoints(fetch, contents): 'type': 'file', 'format': 'text', } - + # Save it. resp = await fetch( 'api', 'contents', path, method='PUT', body=json.dumps(model) ) - + # List checkpoints r = await fetch( 'api', 'contents', path, 'checkpoints', @@ -806,7 +806,7 @@ async def test_file_checkpoints(fetch, contents): r = await fetch( 'api', 'contents', path, - method='GET' + method='GET' ) content = json.loads(r.body.decode())['content'] assert content == new_content diff --git a/tests/services/contents/test_largefilemanager.py b/tests/services/contents/test_largefilemanager.py index bf4e3d24c9..fcacaf2729 100644 --- a/tests/services/contents/test_largefilemanager.py +++ b/tests/services/contents/test_largefilemanager.py @@ -2,7 +2,7 @@ import tornado from jupyter_server.services.contents.largefilemanager import LargeFileManager -from ...conftest import expected_http_error +from ...utils import expected_http_error contents_manager = pytest.fixture(lambda tmp_path: LargeFileManager(root_dir=str(tmp_path))) @@ -28,7 +28,7 @@ def test_save(contents_manager): 'model,err_message', [ ( - {'name': 'test', 'path': 'test', 'chunk': 1}, + {'name': 'test', 'path': 'test', 'chunk': 1}, 'HTTP 400: Bad Request (No file type provided)' ), ( @@ -40,7 +40,7 @@ def test_save(contents_manager): 'HTTP 400: Bad Request (No file content provided)', ), ( - {'name': 'test', 'path': 'test', 'chunk': 2, 'type': 'file', + {'name': 'test', 'path': 'test', 'chunk': 2, 'type': 'file', 'content': u'test', 'format': 'json'}, "HTTP 400: Bad Request (Must specify format of file contents as 'text' or 'base64')" ) diff --git a/tests/services/contents/test_manager.py b/tests/services/contents/test_manager.py index 526e53050e..ef6f29d6cf 100644 --- a/tests/services/contents/test_manager.py +++ b/tests/services/contents/test_manager.py @@ -11,7 +11,7 @@ from nbformat import v4 as nbformat from jupyter_server.services.contents.filemanager import FileContentsManager -from ...conftest import expected_http_error +from ...utils import expected_http_error # -------------- Functions ---------------------------- @@ -141,7 +141,7 @@ def test_checkpoint_subdir(tmp_path): @pytest.mark.skipif( - sys.platform == 'win32' and sys.version_info[0] < 3, + sys.platform == 'win32' and sys.version_info[0] < 3, reason="System platform is Windows, version < 3" ) def test_bad_symlink(tmp_path): @@ -166,7 +166,7 @@ def test_bad_symlink(tmp_path): @pytest.mark.skipif( - sys.platform == 'win32' and sys.version_info[0] < 3, + sys.platform == 'win32' and sys.version_info[0] < 3, reason="System platform is Windows, version < 3" ) def test_good_symlink(tmp_path): @@ -354,7 +354,7 @@ def test_get(contents_manager): for key, value in expected_model.items(): assert file_model[key] == value assert 'created' in file_model - assert 'last_modified' in file_model + assert 'last_modified' in file_model # Create a sub-sub directory to test getting directory contents with a # subdir. @@ -516,7 +516,7 @@ def test_rename(contents_manager): # Creating a notebook in a non_existant directory should fail with pytest.raises(HTTPError) as e: cm.new_untitled("foo/bar_diff", ext=".ipynb") - assert expected_http_error(e, 404) + assert expected_http_error(e, 404) cm.rename("foo/bar", "foo/bar_diff") diff --git a/tests/services/kernels/test_api.py b/tests/services/kernels/test_api.py index cfdc6b80c1..7c1a123033 100644 --- a/tests/services/kernels/test_api.py +++ b/tests/services/kernels/test_api.py @@ -10,7 +10,7 @@ from jupyter_client.kernelspec import NATIVE_KERNEL_NAME from jupyter_server.utils import url_path_join -from ...conftest import expected_http_error +from ...utils import expected_http_error @pytest.fixture @@ -29,7 +29,7 @@ def client_fetch(*parts, headers={}, params={}, **kwargs): headers.update(auth_header) # Make request. req = tornado.httpclient.HTTPRequest( - url, + url, headers=auth_header, connect_timeout=120 ) @@ -204,7 +204,7 @@ async def test_connection(fetch, ws_fetch, http_port, auth_header): }) ) kid = json.loads(r.body.decode())['id'] - + # Get kernel info r = await fetch( 'api', 'kernels', kid, @@ -218,7 +218,7 @@ async def test_connection(fetch, ws_fetch, http_port, auth_header): ws = await ws_fetch( 'api', 'kernels', kid, 'channels' ) - + # Test that it was opened. r = await fetch( 'api', 'kernels', kid, @@ -240,7 +240,7 @@ async def test_connection(fetch, ws_fetch, http_port, auth_header): time.sleep(0.1) else: break - + r = await fetch( 'api', 'kernels', kid, method='GET' diff --git a/tests/services/kernelspecs/test_api.py b/tests/services/kernelspecs/test_api.py index 0d3a2ba387..3768a64d45 100644 --- a/tests/services/kernelspecs/test_api.py +++ b/tests/services/kernelspecs/test_api.py @@ -5,7 +5,7 @@ from jupyter_client.kernelspec import NATIVE_KERNEL_NAME -from ...conftest import expected_http_error +from ...utils import expected_http_error sample_kernel_json = { diff --git a/tests/services/sessions/test_api.py b/tests/services/sessions/test_api.py index 31b9ef5d87..2d9bca0124 100644 --- a/tests/services/sessions/test_api.py +++ b/tests/services/sessions/test_api.py @@ -8,7 +8,7 @@ from nbformat.v4 import new_notebook from nbformat import writes -from ...conftest import expected_http_error +from ...utils import expected_http_error j = lambda r: json.loads(r.body.decode()) @@ -37,9 +37,9 @@ async def get(self, id): return await self._req(id, method='GET') async def create( - self, - path, - type='notebook', + self, + path, + type='notebook', kernel_name='python', kernel_id=None): body = { @@ -114,7 +114,7 @@ def session_client(root_dir, fetch): # Remove subdir shutil.rmtree(str(subdir), ignore_errors=True) - + async def test_create(session_client): # Make sure no sessions exist. @@ -170,7 +170,7 @@ async def test_create_deprecated(session_client): assert resp.code == 201 newsession = j(resp) assert newsession['path'] == 'foo/nb1.ipynb' - assert newsession['type'] == 'notebook' + assert newsession['type'] == 'notebook' assert newsession['notebook']['path'] == 'foo/nb1.ipynb' # Need to find a better solution to this. await session_client.cleanup() diff --git a/tests/test_files.py b/tests/test_files.py index 0185c0e2fa..e65dc2da93 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -2,7 +2,7 @@ import pytest import tornado -from .conftest import expected_http_error +from .utils import expected_http_error from nbformat import writes from nbformat.v4 import (new_notebook, @@ -112,7 +112,7 @@ async def test_contents_manager(fetch, serverapp, root_dir): assert r.headers['content-type'] == 'application/octet-stream' assert r.body[:1] == b'\xff' assert len(r.body) == 6 - + r = await fetch( 'files/test.txt', method='GET' diff --git a/tests/test_gateway.py b/tests/test_gateway.py index 4ad4d71a68..9084f15fb4 100644 --- a/tests/test_gateway.py +++ b/tests/test_gateway.py @@ -14,7 +14,7 @@ from unittest.mock import patch from io import StringIO -from .conftest import expected_http_error +from .utils import expected_http_error def generate_kernelspec(name): diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000000..819d62aa5e --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,31 @@ +import json +import tornado + + +def mkdir(tmp_path, *parts): + path = tmp_path.joinpath(*parts) + if not path.exists(): + path.mkdir(parents=True) + return path + + +def expected_http_error(error, expected_code, expected_message=None): + """Check that the error matches the expected output error.""" + e = error.value + if isinstance(e, tornado.web.HTTPError): + if expected_code != e.status_code: + return False + if expected_message is not None and expected_message != str(e): + return False + return True + elif any([ + isinstance(e, tornado.httpclient.HTTPClientError), + isinstance(e, tornado.httpclient.HTTPError) + ]): + if expected_code != e.code: + return False + if expected_message: + message = json.loads(e.response.body.decode())['message'] + if expected_message != message: + return False + return True