testit-python-commons 4.2.8__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.
Files changed (71) hide show
  1. api_client_syncstorage/__init__.py +27 -0
  2. api_client_syncstorage/api/__init__.py +3 -0
  3. api_client_syncstorage/api/completion_api.py +302 -0
  4. api_client_syncstorage/api/health_api.py +158 -0
  5. api_client_syncstorage/api/system_api.py +158 -0
  6. api_client_syncstorage/api/test_results_api.py +182 -0
  7. api_client_syncstorage/api/workers_api.py +308 -0
  8. api_client_syncstorage/api_client.py +898 -0
  9. api_client_syncstorage/apis/__init__.py +21 -0
  10. api_client_syncstorage/configuration.py +450 -0
  11. api_client_syncstorage/exceptions.py +158 -0
  12. api_client_syncstorage/model/__init__.py +5 -0
  13. api_client_syncstorage/model/completion_response.py +267 -0
  14. api_client_syncstorage/model/health_status_response.py +267 -0
  15. api_client_syncstorage/model/register_request.py +275 -0
  16. api_client_syncstorage/model/register_response.py +279 -0
  17. api_client_syncstorage/model/set_worker_status_request.py +271 -0
  18. api_client_syncstorage/model/set_worker_status_response.py +267 -0
  19. api_client_syncstorage/model/shutdown_response.py +267 -0
  20. api_client_syncstorage/model/test_result_cut_api_model.py +279 -0
  21. api_client_syncstorage/model/test_result_save_response.py +267 -0
  22. api_client_syncstorage/model_utils.py +2058 -0
  23. api_client_syncstorage/models/__init__.py +20 -0
  24. api_client_syncstorage/rest.py +356 -0
  25. testit.py +69 -0
  26. testit_python_commons/__init__.py +0 -0
  27. testit_python_commons/app_properties.py +257 -0
  28. testit_python_commons/client/__init__.py +9 -0
  29. testit_python_commons/client/api_client.py +560 -0
  30. testit_python_commons/client/client_configuration.py +74 -0
  31. testit_python_commons/client/converter.py +618 -0
  32. testit_python_commons/client/helpers/__init__.py +0 -0
  33. testit_python_commons/client/helpers/bulk_autotest_helper.py +246 -0
  34. testit_python_commons/client/helpers/threads_manager.py +86 -0
  35. testit_python_commons/client/models/__init__.py +8 -0
  36. testit_python_commons/client/models/threads_for_create_and_result.py +35 -0
  37. testit_python_commons/client/models/threads_for_update_and_result.py +45 -0
  38. testit_python_commons/configurations/__init__.py +0 -0
  39. testit_python_commons/configurations/properties_names.py +51 -0
  40. testit_python_commons/decorators.py +221 -0
  41. testit_python_commons/dynamic_methods.py +237 -0
  42. testit_python_commons/models/__init__.py +5 -0
  43. testit_python_commons/models/adapter_mode.py +4 -0
  44. testit_python_commons/models/fixture.py +24 -0
  45. testit_python_commons/models/link.py +53 -0
  46. testit_python_commons/models/link_type.py +10 -0
  47. testit_python_commons/models/outcome_type.py +5 -0
  48. testit_python_commons/models/status_type.py +6 -0
  49. testit_python_commons/models/step_result.py +90 -0
  50. testit_python_commons/models/test_result.py +297 -0
  51. testit_python_commons/models/test_result_with_all_fixture_step_results_model.py +28 -0
  52. testit_python_commons/services/__init__.py +41 -0
  53. testit_python_commons/services/adapter_manager.py +318 -0
  54. testit_python_commons/services/adapter_manager_configuration.py +42 -0
  55. testit_python_commons/services/fixture_manager.py +60 -0
  56. testit_python_commons/services/fixture_storage.py +40 -0
  57. testit_python_commons/services/logger.py +32 -0
  58. testit_python_commons/services/plugin_manager.py +101 -0
  59. testit_python_commons/services/retry.py +92 -0
  60. testit_python_commons/services/step_manager.py +36 -0
  61. testit_python_commons/services/step_result_storage.py +26 -0
  62. testit_python_commons/services/sync_storage/__init__.py +9 -0
  63. testit_python_commons/services/sync_storage/sync_storage_runner.py +589 -0
  64. testit_python_commons/services/utils.py +132 -0
  65. testit_python_commons/step.py +101 -0
  66. testit_python_commons/utils/__init__.py +3 -0
  67. testit_python_commons/utils/html_escape_utils.py +219 -0
  68. testit_python_commons-4.2.8.dist-info/METADATA +52 -0
  69. testit_python_commons-4.2.8.dist-info/RECORD +71 -0
  70. testit_python_commons-4.2.8.dist-info/WHEEL +5 -0
  71. testit_python_commons-4.2.8.dist-info/top_level.txt +3 -0
