matlab-proxy 0.26.0__py3-none-any.whl → 0.27.0__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.

Files changed (54) hide show
  1. matlab_proxy/app.py +13 -15
  2. matlab_proxy/app_state.py +8 -2
  3. matlab_proxy/default_configuration.py +2 -2
  4. matlab_proxy/gui/index.html +1 -1
  5. matlab_proxy/gui/static/js/{index.CZgGkMCD.js → index.BcDShXfH.js} +16 -16
  6. matlab_proxy/settings.py +3 -2
  7. matlab_proxy/util/list_servers.py +2 -2
  8. matlab_proxy/util/mwi/environment_variables.py +5 -0
  9. matlab_proxy/util/mwi/session_name.py +28 -0
  10. matlab_proxy/util/mwi/validators.py +2 -4
  11. {matlab_proxy-0.26.0.dist-info → matlab_proxy-0.27.0.dist-info}/METADATA +37 -25
  12. {matlab_proxy-0.26.0.dist-info → matlab_proxy-0.27.0.dist-info}/RECORD +19 -49
  13. {matlab_proxy-0.26.0.dist-info → matlab_proxy-0.27.0.dist-info}/WHEEL +1 -2
  14. matlab_proxy_manager/README.md +85 -0
  15. matlab_proxy_manager/lib/README.md +53 -0
  16. matlab_proxy_manager/storage/README.md +54 -0
  17. matlab_proxy_manager/web/README.md +37 -0
  18. matlab_proxy-0.26.0.dist-info/top_level.txt +0 -3
  19. tests/integration/__init__.py +0 -1
  20. tests/integration/integration_tests_with_license/__init__.py +0 -1
  21. tests/integration/integration_tests_with_license/conftest.py +0 -47
  22. tests/integration/integration_tests_with_license/test_http_end_points.py +0 -397
  23. tests/integration/integration_tests_without_license/__init__.py +0 -1
  24. tests/integration/integration_tests_without_license/conftest.py +0 -116
  25. tests/integration/integration_tests_without_license/test_matlab_is_down_if_unlicensed.py +0 -49
  26. tests/integration/utils/__init__.py +0 -1
  27. tests/integration/utils/integration_tests_utils.py +0 -352
  28. tests/integration/utils/licensing.py +0 -152
  29. tests/unit/__init__.py +0 -1
  30. tests/unit/conftest.py +0 -66
  31. tests/unit/test_app.py +0 -1299
  32. tests/unit/test_app_state.py +0 -1094
  33. tests/unit/test_constants.py +0 -7
  34. tests/unit/test_ddux.py +0 -22
  35. tests/unit/test_devel.py +0 -246
  36. tests/unit/test_non_dev_mode.py +0 -169
  37. tests/unit/test_settings.py +0 -679
  38. tests/unit/util/__init__.py +0 -3
  39. tests/unit/util/mwi/__init__.py +0 -1
  40. tests/unit/util/mwi/embedded_connector/__init__.py +0 -1
  41. tests/unit/util/mwi/embedded_connector/test_helpers.py +0 -29
  42. tests/unit/util/mwi/embedded_connector/test_request.py +0 -64
  43. tests/unit/util/mwi/test_custom_http_headers.py +0 -281
  44. tests/unit/util/mwi/test_download.py +0 -152
  45. tests/unit/util/mwi/test_logger.py +0 -82
  46. tests/unit/util/mwi/test_token_auth.py +0 -303
  47. tests/unit/util/mwi/test_validators.py +0 -364
  48. tests/unit/util/test_cookie_jar.py +0 -252
  49. tests/unit/util/test_mw.py +0 -550
  50. tests/unit/util/test_util.py +0 -221
  51. tests/utils/__init__.py +0 -1
  52. tests/utils/logging_util.py +0 -81
  53. {matlab_proxy-0.26.0.dist-info → matlab_proxy-0.27.0.dist-info}/entry_points.txt +0 -0
  54. {matlab_proxy-0.26.0.dist-info → matlab_proxy-0.27.0.dist-info/licenses}/LICENSE.md +0 -0
