Skip to content

Use pytest.fixtures for starting servers, use importlib*metadata #396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
# NOTE: See CONTRIBUTING.md for a local development setup that differs
# slightly from this.
#
# Pytest options are set in tests/pytest.ini.
# Pytest options are set in `pyproject.toml`.
run: |
pip install -vv $(ls ./dist/jupyter_server_proxy-*.whl)\[acceptance\] 'jupyterlab~=${{ matrix.jupyterlab-version }}.0' 'jupyter_server~=${{ matrix.jupyter_server-version }}.0'

Expand All @@ -76,18 +76,8 @@ jobs:
pip freeze
pip check

- name: Run tests against jupyter-notebook
- name: Run tests
run: |
JUPYTER_TOKEN=secret jupyter-notebook --config=./tests/resources/jupyter_server_config.py &
sleep 5
cd tests
pytest -k "not acceptance"

- name: Run tests against jupyter-lab
run: |
JUPYTER_TOKEN=secret jupyter-lab --config=./tests/resources/jupyter_server_config.py &
sleep 5
cd tests
pytest -k "not acceptance"

- name: Upload pytest and coverage reports
Expand Down
8 changes: 1 addition & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,10 @@ jupyter labextension develop --overwrite .
jupyter server extension enable jupyter_server_proxy
```

Before running tests, you need a server that we can test against.

```bash
JUPYTER_TOKEN=secret jupyter-lab --config=./tests/resources/jupyter_server_config.py --no-browser
```

Run the tests:

```bash
pytest --verbose
pytest
```

These generate test and coverage reports in `build/pytest` and `build/coverage`.
Expand Down
5 changes: 5 additions & 0 deletions jupyter_server_proxy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ def _load_jupyter_server_extension(nbapp):
],
)

nbapp.log.debug(
"[jupyter-server-proxy] Started with known servers: %s",
", ".join([p.name for p in server_processes]),
)


# For backward compatibility
load_jupyter_server_extension = _load_jupyter_server_extension
Expand Down
9 changes: 7 additions & 2 deletions jupyter_server_proxy/config.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"""
Traitlets based configuration for jupyter_server_proxy
"""
import sys
from collections import namedtuple
from warnings import warn

import pkg_resources
if sys.version_info < (3, 10): # pragma: no cover
from importlib_metadata import entry_points
else: # pragma: no cover
from importlib.metadata import entry_points

from jupyter_server.utils import url_path_join as ujoin
from traitlets import Dict, List, Tuple, Union, default, observe
from traitlets.config import Configurable
Expand Down Expand Up @@ -90,7 +95,7 @@ def get_timeout(self):

def get_entrypoint_server_processes(serverproxy_config):
sps = []
for entry_point in pkg_resources.iter_entry_points("jupyter_serverproxy_servers"):
for entry_point in entry_points(group="jupyter_serverproxy_servers"):
name = entry_point.name
server_process_config = entry_point.load()()
sps.append(make_server_process(name, server_process_config, serverproxy_config))
Expand Down
24 changes: 24 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ classifiers = [
]
dependencies = [
"aiohttp",
"importlib_metadata >=4.8.3 ; python_version<\"3.10\"",
"jupyter-server >=1.0",
"simpervisor >=0.4",
]
Expand Down Expand Up @@ -175,3 +176,26 @@ tag_template = "v{new_version}"

[[tool.tbump.file]]
src = "labextension/package.json"

[tool.pytest.ini_options]
cache_dir = "build/.cache/pytest"
addopts = [
"-vv",
"--cov=jupyter_server_proxy",
"--cov-branch",
"--cov-context=test",
"--cov-report=term-missing:skip-covered",
"--cov-report=html:build/coverage",
"--html=build/pytest/index.html",
"--color=yes",
]

[tool.coverage.run]
data_file = "build/.coverage"
concurrency = [
"multiprocessing",
"thread"
]

[tool.coverage.html]
show_contexts = true
113 changes: 113 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""Reusable test fixtures for ``jupyter_server_proxy``."""
import os
import shutil
import socket
import sys
import time
from pathlib import Path
from subprocess import Popen
from typing import Any, Generator, Tuple
from urllib.error import URLError
from urllib.request import urlopen
from uuid import uuid4

from pytest import fixture

HERE = Path(__file__).parent
RESOURCES = HERE / "resources"


@fixture
def a_token() -> str:
"""Get a random UUID to use for a token."""
return str(uuid4())


@fixture
def an_unused_port() -> int:
"""Get a random unused port."""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 0))
s.listen(1)
port = s.getsockname()[1]
s.close()
return port


@fixture(params=["notebook", "lab"])
def a_server_cmd(request: Any) -> str:
"""Get a viable name for a command."""
return request.param


@fixture
def a_server(
a_server_cmd: str,
tmp_path: Path,
an_unused_port: int,
a_token: str,
) -> Generator[str, None, None]:
"""Get a running server."""
# get a copy of the resources
tests = tmp_path / "tests"
tests.mkdir()
shutil.copytree(RESOURCES, tests / "resources")
args = [
sys.executable,
"-m",
"jupyter",
a_server_cmd,
f"--port={an_unused_port}",
"--no-browser",
"--config=./tests/resources/jupyter_server_config.py",
"--debug",
]

# prepare an env
env = dict(os.environ)
env.update(JUPYTER_TOKEN=a_token)

# start the process
server_proc = Popen(args, cwd=str(tmp_path), env=env)

# prepare some URLss
url = f"http://127.0.0.1:{an_unused_port}/"
canary_url = f"{url}favicon.ico"
shutdown_url = f"{url}api/shutdown?token={a_token}"

retries = 10

while retries:
try:
urlopen(canary_url)
break
except URLError as err:
if "Connection refused" in str(err):
print(
f"{a_server_cmd} not ready, will try again in 0.5s [{retries} retries]",
flush=True,
)
time.sleep(0.5)
retries -= 1
continue
raise err

print(f"{a_server_cmd} is ready...", flush=True)

yield url

# clean up after server is no longer needed
print(f"{a_server_cmd} shutting down...", flush=True)
urlopen(shutdown_url, data=[])
server_proc.wait()
print(f"{a_server_cmd} is stopped", flush=True)


@fixture
def a_server_port_and_token(
a_server: str, # noqa
an_unused_port: int,
a_token: str,
) -> Tuple[int, str]:
"""Get the port and token for a running server."""
return an_unused_port, a_token
9 changes: 0 additions & 9 deletions tests/pytest.ini

This file was deleted.

11 changes: 7 additions & 4 deletions tests/resources/jupyter_server_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import sys
from pathlib import Path

HERE = Path(__file__).parent.resolve()

sys.path.append(str(HERE))

# load the config object for traitlets based configuration
c = get_config() # noqa

Expand Down Expand Up @@ -116,9 +123,5 @@ def cats_only(response, path):

c.ServerProxy.non_service_rewrite_response = hello_to_foo

import sys

sys.path.append("./tests/resources")
c.ServerApp.jpserver_extensions = {"proxyextension": True}
c.NotebookApp.nbserver_extensions = {"proxyextension": True}
# c.Application.log_level = 'DEBUG'
Loading