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 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. Inorder to avoid this, adding the below if condition to check to skip sys.exit(0) when testing
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
- from pkg_resources import resource_isdir, resource_listdir
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 name in resource_listdir(mod, ""):
496
- if not resource_isdir(mod, name):
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
- from matlab_proxy.settings import (
21
- get_process_startup_timeout,
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
- "Unable to start MATLAB because of a timeout. Try again by clicking Start MATLAB."
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
+
@@ -1,4 +1,4 @@
1
- % Copyright (c) 2020-2023 The MathWorks, Inc.
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
- matlab_startup_file = str(Path(__file__).resolve().parent / "matlab" / "startup.m")
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
- f"try; run('{matlab_startup_file}'); catch ME; disp(ME.message); end;",
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
 
@@ -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
- raise RuntimeError("No child processes found after multiple attempts.")
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 MatlabInstallError, FatalError
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 pkg_resources.iter_entry_points(
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
@@ -1,9 +1,12 @@
1
- # Copyright 2022-2023 The MathWorks, Inc.
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.17.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=aoKkhw_E71uCJeMzqr9VE4mrIRd_4Aqt3Uw4nrYiZZY,34386
3
- matlab_proxy/app_state.py,sha256=-8gduqMymI7U_NwNRtL26skrjOlnDRiM2vXqBTfMnKo,59525
4
- matlab_proxy/constants.py,sha256=gHL6bYgRDyzufMx1XF9tbtC9vsS_yQlfYPNommti8tA,972
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=Q901JN7zwcySVOs7EQ69m4Ngishmo9ihFzqbPxXajh4,24915
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/startup.m,sha256=YRtI8P2flDJSDcPxJ2008B2l1T9JpqiUbzhQxA0frbc,1558
56
- matlab_proxy/util/__init__.py,sha256=ikj07VxQdIThUWzWLOrdIsO-R1VtEhunB0mjrfnD5jE,8114
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=R9-VA7f0snVanSR7IgbHz3kvSnthZuUYe2UhBd5QWMQ,3440
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=bIz0QFWo0pgyvSeJ_kAobUCS17V05CSmFWm3zYIOIsA,7139
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=BMMOD-0tdjGIALm1MmG4yXVRoD2ZUXkrJXLWP_D5FGo,11712
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=SDJIoyo-hh2Ns9uWaDeO87N0jY3svhKOn29HFFyx_jM,7485
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=xwXOmyzamL89eg4q2s4IpFKWDN4r2lZwsHBafqDkins,21854
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=6KuYcOIqilR5RmaYxHw5mr_4CqDS3iMsouS-IrT9wGk,16644
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=jhZOgErpD6b3JeusLBOPVvu6iZ3_ttwlEVV8uKLUvrw,5060
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.17.0.dist-info/LICENSE.md,sha256=oF0h3UdSF-rlUiMGYwi086ZHqelzz7yOOk9HFDv9ELo,2344
106
- matlab_proxy-0.17.0.dist-info/METADATA,sha256=_UnbcVVZElb72b3PjnP85kmOmqYdzoXv1tcsEGkVD5s,10108
107
- matlab_proxy-0.17.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
108
- matlab_proxy-0.17.0.dist-info/entry_points.txt,sha256=DbBLYgnRt8UGiOpd0zHigRTyyMdZYhMdvCvSYP7wPN0,244
109
- matlab_proxy-0.17.0.dist-info/top_level.txt,sha256=9uVTjsUCAS4TwsxueTBxrBg3PdBiTSsYowAkHPv9VY0,19
110
- matlab_proxy-0.17.0.dist-info/RECORD,,
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
- from tests.utils.logging_util import create_integ_test_logger
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
- def _send_http_get_request(uri, connection_scheme, headers, http_endpoint=""):
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
@@ -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
@@ -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
@@ -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(RuntimeError):
96
+ with pytest.raises(UIVisibleFatalError):
94
97
  get_child_processes(parent_process)
95
98
 
96
99