Skip to content

Commit fb39fa7

Browse files
authored
Merge pull request #396 from bollwyvl/gh-309-server-fixture
Use `pytest.fixture`s for starting servers, use `importlib*metadata`
2 parents 3dde433 + 36d4fdd commit fb39fa7

File tree

9 files changed

+254
-76
lines changed

9 files changed

+254
-76
lines changed

.github/workflows/test.yaml

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
# NOTE: See CONTRIBUTING.md for a local development setup that differs
6868
# slightly from this.
6969
#
70-
# Pytest options are set in tests/pytest.ini.
70+
# Pytest options are set in `pyproject.toml`.
7171
run: |
7272
pip install -vv $(ls ./dist/jupyter_server_proxy-*.whl)\[acceptance\] 'jupyterlab~=${{ matrix.jupyterlab-version }}.0' 'jupyter_server~=${{ matrix.jupyter_server-version }}.0'
7373
@@ -76,18 +76,8 @@ jobs:
7676
pip freeze
7777
pip check
7878
79-
- name: Run tests against jupyter-notebook
79+
- name: Run tests
8080
run: |
81-
JUPYTER_TOKEN=secret jupyter-notebook --config=./tests/resources/jupyter_server_config.py &
82-
sleep 5
83-
cd tests
84-
pytest -k "not acceptance"
85-
86-
- name: Run tests against jupyter-lab
87-
run: |
88-
JUPYTER_TOKEN=secret jupyter-lab --config=./tests/resources/jupyter_server_config.py &
89-
sleep 5
90-
cd tests
9181
pytest -k "not acceptance"
9282
9383
- name: Upload pytest and coverage reports

