Skip to content

Commit 740c31d

Browse files
authored
Fix crash when handling a stream after non-stream output (#48)
* Add a failing test case * Move forked to `build.yaml` * Fix test to run with a real `ycell` * Fix the exception * Add everything to addopts to streamline DevExp, add min cov
1 parent cf195be commit 740c31d

File tree

5 files changed

+99
-14
lines changed

5 files changed

+99
-14
lines changed

.github/workflows/build.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
jlpm
3333
jlpm run lint:check
3434
35-
- name: Test the extension
35+
- name: Frontend test
3636
run: |
3737
set -eux
3838
jlpm run test
@@ -41,15 +41,19 @@ jobs:
4141
run: |
4242
set -eux
4343
python -m pip install .[test]
44-
45-
pytest -vv -r ap --cov jupyter_server_nbmodel
4644
jupyter server extension list
4745
jupyter server extension list 2>&1 | grep -ie "jupyter_server_nbmodel.*OK"
4846
4947
jupyter labextension list
5048
jupyter labextension list 2>&1 | grep -ie "jupyter-server-nbmodel.*OK"
49+
5150
python -m jupyterlab.browser_check
5251
52+
- name: Python test
53+
run: |
54+
set -eux
55+
pytest
56+
5357
- name: Package the extension
5458
run: |
5559
set -eux

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ jupyter labextension develop . --overwrite
230230
To execute them, run:
231231

232232
```sh
233-
pytest -vv -r ap --cov jupyter_server_nbmodel
233+
pytest
234234
```
235235

236236
#### Frontend tests

jupyter_server_nbmodel/actions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def _output_hook(outputs: list[NotebookNode], ycell: y.Map | None, msg: dict) ->
107107
# FIXME Logic is quite complex at https://github.com/jupyterlab/jupyterlab/blob/7ae2d436fc410b0cff51042a3350ba71f54f4445/packages/outputarea/src/model.ts#L518
108108
if text.endswith((os.linesep, "\n")):
109109
text = text[:-1]
110-
if (not cell_outputs) or (cell_outputs[-1]["name"] != output["name"]):
110+
if (not cell_outputs) or (cell_outputs[-1].get("name", None) != output["name"]):
111111
output["text"] = [text]
112112
cell_outputs.append(output)
113113
else:

jupyter_server_nbmodel/tests/test_handlers.py

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,23 @@ async def _(kernel_id, ready=None):
7474
return _
7575

7676

77+
@pytest.fixture()
78+
def rtc_test_notebook(jp_serverapp, rtc_create_notebook):
79+
async def _(notebook, path="test.ipynb"):
80+
nb_content = nbformat.writes(notebook, version=4)
81+
returned_path, _ = await rtc_create_notebook(path, nb_content, store=True)
82+
assert path == returned_path
83+
document_id = get_document_id(jp_serverapp, "test.ipynb")
84+
return document_id
85+
return _
86+
87+
88+
def get_document_id(jp_serverapp, notebook_name):
89+
fim = jp_serverapp.web_app.settings["file_id_manager"]
90+
document_id = f'json:notebook:{fim.get_id(notebook_name)}'
91+
return document_id
92+
93+
7794
@pytest.mark.timeout(TEST_TIMEOUT)
7895
@pytest.mark.parametrize(
7996
"snippet,output",
@@ -82,29 +99,89 @@ async def _(kernel_id, ready=None):
8299
"print('hello buddy')",
83100
'{"output_type": "stream", "name": "stdout", "text": "hello buddy\\n"}',
84101
),
102+
),
103+
)
104+
async def test_post_execute_no_ycell(jp_fetch, pending_kernel_is_ready, snippet, output):
105+
r = await jp_fetch(
106+
"api", "kernels", method="POST", body=json.dumps({"name": NATIVE_KERNEL_NAME})
107+
)
108+
kernel = json.loads(r.body.decode())
109+
await pending_kernel_is_ready(kernel["id"])
110+
111+
response = await wait_for_request(
112+
jp_fetch,
113+
"api",
114+
"kernels",
115+
kernel["id"],
116+
"execute",
117+
method="POST",
118+
body=json.dumps({"code": snippet}),
119+
)
120+
121+
assert response.code == 200
122+
payload = json.loads(response.body)
123+
assert payload == {
124+
"status": "ok",
125+
"execution_count": 1,
126+
"outputs": f"[{output}]",
127+
}
128+
129+
response2 = await jp_fetch("api", "kernels", kernel["id"], method="DELETE")
130+
assert response2.code == 204
131+
132+
await asyncio.sleep(1)
133+
134+
135+
@pytest.mark.timeout(2 * TEST_TIMEOUT)
136+
@pytest.mark.parametrize(
137+
"snippet,output",
138+
(
139+
(
140+
"print('hello buddy')",
141+
'{"output_type": "stream", "name": "stdout", "text": ["hello buddy"]}',
142+
),
85143
("a = 1", ""),
86144
(
87145
"""from IPython.display import HTML
88146
HTML('<p><b>Jupyter</b> rocks.</p>')""",
89147
'{"output_type": "execute_result", "metadata": {}, "data": {"text/plain": "<IPython.core.display.HTML object>", "text/html": "<p><b>Jupyter</b> rocks.</p>"}, "execution_count": 1}', # noqa: E501
90148
),
149+
(
150+
"display('a'); print('b')",
151+
(
152+
'{"output_type": "display_data", "metadata": {}, "data": {"text/plain": "\'a\'"}}'
153+
', {"output_type": "stream", "name": "stdout", "text": ["b"]}'
154+
)
155+
)
91156
),
92157
)
93-
async def test_post_execute(jp_fetch, pending_kernel_is_ready, snippet, output):
158+
async def test_post_execute_wiht_ycell(jp_fetch, pending_kernel_is_ready, snippet, output, rtc_test_notebook):
94159
r = await jp_fetch(
95160
"api", "kernels", method="POST", body=json.dumps({"name": NATIVE_KERNEL_NAME})
96161
)
97162
kernel = json.loads(r.body.decode())
98163
await pending_kernel_is_ready(kernel["id"])
99164