@@ -0,0 +1,589 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import platform
5
+ import subprocess
6
+
7
+ import threading
8
+ import time
9
+ import urllib.error
10
+ import urllib.request
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ import urllib3
15
+ from api_client_syncstorage.api.test_results_api import TestResultsApi
16
+ from api_client_syncstorage.api.workers_api import WorkersApi
17
+ from api_client_syncstorage.api_client import ApiClient as SyncStorageApiClient
18
+ from api_client_syncstorage.configuration import (
19
+ Configuration as SyncStorageConfiguration,
20
+ )
21
+ from api_client_syncstorage.model.test_result_cut_api_model import (
22
+ TestResultCutApiModel,
23
+ )
24
+ from api_client_syncstorage.model.register_request import RegisterRequest
25
+ from urllib3.exceptions import InsecureRequestWarning
26
+ from api_client_syncstorage.model.set_worker_status_request import SetWorkerStatusRequest
27
+ from testit_python_commons.models.test_result import TestResult
28
+
29
+
30
+ # Disable SSL warnings
31
+ urllib3.disable_warnings(InsecureRequestWarning)
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class SyncStorageRunner:
37
+ """
38
+ Python implementation of the SyncStorageRunner following the Java adapter specification.
39
+ Manages interaction with the Sync Storage service for coordinating test execution
40
+ across multiple workers.
41
+ """
42
+
43
+ SYNC_STORAGE_VERSION = "v0.3.7-tms-5.7"
44
+ SYNC_STORAGE_REPO_URL = (
45
+ "https://github.com/testit-tms/sync-storage-public/releases/download/"
46
+ )
47
+ AMD64 = "amd64"
48
+ ARM64 = "arm64"
49
+ SYNC_STORAGE_STARTUP_TIMEOUT = 5 # seconds
50
+ KEEP_ALIVE_INTERVAL_SECONDS = 30
51
+ KEEP_ALIVE_REQUEST_TIMEOUT_SECONDS = 5
52
+
53
+ def __init__(
54
+ self,
55
+ test_run_id: str,
56
+ port: Optional[str] = None,
57
+ base_url: Optional[str] = None,
58
+ private_token: Optional[str] = None,
59
+ ):
60
+ """
61
+ Initialize the SyncStorageRunner.
62
+
63
+ :param test_run_id: The ID of the test run
64
+ :param port: Port for Sync Storage service (default: 49152)
65
+ :param base_url: Test IT server URL
66
+ :param private_token: Authentication token for Test IT API
67
+ """
68
+ self.test_run_id = test_run_id
69
+ self.port = port
70
+ self.base_url = base_url
71
+ self.private_token = private_token
72
+
73
+ # Worker identification
74
+ self.worker_pid: str = f"worker-{threading.get_ident()}-{int(time.time())}"
75
+ self.is_master: bool = False
76
+ self.is_already_in_progress: bool = False
77
+ self.is_running: bool = False
78
+ self.is_external: bool = False
79
+
80
+ # Sync Storage process management
81
+ self.sync_storage_process: Optional[subprocess.Popen] = None
82
+ self.sync_storage_executable_path: Optional[str] = None
83
+
84
+ # API clients
85
+ self.workers_api: Optional[WorkersApi] = None
86
+ self.test_results_api: Optional[TestResultsApi] = None
87
+
88
+ self._keep_alive_stop_event = threading.Event()
89
+ self._keep_alive_thread: Optional[threading.Thread] = None
90
+
91
+ logger.debug(
92
+ f"Initialized SyncStorageRunner with test_run_id={test_run_id}, port={self.port}"
93
+ )
94
+
95
+ def start(self) -> bool:
96
+ """
97
+ Start the Sync Storage service and register the worker.
98
+
99
+ :return: True if successfully started and registered, False otherwise
100
+ """
101
+ logger.info("Starting Sync Storage service!")
102
+ try:
103
+ if self.is_running:
104
+ logger.info("SyncStorage already running")
105
+ return True
106
+
107
+ logger.info("SyncStorage is not running")
108
+
109
+ # Check if Sync Storage is already running
110
+ if self._is_sync_storage_already_running():
111
+ logger.info(
112
+ f"SyncStorage already started {self.port}. Connecting to existing one..."
113
+ )
114
+ self.is_running = True
115
+ self.is_external = True
116
+
117
+ try:
118
+ self._register_worker_with_retry()
119
+ except Exception as e:
120
+ logger.error(f"Error registering worker: {e}")
121
+
122
+ self._start_keep_alive()
123
+ return True
124
+
125
+ # Get executable file name for current platform
126
+ executable_filename = self._get_file_name_by_arch_and_os()
127
+
128
+ # Prepare command to start Sync Storage
129
+ command = [executable_filename]
130
+
131
+ if self.test_run_id:
132
+ command.extend(["--testRunId", self.test_run_id])
133
+
134
+ if self.port:
135
+ command.extend(["--port", self.port])
136
+
137
+ if self.base_url:
138
+ command.extend(["--baseURL", self.base_url])
139
+
140
+ if self.private_token:
141
+ command.extend(["--privateToken", self.private_token])
142
+
143
+ # Prepare executable file (download if needed)
144
+ prepared_executable_path = self._prepare_executable_file(
145
+ executable_filename
146
+ )
147
+
148
+ # Update command with prepared path
149
+ command[0] = prepared_executable_path
150
+
151
+ logger.info(f"Starting SyncStorage with command: {' '.join(command)}")
152
+
153
+ # Start process
154
+ process_builder = subprocess.Popen(
155
+ command,
156
+ cwd=os.path.dirname(prepared_executable_path),
157
+ stdout=subprocess.PIPE,
158
+ stderr=subprocess.STDOUT,
159
+ text=True,
160
+ bufsize=1,
161
+ universal_newlines=True,
162
+ )
163
+
164
+ self.sync_storage_process = process_builder
165
+
166
+ # Start output reader in background thread
167
+ self._start_output_reader()
168
+
169
+ # Wait for server startup
170
+ if self._wait_for_server_startup(self.SYNC_STORAGE_STARTUP_TIMEOUT):
171
+ self.is_running = True
172
+ logger.info(f"SyncStorage started successfully on port {self.port}")
173
+ time.sleep(2) # Wait a bit more as in Java implementation
174
+
175
+ try:
176
+ self._register_worker_with_retry()
177
+ except Exception as e:
178
+ logger.error(f"Error registering worker: {e}")
179
+
180
+ self._start_keep_alive()
181
+ return True
182
+ else:
183
+ raise RuntimeError("Cannot start the SyncStorage until timeout")
184
+
185
+ except Exception as e:
186
+ logger.error(f"Error starting Sync Storage: {e}", exc_info=True)
187
+ return False
188
+
189
+ def get_worker_pid(self) -> str:
190
+ """Get the worker PID."""
191
+ return self.worker_pid
192
+
193
+ def get_test_run_id(self) -> str:
194
+ """Get the test run ID."""
195
+ return self.test_run_id
196
+
197
+ def is_master_worker(self) -> bool:
198
+ """Check if this is the master worker."""
199
+ return self.is_master
200
+
201
+ def is_already_in_progress_flag(self) -> bool:
202
+ """Check if already in progress flag is set."""
203
+ return self.is_already_in_progress
204
+
205
+ def set_is_already_in_progress(self, value: bool):
206
+ """Set the already in progress flag."""
207
+ self.is_already_in_progress = value
208
+
209
+ def is_running_status(self) -> bool:
210
+ """Check if Sync Storage is running."""
211
+ return self.is_running
212
+
213
+ def is_running_as_process(self) -> bool:
214
+ """Check if Sync Storage is running as a process."""
215
+ return (
216
+ self.sync_storage_process is not None
217
+ and self.sync_storage_process.poll() is None
218
+ )
219
+
220
+ def get_url(self) -> str:
221
+ """Get the Sync Storage URL."""
222
+ return f"http://localhost:{self.port}"
223
+
224
+ def _keep_alive(self) -> None:
225
+ try:
226
+ payload = json.dumps({
227
+ "pid": self.worker_pid,
228
+ "testRunId": self.test_run_id,
229
+ }).encode("utf-8")
230
+ request = urllib.request.Request(
231
+ f"{self.get_url()}/keep_alive",
232
+ data=payload,
233
+ headers={"Content-Type": "application/json"},
234
+ method="POST",
235
+ )
236
+ urllib.request.urlopen(
237
+ request,
238
+ timeout=self.KEEP_ALIVE_REQUEST_TIMEOUT_SECONDS,
239
+ )
240
+ except Exception:
241
+ pass
242
+
243
+ def _keep_alive_loop(self) -> None:
244
+ while not self._keep_alive_stop_event.is_set():
245
+ self._keep_alive()
246
+ if self._keep_alive_stop_event.wait(self.KEEP_ALIVE_INTERVAL_SECONDS):
247
+ break
248
+
249
+ def _start_keep_alive(self) -> None:
250
+ if self._keep_alive_thread and self._keep_alive_thread.is_alive():
251
+ return
252
+
253
+ self._keep_alive_stop_event.clear()
254
+ self._keep_alive_thread = threading.Thread(
255
+ target=self._keep_alive_loop,
256
+ name="sync-storage-keep-alive",
257
+ daemon=True,
258
+ )
259
+ self._keep_alive_thread.start()
260
+ logger.debug("Sync Storage keep-alive thread started")
261
+
262
+ def _stop_keep_alive(self) -> None:
263
+ self._keep_alive_stop_event.set()
264
+ if self._keep_alive_thread and self._keep_alive_thread.is_alive():
265
+ self._keep_alive_thread.join(timeout=1)
266
+
267
+ def send_in_progress_test_result(
268
+ self, model: TestResultCutApiModel
269
+ ) -> bool:
270
+ """
271
+ Send test result to Sync Storage if this worker is the master.
272
+
273
+ :param model: TestResultCutApiModel instance
274
+ :return: True if successfully sent, False otherwise
275
+ """
276
+ try:
277
+ # Only master worker sends test results
278
+ if not self.is_master:
279
+ logger.debug(
280
+ "Not master worker, skipping sending test result to Sync Storage"
281
+ )
282
+ return False
283
+
284
+ # Prevent duplicate sending
285
+ if self.is_already_in_progress:
286
+ logger.debug("Test already in progress, skipping duplicate send")
287
+ return False
288
+
289
+ logger.debug("Sending in-progress test result to Sync Storage")
290
+
291
+
292
+ # Send to Sync Storage using API client
293
+ if self.test_results_api is not None:
294
+ response = self.test_results_api.in_progress_test_result_post(
295
+ self.test_run_id,
296
+ model, # test_result_cut_api_model
297
+ )
298
+
299
+ # Mark as in progress to prevent duplicates
300
+ self.is_already_in_progress = True
301
+
302
+ logger.debug(
303
+ f"Successfully sent test result to Sync Storage: {response}"
304
+ )
305
+ return True
306
+ else:
307
+ logger.error("TestResultsApi not initialized")
308
+ return False
309
+
310
+ except Exception as e:
311
+ logger.warning(
312
+ f"Failed to send test result to Sync Storage: {e}", exc_info=True
313
+ )
314
+ return False
315
+
316
+ def _is_sync_storage_already_running(self) -> bool:
317
+ """
318
+ Check if Sync Storage is already running on the specified port.
319
+
320
+
321
+ :return: True if running, False otherwise
322
+
323
+ """
324
+
325
+ try:
326
+ url = f"http://localhost:{self.port}/health"
327
+ req = urllib.request.Request(url)
328
+ with urllib.request.urlopen(req, timeout=5) as response:
329
+ return response.getcode() == 200
330
+ except Exception:
331
+ return False
332
+
333
+ def _register_worker_with_retry(self):
334
+ """Register worker with retry logic."""
335
+ self._register_worker()
336
+
337
+ def _start_output_reader(self):
338
+ """Start reading output from Sync Storage process in a background thread."""
339
+
340
+ def read_output():
341
+ try:
342
+ if self.sync_storage_process and self.sync_storage_process.stdout:
343
+ # Read output with explicit encoding (e.g., 'utf-8' or 'latin-1')
344
+ for line in iter(self.sync_storage_process.stdout.readline, ""):
345
+ if line:
346
+ # Decode the line explicitly to avoid encoding issues
347
+ logger.info(f"SyncStorage: {line.rstrip()}")
348
+ self.sync_storage_process.stdout.close()
349
+ except Exception as e:
350
+ logger.warning(f"Error reading SyncStorage output: {e}")
351
+
352
+ output_thread = threading.Thread(target=read_output, daemon=True)
353
+ output_thread.start()
354
+
355
+ def _wait_for_server_startup(self, timeout_seconds: int) -> bool:
356
+ """
357
+ Wait for Sync Storage server to start up.
358
+
359
+ :param timeout_seconds: Timeout in seconds
360
+ :return: True if server started, False otherwise
361
+ """
362
+ start_time = time.time()
363
+ while time.time() - start_time < timeout_seconds:
364
+ if self._is_sync_storage_already_running():
365
+ return True
366
+ time.sleep(1)
367
+ return False
368
+
369
+ def _prepare_executable_file(self, original_executable_path: str) -> str:
370
+ """
371
+ Prepare executable file by downloading if needed.
372
+
373
+ :param original_executable_path: Original executable path
374
+ :return: Path to prepared executable
375
+ """
376
+ try:
377
+ current_dir = os.getcwd()
378
+ caches_dir = os.path.join(current_dir, "build", ".caches")
379
+
380
+ # Create caches directory if it doesn't exist
381
+ os.makedirs(caches_dir, exist_ok=True)
382
+
383
+ original_path = Path(original_executable_path)
384
+ file_name = original_path.name
385
+ target_path = os.path.join(caches_dir, file_name)
386
+
387
+ if os.path.exists(target_path):
388
+ logger.info(f"Using existing file: {target_path}")
389
+
390
+ # Make file executable for Unix-based systems
391
+ if not platform.system().lower().startswith("windows"):
392
+ os.chmod(target_path, 0o755)
393
+
394
+ return target_path
395
+
396
+ logger.info("File not present, downloading from GitHub Releases")
397
+ self._download_executable_from_github(target_path)
398
+
399
+ return target_path
400
+ except Exception as e:
401
+ logger.error(f"Error preparing executable file: {e}")
402
+ raise
403
+
404
+ def _download_executable_from_github(self, target_path: str):
405
+ """
406
+ Download Sync Storage executable from GitHub.
407
+
408
+ :param target_path: Target path to save the executable
409
+ """
410
+ try:
411
+ # Determine download URL
412
+ download_url = self._get_download_url_for_current_platform()
413
+
414
+ logger.info(f"Downloading file from: {download_url}")
415
+ logger.info(f"Saving in: {target_path}")
416
+
417
+ # Download file
418
+ req = urllib.request.Request(download_url)
419
+ req.add_header("User-Agent", "TestIT Python Adapter")
420
+
421
+ with urllib.request.urlopen(req, timeout=30) as response:
422
+ with open(target_path, "wb") as f:
423
+ while True:
424
+ chunk = response.read(8192)
425
+ if not chunk:
426
+ break
427
+ f.write(chunk)
428
+
429
+ logger.info(f"File downloaded successfully: {target_path}")
430
+
431
+ # Make file executable for Unix-based systems
432
+ if not platform.system().lower().startswith("windows"):
433
+ os.chmod(target_path, 0o755)
434
+
435
+ except Exception as e:
436
+ logger.error(f"Error downloading file: {e}")
437
+ raise
438
+
439
+ def _get_file_name_by_arch_and_os(self) -> str:
440
+ """
441
+ Get executable file name based on OS and architecture.
442
+
443
+ :return: File name
444
+ """
445
+ os_name = platform.system().lower()
446
+ os_arch = platform.machine().lower()
447
+
448
+ # Determine OS part. Check darwin first because "darwin" contains "win".
449
+ if "mac" in os_name or "darwin" in os_name:
450
+ os_part = "darwin"
451
+ elif os_name.startswith("win"):
452
+ os_part = "windows"
453
+ elif "linux" in os_name:
454
+ os_part = "linux"
455
+ else:
456
+ raise RuntimeError(f"Unsupported OS, please contact dev team: {os_name}")
457
+
458
+ return self._make_file_name(os_arch, os_part)
459
+
460
+ def _is_mac_os(self, os_name: str) -> bool:
461
+ """Check if OS is macOS."""
462
+ return "mac" in os_name or "darwin" in os_name
463
+
464
+ def _is_linux(self, os_name: str) -> bool:
465
+ """Check if OS is Linux."""
466
+ return "linux" in os_name
467
+
468
+ def _get_download_url_for_current_platform(self) -> str:
469
+ """
470
+ Get download URL for current platform.
471
+
472
+ :return: Download URL
473
+ """
474
+ file_name = self._get_file_name_by_arch_and_os()
475
+ return f"{self.SYNC_STORAGE_REPO_URL}{self.SYNC_STORAGE_VERSION}/{file_name}"
476
+
477
+ def _make_file_name(self, os_arch: str, os_part: str) -> str:
478
+ """
479
+ Make file name based on OS architecture and platform.
480
+
481
+ :param os_arch: OS architecture
482
+ :param os_part: OS part
483
+ :return: File name
484
+ """
485
+ # Determine architecture part
486
+ if self.AMD64 in os_arch or "x86_64" in os_arch:
487
+ arch_part = self.AMD64
488
+ elif self.ARM64 in os_arch or "aarch64" in os_arch:
489
+ arch_part = self.ARM64
490
+ else:
491
+ raise RuntimeError(f"Unsupported architecture: {os_arch}")
492
+
493
+ # Form file name
494
+ file_name = f"syncstorage-{self.SYNC_STORAGE_VERSION}-{os_part}_{arch_part}"
495
+ if os_part == "windows":
496
+ file_name += ".exe"
497
+ return file_name
498
+
499
+ def _initialize_api_clients(self):
500
+ """
501
+ Initialize the Sync Storage API clients.
502
+ """
503
+ try:
504
+ # Configure API client
505
+ config = SyncStorageConfiguration()
506
+ config.host = f"http://localhost:{self.port}"
507
+ config.verify_ssl = False # Disable SSL verification for localhost
508
+
509
+ api_client = SyncStorageApiClient(configuration=config)
510
+
511
+ # Initialize APIs
512
+ self.workers_api = WorkersApi(api_client=api_client)
513
+ self.test_results_api = TestResultsApi(api_client=api_client)
514
+
515
+ logger.debug("Successfully initialized Sync Storage API clients")
516
+
517
+ except Exception as e:
518
+ logger.error(f"Error initializing API clients: {e}", exc_info=True)
519
+ raise
520
+
521
+ def _register_worker(self) -> bool:
522
+ """
523
+ Register the worker with the Sync Storage service.
524
+
525
+ :return: True if successfully registered, False otherwise
526
+ """
527
+ try:
528
+ # Initialize API clients if not already done
529
+
530
+ if self.workers_api is None:
531
+ self._initialize_api_clients()
532
+
533
+ if self.workers_api is None:
534
+ logger.error("WorkersApi not initialized")
535
+ return False
536
+
537
+ logger.info(
538
+ f"Registering worker {self.worker_pid} with test run {self.test_run_id}"
539
+ )
540
+
541
+ # Create registration request
542
+
543
+ register_request = RegisterRequest(
544
+ pid=self.worker_pid, test_run_id=self.test_run_id, base_url=self.base_url, private_token=self.private_token
545
+ )
546
+
547
+ # Send registration request
548
+
549
+ response = self.workers_api.register_post(register_request=register_request)
550
+
551
+ # Check if this worker is the master
552
+
553
+ self.is_master = getattr(response, "is_master", False)
554
+
555
+ if self.is_master:
556
+ logger.info(f"Master worker registered, PID: {self.worker_pid}")
557
+
558
+ else:
559
+ logger.info(f"Worker registered successfully, PID: {self.worker_pid}")
560
+
561
+ return True
562
+
563
+ except Exception as e:
564
+ logger.error(f"Error registering worker: {e}", exc_info=True)
565
+
566
+ return False
567
+
568
+ def set_worker_status(self, status: str):
569
+ if self.workers_api is None:
570
+ self._initialize_api_clients()
571
+ try:
572
+ request = SetWorkerStatusRequest(pid=self.worker_pid, status=status, test_run_id=self.test_run_id)
573
+ self.workers_api.set_worker_status_post(set_worker_status_request=request)
574
+ logging.info(f"Successfully set worker {self.worker_pid} status to {status}")
575
+ except Exception as e:
576
+ logger.error(f"Error setting worker status: {e}", exc_info=False)
577
+
578
+
579
+ @classmethod
580
+ def test_result_to_test_result_cut_api_model(
581
+ cls,
582
+ test_result: TestResult, project_id: str) -> TestResultCutApiModel:
583
+ return TestResultCutApiModel(
584
+ project_id=project_id,
585
+ auto_test_external_id=test_result.get_external_id(),
586
+ status_type=test_result.get_status_type(),
587
+ status_code=test_result.get_outcome(),
588
+ started_on=test_result.get_started_on(),
589
+ )