matlab-proxy 0.22.0__py3-none-any.whl → 0.23.3__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_state.py +42 -47
- matlab_proxy/default_configuration.py +39 -4
- matlab_proxy/gui/asset-manifest.json +6 -6
- matlab_proxy/gui/index.html +1 -1
- matlab_proxy/gui/static/css/{main.6cd0caba.css → main.efa05ff9.css} +2 -2
- matlab_proxy/gui/static/css/{main.6cd0caba.css.map → main.efa05ff9.css.map} +1 -1
- matlab_proxy/gui/static/js/{main.77e6cbaf.js → main.ce0f5505.js} +3 -3
- matlab_proxy/gui/static/js/main.ce0f5505.js.map +1 -0
- matlab_proxy/settings.py +1 -1
- matlab_proxy/util/__init__.py +1 -1
- matlab_proxy/util/mwi/validators.py +17 -16
- {matlab_proxy-0.22.0.dist-info → matlab_proxy-0.23.3.dist-info}/METADATA +2 -2
- {matlab_proxy-0.22.0.dist-info → matlab_proxy-0.23.3.dist-info}/RECORD +27 -27
- {matlab_proxy-0.22.0.dist-info → matlab_proxy-0.23.3.dist-info}/WHEEL +1 -1
- matlab_proxy_manager/lib/api.py +90 -49
- matlab_proxy_manager/storage/server.py +2 -2
- matlab_proxy_manager/utils/helpers.py +38 -21
- matlab_proxy_manager/web/app.py +92 -49
- matlab_proxy_manager/web/monitor.py +1 -2
- matlab_proxy_manager/web/watcher.py +11 -0
- tests/unit/test_app.py +1 -0
- tests/unit/test_app_state.py +79 -4
- tests/unit/util/mwi/test_validators.py +4 -5
- matlab_proxy/gui/static/js/main.77e6cbaf.js.map +0 -1
- /matlab_proxy/gui/static/js/{main.77e6cbaf.js.LICENSE.txt → main.ce0f5505.js.LICENSE.txt} +0 -0
- {matlab_proxy-0.22.0.dist-info → matlab_proxy-0.23.3.dist-info}/LICENSE.md +0 -0
- {matlab_proxy-0.22.0.dist-info → matlab_proxy-0.23.3.dist-info}/entry_points.txt +0 -0
- {matlab_proxy-0.22.0.dist-info → matlab_proxy-0.23.3.dist-info}/top_level.txt +0 -0
matlab_proxy_manager/web/app.py
CHANGED
|
@@ -14,17 +14,18 @@ from aiohttp import ClientSession, client_exceptions, web
|
|
|
14
14
|
import matlab_proxy.util.mwi.environment_variables as mwi_env
|
|
15
15
|
import matlab_proxy.util.system as mwi_sys
|
|
16
16
|
import matlab_proxy_manager.lib.api as mpm_lib
|
|
17
|
-
from matlab_proxy.util.event_loop import get_event_loop
|
|
18
17
|
from matlab_proxy_manager.utils import constants, helpers, logger
|
|
19
18
|
from matlab_proxy_manager.utils import environment_variables as mpm_env
|
|
20
19
|
from matlab_proxy_manager.utils.auth import authenticate_access_decorator
|
|
21
20
|
from matlab_proxy_manager.web import watcher
|
|
22
21
|
from matlab_proxy_manager.web.monitor import OrphanedProcessMonitor
|
|
23
22
|
|
|
24
|
-
#
|
|
25
|
-
|
|
23
|
+
# List of public-facing APIs exported by this module.
|
|
24
|
+
# This list contains the names of functions or classes that are intended to be
|
|
25
|
+
# used by external code importing this module. Only items listed here will be
|
|
26
|
+
# directly accessible when using "from module import *".
|
|
27
|
+
__all__ = ["proxy"]
|
|
26
28
|
|
|
27
|
-
SHUTDOWN_EVENT = None
|
|
28
29
|
log = logger.get(init=True)
|
|
29
30
|
|
|
30
31
|
|
|
@@ -40,6 +41,8 @@ def init_app() -> web.Application:
|
|
|
40
41
|
web.Application: The configured aiohttp web application.
|
|
41
42
|
"""
|
|
42
43
|
app = web.Application()
|
|
44
|
+
# Async event is utilized to signal app termination from this and other modules
|
|
45
|
+
app["shutdown_event"] = asyncio.Event()
|
|
43
46
|
|
|
44
47
|
# Create and get the proxy manager data directory
|
|
45
48
|
try:
|
|
@@ -53,7 +56,7 @@ def init_app() -> web.Application:
|
|
|
53
56
|
|
|
54
57
|
async def start_idle_monitor(app):
|
|
55
58
|
"""Start the idle timeout monitor."""
|
|
56
|
-
asyncio.create_task(monitor.start())
|
|
59
|
+
app["monitor_task"] = asyncio.create_task(monitor.start())
|
|
57
60
|
|
|
58
61
|
async def create_client_session(app):
|
|
59
62
|
"""Create an aiohttp client session."""
|
|
@@ -65,10 +68,34 @@ def init_app() -> web.Application:
|
|
|
65
68
|
"""Cleanup the aiohttp client session."""
|
|
66
69
|
await app["session"].close()
|
|
67
70
|
|
|
71
|
+
async def cleanup_monitor(app):
|
|
72
|
+
"""Cancel the idle timeout monitor task."""
|
|
73
|
+
if "monitor_task" in app:
|
|
74
|
+
app["monitor_task"].cancel()
|
|
75
|
+
try:
|
|
76
|
+
await app["monitor_task"]
|
|
77
|
+
except asyncio.CancelledError:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
async def cleanup_watcher(app):
|
|
81
|
+
"""Cleanup the filesystem watcher."""
|
|
82
|
+
if "observer" in app:
|
|
83
|
+
loop = asyncio.get_running_loop()
|
|
84
|
+
await loop.run_in_executor(None, watcher.stop_watcher, app)
|
|
85
|
+
|
|
86
|
+
if "watcher_future" in app:
|
|
87
|
+
app["watcher_future"].cancel()
|
|
88
|
+
try:
|
|
89
|
+
await app["watcher_future"]
|
|
90
|
+
except asyncio.CancelledError:
|
|
91
|
+
pass
|
|
92
|
+
|
|
68
93
|
app.on_startup.append(start_idle_monitor)
|
|
69
94
|
app.on_startup.append(create_client_session)
|
|
70
95
|
app.on_cleanup.append(helpers.delete_dangling_servers)
|
|
71
96
|
app.on_cleanup.append(cleanup_client_session)
|
|
97
|
+
app.on_cleanup.append(cleanup_monitor)
|
|
98
|
+
app.on_cleanup.append(cleanup_watcher)
|
|
72
99
|
|
|
73
100
|
app.router.add_route("*", "/{tail:.*}", proxy)
|
|
74
101
|
|
|
@@ -86,11 +113,6 @@ async def start_app(env_vars: namedtuple):
|
|
|
86
113
|
Raises:
|
|
87
114
|
Exception: If any error occurs during the application startup or runtime.
|
|
88
115
|
"""
|
|
89
|
-
# Async events are utilized to signal app termination from other modules,
|
|
90
|
-
# necessitating the use of a global variable. To avoid potential issues with variable attachment
|
|
91
|
-
# to other event loops in Python versions prior to 3.10, the variable is initialized locally
|
|
92
|
-
# rather than globally.
|
|
93
|
-
global SHUTDOWN_EVENT
|
|
94
116
|
app = init_app()
|
|
95
117
|
|
|
96
118
|
app["port"] = env_vars.mpm_port
|
|
@@ -110,47 +132,51 @@ async def start_app(env_vars: namedtuple):
|
|
|
110
132
|
log.debug("Proxy manager started at http://127.0.0.1:%d", site._port)
|
|
111
133
|
|
|
112
134
|
# Get the default event loop
|
|
113
|
-
loop =
|
|
135
|
+
loop = asyncio.get_running_loop()
|
|
114
136
|
|
|
115
|
-
# Run the observer in a separate thread
|
|
116
|
-
loop.run_in_executor(None, watcher.start_watcher, app)
|
|
137
|
+
# Run the observer in a separate thread and store the future
|
|
138
|
+
app["watcher_future"] = loop.run_in_executor(None, watcher.start_watcher, app)
|
|
117
139
|
|
|
118
140
|
# Register signal handler for graceful shutdown
|
|
119
|
-
_register_signal_handler(loop)
|
|
120
|
-
|
|
121
|
-
SHUTDOWN_EVENT = asyncio.Event()
|
|
141
|
+
_register_signal_handler(loop, app)
|
|
122
142
|
|
|
123
143
|
# Wait for receiving shutdown_event (set by interrupts or by monitoring process)
|
|
124
|
-
await
|
|
144
|
+
await app.get("shutdown_event").wait()
|
|
125
145
|
|
|
126
146
|
# After receiving the shutdown signal, perform cleanup by stopping the web server
|
|
127
147
|
await runner.cleanup()
|
|
128
148
|
|
|
129
149
|
|
|
130
|
-
def _register_signal_handler(loop):
|
|
150
|
+
def _register_signal_handler(loop, app):
|
|
131
151
|
"""
|
|
132
|
-
Registers signal handlers for
|
|
133
|
-
|
|
152
|
+
Registers signal handlers for graceful shutdown of the application.
|
|
153
|
+
|
|
154
|
+
This function sets up handlers for supported termination signals to allow
|
|
155
|
+
the application to shut down gracefully. It uses different methods for
|
|
156
|
+
POSIX and non-POSIX systems to add the signal handlers.
|
|
134
157
|
|
|
135
158
|
Args:
|
|
136
159
|
loop (asyncio.AbstractEventLoop): The event loop to which the signal handlers
|
|
137
|
-
|
|
160
|
+
should be added.
|
|
161
|
+
app (aiohttp.web.Application): The web application instance.
|
|
138
162
|
"""
|
|
139
163
|
signals = mwi_sys.get_supported_termination_signals()
|
|
140
164
|
for sig_name in signals:
|
|
141
165
|
if mwi_sys.is_posix():
|
|
142
|
-
loop.add_signal_handler(sig_name,
|
|
166
|
+
loop.add_signal_handler(sig_name, lambda: _catch_signals(app))
|
|
143
167
|
else:
|
|
144
168
|
# loop.add_signal_handler() is not yet supported in Windows.
|
|
145
169
|
# Using the 'signal' package instead.
|
|
146
|
-
signal
|
|
170
|
+
# signal module expects a handler function that takes two arguments:
|
|
171
|
+
# the signal number and the current stack frame
|
|
172
|
+
signal.signal(sig_name, lambda s, f: _catch_signals(app))
|
|
147
173
|
|
|
148
174
|
|
|
149
|
-
def
|
|
175
|
+
def _catch_signals(app):
|
|
150
176
|
"""Handle termination signals for graceful shutdown."""
|
|
151
177
|
# Poll for parent process to clean up to avoid race conditions in cleanup of matlab proxies
|
|
152
178
|
helpers.poll_for_server_deletion()
|
|
153
|
-
|
|
179
|
+
app.get("shutdown_event").set()
|
|
154
180
|
|
|
155
181
|
|
|
156
182
|
async def _start_default_proxy(app):
|
|
@@ -162,7 +188,7 @@ async def _start_default_proxy(app):
|
|
|
162
188
|
"""
|
|
163
189
|
server_process = await mpm_lib.start_matlab_proxy_for_jsp(
|
|
164
190
|
parent_id=app.get("parent_pid"),
|
|
165
|
-
|
|
191
|
+
is_shared_matlab=True,
|
|
166
192
|
mpm_auth_token=app.get("auth_token"),
|
|
167
193
|
)
|
|
168
194
|
if not server_process:
|
|
@@ -206,7 +232,7 @@ async def proxy(req):
|
|
|
206
232
|
|
|
207
233
|
# Set content length in case of modification
|
|
208
234
|
req_headers["Content-Length"] = str(len(req_body))
|
|
209
|
-
req_headers["
|
|
235
|
+
req_headers["X-Forwarded-Proto"] = "http"
|
|
210
236
|
req_path = req.rel_url
|
|
211
237
|
|
|
212
238
|
# Redirect block to move /*/matlab to /*/matlab/default/
|
|
@@ -230,7 +256,7 @@ async def proxy(req):
|
|
|
230
256
|
if not ctx:
|
|
231
257
|
log.debug("MPM Context header not found in the request")
|
|
232
258
|
return _render_error_page(
|
|
233
|
-
"Required header
|
|
259
|
+
f"Required header: ${constants.HEADER_MWI_MPM_CONTEXT} not found in the request"
|
|
234
260
|
)
|
|
235
261
|
|
|
236
262
|
client_key = f"{ctx}_{ident}"
|
|
@@ -246,9 +272,11 @@ async def proxy(req):
|
|
|
246
272
|
and req_headers.get(upgrade, "").lower() == "websocket"
|
|
247
273
|
and req.method == "GET"
|
|
248
274
|
):
|
|
249
|
-
return await
|
|
275
|
+
return await _forward_websocket_request(req, proxy_url)
|
|
250
276
|
try:
|
|
251
|
-
return await
|
|
277
|
+
return await _forward_http_request(
|
|
278
|
+
req, req_body, proxy_url, _collate_headers(req_headers, backend_server)
|
|
279
|
+
)
|
|
252
280
|
except web.HTTPFound:
|
|
253
281
|
log.debug("Redirection to path with /default")
|
|
254
282
|
raise
|
|
@@ -266,7 +294,23 @@ async def proxy(req):
|
|
|
266
294
|
raise web.HTTPNotFound() from err
|
|
267
295
|
|
|
268
296
|
|
|
269
|
-
|
|
297
|
+
# Helper private functions
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _collate_headers(req_headers: dict, backend_server: dict) -> dict:
|
|
301
|
+
"""Combines request headers with backend server (matlab-proxy) headers.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
req_headers (dict): The headers from the incoming request.
|
|
305
|
+
backend_server (dict): The backend server configuration.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
dict: A new dictionary containing all headers from both sources.
|
|
309
|
+
"""
|
|
310
|
+
return {**req_headers, **backend_server.get("headers")}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
async def _forward_websocket_request(
|
|
270
314
|
req: web.Request, proxy_url: str
|
|
271
315
|
) -> web.WebSocketResponse:
|
|
272
316
|
"""Handles a websocket request to the backend matlab proxy server
|
|
@@ -352,11 +396,11 @@ async def _handle_websocket_request(
|
|
|
352
396
|
raise aiohttp.WebSocketError(code=code, message=message)
|
|
353
397
|
|
|
354
398
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
399
|
+
async def _forward_http_request(
|
|
400
|
+
req: web.Request,
|
|
401
|
+
req_body: Optional[bytes],
|
|
402
|
+
proxy_url: str,
|
|
403
|
+
headers: dict,
|
|
360
404
|
) -> web.Response:
|
|
361
405
|
"""
|
|
362
406
|
Forwards an incoming HTTP request to a specified backend server.
|
|
@@ -370,7 +414,7 @@ async def _handle_http_request(
|
|
|
370
414
|
proxy_url,
|
|
371
415
|
allow_redirects=True,
|
|
372
416
|
data=req_body,
|
|
373
|
-
headers=
|
|
417
|
+
headers=headers,
|
|
374
418
|
) as res:
|
|
375
419
|
headers = res.headers.copy()
|
|
376
420
|
body = await res.read()
|
|
@@ -410,16 +454,6 @@ def _render_error_page(error_msg: str) -> web.Response:
|
|
|
410
454
|
return web.HTTPServiceUnavailable(text=f"Error: {error_msg}")
|
|
411
455
|
|
|
412
456
|
|
|
413
|
-
def main() -> None:
|
|
414
|
-
"""
|
|
415
|
-
The main entry point of the application. Starts the app and run until the shutdown
|
|
416
|
-
signal to terminate the app is received.
|
|
417
|
-
"""
|
|
418
|
-
env_vars: namedtuple = _fetch_and_validate_required_env_vars()
|
|
419
|
-
loop: asyncio.AbstractEventLoop | asyncio.ProactorEventLoop = get_event_loop()
|
|
420
|
-
loop.run_until_complete(start_app(env_vars))
|
|
421
|
-
|
|
422
|
-
|
|
423
457
|
def _fetch_and_validate_required_env_vars() -> namedtuple:
|
|
424
458
|
EnvVars = namedtuple("EnvVars", ["mpm_port", "mpm_auth_token", "mpm_parent_pid"])
|
|
425
459
|
|
|
@@ -428,7 +462,7 @@ def _fetch_and_validate_required_env_vars() -> namedtuple:
|
|
|
428
462
|
ctx = os.getenv(mpm_env.get_env_name_mwi_mpm_parent_pid())
|
|
429
463
|
|
|
430
464
|
if not ctx or not port or not mpm_auth_token:
|
|
431
|
-
|
|
465
|
+
log.error("Error: One or more required environment variables are missing.")
|
|
432
466
|
sys.exit(1)
|
|
433
467
|
|
|
434
468
|
try:
|
|
@@ -437,10 +471,19 @@ def _fetch_and_validate_required_env_vars() -> namedtuple:
|
|
|
437
471
|
mpm_port=mwi_mpm_port, mpm_auth_token=mpm_auth_token, mpm_parent_pid=ctx
|
|
438
472
|
)
|
|
439
473
|
except ValueError as ve:
|
|
440
|
-
|
|
474
|
+
log.error("Error: Invalid type for port: %s", ve)
|
|
441
475
|
sys.exit(1)
|
|
442
476
|
|
|
443
477
|
|
|
478
|
+
def main() -> None:
|
|
479
|
+
"""
|
|
480
|
+
The main entry point of the application. Starts the app and run until the shutdown
|
|
481
|
+
signal to terminate the app is received.
|
|
482
|
+
"""
|
|
483
|
+
env_vars: namedtuple = _fetch_and_validate_required_env_vars()
|
|
484
|
+
asyncio.run(start_app(env_vars))
|
|
485
|
+
|
|
486
|
+
|
|
444
487
|
if __name__ == "__main__":
|
|
445
488
|
# This ensures that the app is not created when the module is imported and
|
|
446
489
|
# is only started when the script is run directly or via executable invocation
|
|
@@ -36,10 +36,9 @@ class OrphanedProcessMonitor:
|
|
|
36
36
|
"""
|
|
37
37
|
Triggers the shutdown process by setting the shutdown event.
|
|
38
38
|
"""
|
|
39
|
-
from matlab_proxy_manager.web.app import SHUTDOWN_EVENT
|
|
40
39
|
|
|
41
40
|
try:
|
|
42
41
|
# Set the shutdown async event to signal app shutdown to the app runner
|
|
43
|
-
|
|
42
|
+
self.app.get("shutdown_event").set()
|
|
44
43
|
except Exception as ex:
|
|
45
44
|
log.debug("Unable to set proxy manager shutdown event, err: %s", ex)
|
|
@@ -51,4 +51,15 @@ def start_watcher(app: web.Application):
|
|
|
51
51
|
observer = Observer()
|
|
52
52
|
observer.schedule(event_handler, path_to_watch, recursive=True)
|
|
53
53
|
observer.start()
|
|
54
|
+
app["observer"] = observer
|
|
54
55
|
return observer
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def stop_watcher(app):
|
|
59
|
+
"""
|
|
60
|
+
Stop the file system watcher associated with the application.
|
|
61
|
+
This function stops and joins the observer thread if it exists in the application.
|
|
62
|
+
"""
|
|
63
|
+
if "observer" in app:
|
|
64
|
+
app["observer"].stop()
|
|
65
|
+
app["observer"].join()
|
tests/unit/test_app.py
CHANGED
|
@@ -317,6 +317,7 @@ async def test_get_env_config(test_server):
|
|
|
317
317
|
"doc_url": "foo",
|
|
318
318
|
"extension_name": "bar",
|
|
319
319
|
"extension_name_short_description": "foobar",
|
|
320
|
+
"should_show_shutdown_button": True,
|
|
320
321
|
"isConcurrencyEnabled": "foobar",
|
|
321
322
|
"idleTimeoutDuration": 100,
|
|
322
323
|
}
|
tests/unit/test_app_state.py
CHANGED
|
@@ -364,9 +364,23 @@ def test_are_required_processes_ready(
|
|
|
364
364
|
assert actual == expected
|
|
365
365
|
|
|
366
366
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
# The test: test_track_embedded_connector has been split into:
|
|
368
|
+
# 1) test_track_embedded_connector_posix: Test to check if stop_matlab is called on posix systems.
|
|
369
|
+
# 2) test_track_embedded_connector : Test to check if stop_matlab is not called in windows.
|
|
370
|
+
|
|
371
|
+
# In windows, errors are shown as UI windows and calling stop_matlab() if MATLAB had not started in
|
|
372
|
+
# PROCESS_TIMEOUT seconds would remove the window thereby leaving the user without knowing why MATLAB
|
|
373
|
+
# failed to start.
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@pytest.mark.parametrize("platform", [("linux"), ("mac")])
|
|
377
|
+
async def test_track_embedded_connector_posix(
|
|
378
|
+
mocker_os_patching_fixture, app_state_fixture
|
|
379
|
+
):
|
|
380
|
+
"""Test to check track_embedded_connector task for posix platforms.
|
|
381
|
+
|
|
382
|
+
Checks if stop_matlab() has been called when the embedded connector doesn't respond
|
|
383
|
+
even after PROCESS_TIMEOUT seconds of starting MATLAB.
|
|
370
384
|
|
|
371
385
|
Args:
|
|
372
386
|
mocker_os_patching_fixture (mocker): Custom pytest fixture for mocking
|
|
@@ -374,7 +388,16 @@ async def test_track_embedded_connector(mocker_os_patching_fixture, app_state_fi
|
|
|
374
388
|
"""
|
|
375
389
|
|
|
376
390
|
# Arrange
|
|
377
|
-
#
|
|
391
|
+
# Patching embedded_connector_start_time to EPOCH+1 seconds and state to be "down".
|
|
392
|
+
|
|
393
|
+
# For this test, the embedded_connector_start_time can be patched to ant value 600(default PROCESS_TIMEOUT) seconds
|
|
394
|
+
# before the current time.
|
|
395
|
+
|
|
396
|
+
# To always ensure that the time difference between the embedded_connector_start_time
|
|
397
|
+
# and the current time is greater than PROCESS_TIMEOUT, the embedded_connector_start_time is patched to
|
|
398
|
+
# EPOCH + 1 seconds so that the time_diff = current_time - embedded_connector_start_time is greater
|
|
399
|
+
# than PROCESS_TIMEOUT always evaluates to True.
|
|
400
|
+
|
|
378
401
|
mocker_os_patching_fixture.patch.object(
|
|
379
402
|
app_state_fixture, "embedded_connector_start_time", new=float(1.0)
|
|
380
403
|
)
|
|
@@ -392,6 +415,58 @@ async def test_track_embedded_connector(mocker_os_patching_fixture, app_state_fi
|
|
|
392
415
|
spy.assert_called_once()
|
|
393
416
|
|
|
394
417
|
|
|
418
|
+
@pytest.mark.parametrize("platform", [("windows")])
|
|
419
|
+
async def test_track_embedded_connector(mocker_os_patching_fixture, app_state_fixture):
|
|
420
|
+
"""Test to check track_embedded_connector task on windows.
|
|
421
|
+
|
|
422
|
+
In windows, since errors are shown in native UI windows , calling stop_matlab() would remove them,
|
|
423
|
+
thereby not knowing the error with which MATLAB failed to start.
|
|
424
|
+
|
|
425
|
+
Hence, this test checks that stop_matlab() is not called.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
mocker_os_patching_fixture (mocker): Custom pytest fixture for mocking
|
|
429
|
+
app_state_fixture (AppState): Object of AppState class with defaults set
|
|
430
|
+
"""
|
|
431
|
+
# Arrange
|
|
432
|
+
# Patching embedded_connector_start_time to EPOCH+1 seconds and state to be "down".
|
|
433
|
+
|
|
434
|
+
# For this test, the embedded_connector_start_time can be patched to any value 600(default PROCESS_TIMEOUT) seconds
|
|
435
|
+
# before the current time.
|
|
436
|
+
|
|
437
|
+
# To always ensure that the time difference between the embedded_connector_start_time
|
|
438
|
+
# and the current time is greater than PROCESS_TIMEOUT, the embedded_connector_start_time is patched to
|
|
439
|
+
# EPOCH + 1 seconds so that the time_diff = current_time - embedded_connector_start_time is greater
|
|
440
|
+
# than PROCESS_TIMEOUT always evaluates to True.
|
|
441
|
+
|
|
442
|
+
mocker_os_patching_fixture.patch.object(
|
|
443
|
+
app_state_fixture, "embedded_connector_start_time", new=float(1.0)
|
|
444
|
+
)
|
|
445
|
+
mocker_os_patching_fixture.patch.object(
|
|
446
|
+
app_state_fixture, "embedded_connector_state", return_value="down"
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
spy = mocker_os_patching_fixture.spy(app_state_fixture, "stop_matlab")
|
|
450
|
+
|
|
451
|
+
# Act
|
|
452
|
+
|
|
453
|
+
# Unlike the posix test (test_track_embedded_connector_posix) where the task track_embedded_connector_state()
|
|
454
|
+
# would exit automatically after stopping MATLAB, in windows, the task will never exit(until the user checks the error
|
|
455
|
+
# manually and clicks on "Stop MATLAB").
|
|
456
|
+
|
|
457
|
+
# So, the task is manually stopped by raising a timeout error(set to 3 seconds). This is a generous amount of
|
|
458
|
+
# time for the error to be set as a MatlabError in CI systems.
|
|
459
|
+
with pytest.raises(asyncio.TimeoutError):
|
|
460
|
+
await asyncio.wait_for(
|
|
461
|
+
app_state_fixture._AppState__track_embedded_connector_state(),
|
|
462
|
+
timeout=3, # timeout of 3 seconds to account for CI systems. This is to wait for the error to be set as MatlabError.
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# Assert
|
|
466
|
+
spy.assert_not_called() # In windows, MATLAB process should not be stopped so that the UI error window is not closed.
|
|
467
|
+
assert isinstance(app_state_fixture.error, MatlabError)
|
|
468
|
+
|
|
469
|
+
|
|
395
470
|
@pytest.mark.parametrize(
|
|
396
471
|
"env_var_name, filter_prefix, is_filtered",
|
|
397
472
|
[("MWI_AUTH_TOKEN", "MWI_", None), ("MWIFOO_AUTH_TOKEN", "MWI_", "foo")],
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
# Copyright 2020-
|
|
1
|
+
# Copyright 2020-2024 The MathWorks, Inc.
|
|
2
2
|
|
|
3
|
-
"""Tests for functions in matlab_proxy/util/mwi_validators.py
|
|
4
|
-
"""
|
|
3
|
+
"""Tests for functions in matlab_proxy/util/mwi_validators.py"""
|
|
5
4
|
|
|
6
5
|
import os
|
|
7
6
|
import random
|
|
@@ -185,8 +184,8 @@ def test_validate_env_config_true():
|
|
|
185
184
|
def test_validate_env_config_false():
|
|
186
185
|
"""Passing a non existent config should raise FatalError exception"""
|
|
187
186
|
|
|
188
|
-
with pytest.raises(FatalError)
|
|
189
|
-
|
|
187
|
+
with pytest.raises(FatalError):
|
|
188
|
+
validators.validate_env_config(str(random.randint(10, 100)))
|
|
190
189
|
|
|
191
190
|
|
|
192
191
|
def test_get_configs():
|