165+
nb = nbformat.v4.new_notebook(
166+
cells=[nbformat.v4.new_code_cell(source=snippet)]
167+
)
168+
document_id = await rtc_test_notebook(nb)
169+
cell_id = nb["cells"][0]["id"]
170+
100171
response = await wait_for_request(
101172
jp_fetch,
102173
"api",
103174
"kernels",
104175
kernel["id"],
105176
"execute",
106177
method="POST",
107-
body=json.dumps({"code": snippet}),
178+
body=json.dumps({
179+
"code": snippet,
180+
"metadata": {
181+
"cell_id": cell_id,
182+
"document_id": document_id
183+
}
184+
}),
108185
)
109186

110187
assert response.code == 200
@@ -223,17 +300,15 @@ async def fake_execute(client, ydoc, snippet, metadata, stdin_hook):
223300

224301

225302
@pytest.mark.timeout(TEST_TIMEOUT)
226-
async def test_execution_timing_metadata(jp_root_dir, jp_fetch, pending_kernel_is_ready, rtc_create_notebook, jp_serverapp):
303+
async def test_execution_timing_metadata(jp_root_dir, jp_fetch, pending_kernel_is_ready, rtc_test_notebook, jp_serverapp):
227304
snippet = "a = 1"
228305
nb = nbformat.v4.new_notebook(
229306
cells=[nbformat.v4.new_code_cell(source=snippet, execution_count=1)]
230307
)
231-
nb_content = nbformat.writes(nb, version=4)
232-
path, _ = await rtc_create_notebook("test.ipynb", nb_content, store=True)
308+
path = "test.ipynb"
309+
document_id = await rtc_test_notebook(nb, path=path)
233310
collaboration = jp_serverapp.web_app.settings["jupyter_server_ydoc"]
234-
fim = jp_serverapp.web_app.settings["file_id_manager"]
235-
document_id = f'json:notebook:{fim.get_id("test.ipynb")}'
236-
cell_id = nb["cells"][0].get("id")
311+
cell_id = nb["cells"][0]["id"]
237312

238313
r = await jp_fetch(
239314
"api", "kernels", method="POST", body=json.dumps({"name": NATIVE_KERNEL_NAME})

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,13 @@ source_dir = "src"
9191
build_dir = "jupyter_server_nbmodel/labextension"
9292

9393
[tool.pytest.ini_options]
94-
addopts = "-vv --forked"
94+
addopts = [
95+
"-vv",
96+
"-r ap",
97+
"--forked",
98+
"--cov=jupyter_server_nbmodel",
99+
"--cov-fail-under=80",
100+
]
95101
filterwarnings = [
96102
"error",
97103
"ignore:Unclosed context <zmq.asyncio.Context:ResourceWarning",

0 commit comments

Comments
 (0)