Skip to content

Commit 4e0180c

Browse files
pauldrucePrabhakar Kumar
authored andcommitted
Adding unit tests for jupyter_matlab_proxy
1 parent b83aea0 commit 4e0180c

File tree

10 files changed

+487
-11
lines changed

10 files changed

+487
-11
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ htmlcov/
99
.coverage
1010
coverage.xml
1111
cov_html
12+
.python-version

src/jupyter_matlab_kernel/kernel.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ def start_matlab_proxy():
8080
# Stop iterating over the server list
8181
break
8282

83+
# Error out if the server is not found!
84+
if found_nb_server == False:
85+
raise MATLABConnectionError(
86+
"""
87+
Error: MATLAB Kernel for Jupyter was unable to find the notebook server from which it was spawned!\n
88+
Resolution: Please relaunch kernel from JupyterLab or Classic Jupyter Notebook.
89+
"""
90+
)
91+
8392
# Verify that Password is disabled
8493
if nb_server["password"] is True:
8594
# TODO: To support passwords, we either need to acquire it from Jupyter or ask the user?
@@ -92,15 +101,6 @@ def start_matlab_proxy():
92101
"""
93102
)
94103

95-
# Error out if the server is not found!
96-
if found_nb_server == False:
97-
raise MATLABConnectionError(
98-
"""
99-
Error: MATLAB Kernel for Jupyter was unable to find the notebook server from which it was spawned!\n
100-
Resolution: Please relaunch kernel from JupyterLab or Classic Jupyter Notebook.
101-
"""
102-
)
103-
104104
url = "{protocol}://localhost:{port}{base_url}matlab".format(
105105
protocol="https" if nb_server["secure"] else "http",
106106
port=nb_server["port"],

tests/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Testing Information for jupyter-matlab-proxy
2+
3+
The tests in this project are written using the [Pytest](https://docs.pytest.org/en/latest/) framework.
4+
5+
To run the tests in this project follow these steps:
6+
* From the root directory of this project, run the command `pip install ".[dev]"`
7+
* Run the command `pytest` to run all Python tests for this project.

tests/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Copyright 2020-2023 The MathWorks, Inc.
22

3-
# Pytest won't include files in the coverage metrics unless they are imported in the tests.
4-
# By importing the systems under test here, any files not hit by a test point are still included in the code coverage metrics.
3+
"""Force Pytest to included untested files in coverage calculations.
4+
5+
Pytest won't include files in the coverage metrics unless they are imported in
6+
the tests. By importing the systems under test here, any files not hit by a test
7+
point are still included in the code coverage metrics.
8+
"""
59
import jupyter_matlab_kernel
610
import jupyter_matlab_proxy
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright 2023 The MathWorks, Inc.
2+
"""Mock matlab-proxy HTTP Responses."""
3+
4+
import requests
5+
from requests.exceptions import HTTPError
6+
7+
8+
class MockUnauthorisedRequestResponse:
9+
"""
10+
Emulates an unauthorized request to matlab-proxy.
11+
12+
Raises:
13+
HTTPError: with code unauthorized.
14+
"""
15+
16+
exception_msg = "Mock exception thrown due to unauthorized request status."
17+
status_code = requests.codes.unauthorized
18+
19+
def raise_for_status(self):
20+
"""Raise a HTTPError with unauthorised request message."""
21+
raise HTTPError(self.exception_msg)
22+
23+
24+
class MockMatlabProxyStatusResponse:
25+
"""A mock of a matlab-proxy status response."""
26+
27+
def __init__(self, is_licensed, matlab_status, has_error) -> None:
28+
"""Construct a mock matlab-proxy status response.
29+
30+
Args:
31+
is_licensed (bool): indicates if MATLAB is licensed.
32+
matlab_status (string): indicates the MATLAB status, i.e. is it "starting", "running" etc.
33+
has_error (bool): indicates if there is an error with MATLAB
34+
"""
35+
self.licensed = is_licensed
36+
self.matlab_status = matlab_status
37+
self.error = has_error
38+
39+
status_code = requests.codes.ok
40+
41+
def json(self):
42+
"""Return a matlab-proxy status JSON object."""
43+
return {
44+
"licensing": self.licensed,
45+
"matlab": {"status": self.matlab_status},
46+
"error": self.error,
47+
}
48+
49+
50+
class MockSimpleOkResponse:
51+
"""A mock of a successful http request that returns empty json."""
52+
53+
status_code = requests.codes.ok
54+
55+
@staticmethod
56+
def json():
57+
"""Return an empty JSON struct."""
58+
return {}
59+
60+
61+
class MockSimpleBadResponse:
62+
"""A mock of a bad https request."""
63+
64+
def __init__(self, error_message: str) -> None:
65+
"""Construct a mock bad http requests.
66+
67+
Args:
68+
error_message (str): the mock will raise a HTTPError with this error message.
69+
"""
70+
self.error_message = error_message
71+
72+
status_code = requests.codes.bad
73+
74+
def raise_for_status(self):
75+
"""Raise a HTTPError with custom error message."""
76+
raise HTTPError(self.error_message)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright 2023 The MathWorks, Inc.
2+
"""Mocks matlab-proxy integration with Jupyter Server.
3+
4+
This module provides a pytest fixture that mocks how matlab-proxy integrates
5+
with Jupyter server.
6+
"""
7+
8+
9+
import pytest
10+
11+
import requests
12+
import os
13+
from jupyter_server import serverapp
14+
15+
PID = "server 1"
16+
PORT = "1234/"
17+
BASE_URL = "server_of_nb/"
18+
SECURE = False
19+
TEST_TOKEN = "test_token"
20+
LICENSING = True
21+
AUTHORISED_HEADERS = {"Authorization": "token test_token"}
22+
PASSWORD = ""
23+
24+
25+
@pytest.fixture
26+
def MockJupyterServerFixture(monkeypatch):
27+
"""Mock the matlab-proxy integration with JupyterServer.
28+
29+
This fixture provides the mocked calls to emulate that an instance of matlab proxy
30+
is running.
31+
"""
32+
33+
def fake_getppid():
34+
return PID
35+
36+
def fake_list_running_servers(*args, **kwargs):
37+
return [
38+
{
39+
"pid": PID,
40+
"port": PORT,
41+
"base_url": BASE_URL,
42+
"secure": SECURE,
43+
"token": TEST_TOKEN,
44+
"password": PASSWORD,
45+
}
46+
]
47+
48+
class MockResponse:
49+
status_code = requests.codes.ok
50+
text = "MWI_MATLAB_PROXY_IDENTIFIER"
51+
52+
@staticmethod
53+
def json():
54+
return {
55+
"licensing": LICENSING,
56+
"matlab": {"status": "up"},
57+
"error": False,
58+
}
59+
60+
def mock_get(*args, **kwargs):
61+
return MockResponse()
62+
63+
monkeypatch.setattr(serverapp, "list_running_servers", fake_list_running_servers)
64+
monkeypatch.setattr(os, "getppid", fake_getppid)
65+
monkeypatch.setattr(requests, "get", mock_get)
66+
yield
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright 2023 The MathWorks, Inc.
2+
3+
# This file contains tests for jupyter_matlab_kernel.kernel
4+
from jupyter_matlab_kernel.kernel import (
5+
start_matlab_proxy,
6+
MATLABConnectionError,
7+
)
8+
9+
import pytest
10+
from jupyter_server import serverapp
11+
from mocks.mock_jupyter_server import MockJupyterServerFixture
12+
import mocks.mock_jupyter_server as MockJupyterServer
13+
14+
15+
def test_start_matlab_proxy_without_jupyter_server():
16+
"""
17+
This test checks that trying to start matlab-proxy outside of a Jupyter environment
18+
raises an execption.
19+
"""
20+
with pytest.raises(MATLABConnectionError) as exceptionInfo:
21+
start_matlab_proxy()
22+
23+
assert (
24+
"MATLAB Kernel for Jupyter was unable to find the notebook server from which it was spawned!"
25+
in str(exceptionInfo.value)
26+
)
27+
28+
29+
def test_start_matlab_proxy(MockJupyterServerFixture):
30+
"""
31+
This test checks start_matlab_proxy returns a correctly configured URL
32+
"""
33+
34+
url, server, headers = start_matlab_proxy()
35+
assert server == MockJupyterServer.BASE_URL
36+
assert headers == MockJupyterServer.AUTHORISED_HEADERS
37+
expected_url = (
38+
"http://localhost:"
39+
+ MockJupyterServer.PORT
40+
+ MockJupyterServer.BASE_URL
41+
+ "matlab"
42+
)
43+
assert url == expected_url
44+
45+
46+
def test_start_matlab_proxy_secure(monkeypatch, MockJupyterServerFixture):
47+
"""
48+
This test checks that start_matlab_proxy returns a HTTPS url if configured
49+
to do so.
50+
"""
51+
52+
def fake_list_running_servers(*args, **kwargs):
53+
return [
54+
{
55+
"pid": MockJupyterServer.PID,
56+
"port": MockJupyterServer.PORT,
57+
"base_url": MockJupyterServer.BASE_URL,
58+
"secure": True,
59+
"token": MockJupyterServer.TEST_TOKEN,
60+
"password": MockJupyterServer.PASSWORD,
61+
}
62+
]
63+
64+
monkeypatch.setattr(serverapp, "list_running_servers", fake_list_running_servers)
65+
66+
url, _, _ = start_matlab_proxy()
67+
expected_url = (
68+
"https://localhost:"
69+
+ MockJupyterServer.PORT
70+
+ MockJupyterServer.BASE_URL
71+
+ "matlab"
72+
)
73+
assert url == expected_url
74+
75+
76+
def test_start_matlab_proxy_jh_api_token(monkeypatch, MockJupyterServerFixture):
77+
"""
78+
The test checks that start_matlab_proxy makes use of the environment variable
79+
JUPYTERHUB_API_TOKEN if it is set.
80+
"""
81+
82+
monkeypatch.setattr(MockJupyterServer, "TEST_TOKEN", None)
83+
84+
monkeypatch.setenv("JUPYTERHUB_API_TOKEN", "test_jh_token")
85+
_, _, headers = start_matlab_proxy()
86+
assert headers == {"Authorization": "token test_jh_token"}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2023 The MathWorks, Inc.
2+
import sys
3+
from jupyter_client import kernelspec
4+
import pytest
5+
import os, shutil
6+
7+
TEST_KERNEL_NAME = "jupyter_matlab_kernel_test"
8+
9+
10+
@pytest.fixture
11+
def UninstallKernel():
12+
yield
13+
kernel_list = kernelspec.find_kernel_specs()
14+
kernel_path = kernel_list.get(TEST_KERNEL_NAME)
15+
if kernel_path != None and os.path.exists(kernel_path):
16+
shutil.rmtree(kernel_path)
17+
18+
19+
def test_matlab_kernel_registration(UninstallKernel):
20+
"""This test checks that the kernel.json file can be installed by JupyterLab."""
21+
22+
kernelspec.install_kernel_spec(
23+
"./src/jupyter_matlab_kernel",
24+
kernel_name=TEST_KERNEL_NAME,
25+
prefix=sys.prefix,
26+
)
27+
28+
kernel_list = kernelspec.find_kernel_specs()
29+
30+
assert TEST_KERNEL_NAME in kernel_list

0 commit comments

Comments
 (0)