Skip to content

Commit ad0f710

Browse files
authored
make unit tests backwards compatible without pending kernels (#669)
1 parent d3bc6bf commit ad0f710

File tree

6 files changed

+89
-22
lines changed

6 files changed

+89
-22
lines changed

.github/workflows/python-linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ jobs:
7272
./test_install/bin/python -m pip install -U pip
7373
./test_install/bin/python -m pip install ".[test]"
7474
pushd test_install
75-
./bin/pytest --pyargs jupyter_server
75+
./bin/pytest --pyargs jupyter_server --capture=no
7676
popd
7777
- name: Test the docs
7878
run: |

jupyter_server/pytest_plugin.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,20 @@ def jp_cleanup_subprocesses(jp_serverapp):
476476
async def _():
477477
terminal_cleanup = jp_serverapp.web_app.settings["terminal_manager"].terminate_all
478478
kernel_cleanup = jp_serverapp.kernel_manager.shutdown_all
479+
480+
async def kernel_cleanup_steps():
481+
# Try a graceful shutdown with a timeout
482+
try:
483+
await asyncio.wait_for(kernel_cleanup(), timeout=15.0)
484+
except asyncio.TimeoutError:
485+
# Now force a shutdown
486+
try:
487+
await asyncio.wait_for(kernel_cleanup(now=True), timeout=15.0)
488+
except asyncio.TimeoutError:
489+
print(Exception("Kernel never shutdown!"))
490+
except Exception as e:
491+
print(e)
492+
479493
if asyncio.iscoroutinefunction(terminal_cleanup):
480494
try:
481495
await terminal_cleanup()
@@ -487,10 +501,7 @@ async def _():
487501
except Exception as e:
488502
print(e)
489503
if asyncio.iscoroutinefunction(kernel_cleanup):
490-
try:
491-
await kernel_cleanup()
492-
except Exception as e:
493-
print(e)
504+
await kernel_cleanup_steps()
494505
else:
495506
try:
496507
kernel_cleanup()

jupyter_server/tests/services/kernels/test_api.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from jupyter_server.utils import url_path_join
1313

1414

15+
TEST_TIMEOUT = 20
16+
17+
1518
@pytest.fixture
1619
def pending_kernel_is_ready(jp_serverapp):
1720
async def _(kernel_id):
@@ -63,6 +66,7 @@ async def test_no_kernels(jp_fetch):
6366
assert kernels == []
6467

6568

69+
@pytest.mark.timeout(TEST_TIMEOUT)
6670
async def test_default_kernels(jp_fetch, jp_base_url, jp_cleanup_subprocesses):
6771
r = await jp_fetch("api", "kernels", method="POST", allow_nonstandard_methods=True)
6872
kernel = json.loads(r.body.decode())
@@ -78,6 +82,7 @@ async def test_default_kernels(jp_fetch, jp_base_url, jp_cleanup_subprocesses):
7882
await jp_cleanup_subprocesses()
7983

8084

85+
@pytest.mark.timeout(TEST_TIMEOUT)
8186
async def test_main_kernel_handler(
8287
jp_fetch, jp_base_url, jp_cleanup_subprocesses, jp_serverapp, pending_kernel_is_ready
8388
):
@@ -146,6 +151,7 @@ async def test_main_kernel_handler(
146151
await jp_cleanup_subprocesses()
147152

148153

154+
@pytest.mark.timeout(TEST_TIMEOUT)
149155
async def test_kernel_handler(jp_fetch, jp_cleanup_subprocesses, pending_kernel_is_ready):
150156
# Create a kernel
151157
r = await jp_fetch(
@@ -193,6 +199,7 @@ async def test_kernel_handler(jp_fetch, jp_cleanup_subprocesses, pending_kernel_
193199
await jp_cleanup_subprocesses()
194200

195201

202+
@pytest.mark.timeout(TEST_TIMEOUT)
196203
async def test_kernel_handler_startup_error(
197204
jp_fetch, jp_cleanup_subprocesses, jp_serverapp, jp_kernelspecs
198205
):
@@ -204,6 +211,7 @@ async def test_kernel_handler_startup_error(
204211
await jp_fetch("api", "kernels", method="POST", body=json.dumps({"name": "bad"}))
205212

206213

214+
@pytest.mark.timeout(TEST_TIMEOUT)
207215
async def test_kernel_handler_startup_error_pending(
208216
jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses, jp_serverapp, jp_kernelspecs
209217
):
@@ -219,6 +227,7 @@ async def test_kernel_handler_startup_error_pending(
219227
await jp_ws_fetch("api", "kernels", kid, "channels")
220228

221229

230+
@pytest.mark.timeout(TEST_TIMEOUT)
222231
async def test_connection(
223232
jp_fetch, jp_ws_fetch, jp_http_port, jp_auth_header, jp_cleanup_subprocesses
224233
):

jupyter_server/tests/services/kernels/test_cull.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ async def test_cull_idle(jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses):
8989
)
9090
],
9191
)
92+
@pytest.mark.timeout(30)
9293
async def test_cull_dead(
9394
jp_fetch, jp_ws_fetch, jp_serverapp, jp_cleanup_subprocesses, jp_kernelspecs
9495
):

jupyter_server/tests/services/sessions/test_api.py

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
from jupyter_server.utils import url_path_join
1818

1919

20+
TEST_TIMEOUT = 20
21+
22+
2023
j = lambda r: json.loads(r.body.decode())
2124

2225

@@ -142,12 +145,14 @@ def session_is_ready(jp_serverapp):
142145
"""
143146

144147
async def _(session_id):
145-
sm = jp_serverapp.session_manager
146148
mkm = jp_serverapp.kernel_manager
147-
session = await sm.get_session(session_id=session_id)
148-
kernel_id = session["kernel"]["id"]
149-
kernel = mkm.get_kernel(kernel_id)
150-
await kernel.ready
149+
if getattr(mkm, "use_pending_kernels", False):
150+
sm = jp_serverapp.session_manager
151+
session = await sm.get_session(session_id=session_id)
152+
kernel_id = session["kernel"]["id"]
153+
kernel = mkm.get_kernel(kernel_id)
154+
if getattr(kernel, "ready"):
155+
await kernel.ready
151156

152157
return _
153158

@@ -195,9 +200,8 @@ def assert_session_equality(actual, expected):
195200
assert_kernel_equality(actual["kernel"], expected["kernel"])
196201

197202

198-
async def test_create(
199-
session_client, jp_base_url, jp_cleanup_subprocesses, jp_serverapp, session_is_ready
200-
):
203+
@pytest.mark.timeout(TEST_TIMEOUT)
204+
async def test_create(session_client, jp_base_url, jp_cleanup_subprocesses, jp_serverapp):
201205
# Make sure no sessions exist.
202206
resp = await session_client.list()
203207
sessions = j(resp)
@@ -241,6 +245,7 @@ async def test_create(
241245
await jp_cleanup_subprocesses()
242246

243247

248+
@pytest.mark.timeout(TEST_TIMEOUT)
244249
async def test_create_bad(
245250
session_client, jp_base_url, jp_cleanup_subprocesses, jp_serverapp, jp_kernelspecs
246251
):
@@ -261,6 +266,7 @@ async def test_create_bad(
261266
await jp_cleanup_subprocesses()
262267

263268

269+
@pytest.mark.timeout(TEST_TIMEOUT)
264270
async def test_create_bad_pending(
265271
session_client, jp_base_url, jp_ws_fetch, jp_cleanup_subprocesses, jp_serverapp, jp_kernelspecs
266272
):
@@ -293,25 +299,36 @@ async def test_create_bad_pending(
293299
await jp_cleanup_subprocesses()
294300

295301

296-
async def test_create_file_session(session_client, jp_cleanup_subprocesses, jp_serverapp):
302+
@pytest.mark.timeout(TEST_TIMEOUT)
303+
async def test_create_file_session(
304+
session_client, jp_cleanup_subprocesses, jp_serverapp, session_is_ready
305+
):
297306
resp = await session_client.create("foo/nb1.py", type="file")
298307
assert resp.code == 201
299308
newsession = j(resp)
300309
assert newsession["path"] == "foo/nb1.py"
301310
assert newsession["type"] == "file"
311+
sid = newsession["id"]
312+
await session_is_ready(sid)
302313
await jp_cleanup_subprocesses()
303314

304315

305-
async def test_create_console_session(session_client, jp_cleanup_subprocesses, jp_serverapp):
316+
@pytest.mark.timeout(TEST_TIMEOUT)
317+
async def test_create_console_session(
318+
session_client, jp_cleanup_subprocesses, jp_serverapp, session_is_ready
319+
):
306320
resp = await session_client.create("foo/abc123", type="console")
307321
assert resp.code == 201
308322
newsession = j(resp)
309323
assert newsession["path"] == "foo/abc123"
310324
assert newsession["type"] == "console"
311325
# Need to find a better solution to this.
326+
sid = newsession["id"]
327+
await session_is_ready(sid)
312328
await jp_cleanup_subprocesses()
313329

314330

331+
@pytest.mark.timeout(TEST_TIMEOUT)
315332
async def test_create_deprecated(session_client, jp_cleanup_subprocesses, jp_serverapp):
316333
resp = await session_client.create_deprecated("foo/nb1.ipynb")
317334
assert resp.code == 201
@@ -320,9 +337,11 @@ async def test_create_deprecated(session_client, jp_cleanup_subprocesses, jp_ser
320337
assert newsession["type"] == "notebook"
321338
assert newsession["notebook"]["path"] == "foo/nb1.ipynb"
322339
# Need to find a better solution to this.
340+
sid = newsession["id"]
323341
await jp_cleanup_subprocesses()
324342

325343

344+
@pytest.mark.timeout(TEST_TIMEOUT)
326345
async def test_create_with_kernel_id(
327346
session_client, jp_fetch, jp_base_url, jp_cleanup_subprocesses, jp_serverapp
328347
):
@@ -354,16 +373,23 @@ async def test_create_with_kernel_id(
354373
await jp_cleanup_subprocesses()
355374

356375

357-
async def test_create_with_bad_kernel_id(session_client, jp_cleanup_subprocesses, jp_serverapp):
376+
@pytest.mark.timeout(TEST_TIMEOUT)
377+
async def test_create_with_bad_kernel_id(
378+
session_client, jp_cleanup_subprocesses, jp_serverapp, session_is_ready
379+
):
358380
resp = await session_client.create("foo/nb1.py", type="file")
359381
assert resp.code == 201
360382
newsession = j(resp)
383+
sid = newsession["id"]
384+
await session_is_ready(sid)
385+
361386
# TODO
362387
assert newsession["path"] == "foo/nb1.py"
363388
assert newsession["type"] == "file"
364389
await jp_cleanup_subprocesses()
365390

366391

392+
@pytest.mark.timeout(TEST_TIMEOUT)
367393
async def test_delete(session_client, jp_cleanup_subprocesses, jp_serverapp, session_is_ready):
368394
resp = await session_client.create("foo/nb1.ipynb")
369395

@@ -385,10 +411,12 @@ async def test_delete(session_client, jp_cleanup_subprocesses, jp_serverapp, ses
385411
await jp_cleanup_subprocesses()
386412

387413

388-
async def test_modify_path(session_client, jp_cleanup_subprocesses, jp_serverapp):
414+
@pytest.mark.timeout(TEST_TIMEOUT)
415+
async def test_modify_path(session_client, jp_cleanup_subprocesses, jp_serverapp, session_is_ready):
389416
resp = await session_client.create("foo/nb1.ipynb")
390417
newsession = j(resp)
391418
sid = newsession["id"]
419+
await session_is_ready(sid)
392420

393421
resp = await session_client.modify_path(sid, "nb2.ipynb")
394422
changed = j(resp)
@@ -398,10 +426,14 @@ async def test_modify_path(session_client, jp_cleanup_subprocesses, jp_serverapp
398426
await jp_cleanup_subprocesses()
399427

400428

401-
async def test_modify_path_deprecated(session_client, jp_cleanup_subprocesses, jp_serverapp):
429+
@pytest.mark.timeout(TEST_TIMEOUT)
430+
async def test_modify_path_deprecated(
431+
session_client, jp_cleanup_subprocesses, jp_serverapp, session_is_ready
432+
):
402433
resp = await session_client.create("foo/nb1.ipynb")
403434
newsession = j(resp)
404435
sid = newsession["id"]
436+
await session_is_ready(sid)
405437

406438
resp = await session_client.modify_path_deprecated(sid, "nb2.ipynb")
407439
changed = j(resp)
@@ -411,10 +443,12 @@ async def test_modify_path_deprecated(session_client, jp_cleanup_subprocesses, j
411443
await jp_cleanup_subprocesses()
412444

413445

414-
async def test_modify_type(session_client, jp_cleanup_subprocesses, jp_serverapp):
446+
@pytest.mark.timeout(TEST_TIMEOUT)
447+
async def test_modify_type(session_client, jp_cleanup_subprocesses, jp_serverapp, session_is_ready):
415448
resp = await session_client.create("foo/nb1.ipynb")
416449
newsession = j(resp)
417450
sid = newsession["id"]
451+
await session_is_ready(sid)
418452

419453
resp = await session_client.modify_type(sid, "console")
420454
changed = j(resp)
@@ -424,10 +458,14 @@ async def test_modify_type(session_client, jp_cleanup_subprocesses, jp_serverapp
424458
await jp_cleanup_subprocesses()
425459

426460

427-
async def test_modify_kernel_name(session_client, jp_fetch, jp_cleanup_subprocesses, jp_serverapp):
461+
@pytest.mark.timeout(TEST_TIMEOUT)
462+
async def test_modify_kernel_name(
463+
session_client, jp_fetch, jp_cleanup_subprocesses, jp_serverapp, session_is_ready
464+
):
428465
resp = await session_client.create("foo/nb1.ipynb")
429466
before = j(resp)
430467
sid = before["id"]
468+
await session_is_ready(sid)
431469

432470
resp = await session_client.modify_kernel_name(sid, before["kernel"]["name"])
433471
after = j(resp)
@@ -448,10 +486,14 @@ async def test_modify_kernel_name(session_client, jp_fetch, jp_cleanup_subproces
448486
await jp_cleanup_subprocesses()
449487

450488

451-
async def test_modify_kernel_id(session_client, jp_fetch, jp_cleanup_subprocesses, jp_serverapp):
489+
@pytest.mark.timeout(TEST_TIMEOUT)
490+
async def test_modify_kernel_id(
491+
session_client, jp_fetch, jp_cleanup_subprocesses, jp_serverapp, session_is_ready
492+
):
452493
resp = await session_client.create("foo/nb1.ipynb")
453494
before = j(resp)
454495
sid = before["id"]
496+
await session_is_ready(sid)
455497

456498
# create a new kernel
457499
resp = await jp_fetch("api/kernels", method="POST", allow_nonstandard_methods=True)
@@ -479,8 +521,9 @@ async def test_modify_kernel_id(session_client, jp_fetch, jp_cleanup_subprocesse
479521
await jp_cleanup_subprocesses()
480522

481523

524+
@pytest.mark.timeout(TEST_TIMEOUT)
482525
async def test_restart_kernel(
483-
session_client, jp_base_url, jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses
526+
session_client, jp_base_url, jp_fetch, jp_ws_fetch, jp_cleanup_subprocesses, session_is_ready
484527
):
485528
# Create a session.
486529
resp = await session_client.create("foo/nb1.ipynb")
@@ -492,6 +535,8 @@ async def test_restart_kernel(
492535
assert resp.headers["Location"] == url_path_join(
493536
jp_base_url, "/api/sessions/", new_session["id"]
494537
)
538+
sid = new_session["id"]
539+
await session_is_ready(sid)
495540

496541
kid = new_session["kernel"]["id"]
497542

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ test =
5050
pytest>=6.0
5151
pytest-cov
5252
pytest-mock
53+
pytest-timeout
5354
requests
5455
pytest-tornasync
5556
pytest-console-scripts

0 commit comments

Comments
 (0)