matlab-proxy 0.17.0__py3-none-any.whl → 0.18.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of matlab-proxy might be problematic. Click here for more details.
- matlab_proxy/app.py +10 -8
- matlab_proxy/app_state.py +27 -4
- matlab_proxy/constants.py +1 -0
- matlab_proxy/matlab/evaluateUserMatlabCode.m +51 -0
- matlab_proxy/matlab/startup.m +2 -2
- matlab_proxy/settings.py +18 -2
- matlab_proxy/util/__init__.py +16 -7
- matlab_proxy/util/mwi/environment_variables.py +5 -0
- matlab_proxy/util/mwi/validators.py +13 -11
- matlab_proxy/util/windows.py +6 -2
- {matlab_proxy-0.17.0.dist-info → matlab_proxy-0.18.1.dist-info}/METADATA +4 -2
- {matlab_proxy-0.17.0.dist-info → matlab_proxy-0.18.1.dist-info}/RECORD +21 -19
- tests/integration/integration_tests_with_license/test_http_end_points.py +217 -42
- tests/unit/test_app_state.py +34 -0
- tests/unit/test_settings.py +35 -0
- tests/unit/util/mwi/test_download.py +152 -0
- tests/unit/util/test_util.py +4 -1
- {matlab_proxy-0.17.0.dist-info → matlab_proxy-0.18.1.dist-info}/LICENSE.md +0 -0
- {matlab_proxy-0.17.0.dist-info → matlab_proxy-0.18.1.dist-info}/WHEEL +0 -0
- {matlab_proxy-0.17.0.dist-info → matlab_proxy-0.18.1.dist-info}/entry_points.txt +0 -0
- {matlab_proxy-0.17.0.dist-info → matlab_proxy-0.18.1.dist-info}/top_level.txt +0 -0
matlab_proxy/app.py
CHANGED
|
@@ -16,12 +16,11 @@ from cryptography import fernet
|
|
|
16
16
|
import matlab_proxy
|
|
17
17
|
from matlab_proxy import constants, settings, util
|
|
18
18
|
from matlab_proxy.app_state import AppState
|
|
19
|
+
from matlab_proxy.constants import IS_CONCURRENCY_CHECK_ENABLED
|
|
19
20
|
from matlab_proxy.util import mwi
|
|
21
|
+
from matlab_proxy.util.mwi import download, token_auth
|
|
20
22
|
from matlab_proxy.util.mwi import environment_variables as mwi_env
|
|
21
|
-
from matlab_proxy.util.mwi import token_auth, download
|
|
22
23
|
from matlab_proxy.util.mwi.exceptions import AppError, InvalidTokenError, LicensingError
|
|
23
|
-
from matlab_proxy.constants import IS_CONCURRENCY_CHECK_ENABLED
|
|
24
|
-
|
|
25
24
|
|
|
26
25
|
mimetypes.add_type("font/woff", ".woff")
|
|
27
26
|
mimetypes.add_type("font/woff2", ".woff2")
|
|
@@ -422,7 +421,7 @@ async def termination_integration_delete(req):
|
|
|
422
421
|
await req.app.cleanup()
|
|
423
422
|
"""When testing with pytest, its not possible to catch sys.exit(0) using the construct
|
|
424
423
|
'with pytest.raises()', there by causing the test : test_termination_integration_delete()
|
|
425
|
-
to fail.
|
|
424
|
+
to fail. In order to avoid this, adding the below if condition to check to skip sys.exit(0) when testing
|
|
426
425
|
"""
|
|
427
426
|
logger.debug("Exiting with return code 0")
|
|
428
427
|
if not mwi_env.is_testing_mode_enabled():
|
|
@@ -475,7 +474,7 @@ def make_static_route_table(app):
|
|
|
475
474
|
Returns:
|
|
476
475
|
Dict: Containing information about the static files and header information.
|
|
477
476
|
"""
|
|
478
|
-
|
|
477
|
+
import importlib_resources
|
|
479
478
|
|
|
480
479
|
from matlab_proxy import gui
|
|
481
480
|
from matlab_proxy.gui import static
|
|
@@ -492,8 +491,9 @@ def make_static_route_table(app):
|
|
|
492
491
|
(gui.static.js.__name__, "/static/js"),
|
|
493
492
|
(gui.static.media.__name__, "/static/media"),
|
|
494
493
|
]:
|
|
495
|
-
for
|
|
496
|
-
|
|
494
|
+
for entry in importlib_resources.files(mod).iterdir():
|
|
495
|
+
name = entry.name
|
|
496
|
+
if not importlib_resources.files(mod).joinpath(name).is_dir():
|
|
497
497
|
if name != "__init__.py":
|
|
498
498
|
# Special case for manifest.json
|
|
499
499
|
if "manifest.json" in name:
|
|
@@ -624,10 +624,12 @@ async def matlab_view(req):
|
|
|
624
624
|
|
|
625
625
|
# Standard HTTP Request
|
|
626
626
|
else:
|
|
627
|
-
# Proxy, injecting request header
|
|
627
|
+
# Proxy, injecting request header, disabling request timeouts
|
|
628
|
+
timeout = aiohttp.ClientTimeout(total=None)
|
|
628
629
|
async with aiohttp.ClientSession(
|
|
629
630
|
trust_env=True,
|
|
630
631
|
connector=aiohttp.TCPConnector(verify_ssl=False),
|
|
632
|
+
timeout=timeout,
|
|
631
633
|
) as client_session:
|
|
632
634
|
try:
|
|
633
635
|
req_body = await transform_body(req)
|
matlab_proxy/app_state.py
CHANGED
|
@@ -16,10 +16,11 @@ from matlab_proxy.constants import (
|
|
|
16
16
|
CONNECTOR_SECUREPORT_FILENAME,
|
|
17
17
|
MATLAB_LOGS_FILE_NAME,
|
|
18
18
|
IS_CONCURRENCY_CHECK_ENABLED,
|
|
19
|
+
USER_CODE_OUTPUT_FILE_NAME,
|
|
19
20
|
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
from matlab_proxy.settings import get_process_startup_timeout
|
|
23
|
+
|
|
23
24
|
from matlab_proxy.util import mw, mwi, system, windows
|
|
24
25
|
from matlab_proxy.util.mwi import environment_variables as mwi_env
|
|
25
26
|
from matlab_proxy.util.mwi import token_auth
|
|
@@ -594,6 +595,24 @@ class AppState:
|
|
|
594
595
|
self.matlab_session_files["matlab_ready_file"] = matlab_ready_file
|
|
595
596
|
|
|
596
597
|
logger.debug(f"matlab_session_files:{self.matlab_session_files}")
|
|
598
|
+
|
|
599
|
+
# check if the user has provided any code or not
|
|
600
|
+
if self.settings.get("has_custom_code_to_execute"):
|
|
601
|
+
# Keep a reference to the user code output file in the matlab_session_files for cleanup
|
|
602
|
+
user_code_output_file = mwi_logs_dir / USER_CODE_OUTPUT_FILE_NAME
|
|
603
|
+
self.matlab_session_files["startup_code_output_file"] = (
|
|
604
|
+
user_code_output_file
|
|
605
|
+
)
|
|
606
|
+
logger.info(
|
|
607
|
+
util.prettify(
|
|
608
|
+
boundary_filler="*",
|
|
609
|
+
text_arr=[
|
|
610
|
+
f"When MATLAB starts, you can see the output for your startup code at:",
|
|
611
|
+
f"{self.matlab_session_files.get('startup_code_output_file', ' ')}",
|
|
612
|
+
],
|
|
613
|
+
)
|
|
614
|
+
)
|
|
615
|
+
|
|
597
616
|
return
|
|
598
617
|
|
|
599
618
|
def create_server_info_file(self):
|
|
@@ -846,6 +865,10 @@ class AppState:
|
|
|
846
865
|
|
|
847
866
|
return matlab
|
|
848
867
|
|
|
868
|
+
except UIVisibleFatalError as e:
|
|
869
|
+
self.error = e
|
|
870
|
+
log_error(logger, e)
|
|
871
|
+
|
|
849
872
|
except Exception as err:
|
|
850
873
|
self.error = err
|
|
851
874
|
log_error(logger, err)
|
|
@@ -972,7 +995,7 @@ class AppState:
|
|
|
972
995
|
)
|
|
973
996
|
await self.stop_matlab(force_quit=True)
|
|
974
997
|
self.error = MatlabError(
|
|
975
|
-
"
|
|
998
|
+
"MATLAB startup has timed out. Click Start MATLAB to try again."
|
|
976
999
|
)
|
|
977
1000
|
|
|
978
1001
|
async def __read_matlab_ready_file(self, delay):
|
matlab_proxy/constants.py
CHANGED
|
@@ -7,6 +7,7 @@ CONNECTOR_SECUREPORT_FILENAME: Final[str] = "connector.securePort"
|
|
|
7
7
|
VERSION_INFO_FILE_NAME: Final[str] = "VersionInfo.xml"
|
|
8
8
|
MAX_HTTP_REQUEST_SIZE: Final[int] = 500_000_000 # 500MB
|
|
9
9
|
MATLAB_LOGS_FILE_NAME: Final[str] = "matlab_logs.txt"
|
|
10
|
+
USER_CODE_OUTPUT_FILE_NAME: Final[str] = "startup_code_output.txt"
|
|
10
11
|
|
|
11
12
|
# Max startup duration in seconds for processes launched by matlab-proxy
|
|
12
13
|
# This constant is meant for internal use within matlab-proxy
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
% Copyright 2024 The MathWorks, Inc.
|
|
2
|
+
|
|
3
|
+
% Note:
|
|
4
|
+
% Any extra variable we are creating begins with `mwiInternal` to prevent
|
|
5
|
+
% potential conflicts with variables created by user code evaluated using evalc.
|
|
6
|
+
% Since evalc("user code") is executed in the base workspace, it might create
|
|
7
|
+
% variables that could overwrite our internal variables. To avoid polluting the
|
|
8
|
+
% user's workspace when MATLAB starts, we ensure to clear any internal variable
|
|
9
|
+
% that we create in the base workspace. We do not need to be concerned about
|
|
10
|
+
% variables in the function's workspace.
|
|
11
|
+
|
|
12
|
+
if ~isempty(getenv('MWI_MATLAB_STARTUP_SCRIPT')) && ~all(isspace(getenv('MWI_MATLAB_STARTUP_SCRIPT')))
|
|
13
|
+
try
|
|
14
|
+
% Evaluate the code from the environment variable and capture the output
|
|
15
|
+
mwiInternalResults = evalc(getenv('MWI_MATLAB_STARTUP_SCRIPT'));
|
|
16
|
+
% Write the results to the file
|
|
17
|
+
logOutputOrError(mwiInternalResults);
|
|
18
|
+
clear mwiInternalResults;
|
|
19
|
+
catch mwiInternalException
|
|
20
|
+
% Log the error message to the file
|
|
21
|
+
logOutputOrError(" ", mwiInternalException);
|
|
22
|
+
clear mwiInternalResults mwiInternalException;
|
|
23
|
+
error("Unable to run the startup code you specified. For details of the error, see the output file at " + fullfile(getenv('MATLAB_LOG_DIR'), "startup_code_output.txt"));
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
function logOutputOrError(userCodeResults, mwiInternalException)
|
|
29
|
+
% Logs the results of the user code execution if successful, otherwise logs the
|
|
30
|
+
% error information. It then closes the file handle.
|
|
31
|
+
%
|
|
32
|
+
% Inputs:
|
|
33
|
+
% userCodeResults - String containing the output from the user code.
|
|
34
|
+
% mwiInternalException - (Optional) MException object containing error details.
|
|
35
|
+
filePath = fullfile(getenv('MATLAB_LOG_DIR'), "startup_code_output.txt");
|
|
36
|
+
[fileHandle, ~] = fopen(filePath, 'w');
|
|
37
|
+
if nargin < 2
|
|
38
|
+
% Log the successful output of the user code
|
|
39
|
+
fprintf(fileHandle, " ");
|
|
40
|
+
fprintf(fileHandle, userCodeResults);
|
|
41
|
+
else
|
|
42
|
+
% Log the error information
|
|
43
|
+
fprintf(fileHandle, 'An error occurred in the following code:\n');
|
|
44
|
+
fprintf(fileHandle, getenv('MWI_MATLAB_STARTUP_SCRIPT'));
|
|
45
|
+
fprintf(fileHandle, '\n\nMessage: %s\n', mwiInternalException.message);
|
|
46
|
+
fprintf(fileHandle, '\nError Identifier: %s\n', mwiInternalException.identifier);
|
|
47
|
+
end
|
|
48
|
+
% Close the file handle
|
|
49
|
+
fclose(fileHandle);
|
|
50
|
+
end
|
|
51
|
+
|
matlab_proxy/matlab/startup.m
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
% Copyright
|
|
1
|
+
% Copyright 2020-2024 The MathWorks, Inc.
|
|
2
2
|
|
|
3
3
|
% Configure logged in user if possible
|
|
4
4
|
if ~isempty(getenv('MW_LOGIN_USER_ID'))
|
|
@@ -34,4 +34,4 @@ end
|
|
|
34
34
|
matlab_settings.matlab.addons.explorer.isExplorerSupported.TemporaryValue = false;
|
|
35
35
|
|
|
36
36
|
clear
|
|
37
|
-
clc
|
|
37
|
+
clc
|
matlab_proxy/settings.py
CHANGED
|
@@ -366,7 +366,10 @@ def get_matlab_settings():
|
|
|
366
366
|
flag_to_hide_desktop = ["-nodesktop"]
|
|
367
367
|
if system.is_windows():
|
|
368
368
|
flag_to_hide_desktop.extend(["-noDisplayDesktop", "-wait", "-log"])
|
|
369
|
-
|
|
369
|
+
|
|
370
|
+
matlab_code_dir = Path(__file__).resolve().parent / "matlab"
|
|
371
|
+
matlab_startup_file = str(matlab_code_dir / "startup.m")
|
|
372
|
+
matlab_code_file = str(matlab_code_dir / "evaluateUserMatlabCode.m")
|
|
370
373
|
|
|
371
374
|
matlab_version = get_matlab_version(matlab_root_path)
|
|
372
375
|
|
|
@@ -388,6 +391,18 @@ def get_matlab_settings():
|
|
|
388
391
|
profile_matlab_startup = (
|
|
389
392
|
"-timing" if mwi_env.Experimental.is_matlab_startup_profiling_enabled() else ""
|
|
390
393
|
)
|
|
394
|
+
|
|
395
|
+
has_custom_code_to_execute = (
|
|
396
|
+
len(os.getenv(mwi_env.get_env_name_custom_matlab_code(), "").strip()) > 0
|
|
397
|
+
)
|
|
398
|
+
mp_code_to_execute = f"try; run('{matlab_startup_file}'); catch MATLABProxyInitializationError; disp(MATLABProxyInitializationError.message); end;"
|
|
399
|
+
custom_code_to_execute = f"try; run('{matlab_code_file}'); catch MATLABCustomStartupCodeError; disp(MATLABCustomStartupCodeError.message); end;"
|
|
400
|
+
code_to_execute = (
|
|
401
|
+
mp_code_to_execute + custom_code_to_execute
|
|
402
|
+
if has_custom_code_to_execute
|
|
403
|
+
else mp_code_to_execute
|
|
404
|
+
)
|
|
405
|
+
|
|
391
406
|
return {
|
|
392
407
|
"matlab_path": matlab_root_path,
|
|
393
408
|
"matlab_version": matlab_version,
|
|
@@ -402,13 +417,14 @@ def get_matlab_settings():
|
|
|
402
417
|
*mpa_flags,
|
|
403
418
|
profile_matlab_startup,
|
|
404
419
|
"-r",
|
|
405
|
-
|
|
420
|
+
code_to_execute,
|
|
406
421
|
],
|
|
407
422
|
"ws_env": ws_env,
|
|
408
423
|
"mwa_api_endpoint": f"https://login{ws_env_suffix}.mathworks.com/authenticationws/service/v4",
|
|
409
424
|
"mhlm_api_endpoint": f"https://licensing{ws_env_suffix}.mathworks.com/mls/service/v1/entitlement/list",
|
|
410
425
|
"mwa_login": f"https://login{ws_env_suffix}.mathworks.com",
|
|
411
426
|
"nlm_conn_str": nlm_conn_str,
|
|
427
|
+
"has_custom_code_to_execute": has_custom_code_to_execute,
|
|
412
428
|
}
|
|
413
429
|
|
|
414
430
|
|
matlab_proxy/util/__init__.py
CHANGED
|
@@ -11,6 +11,9 @@ import matlab_proxy
|
|
|
11
11
|
from matlab_proxy.util import mwi, system
|
|
12
12
|
from matlab_proxy.util.event_loop import *
|
|
13
13
|
from matlab_proxy.util.mwi import environment_variables as mwi_env
|
|
14
|
+
from matlab_proxy.util.mwi.exceptions import (
|
|
15
|
+
UIVisibleFatalError,
|
|
16
|
+
)
|
|
14
17
|
|
|
15
18
|
logger = mwi.logger.get()
|
|
16
19
|
|
|
@@ -181,7 +184,7 @@ def prettify(boundary_filler=" ", text_arr=[]):
|
|
|
181
184
|
return result
|
|
182
185
|
|
|
183
186
|
|
|
184
|
-
def get_child_processes(parent_process):
|
|
187
|
+
def get_child_processes(parent_process, max_attempts=10, sleep_interval=1):
|
|
185
188
|
"""Get list of child processes from a parent process.
|
|
186
189
|
|
|
187
190
|
Args:
|
|
@@ -199,7 +202,6 @@ def get_child_processes(parent_process):
|
|
|
199
202
|
# to get hold child processes
|
|
200
203
|
parent_process_psutil = psutil.Process(parent_process.pid)
|
|
201
204
|
|
|
202
|
-
max_attempts = 10
|
|
203
205
|
child_processes = None
|
|
204
206
|
for _ in range(max_attempts):
|
|
205
207
|
try:
|
|
@@ -212,17 +214,24 @@ def get_child_processes(parent_process):
|
|
|
212
214
|
|
|
213
215
|
if not child_processes:
|
|
214
216
|
logger.debug("Waiting for the child processes to be created...")
|
|
217
|
+
time.sleep(sleep_interval)
|
|
215
218
|
continue
|
|
216
219
|
|
|
220
|
+
else:
|
|
221
|
+
logger.debug(f"Found the child process: {child_processes[0]}")
|
|
222
|
+
break
|
|
223
|
+
|
|
217
224
|
except AssertionError as err:
|
|
218
225
|
raise err
|
|
219
226
|
|
|
220
|
-
if child_processes:
|
|
221
|
-
break
|
|
222
|
-
time.sleep(0.1)
|
|
223
|
-
|
|
224
227
|
if not child_processes:
|
|
225
|
-
|
|
228
|
+
logger.debug(
|
|
229
|
+
f"MATLAB process was not found while searching for the child processes."
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
raise UIVisibleFatalError(
|
|
233
|
+
"Unable to create MATLAB process. Click Start MATLAB to try again."
|
|
234
|
+
)
|
|
226
235
|
|
|
227
236
|
return child_processes
|
|
228
237
|
|
|
@@ -162,6 +162,11 @@ def get_env_name_process_startup_timeout():
|
|
|
162
162
|
return "MWI_PROCESS_START_TIMEOUT"
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
def get_env_name_custom_matlab_code():
|
|
166
|
+
"""User specified MATLAB code that will be executed by matlab-proxy upon its start"""
|
|
167
|
+
return "MWI_MATLAB_STARTUP_SCRIPT"
|
|
168
|
+
|
|
169
|
+
|
|
165
170
|
class Experimental:
|
|
166
171
|
"""This class houses functions which are undocumented APIs and Environment variables.
|
|
167
172
|
Note: Never add any state to this class. Its only intended for use as an abstraction layer
|
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
# Copyright 2020-2024 The MathWorks, Inc.
|
|
2
2
|
"""This file contains validators for various runtime artifacts.
|
|
3
|
-
A validator is defined as a function which verifies the input and
|
|
4
|
-
returns it unchanged if validation passes.
|
|
3
|
+
A validator is defined as a function which verifies the input and
|
|
4
|
+
returns it unchanged if validation passes.
|
|
5
5
|
Returning inputs allows validators to be used inline with the input.
|
|
6
6
|
|
|
7
|
-
Example:
|
|
7
|
+
Example:
|
|
8
8
|
Original code: if( input ):
|
|
9
9
|
With validator: if (valid(input)):
|
|
10
10
|
|
|
11
11
|
Exceptions are thrown to signal failure.
|
|
12
12
|
"""
|
|
13
|
+
|
|
13
14
|
import errno
|
|
14
15
|
import os
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
import pkg_resources
|
|
17
16
|
import socket
|
|
17
|
+
from pathlib import Path
|
|
18
18
|
from typing import List
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
import matlab_proxy
|
|
22
21
|
from matlab_proxy import util
|
|
23
|
-
from matlab_proxy.util import system
|
|
24
22
|
from matlab_proxy.constants import VERSION_INFO_FILE_NAME
|
|
23
|
+
from matlab_proxy.util import system
|
|
25
24
|
|
|
26
25
|
from . import environment_variables as mwi_env
|
|
27
26
|
from . import logger as mwi_logger
|
|
28
|
-
from .exceptions import
|
|
27
|
+
from .exceptions import FatalError, MatlabInstallError
|
|
29
28
|
|
|
30
29
|
logger = mwi_logger.get()
|
|
31
30
|
|
|
@@ -213,10 +212,13 @@ def __get_configs():
|
|
|
213
212
|
Dict: Contains all the values present in 'matlab_web_desktop_configs' entry_point from all the packages
|
|
214
213
|
installed in the current environment.
|
|
215
214
|
"""
|
|
215
|
+
import importlib_metadata
|
|
216
|
+
|
|
217
|
+
matlab_proxy_eps = importlib_metadata.entry_points(
|
|
218
|
+
group=matlab_proxy.get_entrypoint_name()
|
|
219
|
+
)
|
|
216
220
|
configs = {}
|
|
217
|
-
for entry_point in
|
|
218
|
-
matlab_proxy.get_entrypoint_name()
|
|
219
|
-
):
|
|
221
|
+
for entry_point in matlab_proxy_eps:
|
|
220
222
|
configs[entry_point.name.lower()] = entry_point.load()
|
|
221
223
|
|
|
222
224
|
return configs
|
matlab_proxy/util/windows.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
# Copyright 2022-
|
|
1
|
+
# Copyright 2022-2024 The MathWorks, Inc.
|
|
2
2
|
import asyncio
|
|
3
3
|
|
|
4
4
|
from matlab_proxy import util
|
|
5
5
|
from matlab_proxy.util import mwi
|
|
6
6
|
from matlab_proxy.util.mwi import environment_variables as mwi_env
|
|
7
|
+
from matlab_proxy.util.mwi.exceptions import (
|
|
8
|
+
UIVisibleFatalError,
|
|
9
|
+
)
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
""" This file contains methods specific to non-posix / windows OS.
|
|
@@ -77,8 +80,9 @@ async def start_matlab(matlab_cmd, matlab_env):
|
|
|
77
80
|
"MATLAB.exe" == matlab.name()
|
|
78
81
|
), "Expecting the child process name to be MATLAB.exe"
|
|
79
82
|
|
|
80
|
-
except AssertionError as err:
|
|
83
|
+
except (AssertionError, UIVisibleFatalError) as err:
|
|
81
84
|
raise err
|
|
85
|
+
|
|
82
86
|
except psutil.NoSuchProcess:
|
|
83
87
|
# We reach here when the intermediate process launched by matlab-proxy died
|
|
84
88
|
# before we can query for its child processes. Hence, to find the actual MATLAB
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: matlab-proxy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.1
|
|
4
4
|
Summary: Python® package enables you to launch MATLAB® and access it from a web browser.
|
|
5
5
|
Home-page: https://github.com/mathworks/matlab-proxy/
|
|
6
6
|
Author: The MathWorks, Inc.
|
|
@@ -18,8 +18,10 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
18
|
Requires-Python: ~=3.8
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
Requires-Dist: aiohttp >=3.7.4
|
|
21
|
-
Requires-Dist: psutil
|
|
22
21
|
Requires-Dist: aiohttp-session[secure]
|
|
22
|
+
Requires-Dist: importlib-metadata
|
|
23
|
+
Requires-Dist: importlib-resources
|
|
24
|
+
Requires-Dist: psutil
|
|
23
25
|
Provides-Extra: dev
|
|
24
26
|
Requires-Dist: aiohttp-devtools ; extra == 'dev'
|
|
25
27
|
Requires-Dist: black ; extra == 'dev'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
matlab_proxy/__init__.py,sha256=6cwi8buKCMtw9OeWaOYUHEoqwl5MyJ_s6GxgNuqPuNg,1673
|
|
2
|
-
matlab_proxy/app.py,sha256=
|
|
3
|
-
matlab_proxy/app_state.py,sha256
|
|
4
|
-
matlab_proxy/constants.py,sha256=
|
|
2
|
+
matlab_proxy/app.py,sha256=ri2Dm3G3uVmIhoEV01awazsO4t3Z92LEwin657bq8bw,34538
|
|
3
|
+
matlab_proxy/app_state.py,sha256=i5AVaNroFSld4Y36BNil5lwS0PNrHnceYq7swXY5AI4,60502
|
|
4
|
+
matlab_proxy/constants.py,sha256=CrbIA098b5LMsqxY7nbap0_tqA2tIrIckGAffTgIkrA,1039
|
|
5
5
|
matlab_proxy/default_configuration.py,sha256=DxQaHzAivzstiPl_nDfxs8SOyP9oaK9v3RP4LtroJl4,843
|
|
6
6
|
matlab_proxy/devel.py,sha256=nR6XPVBUEdQ-RZGtYvX1YHTp8gj9cuw5Hp8ahasMBc8,14310
|
|
7
|
-
matlab_proxy/settings.py,sha256=
|
|
7
|
+
matlab_proxy/settings.py,sha256=I4nvFZzACKx-JBlAFCFWHA60gg66bR4hrXj1f5awfbA,25616
|
|
8
8
|
matlab_proxy/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
matlab_proxy/gui/asset-manifest.json,sha256=XqmLvi3DDOGSbBxNZwjuaNJXoYjZiG2HasxvZnEzPEo,3516
|
|
10
10
|
matlab_proxy/gui/favicon.ico,sha256=7w7Ki1uQP2Rgwc64dOV4-NrTu97I3WsZw8OvRSoY1A0,130876
|
|
@@ -52,28 +52,29 @@ matlab_proxy/gui/static/media/terminate.7ea1363ee0fa72344ef6.svg,sha256=LDLYNWk_
|
|
|
52
52
|
matlab_proxy/gui/static/media/trigger-error.3f1c4ef23ab8f3e60b0e.svg,sha256=3zzT0uTrl4M_HlAjjVy_9F6fJDY9TifdQCFYX36LCOY,5017
|
|
53
53
|
matlab_proxy/gui/static/media/trigger-ok.7b9c238be42f685c4fa7.svg,sha256=mD-7N9cc4ARdMBFcplnogJv6nA4Yh3jQuYbZDUi18LU,4997
|
|
54
54
|
matlab_proxy/icons/matlab.svg,sha256=xh5uYebQd8I-ISvenjU9A-PkClzW_lU9wvm3doXOFKM,13366
|
|
55
|
-
matlab_proxy/matlab/
|
|
56
|
-
matlab_proxy/
|
|
55
|
+
matlab_proxy/matlab/evaluateUserMatlabCode.m,sha256=R8w6nPdGtadR4UUFJaspcrGQL7cJwUItdrfc531w3bM,2420
|
|
56
|
+
matlab_proxy/matlab/startup.m,sha256=1hIvfWgBDcvM3wW__X2yaQ6cBwKjdh5eO0zycbJB78k,1555
|
|
57
|
+
matlab_proxy/util/__init__.py,sha256=rhBsXJT-5v67FuJ9Rz23yDT7afYfwrS5y_oMQSTbCwg,8456
|
|
57
58
|
matlab_proxy/util/event_loop.py,sha256=Zqd282jlvPHHyc4kg8IjIzlzh9zLM_SAc5xjqUOrm04,1144
|
|
58
59
|
matlab_proxy/util/list_servers.py,sha256=M93coVZjyQCdIvCCxsNOU_XDWNjBSysOJ5tWXaTjP8Y,1369
|
|
59
60
|
matlab_proxy/util/mw.py,sha256=dLGSdfcTZiwKR1MMZA-39o-8na13IEPZOGBqcaHmKVI,11086
|
|
60
61
|
matlab_proxy/util/system.py,sha256=XoT3Rv5MwPkdfhk2oMvUwxxlzZmADMlxzi9IRQyGgbA,1692
|
|
61
|
-
matlab_proxy/util/windows.py,sha256=
|
|
62
|
+
matlab_proxy/util/windows.py,sha256=J5O-wihOxEex43_AzwvFylNlN4hcZdO6KD5cpLv1FX8,3538
|
|
62
63
|
matlab_proxy/util/mwi/__init__.py,sha256=zI-X1lafr8H3j17PyA0oSZ0q5nINfK-WDA7VmJKmSAQ,158
|
|
63
64
|
matlab_proxy/util/mwi/custom_http_headers.py,sha256=kfDjSnEXEVzoF2pZuEn76LKayeD2WKoQEDu2Y9EMOAo,7154
|
|
64
65
|
matlab_proxy/util/mwi/download.py,sha256=-GJj3yOsL4vF_9baqRXkgBI-vu_OwjZMQVkJXFS8GMc,4965
|
|
65
|
-
matlab_proxy/util/mwi/environment_variables.py,sha256=
|
|
66
|
+
matlab_proxy/util/mwi/environment_variables.py,sha256=SC5pMCs2EVWd9NrvA8yYbCOm998SXDhQCIG1h8Ilysc,7309
|
|
66
67
|
matlab_proxy/util/mwi/exceptions.py,sha256=93HrHbOq24KI4Md2el23XN01wIqVShEK29Pbt2V0Jr8,4628
|
|
67
68
|
matlab_proxy/util/mwi/logger.py,sha256=e7wTPclrtJ-aX5mPk_pUJMX7-1QD_snGBW1P2ks-ETE,3311
|
|
68
69
|
matlab_proxy/util/mwi/token_auth.py,sha256=UbIWqo7qADaZdijFvorLYsZbxzaB8TycGP8nk305ru0,9997
|
|
69
|
-
matlab_proxy/util/mwi/validators.py,sha256=
|
|
70
|
+
matlab_proxy/util/mwi/validators.py,sha256=QEaP0N6U8BF4MglxrkM1phK0lWNiWBK7JdJbcfFRACY,11765
|
|
70
71
|
matlab_proxy/util/mwi/embedded_connector/__init__.py,sha256=SVSckEJ4zQQ2KkNPT_un8ndMAImcMOTrai7ShAbmFY4,114
|
|
71
72
|
matlab_proxy/util/mwi/embedded_connector/helpers.py,sha256=p6TedefbvhlZT64IMwFjrb0panWCXf-T3XPoztDbxM0,3391
|
|
72
73
|
matlab_proxy/util/mwi/embedded_connector/request.py,sha256=-6DL9K8JWjX5u5XVOEGaqBUIwQ-oCVW30-VP3qk_rzw,3730
|
|
73
74
|
tests/integration/__init__.py,sha256=ttzJ8xKWGxOJZz56qOiWOn6sp5LGomkZMn_w4KJLRMU,42
|
|
74
75
|
tests/integration/integration_tests_with_license/__init__.py,sha256=vVYZCur-QhmIGCxUmn-WZjIywtDQidaLDmlmrRHRlgY,37
|
|
75
76
|
tests/integration/integration_tests_with_license/conftest.py,sha256=sCaIXB8d4vf05C7JWSVA7g5gnPjbpRq3dftuBpWyp1s,1599
|
|
76
|
-
tests/integration/integration_tests_with_license/test_http_end_points.py,sha256=
|
|
77
|
+
tests/integration/integration_tests_with_license/test_http_end_points.py,sha256=51r3foIshg80NLgvsxaQUxlnnZiKs0O6gLvdF3Y7sWk,14448
|
|
77
78
|
tests/integration/integration_tests_without_license/__init__.py,sha256=vVYZCur-QhmIGCxUmn-WZjIywtDQidaLDmlmrRHRlgY,37
|
|
78
79
|
tests/integration/integration_tests_without_license/conftest.py,sha256=n-oppKWxavyy1O0J6DywO3DnOHuYc7yUZRXm3Bt4szU,3526
|
|
79
80
|
tests/integration/integration_tests_without_license/test_matlab_is_down_if_unlicensed.py,sha256=tkdyhfZBpfJpbnEzjURyV-GE0p43YxOa9xooJf-JoM4,1653
|
|
@@ -83,17 +84,18 @@ tests/integration/utils/licensing.py,sha256=rEBjvMXO8R3mL6KnePu2lojmOsjD4GXl9frf
|
|
|
83
84
|
tests/unit/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
|
|
84
85
|
tests/unit/conftest.py,sha256=Hfxq3h8IZuLJkRMh5jdEFiq78CIAdKvm-6KryRDZ0FY,1918
|
|
85
86
|
tests/unit/test_app.py,sha256=xiiEQu1iNK0h3Yuwn3c-hgvNhC1EXKFgTbE_7U3mSrs,37731
|
|
86
|
-
tests/unit/test_app_state.py,sha256=
|
|
87
|
+
tests/unit/test_app_state.py,sha256=qD2I7qfUwME_M8lCMsg62Kmky5xdCBav3r3em39Zkt0,22906
|
|
87
88
|
tests/unit/test_constants.py,sha256=ieDKc7bL8zWsd_D4dv2n8iftXr2h-bkS5p6zVan0BtQ,125
|
|
88
89
|
tests/unit/test_ddux.py,sha256=a2J2iM8j_nnfJVuMI38p5AjwrRdoMj3N88gFgS2I4hg,713
|
|
89
90
|
tests/unit/test_devel.py,sha256=A-1iVhSSwmywaW65QIRcUS2Fk7nJxceCcCm7CJtNdEc,7982
|
|
90
91
|
tests/unit/test_non_dev_mode.py,sha256=0v27y27SLOWvw6jf_GhLLNg-RMsZS_OyGAnqoQYPpSA,5515
|
|
91
|
-
tests/unit/test_settings.py,sha256=
|
|
92
|
+
tests/unit/test_settings.py,sha256=eBlV-ME_O8oLoOjJqqYTDTXJs-0smnM0oIMwHZEjUbo,17727
|
|
92
93
|
tests/unit/util/__init__.py,sha256=GInSQBb2SoVD5LZfmSCQa9-UZJT_UP-jNfrojkgCybU,87
|
|
93
94
|
tests/unit/util/test_mw.py,sha256=YC4mjn6G6_XuHELt8uW9F6g2K0_fWtQl1R0kWFvWbAo,18565
|
|
94
|
-
tests/unit/util/test_util.py,sha256=
|
|
95
|
+
tests/unit/util/test_util.py,sha256=vqTPgmaKDWhVBRnCKtCNg-OtyR5bP8jeH9DnpcbfVTk,5141
|
|
95
96
|
tests/unit/util/mwi/__init__.py,sha256=pl5jqyCHEwZEviiL8OC-SHulb1rBecstQCFF6qVjL9Y,37
|
|
96
97
|
tests/unit/util/mwi/test_custom_http_headers.py,sha256=UfrhclS0j6WhShtg1ki2oF1kK8JqRC29uevH4tuDqF4,11182
|
|
98
|
+
tests/unit/util/mwi/test_download.py,sha256=jYwPJFYGrPKqnkIJW42XYSe1fowmzChAkOx0k0xVldo,4779
|
|
97
99
|
tests/unit/util/mwi/test_logger.py,sha256=zWImNitMYKPJunXWJjEDEtCEKwBz615PC844ZLwoxIg,1845
|
|
98
100
|
tests/unit/util/mwi/test_token_auth.py,sha256=-eBsaQ5JC7pyd9PXt48Rqs4cWjg6I-eOkp_gFVEwYhk,10538
|
|
99
101
|
tests/unit/util/mwi/test_validators.py,sha256=YeOP0-T7SFNeiC7JIQj7cV4ja3d6PhswsTz27IEgJHQ,10852
|
|
@@ -102,9 +104,9 @@ tests/unit/util/mwi/embedded_connector/test_helpers.py,sha256=vYTWNUTuDeaygo16JG
|
|
|
102
104
|
tests/unit/util/mwi/embedded_connector/test_request.py,sha256=PR-jddnXDEiip-lD7A_QSvRwEkwo3eQ8owZlk-r9vnk,1867
|
|
103
105
|
tests/utils/__init__.py,sha256=ttzJ8xKWGxOJZz56qOiWOn6sp5LGomkZMn_w4KJLRMU,42
|
|
104
106
|
tests/utils/logging_util.py,sha256=VBy_NRvwau3C_CVTBjK5RMROrQimnJYHO2U0aKSZiRw,2234
|
|
105
|
-
matlab_proxy-0.
|
|
106
|
-
matlab_proxy-0.
|
|
107
|
-
matlab_proxy-0.
|
|
108
|
-
matlab_proxy-0.
|
|
109
|
-
matlab_proxy-0.
|
|
110
|
-
matlab_proxy-0.
|
|
107
|
+
matlab_proxy-0.18.1.dist-info/LICENSE.md,sha256=oF0h3UdSF-rlUiMGYwi086ZHqelzz7yOOk9HFDv9ELo,2344
|
|
108
|
+
matlab_proxy-0.18.1.dist-info/METADATA,sha256=Q5A3PKIyRDzKYk96M5XLsxeh6vHUIl4mDubWPAQvock,10177
|
|
109
|
+
matlab_proxy-0.18.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
110
|
+
matlab_proxy-0.18.1.dist-info/entry_points.txt,sha256=DbBLYgnRt8UGiOpd0zHigRTyyMdZYhMdvCvSYP7wPN0,244
|
|
111
|
+
matlab_proxy-0.18.1.dist-info/top_level.txt,sha256=9uVTjsUCAS4TwsxueTBxrBg3PdBiTSsYowAkHPv9VY0,19
|
|
112
|
+
matlab_proxy-0.18.1.dist-info/RECORD,,
|
|
@@ -1,27 +1,167 @@
|
|
|
1
1
|
# Copyright 2023-2024 The MathWorks, Inc.
|
|
2
2
|
|
|
3
|
+
"""
|
|
4
|
+
Contains integration tests which exercise HTTP endpoints of interest exposed by matlab-proxy-app
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Imports
|
|
8
|
+
from enum import Enum
|
|
3
9
|
import json
|
|
4
10
|
import os
|
|
5
|
-
import time
|
|
6
|
-
import matlab_proxy.settings as settings
|
|
7
|
-
from tests.integration.utils import integration_tests_utils as utils
|
|
8
11
|
import pytest
|
|
9
|
-
from matlab_proxy.util import system
|
|
10
|
-
import requests
|
|
11
12
|
import re
|
|
13
|
+
import requests
|
|
14
|
+
import time
|
|
12
15
|
from requests.adapters import HTTPAdapter, Retry
|
|
13
16
|
from urllib.parse import urlparse, parse_qs
|
|
14
|
-
|
|
17
|
+
|
|
18
|
+
# Local module imports
|
|
19
|
+
import matlab_proxy.settings as settings
|
|
15
20
|
from matlab_proxy.constants import MWI_AUTH_TOKEN_NAME_FOR_HTTP
|
|
21
|
+
from matlab_proxy.util import system
|
|
22
|
+
from tests.integration.utils import integration_tests_utils as utils
|
|
23
|
+
from tests.utils.logging_util import create_integ_test_logger
|
|
16
24
|
|
|
25
|
+
# Logger Setup
|
|
17
26
|
_logger = create_integ_test_logger(__name__)
|
|
18
27
|
|
|
28
|
+
# Constants
|
|
29
|
+
|
|
19
30
|
# Timeout for polling the matlab-proxy http endpoints
|
|
20
31
|
# matlab proxy in Mac machines takes more time to be 'up'
|
|
21
|
-
|
|
22
32
|
MAX_TIMEOUT = settings.get_process_startup_timeout()
|
|
23
33
|
|
|
24
34
|
|
|
35
|
+
class Format(Enum):
|
|
36
|
+
"""
|
|
37
|
+
An enumeration to specify different format types.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
JSON (int): Represents the JSON format type.
|
|
41
|
+
TEXT (int): Represents the plain text format type.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
JSON = 1
|
|
45
|
+
TEXT = 2
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Utility Functions
|
|
49
|
+
def _http_get_request(
|
|
50
|
+
uri, connection_scheme, headers, http_endpoint="", outputFormat=Format.TEXT
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Sends an HTTP GET request to a specified URI, optionally appending an endpoint to the URI.
|
|
54
|
+
|
|
55
|
+
This function uses a session with retries configured for transient network errors. It can return
|
|
56
|
+
the response in either text or JSON format, based on the outputFormat parameter.
|
|
57
|
+
|
|
58
|
+
Parameters:
|
|
59
|
+
- uri (str): The base URI for the HTTP request.
|
|
60
|
+
- connection_scheme (str): The scheme to use for the connection (e.g., 'http' or 'https').
|
|
61
|
+
- headers (dict): A dictionary of HTTP headers to include in the request.
|
|
62
|
+
- http_endpoint (str, optional): An additional endpoint to append to the base URI. Defaults to an empty string.
|
|
63
|
+
- outputFormat (format, optional): The desired format for the response content. This should be an attribute
|
|
64
|
+
of a format enumeration, supporting at least 'TEXT' and 'JSON' options. Defaults to Format.TEXT.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
- str or dict: The response content as a string if outputFormat is Format.TEXT, or as a dictionary
|
|
68
|
+
if outputFormat is Format.JSON.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
- Exception: If an invalid output format is specified.
|
|
72
|
+
|
|
73
|
+
Note:
|
|
74
|
+
- The function disables SSL certificate verification (`verify=False`). This may introduce security risks,
|
|
75
|
+
such as vulnerability to man-in-the-middle attacks. Use with caution in a production environment.
|
|
76
|
+
"""
|
|
77
|
+
request_uri = uri + http_endpoint
|
|
78
|
+
with requests.Session() as s:
|
|
79
|
+
retries = Retry(total=10, backoff_factor=0.1)
|
|
80
|
+
s.mount(f"{connection_scheme}://", HTTPAdapter(max_retries=retries))
|
|
81
|
+
response = s.get(request_uri, headers=headers, verify=False)
|
|
82
|
+
|
|
83
|
+
if outputFormat == Format.TEXT:
|
|
84
|
+
return response.text
|
|
85
|
+
elif outputFormat == Format.JSON:
|
|
86
|
+
return json.loads(response.text)
|
|
87
|
+
|
|
88
|
+
raise Exception("Invalid output format specified.")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _check_matlab_status(matlab_proxy_app_fixture, status):
|
|
92
|
+
"""
|
|
93
|
+
Check the status of a MATLAB session until a specified status is reached or a timeout occurs.
|
|
94
|
+
|
|
95
|
+
This function repeatedly sends HTTP GET requests to a MATLAB proxy application to check the current
|
|
96
|
+
status of MATLAB. It continues checking until MATLAB's status matches the specified target status or
|
|
97
|
+
until a maximum timeout is reached.
|
|
98
|
+
|
|
99
|
+
Parameters:
|
|
100
|
+
- matlab_proxy_app_fixture: An object containing configuration for connecting to the MATLAB proxy application.
|
|
101
|
+
This object must have the following attributes:
|
|
102
|
+
- url (str): The base URL of the MATLAB proxy application.
|
|
103
|
+
- connection_scheme (str): The scheme used for the connection (e.g., 'http' or 'https').
|
|
104
|
+
- headers (dict): A dictionary of HTTP headers to be sent with each request.
|
|
105
|
+
- status (str): The target status to wait for MATLAB to reach.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
- str: The status of MATLAB at the end of the function execution. This could be the target status if
|
|
109
|
+
it was reached within the timeout period, or the last known status of MATLAB if the timeout was reached
|
|
110
|
+
first.
|
|
111
|
+
|
|
112
|
+
Notes:
|
|
113
|
+
- The function waits for a maximum of MAX_TIMEOUT seconds, defined elsewhere, before exiting.
|
|
114
|
+
- It checks the MATLAB status every 1 second.
|
|
115
|
+
- The MATLAB status is obtained by sending a GET request to the '/get_status' endpoint of the proxy application.
|
|
116
|
+
- The response from the proxy application is expected to be in JSON format, with MATLAB's status accessible
|
|
117
|
+
via `res["matlab"]["status"]`.
|
|
118
|
+
|
|
119
|
+
Exceptions:
|
|
120
|
+
- This function may raise exceptions related to network issues or JSON parsing errors, which are not
|
|
121
|
+
explicitly handled within the function.
|
|
122
|
+
"""
|
|
123
|
+
uri = matlab_proxy_app_fixture.url
|
|
124
|
+
connection_scheme = matlab_proxy_app_fixture.connection_scheme
|
|
125
|
+
headers = matlab_proxy_app_fixture.headers
|
|
126
|
+
|
|
127
|
+
matlab_status = None
|
|
128
|
+
|
|
129
|
+
start_time = time.time()
|
|
130
|
+
while matlab_status != status and (time.time() - start_time < MAX_TIMEOUT):
|
|
131
|
+
time.sleep(1)
|
|
132
|
+
res = _http_get_request(
|
|
133
|
+
uri,
|
|
134
|
+
connection_scheme,
|
|
135
|
+
headers,
|
|
136
|
+
http_endpoint="/get_status",
|
|
137
|
+
outputFormat=Format.JSON,
|
|
138
|
+
)
|
|
139
|
+
matlab_status = res["matlab"]["status"]
|
|
140
|
+
|
|
141
|
+
return matlab_status
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _download_test_file(matlab_proxy_app_fixture, test_file):
|
|
145
|
+
"""Returns result of hitting the /download endpoint for test_file.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
str: The contents of the test_file being downloaded through matlab-proxy.
|
|
149
|
+
"""
|
|
150
|
+
uri = matlab_proxy_app_fixture.url
|
|
151
|
+
connection_scheme = matlab_proxy_app_fixture.connection_scheme
|
|
152
|
+
headers = matlab_proxy_app_fixture.headers
|
|
153
|
+
|
|
154
|
+
res = _http_get_request(
|
|
155
|
+
uri,
|
|
156
|
+
connection_scheme,
|
|
157
|
+
headers,
|
|
158
|
+
http_endpoint="/download/" + test_file,
|
|
159
|
+
outputFormat=Format.TEXT,
|
|
160
|
+
)
|
|
161
|
+
return res
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# Main Classes
|
|
25
165
|
class RealMATLABServer:
|
|
26
166
|
"""
|
|
27
167
|
Context Manager class which returns matlab proxy web server serving real MATLAB
|
|
@@ -103,40 +243,7 @@ class RealMATLABServer:
|
|
|
103
243
|
_logger.debug("Terminated the MATLAB process.")
|
|
104
244
|
|
|
105
245
|
|
|
106
|
-
|
|
107
|
-
"""Send HTTP request to matlab-proxy server.
|
|
108
|
-
Returns HTTP response JSON"""
|
|
109
|
-
|
|
110
|
-
request_uri = uri + http_endpoint
|
|
111
|
-
|
|
112
|
-
json_response = None
|
|
113
|
-
with requests.Session() as s:
|
|
114
|
-
retries = Retry(total=10, backoff_factor=0.1)
|
|
115
|
-
s.mount(f"{connection_scheme}://", HTTPAdapter(max_retries=retries))
|
|
116
|
-
response = s.get(request_uri, headers=headers, verify=False)
|
|
117
|
-
json_response = json.loads(response.text)
|
|
118
|
-
|
|
119
|
-
return json_response
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _check_matlab_status(matlab_proxy_app_fixture, status):
|
|
123
|
-
uri = matlab_proxy_app_fixture.url
|
|
124
|
-
connection_scheme = matlab_proxy_app_fixture.connection_scheme
|
|
125
|
-
headers = matlab_proxy_app_fixture.headers
|
|
126
|
-
|
|
127
|
-
matlab_status = None
|
|
128
|
-
|
|
129
|
-
start_time = time.time()
|
|
130
|
-
while matlab_status != status and (time.time() - start_time < MAX_TIMEOUT):
|
|
131
|
-
time.sleep(1)
|
|
132
|
-
res = _send_http_get_request(
|
|
133
|
-
uri, connection_scheme, headers, http_endpoint="/get_status"
|
|
134
|
-
)
|
|
135
|
-
matlab_status = res["matlab"]["status"]
|
|
136
|
-
|
|
137
|
-
return matlab_status
|
|
138
|
-
|
|
139
|
-
|
|
246
|
+
# Fixtures
|
|
140
247
|
@pytest.fixture
|
|
141
248
|
def matlab_proxy_app_fixture(
|
|
142
249
|
loop,
|
|
@@ -159,6 +266,44 @@ def matlab_proxy_app_fixture(
|
|
|
159
266
|
pass
|
|
160
267
|
|
|
161
268
|
|
|
269
|
+
@pytest.fixture
|
|
270
|
+
def test_file_contents():
|
|
271
|
+
"""
|
|
272
|
+
A pytest fixture that provides a string for testing purposes.
|
|
273
|
+
|
|
274
|
+
This fixture returns a predefined string that can be used in tests to simulate
|
|
275
|
+
the contents of a file or any scenario where a constant string value is needed.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
str: A string containing the text "I LOVE MATLAB."
|
|
279
|
+
"""
|
|
280
|
+
return "I LOVE MATLAB."
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@pytest.fixture
|
|
284
|
+
def test_file(tmp_path, test_file_contents):
|
|
285
|
+
"""
|
|
286
|
+
A pytest fixture that creates a temporary test file with given contents.
|
|
287
|
+
|
|
288
|
+
This fixture utilizes pytest's `tmp_path` fixture to generate a temporary directory,
|
|
289
|
+
then creates a file named "temporary_test_file.txt" within this directory,
|
|
290
|
+
and writes the provided contents to this file. It is useful for tests that require
|
|
291
|
+
reading from or writing to files without affecting the actual file system.
|
|
292
|
+
|
|
293
|
+
Parameters:
|
|
294
|
+
- tmp_path (Path): A pytest fixture that provides a temporary directory unique to the test function.
|
|
295
|
+
- test_file_contents (str): The content to be written into the temporary test file.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
- str: The path to the created temporary test file as a string.
|
|
299
|
+
"""
|
|
300
|
+
test_file = os.path.join(tmp_path, "temporary_test_file.txt")
|
|
301
|
+
with open(test_file, "w+") as f:
|
|
302
|
+
f.write(test_file_contents)
|
|
303
|
+
return test_file
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# Test Functions
|
|
162
307
|
def test_matlab_is_up(matlab_proxy_app_fixture):
|
|
163
308
|
"""Test that the status switches from 'starting' to 'up' within a timeout.
|
|
164
309
|
|
|
@@ -195,13 +340,13 @@ def test_stop_matlab(matlab_proxy_app_fixture):
|
|
|
195
340
|
assert status == "down"
|
|
196
341
|
|
|
197
342
|
|
|
198
|
-
# FIXME: If output has logging or extra debug info, 600 bytes might not be enough.
|
|
199
343
|
async def test_print_message(matlab_proxy_app_fixture):
|
|
200
344
|
"""Test if the right logs are printed
|
|
201
345
|
|
|
202
346
|
Args:
|
|
203
347
|
matlab_proxy_app_fixture: A pytest fixture which yields a real matlab server to be used by tests.
|
|
204
348
|
|
|
349
|
+
FIXME: If output has logging or extra debug info, 600 bytes might not be enough.
|
|
205
350
|
"""
|
|
206
351
|
# Checks if matlab proxy is in "up" state or not
|
|
207
352
|
status = _check_matlab_status(matlab_proxy_app_fixture, "up")
|
|
@@ -221,3 +366,33 @@ async def test_print_message(matlab_proxy_app_fixture):
|
|
|
221
366
|
# Close the read and write descriptors.
|
|
222
367
|
os.close(read_descriptor)
|
|
223
368
|
os.close(write_descriptor)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def test_download_file_from_matlab(
|
|
372
|
+
matlab_proxy_app_fixture, test_file, test_file_contents
|
|
373
|
+
):
|
|
374
|
+
"""
|
|
375
|
+
Test the downloading of a file from a MATLAB proxy application.
|
|
376
|
+
|
|
377
|
+
This test function checks if the MATLAB proxy application is up and running, and then attempts to download
|
|
378
|
+
a specific test file from it. It validates both the status of the MATLAB proxy and the contents of the downloaded file.
|
|
379
|
+
|
|
380
|
+
Parameters:
|
|
381
|
+
- matlab_proxy_app_fixture (fixture): A test fixture representing the MATLAB proxy application environment.
|
|
382
|
+
- test_file (str): The name or path of the test file to be downloaded from the MATLAB proxy application.
|
|
383
|
+
- test_file_contents (str): The expected contents of the test file to validate the download operation.
|
|
384
|
+
|
|
385
|
+
Assertions:
|
|
386
|
+
- Asserts that the MATLAB proxy application is "up".
|
|
387
|
+
- Asserts that the content of the downloaded file matches the expected `test_file_contents`.
|
|
388
|
+
|
|
389
|
+
Raises:
|
|
390
|
+
- AssertionError: If any of the assertions fail, indicating either the MATLAB proxy application is not running
|
|
391
|
+
as expected or there is a mismatch in the file content.
|
|
392
|
+
"""
|
|
393
|
+
status = _check_matlab_status(matlab_proxy_app_fixture, "up")
|
|
394
|
+
assert status == "up"
|
|
395
|
+
|
|
396
|
+
# Once MATLAB is up, we can then attempt to download
|
|
397
|
+
result = _download_test_file(matlab_proxy_app_fixture, test_file)
|
|
398
|
+
assert result == test_file_contents
|
tests/unit/test_app_state.py
CHANGED
|
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Optional
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
|
+
from matlab_proxy import settings
|
|
10
11
|
|
|
11
12
|
from matlab_proxy import settings
|
|
12
13
|
from matlab_proxy.app_state import AppState
|
|
@@ -14,6 +15,11 @@ from matlab_proxy.constants import MWI_AUTH_TOKEN_NAME_FOR_HTTP
|
|
|
14
15
|
from matlab_proxy.util.mwi.exceptions import LicensingError, MatlabError
|
|
15
16
|
from tests.unit.util import MockResponse
|
|
16
17
|
|
|
18
|
+
from matlab_proxy.constants import (
|
|
19
|
+
CONNECTOR_SECUREPORT_FILENAME,
|
|
20
|
+
USER_CODE_OUTPUT_FILE_NAME,
|
|
21
|
+
)
|
|
22
|
+
|
|
17
23
|
|
|
18
24
|
@pytest.fixture
|
|
19
25
|
def sample_settings_fixture(tmp_path):
|
|
@@ -35,6 +41,7 @@ def sample_settings_fixture(tmp_path):
|
|
|
35
41
|
"mwi_logs_root_dir": Path(settings.get_mwi_config_folder(dev=True)),
|
|
36
42
|
"app_port": 12345,
|
|
37
43
|
"mwapikey": "asdf",
|
|
44
|
+
"has_custom_code_to_execute": False,
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
|
|
@@ -662,3 +669,30 @@ async def test_detect_active_client_status_can_reset_active_client(app_state_fix
|
|
|
662
669
|
assert (
|
|
663
670
|
app_state_fixture.active_client == None
|
|
664
671
|
), f"Expected the active_client to be None"
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@pytest.mark.parametrize(
|
|
675
|
+
"session_file_count, has_custom_code_to_execute", [(2, True), (1, False)]
|
|
676
|
+
)
|
|
677
|
+
def test_create_logs_dir_for_MATLAB(
|
|
678
|
+
app_state_fixture, session_file_count, has_custom_code_to_execute
|
|
679
|
+
):
|
|
680
|
+
"""Test to check create_logs_dir_for_MATLAB()
|
|
681
|
+
|
|
682
|
+
Args:
|
|
683
|
+
app_state_fixture (AppState): Object of AppState class with defaults set
|
|
684
|
+
"""
|
|
685
|
+
# Arrange
|
|
686
|
+
app_state_fixture.settings["has_custom_code_to_execute"] = (
|
|
687
|
+
has_custom_code_to_execute
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
# Act
|
|
691
|
+
app_state_fixture.create_logs_dir_for_MATLAB()
|
|
692
|
+
|
|
693
|
+
# Assert
|
|
694
|
+
for _, session_file_path in app_state_fixture.matlab_session_files.items():
|
|
695
|
+
# Check session files are present in mwi logs directory
|
|
696
|
+
assert app_state_fixture.mwi_logs_dir == Path(session_file_path).parent
|
|
697
|
+
|
|
698
|
+
assert len(app_state_fixture.matlab_session_files) == session_file_count
|
tests/unit/test_settings.py
CHANGED
|
@@ -458,3 +458,38 @@ def test_get_ssl_context_with_invalid_custom_ssl_files_raises_exception(
|
|
|
458
458
|
|
|
459
459
|
with pytest.raises(Exception, match=exception_msg):
|
|
460
460
|
settings._validate_ssl_files_and_get_ssl_context(mwi_certs_dir)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@pytest.mark.parametrize(
|
|
464
|
+
"expected_value_for_has_custom_code, custom_code, has_custom_code_exception_matlab_cmd",
|
|
465
|
+
[(False, "", False), (True, "run(disp('MATLAB'))", True)],
|
|
466
|
+
ids=["No custom code to execute", "Has custom code to execute"],
|
|
467
|
+
)
|
|
468
|
+
def test_get_matlab_settings_custom_code(
|
|
469
|
+
monkeypatch,
|
|
470
|
+
mocker,
|
|
471
|
+
expected_value_for_has_custom_code,
|
|
472
|
+
custom_code,
|
|
473
|
+
has_custom_code_exception_matlab_cmd,
|
|
474
|
+
):
|
|
475
|
+
# Arrange
|
|
476
|
+
monkeypatch.setenv(mwi_env.get_env_name_custom_matlab_code(), custom_code)
|
|
477
|
+
mocker.patch(
|
|
478
|
+
"matlab_proxy.settings.get_matlab_executable_and_root_path",
|
|
479
|
+
return_value=("matlab", None),
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Act
|
|
483
|
+
matlab_settings = settings.get_matlab_settings()
|
|
484
|
+
exception_present_in_matlab_cmd = (
|
|
485
|
+
"MATLABCustomStartupCodeError" in matlab_settings["matlab_cmd"][-1]
|
|
486
|
+
)
|
|
487
|
+
print(matlab_settings)
|
|
488
|
+
|
|
489
|
+
# Assert
|
|
490
|
+
assert (
|
|
491
|
+
matlab_settings["has_custom_code_to_execute"]
|
|
492
|
+
== expected_value_for_has_custom_code
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
assert exception_present_in_matlab_cmd == has_custom_code_exception_matlab_cmd
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Copyright 2024 The MathWorks, Inc.
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from matlab_proxy.util.mwi.download import (
|
|
5
|
+
_get_download_payload_path,
|
|
6
|
+
get_download_url,
|
|
7
|
+
is_download_request,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Mock the request object
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def mock_request_fixture(mocker):
|
|
14
|
+
mock_req = mocker.MagicMock()
|
|
15
|
+
mock_req.app = {
|
|
16
|
+
"settings": {"base_url": ""},
|
|
17
|
+
"state": mocker.MagicMock(),
|
|
18
|
+
}
|
|
19
|
+
mock_req.rel_url = mocker.MagicMock()
|
|
20
|
+
return mock_req
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_expected_output_based_on_os_type(paths: list) -> str:
|
|
24
|
+
import matlab_proxy.util.system as system
|
|
25
|
+
|
|
26
|
+
return "\\".join(paths) if system.is_windows() else "/".join(paths)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Test for is_download_request function
|
|
30
|
+
@pytest.mark.parametrize(
|
|
31
|
+
"test_base_url, path, expected",
|
|
32
|
+
[
|
|
33
|
+
("/", "/download/something", True),
|
|
34
|
+
("", "/download/something", True),
|
|
35
|
+
("/base", "/base/download/something", True),
|
|
36
|
+
("/base", "/download/something", False),
|
|
37
|
+
],
|
|
38
|
+
ids=[
|
|
39
|
+
"/ base url and path starting with /download",
|
|
40
|
+
"empty base url and path starting with /download",
|
|
41
|
+
"non-empty base url and path starting with that base url",
|
|
42
|
+
"non-empty base url and path not starting with that base url",
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
def test_is_download_request(mock_request_fixture, test_base_url, path, expected):
|
|
46
|
+
mock_request_fixture.app["settings"]["base_url"] = test_base_url
|
|
47
|
+
mock_request_fixture.rel_url.path = path
|
|
48
|
+
assert is_download_request(mock_request_fixture) == expected
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Test for _get_download_payload_path function
|
|
52
|
+
# This test is a bit tricky since it involves file system operations and OS checks.
|
|
53
|
+
# We will mock system.is_windows() and test for both Windows and Posix systems.
|
|
54
|
+
@pytest.mark.parametrize(
|
|
55
|
+
"is_windows, test_base_url, path, expected",
|
|
56
|
+
[
|
|
57
|
+
(
|
|
58
|
+
True,
|
|
59
|
+
"",
|
|
60
|
+
"/downloadC:\\some\\path\\to\\file.txt",
|
|
61
|
+
"C:\\some\\path\\to\\file.txt",
|
|
62
|
+
),
|
|
63
|
+
(
|
|
64
|
+
True,
|
|
65
|
+
"/base",
|
|
66
|
+
"/base/downloadC:\\some\\path\\to\\file.txt",
|
|
67
|
+
"C:\\some\\path\\to\\file.txt",
|
|
68
|
+
),
|
|
69
|
+
(
|
|
70
|
+
False,
|
|
71
|
+
"",
|
|
72
|
+
"/download/some/path/to/file.txt",
|
|
73
|
+
_get_expected_output_based_on_os_type(["/some", "path", "to", "file.txt"]),
|
|
74
|
+
),
|
|
75
|
+
(
|
|
76
|
+
False,
|
|
77
|
+
"/base",
|
|
78
|
+
"/base/download/some/path/to/file.txt",
|
|
79
|
+
_get_expected_output_based_on_os_type(["/some", "path", "to", "file.txt"]),
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
ids=[
|
|
83
|
+
"Windows with null base url",
|
|
84
|
+
"Windows with non-null base url",
|
|
85
|
+
"Linux with null base url",
|
|
86
|
+
"Linux with non-null base url",
|
|
87
|
+
],
|
|
88
|
+
)
|
|
89
|
+
def test_get_download_payload_path(
|
|
90
|
+
mock_request_fixture, mocker, is_windows, test_base_url, path, expected
|
|
91
|
+
):
|
|
92
|
+
mocker.patch("matlab_proxy.util.system.is_windows", return_value=is_windows)
|
|
93
|
+
mock_request_fixture.app["settings"]["base_url"] = test_base_url
|
|
94
|
+
mock_request_fixture.rel_url.path = path
|
|
95
|
+
assert _get_download_payload_path(mock_request_fixture) == expected
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_get_download_payload_path_invalid_request(mock_request_fixture):
|
|
99
|
+
test_base_url = "/base"
|
|
100
|
+
path = "/download/something"
|
|
101
|
+
|
|
102
|
+
mock_request_fixture.app["settings"]["base_url"] = test_base_url
|
|
103
|
+
mock_request_fixture.rel_url.path = path
|
|
104
|
+
|
|
105
|
+
assert _get_download_payload_path(mock_request_fixture) is None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.parametrize(
|
|
109
|
+
"response_json, expected_url",
|
|
110
|
+
[
|
|
111
|
+
(
|
|
112
|
+
{
|
|
113
|
+
"messages": {
|
|
114
|
+
"FEvalResponse": [
|
|
115
|
+
{"isError": False, "results": ["http://download-url.com"]}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"http://download-url.com",
|
|
120
|
+
),
|
|
121
|
+
({"messages": {"FEvalResponse": [{"isError": True}]}}, None),
|
|
122
|
+
],
|
|
123
|
+
ids=["connector returning correct download url", "connector returning an error"],
|
|
124
|
+
)
|
|
125
|
+
async def test_get_download_url(
|
|
126
|
+
mock_request_fixture, mocker, response_json, expected_url
|
|
127
|
+
):
|
|
128
|
+
test_base_url = "/"
|
|
129
|
+
path = "/download/some/path/to/file.txt"
|
|
130
|
+
|
|
131
|
+
mock_request_fixture.app["state"].settings = {
|
|
132
|
+
"mwi_server_url": "http://mwi-server.com"
|
|
133
|
+
}
|
|
134
|
+
mock_request_fixture.app["settings"]["base_url"] = test_base_url
|
|
135
|
+
mock_request_fixture.rel_url.path = path
|
|
136
|
+
|
|
137
|
+
mocker.patch(
|
|
138
|
+
"matlab_proxy.util.mwi.embedded_connector.helpers.get_data_to_feval_mcode",
|
|
139
|
+
return_value={},
|
|
140
|
+
)
|
|
141
|
+
mocker.patch(
|
|
142
|
+
"matlab_proxy.util.mwi.embedded_connector.helpers.get_mvm_endpoint",
|
|
143
|
+
return_value="http://mwi-server.com",
|
|
144
|
+
)
|
|
145
|
+
mocker.patch(
|
|
146
|
+
"matlab_proxy.util.mwi.embedded_connector.send_request",
|
|
147
|
+
return_value=response_json,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
download_url = await get_download_url(mock_request_fixture)
|
|
151
|
+
|
|
152
|
+
assert download_url == expected_url
|
tests/unit/util/test_util.py
CHANGED
|
@@ -6,6 +6,9 @@ import psutil
|
|
|
6
6
|
|
|
7
7
|
from matlab_proxy.util import get_child_processes, system, add_signal_handlers, prettify
|
|
8
8
|
from matlab_proxy.util import system
|
|
9
|
+
from matlab_proxy.util.mwi.exceptions import (
|
|
10
|
+
UIVisibleFatalError,
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
def test_get_supported_termination_signals():
|
|
@@ -90,7 +93,7 @@ def test_get_child_processes_no_children(mocker):
|
|
|
90
93
|
mock_parent_process_psutil.children.return_value = []
|
|
91
94
|
|
|
92
95
|
# Call the function with the mocked parent process
|
|
93
|
-
with pytest.raises(
|
|
96
|
+
with pytest.raises(UIVisibleFatalError):
|
|
94
97
|
get_child_processes(parent_process)
|
|
95
98
|
|
|
96
99
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|