Skip to content

Commit

Permalink
Merge pull request #12 from blink1073/more-testing
Browse files Browse the repository at this point in the history
  • Loading branch information
blink1073 authored Apr 3, 2022
2 parents 77a4f85 + 65a230c commit 22d210e
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 52 deletions.
55 changes: 44 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ jobs:
- name: Install the Python dependencies
run: |
pip install -e ".[test]" codecov
- name: Point at Jupyter Server branch with terminals removed.
run: |
pip install -U git+https://github.com/Zsailer/jupyter_server.git@jupyter_server_terminals
- name: List installed packages
run: |
pip freeze
Expand All @@ -75,6 +72,42 @@ jobs:
run: |
codecov
test_minimum_versions:
name: Test Minimum Versions
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
with:
python_version: "3.7"
- name: Install miniumum versions
uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1
- name: Run the unit tests
run: |
pytest -vv -W default || pytest -vv -W default --lf
test_prereleases:
name: Test Prereleases
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
- name: Install the Python dependencies
run: |
pip install --pre -e ".[test]"
- name: List installed packages
run: |
pip freeze
pip check
- name: Run the tests
run: |
pytest -vv || pytest -vv --lf
make_sdist:
name: Make SDist
runs-on: ubuntu-latest
Expand All @@ -84,11 +117,11 @@ jobs:
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
- uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1

# test_sdist:
# runs-on: ubuntu-latest
# needs: [make_sdist]
# name: Install from SDist and Test
# timeout-minutes: 15
# steps:
# - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
# - uses: jupyterlab/maintainer-tools/.github/actions/test-sdist@v1
test_sdist:
runs-on: ubuntu-latest
needs: [make_sdist]
name: Install from SDist and Test
timeout-minutes: 15
steps:
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
- uses: jupyterlab/maintainer-tools/.github/actions/test-sdist@v1
22 changes: 22 additions & 0 deletions jupyter_server_terminals/api_handlers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from pathlib import Path

from tornado import web

Expand Down Expand Up @@ -31,6 +32,27 @@ def post(self):
"""POST /terminals creates a new terminal and redirects to it"""
data = self.get_json_body() or {}

# if cwd is a relative path, it should be relative to the root_dir,
# but if we pass it as relative, it will we be considered as relative to
# the path jupyter_server was started in
if "cwd" in data:
cwd = Path(data["cwd"])
if not cwd.resolve().exists():
cwd = Path(self.settings["server_root_dir"]).expanduser() / cwd
if not cwd.resolve().exists():
cwd = None

if cwd is None:
server_root_dir = self.settings["server_root_dir"]
self.log.debug(
f"Failed to find requested terminal cwd: {data.get('cwd')}\n"
f" It was not found within the server root neither: {server_root_dir}."
)
del data["cwd"]
else:
self.log.debug(f"Opening terminal in: {cwd.resolve()!s}")
data["cwd"] = str(cwd.resolve())

model = self.terminal_manager.create(**data)
self.finish(json.dumps(model))

Expand Down
5 changes: 4 additions & 1 deletion jupyter_server_terminals/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def initialize_handlers(self):
)
self.handlers.extend(api_handlers.default_handlers)
self.serverapp.web_app.settings["terminal_manager"] = self.terminal_manager
self.serverapp.web_app.settings["terminals_available"] = self.settings[
"terminals_available"
]

def current_activity(self):
if self.terminals_available:
Expand All @@ -89,7 +92,7 @@ async def cleanup_terminals(self):
if not self.terminals_available:
return

