Skip to content

Commit 7e7c286

Browse files
authored
Add more coverage (#877)
* Add more coverage * debug * more debug * more debug * disable stricthostchecking * add windows cov and depug ssh * more debug * try verbose * try ssh-add * try ssh-agent * try ssh-add too * try with ssh agent * add required arg * use real fake key * force a run * try again * try again * try again * run just the test * try ssh-add too * use check call * try another way * simplify * lint * undo change * try the test up front * more cleanup * cleanup * undo changes * add more tests * fixups * fix failing test and add localinterfaces tests * fix test * add more client tests * add a skip * add windows cov * add skips on windows
1 parent 53e1b29 commit 7e7c286

File tree

13 files changed

+213
-18
lines changed

13 files changed

+213
-18
lines changed

.github/workflows/main.yml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,19 @@ jobs:
5757
- name: Run the tests
5858
if: ${{ !startsWith(matrix.python-version, 'pypy') && !startsWith(matrix.os, 'windows') }}
5959
run: |
60-
hatch run cov:test || hatch run test:test --lf
61-
- name: Run the tests on pypy and windows
62-
if: ${{ startsWith(matrix.python-version, 'pypy') || startsWith(matrix.os, 'windows') }}
60+
hatch run cov:test --cov-fail-under 75 || hatch run test:test --lf
61+
- name: Run the tests on pypy
62+
if: ${{ startsWith(matrix.python-version, 'pypy') }}
6363
run: |
64-
# Ignore warnings on Windows and PyPI
65-
pip install -e ".[test]"
66-
python -m pytest -vv -W ignore || python -m pytest -vv -W ignore --lf
67-
64+
hatch run test:test -W default || hatch run test:test -W default --lf
65+
- name: Run the tests on windows
66+
if: ${{ startsWith(matrix.os, 'windows') }}
67+
run: |
68+
hatch run cov:test -W default || hatch run test:test -W default --lf
6869
- name: Code coverage
6970
run: |
7071
pip install codecov
7172
codecov
72-
7373
docs:
7474
runs-on: ubuntu-latest
7575
steps:
@@ -111,7 +111,6 @@ jobs:
111111
- name: Run the tests
112112
run: |
113113
pytest -vv -W default || pytest -vv -W default --lf
114-
115114
make_sdist:
116115
name: Make SDist
117116
runs-on: ubuntu-latest

jupyter_client/consoleapp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ def initialize(self, argv: object = None) -> None:
361361
Classes which mix this class in should call:
362362
JupyterConsoleApp.initialize(self,argv)
363363
"""
364-
if self._dispatching: # type:ignore[attr-defined]
364+
if getattr(self, "_dispatching", False):
365365
return
366366
self.init_connection_file()
367367
self.init_ssh()

jupyter_client/kernelspecapp.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def path_key(item):
6666
print(f" {kernelname.ljust(name_len)} {path}")
6767
else:
6868
print(json.dumps({"kernelspecs": specs}, indent=2))
69+
return specs
6970

7071

7172
class InstallKernelSpec(JupyterApp):
@@ -228,7 +229,7 @@ class InstallNativeKernelSpec(JupyterApp):
228229
description = """[DEPRECATED] Install the IPython kernel spec directory for this Python."""
229230
kernel_spec_manager = Instance(KernelSpecManager)
230231

231-
def _kernel_spec_manager_default(self):
232+
def _kernel_spec_manager_default(self): # pragma: no cover
232233
return KernelSpecManager(data_dir=self.data_dir)
233234

234235
user = Bool(
@@ -248,7 +249,7 @@ def _kernel_spec_manager_default(self):
248249
"debug": base_flags["debug"],
249250
}
250251

251-
def start(self):
252+
def start(self): # pragma: no cover
252253
self.log.warning(
253254
"`jupyter kernelspec install-self` is DEPRECATED as of 4.0."
254255
" You probably want `ipython kernel install` to install the IPython kernelspec."

jupyter_client/session.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
def squash_unicode(obj):
7070
"""coerce unicode back to bytestrings."""
7171
if isinstance(obj, dict):
72-
for key in obj.keys():
72+
for key in list(obj.keys()):
7373
obj[key] = squash_unicode(obj[key])
7474
if isinstance(key, str):
7575
obj[squash_unicode(key)] = obj.pop(key)
@@ -184,7 +184,7 @@ def new_id_bytes() -> bytes:
184184
}
185185

186186

187-
def default_secure(cfg: t.Any) -> None:
187+
def default_secure(cfg: t.Any) -> None: # pragma: no cover
188188
"""Set the default behavior for a config environment to be secure.
189189
190190
If Session.key/keyfile have not been set, set Session.key to
@@ -255,7 +255,7 @@ def __init__(self, msg_dict: t.Dict[str, t.Any]) -> None:
255255

256256
# Having this iterator lets dict(msg_obj) work out of the box.
257257
def __iter__(self) -> t.ItemsView[str, t.Any]:
258-
return self.__dict__.items()
258+
return iter(self.__dict__.items()) # type:ignore[return-value]
259259

260260
def __repr__(self) -> str:
261261
return repr(self.__dict__)
@@ -1086,7 +1086,7 @@ def deserialize(
10861086
# adapt to the current version
10871087
return adapt(message)
10881088

1089-
def unserialize(self, *args: t.Any, **kwargs: t.Any) -> t.Dict[str, t.Any]:
1089+
def unserialize(self, *args: t.Any, **kwargs: t.Any) -> t.Dict[str, t.Any]: # pragma: no cover
10901090
warnings.warn(
10911091
"Session.unserialize is deprecated. Use Session.deserialize.",
10921092
DeprecationWarning,

jupyter_client/ssh/tunnel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ def openssh_tunnel(
266266
except pexpect.TIMEOUT:
267267
continue
268268
except pexpect.EOF as e:
269+
tunnel.wait()
269270
if tunnel.exitstatus:
270271
print(tunnel.exitstatus)
271272
print(tunnel.before)

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ test = [
5252
"coverage",
5353
"ipykernel>=6.11",
5454
"mypy",
55+
"paramiko; sys_platform == 'win32'",
5556
"pre-commit",
5657
"pytest",
5758
"pytest-asyncio>=0.19",
@@ -61,7 +62,7 @@ test = [
6162
docs = [
6263
"ipykernel",
6364
"myst-parser",
64-
"sphinx>=1.3.6",
65+
"sphinx>=4",
6566
"pydata_sphinx_theme",
6667
"sphinxcontrib_github_alt",
6768
]
@@ -95,7 +96,7 @@ dependencies = ["coverage", "pytest-cov"]
9596
[tool.hatch.envs.cov.env-vars]
9697
ARGS = "-vv --cov jupyter_client --cov-branch --cov-report term-missing:skip-covered"
9798
[tool.hatch.envs.cov.scripts]
98-
test = "python -m pytest $ARGS --cov-fail-under 70 {args}"
99+
test = "python -m pytest $ARGS {args}"
99100

100101
[tool.black]
101102
line-length = 100
@@ -135,6 +136,9 @@ exclude_lines = [
135136
"class .*\bProtocol\\):",
136137
"@(abc\\.)?abstractmethod",
137138
]
139+
omit = [
140+
"jupyter_client/ssh/forward.py"
141+
]
138142

139143
[tool.mypy]
140144
check_untyped_defs = true

tests/conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import asyncio
2+
import json
23
import os
34
import sys
5+
from pathlib import Path
46

57
import pytest
68

@@ -64,3 +66,13 @@ def env():
6466
@pytest.fixture()
6567
def kernel_dir():
6668
return pjoin(paths.jupyter_data_dir(), 'kernels')
69+
70+
71+
@pytest.fixture
72+
def kernel_spec(tmpdir):
73+
test_dir = Path(tmpdir / "test")
74+
test_dir.mkdir()
75+
kernel_data = {"argv": [], "display_name": "test", "language": "test"}
76+
spec_file_path = Path(test_dir / "kernel.json")
77+
spec_file_path.write_text(json.dumps(kernel_data), 'utf8')
78+
return str(test_dir)

tests/test_client.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
# Copyright (c) Jupyter Development Team.
33
# Distributed under the terms of the Modified BSD License.
44
import os
5+
import platform
6+
import sys
57
from threading import Event
8+
from unittest import mock
69
from unittest import TestCase
710

811
import pytest
@@ -11,6 +14,7 @@
1114
from traitlets import Type
1215

1316
from .utils import test_env
17+
from jupyter_client.client import validate_string_dict
1418
from jupyter_client.kernelspec import KernelSpecManager
1519
from jupyter_client.kernelspec import NATIVE_KERNEL_NAME
1620
from jupyter_client.kernelspec import NoSuchKernel
@@ -128,6 +132,29 @@ def _check_reply(self, reply_type, reply):
128132
assert reply["header"]["msg_type"] == reply_type + "_reply"
129133
assert reply["parent_header"]["msg_type"] == reply_type + "_request"
130134

135+
@pytest.mark.skipif(
136+
sys.platform != 'linux' or platform.python_implementation().lower() == 'pypy',
137+
reason='only works with cpython on ubuntu in ci',
138+
)
139+
async def test_input_request(self, kc):
140+
with mock.patch('builtins.input', return_value='test\n'):
141+
reply = await kc.execute_interactive("a = input()", timeout=TIMEOUT)
142+
assert reply["content"]["status"] == "ok"
143+
144+
async def test_output_hook(self, kc):
145+
called = False
146+
147+
def output_hook(msg):
148+
nonlocal called
149+
if msg['header']['msg_type'] == 'stream':
150+
called = True
151+
152+
reply = await kc.execute_interactive(
153+
"print('hello')", timeout=TIMEOUT, output_hook=output_hook
154+
)
155+
assert reply["content"]["status"] == "ok"
156+
assert called
157+
131158
async def test_history(self, kc):
132159
msg_id = await kc.history(session=0)
133160
assert isinstance(msg_id, str)
@@ -146,6 +173,12 @@ async def test_complete(self, kc):
146173
reply = await kc.complete("code", reply=True, timeout=TIMEOUT)
147174
self._check_reply("complete", reply)
148175

176+
async def test_is_complete(self, kc):
177+
msg_id = await kc.is_complete("who cares")
178+
assert isinstance(msg_id, str)
179+
reply = await kc.is_complete("code", reply=True, timeout=TIMEOUT)
180+
self._check_reply("is_complete", reply)
181+
149182
async def test_kernel_info(self, kc):
150183
msg_id = await kc.kernel_info()
151184
assert isinstance(msg_id, str)
@@ -272,3 +305,10 @@ def test_shutdown_id(self):
272305
kc = self.kc
273306
msg_id = kc.shutdown()
274307
self.assertIsInstance(msg_id, str)
308+
309+
310+
def test_validate_string_dict():
311+
with pytest.raises(ValueError):
312+
validate_string_dict(dict(a=1))
313+
with pytest.raises(ValueError):
314+
validate_string_dict({1: 'a'})

tests/test_consoleapp.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Tests for the JupyterConsoleApp"""
2+
# Copyright (c) Jupyter Development Team.
3+
# Distributed under the terms of the Modified BSD License.
4+
import os
5+
6+
import pytest
7+
from jupyter_core.application import JupyterApp
8+
9+
from jupyter_client.consoleapp import JupyterConsoleApp
10+
from jupyter_client.manager import start_new_kernel
11+
12+
13+
class MockConsoleApp(JupyterConsoleApp, JupyterApp):
14+
pass
15+
16+
17+
def test_console_app_no_existing():
18+
app = MockConsoleApp()
19+
app.initialize([])
20+
21+
22+
def test_console_app_existing(tmp_path):
23+
km, kc = start_new_kernel()
24+
cf = kc.connection_file
25+
app = MockConsoleApp(connection_file=cf, existing=cf)
26+
app.initialize([])
27+
kc.stop_channels()
28+
km.shutdown_kernel()
29+
30+
31+
def test_console_app_ssh(tmp_path):
32+
km, kc = start_new_kernel()
33+
cf = kc.connection_file
34+
os.chdir(tmp_path)
35+
app = MockConsoleApp(
36+
connection_file=cf, existing=cf, sshserver="does_not_exist", sshkey="test_console_app"
37+
)
38+
with pytest.raises(SystemExit):
39+
app.initialize([])
40+
kc.stop_channels()
41+
km.shutdown_kernel()

tests/test_kernelspecapp.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Tests for the kernelspecapp"""
2+
# Copyright (c) Jupyter Development Team.
3+
# Distributed under the terms of the Modified BSD License.
4+
import os
5+
import warnings
6+
7+
from jupyter_client.kernelspecapp import InstallKernelSpec
8+
from jupyter_client.kernelspecapp import KernelSpecApp
9+
from jupyter_client.kernelspecapp import ListKernelSpecs
10+
from jupyter_client.kernelspecapp import ListProvisioners
11+
from jupyter_client.kernelspecapp import RemoveKernelSpec
12+
13+
14+
def test_kernelspec_sub_apps(kernel_spec):
15+
app = InstallKernelSpec()
16+
prefix = os.path.dirname(os.environ['JUPYTER_DATA_DIR'])
17+
kernel_dir = os.path.join(prefix, 'share/jupyter/kernels')
18+
app.kernel_spec_manager.kernel_dirs.append(kernel_dir)
19+
app.prefix = prefix = prefix
20+
app.initialize([kernel_spec])
21+
with warnings.catch_warnings():
22+
warnings.simplefilter("ignore")
23+
app.start()
24+
25+
app = ListKernelSpecs()
26+
app.kernel_spec_manager.kernel_dirs.append(kernel_dir)
27+
specs = app.start()
28+
assert 'test' in specs
29+
30+
app = RemoveKernelSpec(spec_names=['test'], force=True)
31+
app.kernel_spec_manager.kernel_dirs.append(kernel_dir)
32+
app.start()
33+
34+
app = ListKernelSpecs()
35+
app.kernel_spec_manager.kernel_dirs.append(kernel_dir)
36+
specs = app.start()
37+
assert 'test' not in specs
38+
39+
40+
def test_kernelspec_app():
41+
app = KernelSpecApp()
42+
app.initialize(["list"])
43+
app.start()
44+
45+
46+
def test_list_provisioners_app():
47+
app = ListProvisioners()
48+
app.initialize([])
49+
app.start()

0 commit comments

Comments
 (0)