CONTRIBUTING.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,10 @@ jupyter labextension develop --overwrite .
2222
jupyter server extension enable jupyter_server_proxy
2323
```
2424

25-
Before running tests, you need a server that we can test against.
26-
27-
```bash
28-
JUPYTER_TOKEN=secret jupyter-lab --config=./tests/resources/jupyter_server_config.py --no-browser
29-
```
30-
3125
Run the tests:
3226

3327
```bash
34-
pytest --verbose
28+
pytest
3529
```
3630

3731
These generate test and coverage reports in `build/pytest` and `build/coverage`.

jupyter_server_proxy/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ def _load_jupyter_server_extension(nbapp):
7171
],
7272
)
7373

74+
nbapp.log.debug(
75+
"[jupyter-server-proxy] Started with known servers: %s",
76+
", ".join([p.name for p in server_processes]),
77+
)
78+
7479

7580
# For backward compatibility
7681
load_jupyter_server_extension = _load_jupyter_server_extension

jupyter_server_proxy/config.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
"""
22
Traitlets based configuration for jupyter_server_proxy
33
"""
4+
import sys
45
from collections import namedtuple
56
from warnings import warn
67

7-
import pkg_resources
8+
if sys.version_info < (3, 10): # pragma: no cover
9+
from importlib_metadata import entry_points
10+
else: # pragma: no cover
11+
from importlib.metadata import entry_points
12+
813
from jupyter_server.utils import url_path_join as ujoin
914
from traitlets import Dict, List, Tuple, Union, default, observe
1015
from traitlets.config import Configurable
@@ -90,7 +95,7 @@ def get_timeout(self):
9095

9196
def get_entrypoint_server_processes(serverproxy_config):
9297
sps = []
93-
for entry_point in pkg_resources.iter_entry_points("jupyter_serverproxy_servers"):
98+
for entry_point in entry_points(group="jupyter_serverproxy_servers"):
9499
name = entry_point.name
95100
server_process_config = entry_point.load()()
96101
sps.append(make_server_process(name, server_process_config, serverproxy_config))

pyproject.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ classifiers = [
4747
]
4848
dependencies = [
4949
"aiohttp",
50+
"importlib_metadata >=4.8.3 ; python_version<\"3.10\"",
5051
"jupyter-server >=1.0",
5152
"simpervisor >=0.4",
5253
]
@@ -175,3 +176,26 @@ tag_template = "v{new_version}"
175176

176177
[[tool.tbump.file]]
177178
src = "labextension/package.json"
179+
180+
[tool.pytest.ini_options]
181+
cache_dir = "build/.cache/pytest"
182+
addopts = [
183+
"-vv",
184+
"--cov=jupyter_server_proxy",
185+
"--cov-branch",
186+
"--cov-context=test",
187+
"--cov-report=term-missing:skip-covered",
188+
"--cov-report=html:build/coverage",
189+
"--html=build/pytest/index.html",
190+
"--color=yes",
191+
]
192+
193+
[tool.coverage.run]
194+
data_file = "build/.coverage"
195+
concurrency = [
196+
"multiprocessing",
197+
"thread"
198+
]
199+
200+
[tool.coverage.html]
201+
show_contexts = true

tests/conftest.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""Reusable test fixtures for ``jupyter_server_proxy``."""
2+
import os
3+
import shutil
4+
import socket
5+
import sys
6+
import time
7+
from pathlib import Path
8+
from subprocess import Popen
9+
from typing import Any, Generator, Tuple
10+
from urllib.error import URLError
11+
from urllib.request import urlopen
12+
from uuid import uuid4
13+
14+
from pytest import fixture
15+
16+
HERE = Path(__file__).parent
17+
RESOURCES = HERE / "resources"
18+
19+
20+
@fixture
21+
def a_token() -> str:
22+
"""Get a random UUID to use for a token."""
23+
return str(uuid4())
24+
25+
26+
@fixture
27+
def an_unused_port() -> int:
28+
"""Get a random unused port."""
29+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
30+
s.bind(("127.0.0.1", 0))
31+
s.listen(1)
32+
port = s.getsockname()[1]
33+
s.close()
34+
return port
35+
36+
37+
@fixture(params=["notebook", "lab"])
38+
def a_server_cmd(request: Any) -> str:
39+
"""Get a viable name for a command."""
40+
return request.param
41+
42+
43+
@fixture
44+
def a_server(
45+
a_server_cmd: str,
46+
tmp_path: Path,
47+
an_unused_port: int,
48+
a_token: str,
49+
) -> Generator[str, None, None]:
50+
"""Get a running server."""
51+
# get a copy of the resources
52+
tests = tmp_path / "tests"
53+
tests.mkdir()
54+
shutil.copytree(RESOURCES, tests / "resources")
55+
args = [
56+
sys.executable,
57+
"-m",
58+
"jupyter",
59+
a_server_cmd,
60+
f"--port={an_unused_port}",
61+
"--no-browser",
62+
"--config=./tests/resources/jupyter_server_config.py",
63+
"--debug",
64+
]
65+
66+
# prepare an env
67+
env = dict(os.environ)
68+
env.update(JUPYTER_TOKEN=a_token)
69+
70+
# start the process
71+
server_proc = Popen(args, cwd=str(tmp_path), env=env)
72+
73+
# prepare some URLss
74+
url = f"http://127.0.0.1:{an_unused_port}/"
75+
canary_url = f"{url}favicon.ico"
76+
shutdown_url = f"{url}api/shutdown?token={a_token}"
77+
78+
retries = 10
79+
80+
while retries:
81+
try:
82+
urlopen(canary_url)
83+
break
84+
except URLError as err:
85+
if "Connection refused" in str(err):
86+
print(
87+
f"{a_server_cmd} not ready, will try again in 0.5s [{retries} retries]",
88+
flush=True,
89+
)
90+
time.sleep(0.5)
91+
retries -= 1
92+
continue
93+
raise err
94+
95+
print(f"{a_server_cmd} is ready...", flush=True)
96+
97+
yield url
98+
99+
# clean up after server is no longer needed
100+
print(f"{a_server_cmd} shutting down...", flush=True)
101+
urlopen(shutdown_url, data=[])
102+
server_proc.wait()
103+
print(f"{a_server_cmd} is stopped", flush=True)
104+
105+
106+
@fixture
107+
def a_server_port_and_token(
108+
a_server: str, # noqa
109+
an_unused_port: int,
110+
a_token: str,
111+
) -> Tuple[int, str]:
112+
"""Get the port and token for a running server."""
113+
return an_unused_port, a_token

tests/pytest.ini

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/resources/jupyter_server_config.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import sys
2+
from pathlib import Path
3+
4+
HERE = Path(__file__).parent.resolve()
5+
6+
sys.path.append(str(HERE))
7+
18
# load the config object for traitlets based configuration
29
c = get_config() # noqa
310

@@ -116,9 +123,5 @@ def cats_only(response, path):
116123

117124
c.ServerProxy.non_service_rewrite_response = hello_to_foo
118125

119-
import sys
120-
121-
sys.path.append("./tests/resources")
122126
c.ServerApp.jpserver_extensions = {"proxyextension": True}
123127
c.NotebookApp.nbserver_extensions = {"proxyextension": True}
124-
# c.Application.log_level = 'DEBUG'

0 commit comments

Comments
 (0)