terminal_manager = self.web_app.settings["terminal_manager"]
terminal_manager = self.terminal_manager
n_terminals = len(terminal_manager.list())
terminal_msg = trans.ngettext(
"Shutting down %d terminal", "Shutting down %d terminals", n_terminals
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test =
pytest_tornasync
pytest-cov
pytest-timeout
jupyter_server[test]>=1
jupyter_server[test] @ git+https://github.com/Zsailer/jupyter_server.git@jupyter_server_terminals

[options.packages.find]
exclude = ['docs*', 'tests*']
Expand Down
43 changes: 14 additions & 29 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""Tests for authorization"""
import asyncio

import pytest
from jupyter_client.kernelspec import NATIVE_KERNEL_NAME
from jupyter_server.auth.authorizer import Authorizer
from jupyter_server.auth.utils import HTTP_METHOD_TO_AUTH_ACTION, match_url_to_resource
from nbformat import writes
from nbformat.v4 import new_notebook
from tornado.httpclient import HTTPClientError
from tornado.websocket import WebSocketHandler
from traitlets.config import Config
Expand Down Expand Up @@ -123,37 +121,19 @@ async def test_authorized_requests(
request,
io_loop,
send_request,
tmp_path,
jp_serverapp,
jp_cleanup_subprocesses,
method,
url,
body,
allowed,
):
# Setup stuff for the Contents API
# Add a notebook on disk
contents_dir = tmp_path / jp_serverapp.root_dir
p = contents_dir / "dir_for_testing"
p.mkdir(parents=True, exist_ok=True)

# Create a notebook
nb = writes(new_notebook(), version=4)
nbname = p.joinpath("nb_for_testing.ipynb")
nbname.write_text(nb, encoding="utf-8")

# Setup
nbpath = "dir_for_testing/nb_for_testing.ipynb"
kernelspec = NATIVE_KERNEL_NAME
km = jp_serverapp.kernel_manager

if "terminal" in url:
term_manager = jp_serverapp.web_app.settings["terminal_manager"]
request.addfinalizer(lambda: io_loop.run_sync(term_manager.terminate_all))
term_model = term_manager.create()
term_name = term_model["name"]

url = url.format(**locals())
term_manager = jp_serverapp.web_app.settings["terminal_manager"]
request.addfinalizer(lambda: io_loop.run_sync(term_manager.terminate_all))
term_model = term_manager.create()
term_name = term_model["name"]

url = url.format(term_name=term_name)
if allowed:
# Create a server with full permissions
permissions = {
Expand All @@ -168,7 +148,12 @@ async def test_authorized_requests(
expected_codes = {403}
jp_serverapp.authorizer.permissions = permissions

code = await send_request(url, body=body, method=method)
assert code in expected_codes
while True:
code = await send_request(url, body=body, method=method)
if code == 404:
await asyncio.sleep(1)
continue
assert code in expected_codes
break

await jp_cleanup_subprocesses()
114 changes: 104 additions & 10 deletions tests/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import json
import os
import shutil
import time
import sys

import pytest
from tornado.httpclient import HTTPClientError
Expand All @@ -19,6 +19,16 @@ def terminal_path(tmp_path):
shutil.rmtree(str(subdir), ignore_errors=True)


@pytest.fixture
def terminal_root_dir(jp_root_dir):
subdir = jp_root_dir.joinpath("terminal_path")
subdir.mkdir()

yield subdir

shutil.rmtree(str(subdir), ignore_errors=True)


CULL_TIMEOUT = 10
CULL_INTERVAL = 3

Expand All @@ -38,7 +48,7 @@ def jp_server_config():
)


async def test_no_terminals(jp_fetch, jp_server_config):
async def test_no_terminals(jp_fetch):
resp_list = await jp_fetch(
"api",
"terminals",
Expand All @@ -51,7 +61,7 @@ async def test_no_terminals(jp_fetch, jp_server_config):
assert len(data) == 0


async def test_terminal_create(jp_server_config, jp_fetch, jp_cleanup_subprocesses):
async def test_terminal_create(jp_fetch, jp_cleanup_subprocesses):
resp = await jp_fetch(
"api",
"terminals",
Expand All @@ -77,9 +87,7 @@ async def test_terminal_create(jp_server_config, jp_fetch, jp_cleanup_subprocess
await jp_cleanup_subprocesses()


async def test_terminal_create_with_kwargs(
jp_server_config, jp_fetch, terminal_path, jp_cleanup_subprocesses
):
async def test_terminal_create_with_kwargs(jp_fetch, terminal_path, jp_cleanup_subprocesses):
resp_create = await jp_fetch(
"api",
"terminals",
Expand All @@ -106,7 +114,7 @@ async def test_terminal_create_with_kwargs(


async def test_terminal_create_with_cwd(
jp_server_config, jp_fetch, jp_ws_fetch, terminal_path, jp_cleanup_subprocesses
jp_fetch, jp_ws_fetch, terminal_path, jp_cleanup_subprocesses
):
resp = await jp_fetch(
"api",
Expand All @@ -126,7 +134,7 @@ async def test_terminal_create_with_cwd(
except HTTPClientError as e:
if e.code != 404:
raise
time.sleep(1)
await asyncio.sleep(1)

ws.write_message(json.dumps(["stdin", "pwd\r\n"]))

Expand All @@ -148,7 +156,93 @@ async def test_terminal_create_with_cwd(
await jp_cleanup_subprocesses()


async def test_culling_config(jp_server_config, jp_configurable_serverapp):
async def test_terminal_create_with_relative_cwd(
jp_fetch, jp_ws_fetch, jp_root_dir, terminal_root_dir, jp_cleanup_subprocesses
):
resp = await jp_fetch(
"api",
"terminals",
method="POST",
body=json.dumps({"cwd": str(terminal_root_dir.relative_to(jp_root_dir))}),
allow_nonstandard_methods=True,
)

data = json.loads(resp.body.decode())
term_name = data["name"]

while True:
try:
ws = await jp_ws_fetch("terminals", "websocket", term_name)
break
except HTTPClientError as e:
if e.code != 404:
raise
await asyncio.sleep(1)

ws.write_message(json.dumps(["stdin", "pwd\r\n"]))

message_stdout = ""
while True:
try:
message = await asyncio.wait_for(ws.read_message(), timeout=5.0)
except asyncio.TimeoutError:
break

message = json.loads(message)

if message[0] == "stdout":
message_stdout += message[1]

ws.close()

expected = terminal_root_dir.name if sys.platform == "win32" else str(terminal_root_dir)
assert expected in message_stdout
await jp_cleanup_subprocesses()


async def test_terminal_create_with_bad_cwd(jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses):
non_existing_path = "/tmp/path/to/nowhere"
resp = await jp_fetch(
"api",
"terminals",
method="POST",
body=json.dumps({"cwd": non_existing_path}),
allow_nonstandard_methods=True,
)

data = json.loads(resp.body.decode())
term_name = data["name"]

while True:
try:
ws = await jp_ws_fetch("terminals", "websocket", term_name)
break
except HTTPClientError as e:
if e.code != 404:
raise
await asyncio.sleep(1)

ws.write_message(json.dumps(["stdin", "pwd\r\n"]))

message_stdout = ""
while True:
try:
message = await asyncio.wait_for(ws.read_message(), timeout=5.0)
except asyncio.TimeoutError:
break

message = json.loads(message)

if message[0] == "stdout":
message_stdout += message[1]

ws.close()

assert non_existing_path not in message_stdout
await jp_cleanup_subprocesses()


async def test_culling_config(jp_configurable_serverapp):
terminal_mgr_config = jp_configurable_serverapp().config.ServerApp.TerminalManager
assert terminal_mgr_config.cull_inactive_timeout == CULL_TIMEOUT
assert terminal_mgr_config.cull_interval == CULL_INTERVAL
Expand All @@ -158,7 +252,7 @@ async def test_culling_config(jp_server_config, jp_configurable_serverapp):


@pytest.mark.skipif(os.name == "nt", reason="Not currently working on Windows")
async def test_culling(jp_server_config, jp_fetch, jp_cleanup_subprocesses):
async def test_culling(jp_fetch, jp_cleanup_subprocesses):
# POST request
resp = await jp_fetch(
"api",
Expand Down

0 comments on commit 22d210e

Please sign in to comment.