@@ -1,1094 +0,0 @@
1
- # Copyright 2023-2025 The MathWorks, Inc.
2
-
3
- import asyncio
4
- import json
5
- import os
6
- from dataclasses import dataclass
7
- from pathlib import Path
8
- from typing import Optional
9
-
10
- import pytest
11
- from matlab_proxy import settings
12
-
13
- from matlab_proxy import settings
14
- from matlab_proxy.app_state import AppState
15
- from matlab_proxy.constants import MWI_AUTH_TOKEN_NAME_FOR_HTTP
16
- from matlab_proxy.util.mwi.exceptions import (
17
- LicensingError,
18
- MatlabError,
19
- MatlabInstallError,
20
- )
21
- from matlab_proxy.constants import (
22
- CONNECTOR_SECUREPORT_FILENAME,
23
- USER_CODE_OUTPUT_FILE_NAME,
24
- )
25
-
26
- from tests.unit.util import MockResponse
27
- from tests.unit.test_constants import CHECK_MATLAB_STATUS_INTERVAL, FIVE_MAX_TRIES
28
-
29
-
30
- @pytest.fixture
31
- def sample_settings_fixture(tmp_path):
32
- """A pytest fixture which returns a dict containing sample settings for the AppState class.
33
-
34
- Args:
35
- tmp_path : Builtin pytest fixture
36
-
37
- Returns:
38
- dict: A dictionary of sample settings
39
- """
40
- tmp_file = tmp_path / "parent_1" / "parent_2" / "tmp_file.json"
41
- return {
42
- "error": None,
43
- "warnings": [],
44
- "matlab_config_file": tmp_file,
45
- "is_xvfb_available": True,
46
- "is_windowmanager_available": True,
47
- "mwi_server_url": "dummy",
48
- "mwi_logs_root_dir": Path(settings.get_mwi_config_folder(dev=True)),
49
- "app_port": 12345,
50
- "mwapikey": "asdf",
51
- "has_custom_code_to_execute": False,
52
- "mwi_idle_timeout": 100,
53
- "mwi_is_token_auth_enabled": False,
54
- "integration_name": "MATLAB Desktop",
55
- }
56
-
57
-
58
- @pytest.fixture
59
- def app_state_fixture(sample_settings_fixture, event_loop):
60
- """A pytest fixture which returns an instance of AppState class with no errors.
61
-
62
- Args:
63
- sample_settings_fixture (dict): A dictionary of sample settings to be used by
64
- event_loop : A pytest builtin fixture
65
-
66
- Returns:
67
- AppState: An object of the AppState class
68
- """
69
- app_state = AppState(settings=sample_settings_fixture)
70
- app_state.processes = {"matlab": None, "xvfb": None}
71
- app_state.licensing = {"type": "existing_license"}
72
-
73
- yield app_state
74
-
75
- event_loop.run_until_complete(app_state.stop_server_tasks())
76
-
77
-
78
- @pytest.fixture
79
- def sample_token_headers_fixture():
80
- return {MWI_AUTH_TOKEN_NAME_FOR_HTTP: "asdf"}
81
-
82
-
83
- @pytest.fixture
84
- def app_state_with_token_auth_fixture(
85
- app_state_fixture, sample_token_headers_fixture, tmp_path
86
- ):
87
- """Pytest fixture which returns AppState instance with token authentication enabled.
88
-
89
- Args:
90
- app_state_fixture (AppState): Pytest fixture
91
- tmp_path (str): Built-in pytest fixture
92
-
93
- Returns:
94
- (AppState, dict): Instance of the AppState class with token authentication enabled and token headers
95
- """
96
- tmp_matlab_ready_file = Path(tmp_path) / "tmp_file.txt"
97
- tmp_matlab_ready_file.touch()
98
- ((mwi_auth_token_name, mwi_auth_token_hash),) = sample_token_headers_fixture.items()
99
- app_state_fixture.matlab_session_files["matlab_ready_file"] = tmp_matlab_ready_file
100
- app_state_fixture.settings["mwi_is_token_auth_enabled"] = True
101
- app_state_fixture.settings["mwi_auth_token_name_for_env"] = mwi_auth_token_name
102
- app_state_fixture.settings["mwi_auth_token_name_for_http"] = (
103
- MWI_AUTH_TOKEN_NAME_FOR_HTTP
104
- )
105
- app_state_fixture.settings["mwi_auth_token_hash"] = mwi_auth_token_hash
106
- app_state_fixture.settings["mwi_server_url"] = "http://localhost:8888"
107
-
108
- return app_state_fixture
109
-
110
-
111
- @pytest.fixture
112
- def mocker_os_patching_fixture(mocker, platform, event_loop):
113
- """A pytest fixture which patches the is_* functions in system.py module
114
-
115
- Args:
116
- mocker : Built in pytest fixture
117
- platform (str): A string representing "windows", "linux" or "mac"
118
- event_loop : A pytest builtin fixture
119
-
120
- Returns:
121
- mocker: Built in pytest fixture with patched calls to system.py module.
122
- """
123
- mocker.patch("matlab_proxy.app_state.system.is_linux", return_value=False)
124
- mocker.patch("matlab_proxy.app_state.system.is_windows", return_value=False)
125
- mocker.patch("matlab_proxy.app_state.system.is_mac", return_value=False)
126
- mocker.patch("matlab_proxy.app_state.system.is_posix", return_value=False)
127
- mocker.patch("matlab_proxy.app_state.util.get_event_loop", return_value=event_loop)
128
-
129
- if platform == "linux":
130
- mocker.patch("matlab_proxy.app_state.system.is_linux", return_value=True)
131
- mocker.patch("matlab_proxy.app_state.system.is_posix", return_value=True)
132
-
133
- elif platform == "windows":
134
- mocker.patch("matlab_proxy.app_state.system.is_windows", return_value=True)
135
- mocker.patch("matlab_proxy.app_state.system.is_posix", return_value=False)
136
-
137
- else:
138
- mocker.patch("matlab_proxy.app_state.system.is_mac", return_value=True)
139
- mocker.patch("matlab_proxy.app_state.system.is_posix", return_value=True)
140
-
141
- return mocker
142
-
143
-
144
- @dataclass(frozen=True)
145
- class Mock_xvfb:
146
- """An immutable dataclass representing a mocked Xvfb process"""
147
-
148
- returncode: Optional[int]
149
- pid: Optional[int]
150
-
151
-
152
- @dataclass(frozen=True)
153
- class Mock_matlab:
154
- """An immutable dataclass representing a mocked MATLAB process"""
155
-
156
- returncode: Optional[int]
157
- pid: Optional[int]
158
-
159
- def is_running(self) -> bool:
160
- return self.returncode is None
161
-
162
- def wait(self) -> int:
163
- return self.returncode
164
-
165
-
166
- @pytest.mark.parametrize(
167
- "licensing, expected",
168
- [
169
- (None, False),
170
- ({"type": "nlm", "conn_str": "123@host"}, True),
171
- ({"type": "nlm"}, False),
172
- ({"type": "mhlm", "identity_token": "random_token"}, False),
173
- (
174
- {
175
- "type": "mhlm",
176
- "identity_token": "random_token",
177
- "source_id": "dummy_id",
178
- "expiry": "Jan 1, 1970",
179
- "entitlement_id": "123456",
180
- },
181
- True,
182
- ),
183
- ({"type": "existing_license"}, True),
184
- ({"type": "invalid_type"}, False),
185
- ],
186
- ids=[
187
- "None licensing",
188
- "happy path-nlm",
189
- "incomplete nlm data",
190
- "incomplete mhlm data",
191
- "happy path-mhlm",
192
- "happy path-existing license",
193
- "invalid license",
194
- ],
195
- )
196
- def test_is_licensed(app_state_fixture, licensing, expected):
197
- """Test to check is_licensed()
198
-
199
- Args:
200
- app_state_fixture (AppState): Object of AppState class with defaults set
201
- licensing (dict): Represents licensing information
202
- expected (bool): Expected return value.
203
- """
204
- # Arrange
205
- # Nothing to arrange
206
-
207
- # Act
208
- app_state_fixture.licensing = licensing
209
-
210
- # Assert
211
- assert app_state_fixture.is_licensed() == expected
212
-
213
-
214
- @pytest.mark.parametrize(
215
- "err, expected_err",
216
- [
217
- (MatlabError(message="dummy error"), MatlabError(message="dummy")),
218
- (LicensingError(message="license issue"), None),
219
- ],
220
- ids=["Any error except licensing error", "licensing error"],
221
- )
222
- def test_unset_licensing(err, app_state_fixture, expected_err):
223
- """Test to check unset_liecnsing removes licensing from the AppState object
224
-
225
- Args:
226
- err (Exception): Custom exceptions defined in exceptions.py
227
- licensing (bool): Whether licensing info is removed
228
- expected_err (Exception): Expected exception
229
- """
230
- # Arrange
231
- app_state_fixture.error = err
232
-
233
- # Act
234
- app_state_fixture.unset_licensing()
235
-
236
- # Assert
237
- assert app_state_fixture.licensing == None
238
- assert type(app_state_fixture.error) is type(expected_err)
239
-
240
-
241
- # config file is deleted when licensing info is not set i.e. set to None
242
- def test_persist_licensing_when_licensing_info_is_not_set(app_state_fixture):
243
- """Test to check if data is not persisted to a file if licensing info is not present
244
-
245
- Args:
246
- tmp_path (Path): Built in pytest fixture
247
- """
248
- # Arrange
249
- # Nothing to arrange
250
- app_state_fixture.licensing = None
251
-
252
- # Act
253
- app_state_fixture.persist_config_data()
254
-
255
- # Assert
256
- assert os.path.exists(app_state_fixture.settings["matlab_config_file"]) is False
257
-
258
-
259
- @pytest.mark.parametrize(
260
- "licensing_data",
261
- [
262
- ({"type": "nlm", "conn_str": "123@host"}),
263
- (
264
- {
265
- "type": "mhlm",
266
- "identity_token": "random_token",
267
- "source_id": "dummy_id",
268
- "expiry": "Jan 1, 1970",
269
- "entitlement_id": "123456",
270
- }
271
- ),
272
- ({"type": "existing_license"}),
273
- ],
274
- ids=["nlm type", "mhlm type", "existing license type"],
275
- )
276
- def test_persist_config_data(licensing_data: dict, tmp_path):
277
- """Test to check if persist_licensing() writes data to the file system
278
-
279
- Args:
280
- data (dict): Represents matlab-proxy licensing data
281
- tmp_path : Built-in pytest fixture.
282
- """
283
- # Arrange
284
- tmp_file = tmp_path / "parent_1" / "parent_2" / "tmp_file.json"
285
- settings = {
286
- "matlab_config_file": tmp_file,
287
- "error": None,
288
- "matlab_version": None,
289
- "warnings": [],
290
- "mwi_idle_timeout": None,
291
- }
292
- app_state = AppState(settings=settings)
293
- app_state.licensing = licensing_data
294
-
295
- cached_data = {"licensing": licensing_data, "matlab": {"version": None}}
296
-
297
- # Act
298
- app_state.persist_config_data()
299
- with open(tmp_file, "r") as file:
300
- got = file.read()
301
-
302
- # Assert
303
- assert json.loads(got) == cached_data
304
-
305
-
306
- validate_required_processes_test_data = [
307
- (None, None, "linux", False), # xvfb is None == True
308
- (None, Mock_xvfb(None, 1), "linux", False), # matlab is None == True
309
- (
310
- Mock_matlab(None, 1),
311
- Mock_xvfb(None, 1),
312
- "linux",
313
- True,
314
- ), # All branches are skipped and nothing returned
315
- (
316
- Mock_matlab(None, 1),
317
- Mock_xvfb(123, 2),
318
- "linux",
319
- False,
320
- ), # xvfb.returncode is not None == True
321
- (
322
- Mock_matlab(123, 1),
323
- Mock_xvfb(None, 2),
324
- "linux",
325
- False,
326
- ), # matlab.returncode is not None == True
327
- (
328
- Mock_matlab(None, 1),
329
- None,
330
- "linux",
331
- True,
332
- ), # Xvfb not found on path
333
- ]
334
-
335
-
336
- @pytest.mark.parametrize(
337
- "matlab, xvfb, platform, expected",
338
- validate_required_processes_test_data,
339
- ids=[
340
- "processes_not_running",
341
- "matlab_not_running",
342
- "All_required_processes_running",
343
- "All_processes_running_with_xvfb_returning_non_zero_code",
344
- "All_processes_running_with_matlab_returning_non_zero_code",
345
- "xvfb_is_optional_matlab_starts_without_it",
346
- ],
347
- )
348
- def test_are_required_processes_ready(
349
- app_state_fixture, mocker_os_patching_fixture, matlab, xvfb, expected
350
- ):
351
- """Test to check if required processes are ready
352
-
353
- Args:
354
- app_state_fixture (AppState): Object of AppState class with defaults set
355
- mocker_os_patching_fixture (mocker): Custom pytest fixture for mocking
356
- matlab (Mock_matlab): Represents a mocked MATLAB process
357
- xvfb (Mock_xvfb): Represents a mocked Xvfb process
358
- expected (bool): Expected return value based on process return code
359
- """
360
- # Arrange
361
- app_state_fixture.processes = {"matlab": matlab, "xvfb": xvfb}
362
- if not xvfb:
363
- app_state_fixture.settings["is_xvfb_available"] = False
364
-
365
- # Act
366
- actual = app_state_fixture._are_required_processes_ready()
367
-
368
- # Assert
369
- assert actual == expected
370
-
371
-
372
- # The test: test_track_embedded_connector has been split into:
373
- # 1) test_track_embedded_connector_posix: Test to check if stop_matlab is called on posix systems.
374
- # 2) test_track_embedded_connector : Test to check if stop_matlab is not called in windows.
375
-
376
- # In windows, errors are shown as UI windows and calling stop_matlab() if MATLAB had not started in
377
- # PROCESS_TIMEOUT seconds would remove the window thereby leaving the user without knowing why MATLAB
378
- # failed to start.
379
-
380
-
381
- @pytest.mark.parametrize("platform", [("linux"), ("mac")])
382
- async def test_track_embedded_connector_posix(
383
- mocker_os_patching_fixture, app_state_fixture
384
- ):
385
- """Test to check track_embedded_connector task for posix platforms.
386
-
387
- Checks if stop_matlab() has been called when the embedded connector doesn't respond
388
- even after PROCESS_TIMEOUT seconds of starting MATLAB.
389
-
390
- Args:
391
- mocker_os_patching_fixture (mocker): Custom pytest fixture for mocking
392
- app_state_fixture (AppState): Object of AppState class with defaults set
393
- """
394
-
395
- # Arrange
396
- # Patching embedded_connector_start_time to EPOCH+1 seconds and state to be "down".
397
-
398
- # For this test, the embedded_connector_start_time can be patched to ant value 600(default PROCESS_TIMEOUT) seconds
399
- # before the current time.
400
-
401
- # To always ensure that the time difference between the embedded_connector_start_time
402
- # and the current time is greater than PROCESS_TIMEOUT, the embedded_connector_start_time is patched to
403
- # EPOCH + 1 seconds so that the time_diff = current_time - embedded_connector_start_time is greater
404
- # than PROCESS_TIMEOUT always evaluates to True.
405
-
406
- mocker_os_patching_fixture.patch.object(
407
- app_state_fixture, "embedded_connector_start_time", new=float(1.0)
408
- )
409
- mocker_os_patching_fixture.patch.object(
410
- app_state_fixture, "embedded_connector_state", return_value="down"
411
- )
412
-
413
- # verify that stop_matlab() is called once
414
- spy = mocker_os_patching_fixture.spy(app_state_fixture, "stop_matlab")
415
-
416
- # Act
417
- await app_state_fixture._AppState__track_embedded_connector_state()
418
-
419
- # Assert
420
- spy.assert_called_once()
421
-
422
-
423
- @pytest.mark.parametrize("platform", ["windows"])
424
- async def test_track_embedded_connector(mocker_os_patching_fixture, app_state_fixture):
425
- """Test to check track_embedded_connector task on windows.
426
-
427
- In windows, since errors are shown in native UI windows , calling stop_matlab() would remove them,
428
- thereby not knowing the error with which MATLAB failed to start.
429
-
430
- Hence, this test checks that stop_matlab() is not called.
431
-
432
- Args:
433
- mocker_os_patching_fixture (mocker): Custom pytest fixture for mocking
434
- app_state_fixture (AppState): Object of AppState class with defaults set
435
- """
436
- # Arrange
437
- # Patching embedded_connector_start_time to EPOCH+1 seconds and state to be "down".
438
-
439
- # For this test, the embedded_connector_start_time can be patched to any value 600(default PROCESS_TIMEOUT) seconds
440
- # before the current time.
441
-
442
- # To always ensure that the time difference between the embedded_connector_start_time
443
- # and the current time is greater than PROCESS_TIMEOUT, the embedded_connector_start_time is patched to
444
- # EPOCH + 1 seconds so that the time_diff = current_time - embedded_connector_start_time is greater
445
- # than PROCESS_TIMEOUT always evaluates to True.
446
-
447
- mocker_os_patching_fixture.patch.object(
448
- app_state_fixture, "embedded_connector_start_time", new=float(1.0)
449
- )
450
- mocker_os_patching_fixture.patch.object(
451
- app_state_fixture, "embedded_connector_state", return_value="down"
452
- )
453
-
454
- spy = mocker_os_patching_fixture.spy(app_state_fixture, "stop_matlab")
455
-
456
- # Act
457
-
458
- # Unlike the posix test (test_track_embedded_connector_posix) where the task track_embedded_connector_state()
459
- # would exit automatically after stopping MATLAB, in windows, the task will never exit(until the user checks the error
460
- # manually and clicks on "Stop MATLAB").
461
-
462
- # So, the task is manually stopped by raising a timeout error(set to 3 seconds). This is a generous amount of
463
- # time for the error to be set as a MatlabError in CI systems.
464
- with pytest.raises(asyncio.TimeoutError):
465
- await asyncio.wait_for(
466
- app_state_fixture._AppState__track_embedded_connector_state(),
467
- timeout=3, # timeout of 3 seconds to account for CI systems. This is to wait for the error to be set as MatlabError.
468
- )
469
-
470
- # Assert
471
- spy.assert_not_called() # In windows, MATLAB process should not be stopped so that the UI error window is not closed.
472
- assert isinstance(app_state_fixture.error, MatlabError)
473
-
474
-
475
- @pytest.mark.parametrize(
476
- "env_var_name, filter_prefix, is_filtered",
477
- [("MWI_AUTH_TOKEN", "MWI_", None), ("MWIFOO_AUTH_TOKEN", "MWI_", "foo")],
478
- ids=["env_var_is_filtered", "env_var_is_not_filtered"],
479
- )
480
- def test_env_variables_filtration_for_xvfb_process(
481
- monkeypatch, env_var_name, filter_prefix, is_filtered
482
- ):
483
- """Test to check if __filter_env_variables filters environment variables with a certain prefix correctly.
484
-
485
- Args:
486
- monkeypatch (Object): Built-in pytest fixture for monkeypatching
487
- env_var_name (str): Name of the environment variable
488
- filter_prefix (str): Prefix to check for filtering
489
- is_filtered (bool): To check if the env variable with specified prefix is filtered.
490
- """
491
- # Arrange
492
- env_var = env_var_name
493
- monkeypatch.setenv(env_var, "foo")
494
-
495
- # Act
496
- filtered_env_vars: dict = AppState._AppState__filter_env_variables(
497
- os.environ, filter_prefix
498
- )
499
-
500
- # Assert
501
- assert filtered_env_vars.get(env_var) == is_filtered
502
-
503
-
504
- @pytest.mark.parametrize(
505
- "platform, expected_output",
506
- [("linux", "stdout"), ("windows", "file"), ("mac", "stdout")],
507
- )
508
- async def test_setup_env_for_matlab(
509
- mocker_os_patching_fixture, platform, expected_output, app_state_fixture, tmp_path
510
- ):
511
- """Test to check MW_DIAGNOSTIC_DEST is set appropriately for posix and non-posix systems
512
-
513
- Args:
514
- mocker_os_patching_fixture (mocker): Custom pytest fixture for mocking
515
- platform (str): string describing a platform
516
- app_state_fixture (AppState): Object of AppState class with defaults set
517
- tmp_path (Path): Built-in pytest fixture for temporary paths
518
- """
519
-
520
- # Arrange
521
- app_state_fixture.licensing = {"type": "existing_license"}
522
- app_state_fixture.settings = {"mwapikey": None, "matlab_display": ":1"}
523
- app_state_fixture.mwi_logs_dir = tmp_path
524
- mocker_os_patching_fixture.patch(
525
- "matlab_proxy.app_state.logger.isEnabledFor", return_value=True
526
- )
527
-
528
- # Act
529
- matlab_env = await app_state_fixture._AppState__setup_env_for_matlab()
530
-
531
- # Assert
532
- assert expected_output in matlab_env["MW_DIAGNOSTIC_DEST"]
533
-
534
-
535
- async def test_requests_sent_by_matlab_proxy_have_headers(
536
- app_state_with_token_auth_fixture,
537
- sample_token_headers_fixture,
538
- mocker,
539
- ):
540
- """Test to check if token headers are included in requests sent by matlab-proxy when authentication is enabled.
541
- Test checks if the headers are included in the request to stop matlab and get connector status.
542
-
543
- Args:
544
- app_state_fixture_with_token_auth (AppState): Instance of AppState class with token authentication enabled
545
- sample_token_headers_fixture (dict): Dict which represents the token headers
546
- mocker : Built-in pytest fixture
547
- """
548
- # Arrange
549
- mock_resp = MockResponse(
550
- ok=True, payload={"messages": {"EvalResponse": [{"isError": None}]}}
551
- )
552
- mocked_req = mocker.patch("aiohttp.ClientSession.request", return_value=mock_resp)
553
-
554
- # Patching to make _are_required_processes_ready() to return True
555
- mocker.patch.object(
556
- AppState,
557
- "_are_required_processes_ready",
558
- return_value=True,
559
- )
560
- # Patching to make get_matlab_state() to return up
561
- mocker.patch.object(
562
- AppState,
563
- "get_matlab_state",
564
- return_value="up",
565
- )
566
- # Wait for _update_matlab_connector_status to run
567
- await asyncio.sleep(CHECK_MATLAB_STATUS_INTERVAL)
568
-
569
- # Act
570
- await app_state_with_token_auth_fixture._AppState__send_stop_request_to_matlab()
571
-
572
- # Assert
573
-
574
- # 1 request from _update_matlab_connector_status() and another from
575
- # /stop_matlab request
576
- connector_status_request_headers = list(mocked_req.call_args_list)[0].kwargs[
577
- "headers"
578
- ]
579
- send_stop_matlab_request_headers = list(mocked_req.call_args_list)[1].kwargs[
580
- "headers"
581
- ]
582
- assert sample_token_headers_fixture == connector_status_request_headers
583
- assert sample_token_headers_fixture == send_stop_matlab_request_headers
584
-
585
-
586
- async def test_start_matlab_without_xvfb(app_state_fixture, mocker):
587
- """Test to check if Matlab process starts without throwing errors when Xvfb is not present
588
-
589
- Args:
590
- app_state_fixture (AppState): Object of AppState class with defaults set
591
- mocker : Built-in pytest fixture
592
- """
593
- # Arrange
594
- app_state_fixture.settings["is_xvfb_available"] = False
595
- mock_matlab = Mock_matlab(None, 1)
596
-
597
- # Starting asyncio tasks related to matlab is not required here as only Xvfb check is required.
598
- mocker.patch.object(
599
- AppState, "_AppState__start_matlab_process", return_value=mock_matlab
600
- )
601
- mocker.patch.object(
602
- AppState, "_AppState__matlab_stderr_reader_posix", return_value=None
603
- )
604
- mocker.patch.object(
605
- AppState, "_AppState__track_embedded_connector_state", return_value=None
606
- )
607
- mocker.patch.object(AppState, "_AppState__update_matlab_port", return_value=None)
608
-
609
- # Act
610
- await app_state_fixture.start_matlab()
611
-
612
- # Assert
613
- # Check if Xvfb has not started
614
- assert app_state_fixture.processes["xvfb"] is None
615
- # Check if Matlab started
616
- assert app_state_fixture.processes["matlab"] is mock_matlab
617
-
618
-
619
- async def test_start_matlab_without_xvfb_and_matlab(app_state_fixture):
620
- """Test to check if MATLAB doesn't start and sets the error variable to MatlabInstallError when
621
- there is not MATLAB on system PATH
622
-
623
- Args:
624
- app_state_fixture (AppState): Object of AppState class with defaults set
625
- """
626
- # Arrange
627
- app_state_fixture.settings["is_xvfb_available"] = False
628
- app_state_fixture.settings["matlab_cmd"] = None
629
-
630
- # Act
631
- await app_state_fixture.start_matlab()
632
-
633
- # Assert
634
- # Check if Xvfb has not started
635
- assert app_state_fixture.processes["xvfb"] is None
636
- # Check if Matlab has not started
637
- assert app_state_fixture.processes["matlab"] is None
638
- # Check if MatlabInstallError is set as the error
639
- assert isinstance(app_state_fixture.error, MatlabInstallError)
640
-
641
-
642
- @pytest.mark.parametrize(
643
- "is_desktop, client_id, is_client_id_present, expected_is_active_client",
644
- [
645
- (False, None, False, None),
646
- (False, "mock_id", False, None),
647
- (True, None, True, True),
648
- (True, "mock_id", False, True),
649
- ],
650
- ids=[
651
- "request_from_non-desktop_client",
652
- "request_from_non-desktop_client_having_mock_id",
653
- "request_from_desktop_client",
654
- "request_from_desktop_client_having_mock_id",
655
- ],
656
- )
657
- async def test_get_session_status(
658
- app_state_fixture,
659
- is_desktop,
660
- client_id,
661
- is_client_id_present,
662
- expected_is_active_client,
663
- ):
664
- """Test to check if correnct session response is returned based on various conditions.
665
-
666
- Args:
667
- app_state_fixture (AppState): Object of AppState class with defaults set
668
- is_desktop (bool): A flag indicating whether the client is a desktop client.
669
- client_id (str or None): The client ID. If None, a new client ID may be generated.
670
- is_client_id_present (bool): Indicates whether the expected value of client_id is string or not.
671
- expected_is_active_client (bool): Indicates the expected value of is_active_client
672
-
673
- """
674
- # The value of transfer_session is a Don't Care condition as initially the value of client_id is always None.
675
- output_client_id, output_is_active_client = app_state_fixture.get_session_status(
676
- is_desktop, client_id, transfer_session=False
677
- )
678
- assert isinstance(output_client_id, str) == is_client_id_present, (
679
- "Expected client_id to be a string got None"
680
- if is_client_id_present
681
- else "Expected client_id to be None got a string value"
682
- )
683
- assert (
684
- output_is_active_client == expected_is_active_client
685
- ), f"Expected is_active_client to be {expected_is_active_client} got {output_is_active_client}"
686
- # For clean up of task_detect_client_status
687
- app_state_fixture.active_client = None
688
-
689
-
690
- async def test_get_session_status_can_transfer_session(app_state_fixture):
691
- """Test to check whether transer session changes client id to the new id
692
-
693
- Args:
694
- app_state_fixture (AppState): Object of AppState class with defaults set
695
- """
696
- app_state_fixture.active_client = "mock_id"
697
- app_state_fixture.get_session_status(
698
- is_desktop=True, client_id="new_id", transfer_session=True
699
- )
700
- assert app_state_fixture.active_client == "new_id"
701
- # For clean up of task_detect_client_status
702
- app_state_fixture.active_client = None
703
-
704
-
705
- async def test_detect_active_client_status_can_reset_active_client(app_state_fixture):
706
- """Test to check whether the value of active client is being reset due to the client inactivity.
707
-
708
- Args:
709
- app_state_fixture (AppState): Object of AppState class with defaults set
710
- """
711
- app_state_fixture.active_client = "mock_id"
712
- await app_state_fixture.detect_active_client_status(
713
- sleep_time=0, max_inactive_count=0
714
- )
715
- assert (
716
- app_state_fixture.active_client == None
717
- ), f"Expected the active_client to be None"
718
-
719
-
720
- @pytest.mark.parametrize(
721
- "session_file_count, has_custom_code_to_execute", [(2, True), (1, False)]
722
- )
723
- def test_create_logs_dir_for_MATLAB(
724
- app_state_fixture, session_file_count, has_custom_code_to_execute
725
- ):
726
- """Test to check create_logs_dir_for_MATLAB()
727
-
728
- Args:
729
- app_state_fixture (AppState): Object of AppState class with defaults set
730
- """
731
- # Arrange
732
- app_state_fixture.settings["has_custom_code_to_execute"] = (
733
- has_custom_code_to_execute
734
- )
735
-
736
- # Act
737
- app_state_fixture.create_logs_dir_for_MATLAB()
738
-
739
- # Assert
740
- for _, session_file_path in app_state_fixture.matlab_session_files.items():
741
- # Check session files are present in mwi logs directory
742
- assert app_state_fixture.mwi_logs_dir == Path(session_file_path).parent
743
-
744
- assert len(app_state_fixture.matlab_session_files) == session_file_count
745
-
746
-
747
- async def test_check_idle_timer_started(app_state_fixture):
748
- """Test to check if the IDLE timer starts automatically
749
-
750
- Args:
751
- app_state_fixture (AppState): Object of AppState class with defaults set
752
- """
753
- # Arrange
754
- # Nothing to arrange
755
-
756
- # Act
757
- # constructor is called automatically
758
-
759
- # Assert
760
- assert app_state_fixture.is_idle_timeout_enabled is True
761
- assert "decrement_idle_timer" in app_state_fixture.server_tasks
762
- assert app_state_fixture.idle_timeout_lock is not None
763
-
764
-
765
- async def test_reset_timer(app_state_fixture):
766
- """Test to check if the IDLE timer is reset to its initial value
767
-
768
- Args:
769
- app_state_fixture (AppState): Object of AppState class with defaults set
770
- """
771
- # Arrange
772
- # Sleep for 1 second for the decrement_timer task to decrease IDLE timer by
773
- # more than 1 second. This is for decreasing flakiness of this test on different platforms.
774
- await asyncio.sleep(CHECK_MATLAB_STATUS_INTERVAL + CHECK_MATLAB_STATUS_INTERVAL)
775
-
776
- # Act
777
- await app_state_fixture.reset_timer()
778
-
779
- # Assert
780
- assert (
781
- app_state_fixture.get_remaining_idle_timeout()
782
- == app_state_fixture.settings["mwi_idle_timeout"]
783
- )
784
-
785
-
786
- async def test_decrement_timer(app_state_fixture):
787
- """Test to check if the IDLE timer value decrements automatically
788
-
789
- Args:
790
- app_state_fixture (AppState): Object of AppState class with defaults set
791
- """
792
- # Arrange
793
- # Nothing to arrange
794
- # decrement_timer task is started automatically by the constructor
795
-
796
- # Sleep for 1 second for the decrement_timer task to decrease IDLE timer by
797
- # more than 1 second. This is for decreasing flakiness of this test on different platforms.
798
- await asyncio.sleep(CHECK_MATLAB_STATUS_INTERVAL + CHECK_MATLAB_STATUS_INTERVAL)
799
-
800
- # Act
801
- # Nothing to act
802
-
803
- # Assert
804
- assert (
805
- app_state_fixture.get_remaining_idle_timeout()
806
- < app_state_fixture.settings["mwi_idle_timeout"]
807
- )
808
-
809
-
810
- async def test_decrement_timer_runs_out(sample_settings_fixture, mocker):
811
- """Test to check if the IDLE timer eventually runs out.
812
-
813
- Args:
814
- app_state_fixture (AppState): Object of AppState class with defaults set
815
- """
816
- # Arrange
817
- # Set the IDLE timeout to a low value
818
- idle_timeout = 1
819
- sample_settings_fixture["mwi_idle_timeout"] = idle_timeout
820
- app_state = AppState(settings=sample_settings_fixture)
821
- app_state.processes = {"matlab": None, "xvfb": None}
822
- app_state.licensing = {"type": "existing_license"}
823
-
824
- # mock util.get_event_loop() to return a new event_loop for the test to assert
825
- mock_loop = asyncio.new_event_loop()
826
- mocker.patch("matlab_proxy.app_state.util.get_event_loop", return_value=mock_loop)
827
-
828
- # Act
829
- # Wait for a little more time than idle_timeout to decrease flakiness of this test on different platforms.
830
- # MATLAB state changes from down -> starting -> up -> down (idle timer runs out)
831
- await asyncio.sleep(idle_timeout * FIVE_MAX_TRIES)
832
-
833
- # Assert
834
- assert not mock_loop.is_running()
835
- assert app_state.get_matlab_state() == "down"
836
-
837
- # Cleanup
838
- mock_loop.stop()
839
- await app_state.stop_server_tasks()
840
-
841
-
842
- @pytest.mark.parametrize(
843
- "connector_status, matlab_status",
844
- [("down", "starting"), ("up", "up")],
845
- ids=["connector_down", "connector_up"],
846
- )
847
- async def test_update_matlab_state_based_on_connector_state(
848
- app_state_fixture, connector_status, matlab_status
849
- ):
850
- """Test to check if MATLAB state is updated correctly based on connector state
851
-
852
- Args:
853
- app_state_fixture (AppState): Object of AppState class with defaults set
854
- connector_status (str): Represents connector status
855
- matlab_status (str): Represents expected MATLAB status
856
- """
857
- # Arrange
858
- app_state_fixture.embedded_connector_state = connector_status
859
-
860
- # Act
861
- await app_state_fixture._AppState__update_matlab_state_based_on_connector_state()
862
-
863
- # Assert
864
- assert app_state_fixture.get_matlab_state() == matlab_status
865
-
866
-
867
- @pytest.mark.parametrize(
868
- "matlab_status, matlab_busy_status",
869
- [("starting", None), ("up", "busy"), ("up", "idle")],
870
- ids=["No response from busy status endpoint", "MATLAB is busy", "MATLAB is idle"],
871
- )
872
- async def test_update_matlab_state_using_busy_endpoint(
873
- mocker, app_state_fixture, matlab_status, matlab_busy_status
874
- ):
875
- """Test to check if MATLAB and its busy status updates correctly when the
876
- busy status endpoint is used.
877
-
878
- Args:
879
- mocker (mocker): Built-in pytest fixture
880
- app_state_fixture (AppState): Object of AppState class with defaults set
881
- matlab_status (str): Represents MATLAB status
882
- matlab_busy_status (str): Represents MATLAB busy status
883
- """
884
- # Arrange
885
- mocker.patch(
886
- "matlab_proxy.app_state.mwi.embedded_connector.request.get_busy_state",
887
- return_value=matlab_busy_status,
888
- )
889
-
890
- # Act
891
- await app_state_fixture._AppState__update_matlab_state_using_busy_status_endpoint()
892
-
893
- # Assert
894
- assert app_state_fixture.get_matlab_state() == matlab_status
895
- assert app_state_fixture.matlab_busy_state == matlab_busy_status
896
-
897
-
898
- @pytest.mark.parametrize(
899
- "connector_status, matlab_status, matlab_busy_status",
900
- [("down", "starting", None), ("up", "up", "busy")],
901
- ids=["connector_down", "connector_up"],
902
- )
903
- async def test_update_matlab_state_using_ping_endpoint(
904
- mocker, app_state_fixture, connector_status, matlab_status, matlab_busy_status
905
- ):
906
- """Test to check if MATLAB and its busy status updates correctly when the
907
- ping endpoint is used.
908
-
909
- Args:
910
- mocker (mocker): Built-in pytest fixture
911
- app_state_fixture (AppState): Object of AppState class with defaults set
912
- connector_status (str): Represents Connector status
913
- matlab_status (str): Represents MATLAB status
914
- matlab_busy_status (str): Represents MATLAB busy status
915
- """
916
- # Arrange
917
- mocker.patch(
918
- "matlab_proxy.app_state.mwi.embedded_connector.request.get_state",
919
- return_value=connector_status,
920
- )
921
-
922
- # Act
923
- await app_state_fixture._AppState__update_matlab_state_using_ping_endpoint()
924
-
925
- # Assert
926
- assert app_state_fixture.get_matlab_state() == matlab_status
927
- assert app_state_fixture.matlab_busy_state == matlab_busy_status
928
-
929
-
930
- async def test_update_matlab_state_based_on_endpoint_to_use_required_processes_not_ready(
931
- mocker, app_state_fixture
932
- ):
933
- """Test to check if MATLAB state is 'down' when the required processes are not ready
934
-
935
- Args:
936
- mocker (mocker): Built-in pytest fixture
937
- app_state_fixture (AppState): Object of AppState class with defaults set
938
- """
939
- # Arrange
940
- mocker.patch(
941
- "matlab_proxy.app_state.mwi.embedded_connector.request.get_state",
942
- return_value="up",
943
- )
944
-
945
- await app_state_fixture._AppState__update_matlab_state_based_on_endpoint_to_use(
946
- app_state_fixture._AppState__update_matlab_state_using_ping_endpoint
947
- )
948
-
949
- assert app_state_fixture.get_matlab_state() == "down"
950
-
951
-
952
- async def test_update_matlab_state_based_on_endpoint_to_use_happy_path(
953
- mocker, tmp_path, app_state_fixture
954
- ):
955
- """Test to check if MATLAB state is 'starting' when the required processes
956
- are up but the ready file is not created yet.
957
-
958
- Args:
959
- mocker (mocker): Built-in pytest fixture
960
- tmp_path (Path): Built-in pytest fixture
961
- app_state_fixture (AppState): Object of AppState class with defaults set
962
- """
963
- # Arrange
964
- mocker.patch.object(
965
- AppState,
966
- "_are_required_processes_ready",
967
- return_value=True,
968
- )
969
- tmp_file = tmp_path / Path("dummy")
970
- tmp_file.touch()
971
- app_state_fixture.matlab_session_files["matlab_ready_file"] = tmp_file
972
-
973
- # Act
974
- await app_state_fixture._AppState__update_matlab_state_based_on_endpoint_to_use(
975
- app_state_fixture._AppState__update_matlab_state_using_ping_endpoint
976
- )
977
-
978
- # Assert
979
- await assert_matlab_state(app_state_fixture, "starting", FIVE_MAX_TRIES)
980
-
981
-
982
- async def assert_matlab_state(app_state_fixture, expected_matlab_status, count):
983
- """Tries to assert the MATLAB state to expected_matlab_status for count times.
984
- Will raise Assertion error after.
985
-
986
- The count is needed to decrease flakiness of this tests when run on different platforms.
987
-
988
- Args:
989
- app_state_fixture (AppState): Instance of AppState class.
990
- expected_matlab_status (str): Expected MATLAB status
991
- count (int): Max tries for assertion before AssertionError is raised.
992
-
993
- Raises:
994
- AssertionError: Raised when assertion fails after 'count' tries
995
- """
996
- i = 0
997
- while i < count:
998
- try:
999
- assert app_state_fixture.get_matlab_state() == expected_matlab_status
1000
- return
1001
-
1002
- except:
1003
- await asyncio.sleep(CHECK_MATLAB_STATUS_INTERVAL)
1004
-
1005
- i += 1
1006
-
1007
- raise AssertionError(
1008
- f"MATLAB status failed to change to '{expected_matlab_status}'"
1009
- )
1010
-
1011
-
1012
- @pytest.mark.parametrize(
1013
- "matlab_ready_file, expected_matlab_status",
1014
- [
1015
- (None, "down"),
1016
- (Path("dummy"), "starting"),
1017
- ],
1018
- ids=[
1019
- "no_matlab_ready_file_formed",
1020
- "no_matlab_ready_file_created",
1021
- ],
1022
- )
1023
- async def test_check_matlab_connector_status_auto_updates_based_on_matlab_ready_file(
1024
- mocker, app_state_fixture, matlab_ready_file, expected_matlab_status
1025
- ):
1026
- """Test to check if the status of MATLAB is updated automatically
1027
-
1028
- Args:
1029
- mocker (mocker): Built-in pytest fixture
1030
- app_state_fixture (AppState): Object of AppState class with defaults set
1031
- matlab_ready_file (Path): Path to the ready file
1032
- expected_matlab_status (str): Expected MATLAB status
1033
- """
1034
- # Arrange
1035
- mocker.patch.object(
1036
- AppState,
1037
- "_are_required_processes_ready",
1038
- return_value=True,
1039
- )
1040
- app_state_fixture.matlab_session_files["matlab_ready_file"] = matlab_ready_file
1041
-
1042
- # Act
1043
- # Nothing to act upon as the _update_matlab_state() is started automatically in the constructor.
1044
- # Have to wait here for the atleast the same interval as the __update_matlab_state_based_on_endpoint_to_use()
1045
- # for the MATLAB status to update from 'down'
1046
-
1047
- # Assert
1048
- # MATLAB state should be 'down' first
1049
- assert app_state_fixture.get_matlab_state() == "down"
1050
- await asyncio.sleep(CHECK_MATLAB_STATUS_INTERVAL)
1051
-
1052
- await assert_matlab_state(app_state_fixture, expected_matlab_status, FIVE_MAX_TRIES)
1053
-
1054
-
1055
- async def test_update_matlab_state_switches_to_busy_endpoint(
1056
- mocker, tmp_path, app_state_fixture
1057
- ):
1058
- """Test to check if the endpoint to determine MATLAB state changes from the ping
1059
- endpoint to busy status endpoint after the first successful ping request.
1060
-
1061
- Args:
1062
- mocker (mocker): Built-in pytest fixture
1063
- tmp_path (Path): Built-in pytest fixture
1064
- app_state_fixture (AppState): Object of AppState class with defaults set
1065
- """
1066
- # Arrange
1067
- # Setup mocks for the first ping request to be successful
1068
- mocker.patch.object(
1069
- AppState,
1070
- "_are_required_processes_ready",
1071
- return_value=True,
1072
- )
1073
- mocker.patch(
1074
- "matlab_proxy.app_state.mwi.embedded_connector.request.get_state",
1075
- return_value="up",
1076
- )
1077
- tmp_file = tmp_path / Path("dummy")
1078
- tmp_file.touch()
1079
- app_state_fixture.matlab_session_files["matlab_ready_file"] = tmp_file
1080
- mocked_busy_status_endpoint_function = mocker.patch.object(
1081
- app_state_fixture, "_AppState__update_matlab_state_using_busy_status_endpoint"
1082
- )
1083
-
1084
- # Act
1085
- # Nothing to act upon as the _update_matlab_state() is started automatically in the constructor.
1086
- # Have to wait here for the atleast the same interval as the __update_matlab_state_based_on_endpoint_to_use()
1087
- # for the MATLAB status to update from 'down'
1088
-
1089
- # Wait for the ping endpoint request. Waiting for more time than what
1090
- # is needed to decrease flakiness of this test on different platforms.
1091
- await asyncio.sleep(1 * FIVE_MAX_TRIES)
1092
-
1093
- # Assert
1094
- assert mocked_busy_status_endpoint_function.call_count > 1