je-load-density-dev 0.0.76__tar.gz → 0.0.78__tar.gz

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 (102) hide show
  1. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/PKG-INFO +2 -2
  2. je_load_density_dev-0.0.78/je_load_density/gui/load_density_gui_thread.py +59 -0
  3. je_load_density_dev-0.0.78/je_load_density/gui/log_to_ui_filter.py +30 -0
  4. je_load_density_dev-0.0.78/je_load_density/gui/main_widget.py +131 -0
  5. je_load_density_dev-0.0.78/je_load_density/gui/main_window.py +49 -0
  6. je_load_density_dev-0.0.78/je_load_density/utils/callback/callback_function_executor.py +99 -0
  7. je_load_density_dev-0.0.78/je_load_density/utils/executor/action_executor.py +167 -0
  8. je_load_density_dev-0.0.78/je_load_density/utils/file_process/create_project_structure.py +35 -0
  9. je_load_density_dev-0.0.78/je_load_density/utils/file_process/get_dir_file_list.py +28 -0
  10. je_load_density_dev-0.0.78/je_load_density/utils/generate_report/generate_html_report.py +129 -0
  11. je_load_density_dev-0.0.78/je_load_density/utils/generate_report/generate_json_report.py +76 -0
  12. je_load_density_dev-0.0.78/je_load_density/utils/generate_report/generate_xml_report.py +58 -0
  13. je_load_density_dev-0.0.78/je_load_density/utils/get_data_strcture/get_api_data.py +59 -0
  14. je_load_density_dev-0.0.78/je_load_density/utils/json/json_file/json_file.py +49 -0
  15. je_load_density_dev-0.0.78/je_load_density/utils/logging/loggin_instance.py +58 -0
  16. je_load_density_dev-0.0.78/je_load_density/utils/package_manager/package_manager_class.py +62 -0
  17. je_load_density_dev-0.0.78/je_load_density/utils/project/create_project_structure.py +73 -0
  18. je_load_density_dev-0.0.78/je_load_density/utils/socket_server/load_density_socket_server.py +95 -0
  19. je_load_density_dev-0.0.78/je_load_density/utils/test_record/test_record_class.py +30 -0
  20. je_load_density_dev-0.0.78/je_load_density/utils/xml/change_xml_structure/change_xml_structure.py +82 -0
  21. je_load_density_dev-0.0.78/je_load_density/utils/xml/xml_file/xml_file.py +96 -0
  22. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/create_locust_env/create_locust_env.py +4 -2
  23. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/event/request_hook.py +23 -15
  24. je_load_density_dev-0.0.78/je_load_density/wrapper/proxy/proxy_user.py +45 -0
  25. je_load_density_dev-0.0.78/je_load_density/wrapper/proxy/user/fast_http_user_proxy.py +28 -0
  26. je_load_density_dev-0.0.78/je_load_density/wrapper/proxy/user/http_user_proxy.py +40 -0
  27. je_load_density_dev-0.0.78/je_load_density/wrapper/start_wrapper/start_test.py +68 -0
  28. je_load_density_dev-0.0.78/je_load_density/wrapper/user_template/fast_http_user_template.py +52 -0
  29. je_load_density_dev-0.0.78/je_load_density/wrapper/user_template/http_user_template.py +54 -0
  30. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density_dev.egg-info/PKG-INFO +2 -2
  31. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density_dev.egg-info/requires.txt +1 -1
  32. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/pyproject.toml +2 -2
  33. je_load_density_dev-0.0.76/je_load_density/gui/load_density_gui_thread.py +0 -26
  34. je_load_density_dev-0.0.76/je_load_density/gui/log_to_ui_filter.py +0 -10
  35. je_load_density_dev-0.0.76/je_load_density/gui/main_widget.py +0 -81
  36. je_load_density_dev-0.0.76/je_load_density/gui/main_window.py +0 -25
  37. je_load_density_dev-0.0.76/je_load_density/utils/callback/callback_function_executor.py +0 -59
  38. je_load_density_dev-0.0.76/je_load_density/utils/executor/action_executor.py +0 -118
  39. je_load_density_dev-0.0.76/je_load_density/utils/file_process/create_project_structure.py +0 -17
  40. je_load_density_dev-0.0.76/je_load_density/utils/file_process/get_dir_file_list.py +0 -18
  41. je_load_density_dev-0.0.76/je_load_density/utils/generate_report/generate_html_report.py +0 -222
  42. je_load_density_dev-0.0.76/je_load_density/utils/generate_report/generate_json_report.py +0 -72
  43. je_load_density_dev-0.0.76/je_load_density/utils/generate_report/generate_xml_report.py +0 -45
  44. je_load_density_dev-0.0.76/je_load_density/utils/get_data_strcture/get_api_data.py +0 -34
  45. je_load_density_dev-0.0.76/je_load_density/utils/json/json_file/json_file.py +0 -42
  46. je_load_density_dev-0.0.76/je_load_density/utils/logging/loggin_instance.py +0 -15
  47. je_load_density_dev-0.0.76/je_load_density/utils/package_manager/package_manager_class.py +0 -36
  48. je_load_density_dev-0.0.76/je_load_density/utils/project/create_project_structure.py +0 -58
  49. je_load_density_dev-0.0.76/je_load_density/utils/socket_server/load_density_socket_server.py +0 -61
  50. je_load_density_dev-0.0.76/je_load_density/utils/test_record/test_record_class.py +0 -15
  51. je_load_density_dev-0.0.76/je_load_density/utils/xml/change_xml_structure/change_xml_structure.py +0 -61
  52. je_load_density_dev-0.0.76/je_load_density/utils/xml/xml_file/xml_file.py +0 -67
  53. je_load_density_dev-0.0.76/je_load_density/wrapper/proxy/proxy_user.py +0 -17
  54. je_load_density_dev-0.0.76/je_load_density/wrapper/proxy/user/fast_http_user_proxy.py +0 -9
  55. je_load_density_dev-0.0.76/je_load_density/wrapper/proxy/user/http_user_proxy.py +0 -9
  56. je_load_density_dev-0.0.76/je_load_density/wrapper/start_wrapper/start_test.py +0 -41
  57. je_load_density_dev-0.0.76/je_load_density/wrapper/user_template/fast_http_user_template.py +0 -34
  58. je_load_density_dev-0.0.76/je_load_density/wrapper/user_template/http_user_template.py +0 -35
  59. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/LICENSE +0 -0
  60. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/README.md +0 -0
  61. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/__init__.py +0 -0
  62. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/__main__.py +0 -0
  63. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/gui/__init__.py +0 -0
  64. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/gui/language_wrapper/__init__.py +0 -0
  65. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/gui/language_wrapper/english.py +0 -0
  66. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/gui/language_wrapper/multi_language_wrapper.py +0 -0
  67. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/gui/language_wrapper/traditional_chinese.py +0 -0
  68. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/__init__.py +0 -0
  69. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/callback/__init__.py +0 -0
  70. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/exception/__init__.py +0 -0
  71. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/exception/exception_tags.py +0 -0
  72. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/exception/exceptions.py +0 -0
  73. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/executor/__init__.py +0 -0
  74. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/file_process/__init__.py +0 -0
  75. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/generate_report/__init__.py +0 -0
  76. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/get_data_strcture/__init__.py +0 -0
  77. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/json/__init__.py +0 -0
  78. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/json/json_file/__init__.py +0 -0
  79. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/json/json_format/__init__.py +0 -0
  80. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/json/json_format/json_process.py +0 -0
  81. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/logging/__init__.py +0 -0
  82. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/package_manager/__init__.py +0 -0
  83. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/project/__init__.py +0 -0
  84. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/project/template/__init__.py +0 -0
  85. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/project/template/template_executor.py +0 -0
  86. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/project/template/template_keyword.py +0 -0
  87. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/socket_server/__init__.py +0 -0
  88. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/test_record/__init__.py +0 -0
  89. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/xml/__init__.py +0 -0
  90. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/xml/change_xml_structure/__init__.py +0 -0
  91. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/utils/xml/xml_file/__init__.py +0 -0
  92. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/__init__.py +0 -0
  93. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/create_locust_env/__init__.py +0 -0
  94. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/event/__init__.py +0 -0
  95. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/proxy/__init__.py +0 -0
  96. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/proxy/user/__init__.py +0 -0
  97. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/start_wrapper/__init__.py +0 -0
  98. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density/wrapper/user_template/__init__.py +0 -0
  99. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density_dev.egg-info/SOURCES.txt +0 -0
  100. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density_dev.egg-info/dependency_links.txt +0 -0
  101. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/je_load_density_dev.egg-info/top_level.txt +0 -0
  102. {je_load_density_dev-0.0.76 → je_load_density_dev-0.0.78}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: je_load_density_dev
3
- Version: 0.0.76
3
+ Version: 0.0.78
4
4
  Summary: Load & Stress Automation Freamework
5
5
  Author-email: JE-Chen <jechenmailman@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/Intergration-Automation-Testing/LoadDensity
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: locust
19
19
  Provides-Extra: gui
20
- Requires-Dist: PySide6==6.9.2; extra == "gui"
20
+ Requires-Dist: PySide6==6.10.0; extra == "gui"
21
21
  Requires-Dist: qt-material; extra == "gui"
22
22
  Dynamic: license-file
23
23
 
@@ -0,0 +1,59 @@
1
+ from PySide6.QtCore import QThread
2
+ from je_load_density.wrapper.start_wrapper.start_test import start_test
3
+
4
+ # 定義常數,避免硬編碼字串
5
+ # Define constant to avoid hard-coded string
6
+ DEFAULT_USER_TYPE = "fast_http_user"
7
+
8
+
9
+ class LoadDensityGUIThread(QThread):
10
+ """
11
+ GUI 測試執行緒
12
+ GUI Test Thread
13
+
14
+ 用於在背景執行負載測試,避免阻塞主介面。
15
+ Used to run load tests in the background without blocking the main GUI.
16
+ """
17
+
18
+ def __init__(self,
19
+ request_url: str = None,
20
+ test_duration: int = None,
21
+ user_count: int = None,
22
+ spawn_rate: int = None,
23
+ http_method: str = None):
24
+ """
25
+ 初始化執行緒參數
26
+ Initialize thread parameters
27
+
28
+ :param request_url: 測試目標 URL (Target request URL)
29
+ :param test_duration: 測試持續時間 (Test duration in seconds)
30
+ :param user_count: 使用者數量 (Number of simulated users)
31
+ :param spawn_rate: 使用者生成速率 (User spawn rate)
32
+ :param http_method: HTTP 方法 (HTTP method, e.g., "GET", "POST")
33
+ """
34
+ super().__init__()
35
+ self.request_url = request_url
36
+ self.test_duration = test_duration
37
+ self.user_count = user_count
38
+ self.spawn_rate = spawn_rate
39
+ self.http_method = http_method
40
+
41
+ def run(self):
42
+ """
43
+ 執行負載測試
44
+ Run the load test
45
+ """
46
+ if not self.request_url or not self.http_method:
47
+ # 基本檢查,避免傳入 None
48
+ # Basic validation to avoid None values
49
+ raise ValueError("Request URL and HTTP method must be provided.")
50
+
51
+ start_test(
52
+ {"user": DEFAULT_USER_TYPE}, # 使用者類型 (User type)
53
+ self.user_count,
54
+ self.spawn_rate,
55
+ self.test_duration,
56
+ tasks={
57
+ self.http_method: {"request_url": self.request_url}
58
+ }
59
+ )
@@ -0,0 +1,30 @@
1
+ import logging
2
+ import queue
3
+
4
+ # 建立一個佇列,用來存放攔截到的日誌訊息
5
+ # Create a queue to store intercepted log messages
6
+ log_message_queue: queue.Queue[str] = queue.Queue()
7
+
8
+
9
+ class InterceptAllFilter(logging.Filter):
10
+ """
11
+ 攔截所有日誌訊息並存入佇列
12
+ Intercept all log messages and store them into a queue
13
+
14
+ 此 Filter 可用於將 logging 模組的輸出導向 GUI 或其他處理流程。
15
+ This filter can be used to redirect logging outputs to a GUI or other processing pipelines.
16
+ """
17
+
18
+ def filter(self, record: logging.LogRecord) -> bool:
19
+ """
20
+ 攔截日誌紀錄並存入佇列
21
+ Intercept log record and put it into the queue
22
+
23
+ :param record: logging.LogRecord 物件 (Log record object)
24
+ :return: False → 阻止訊息繼續傳遞到其他 Handler
25
+ False → Prevents the message from propagating to other handlers
26
+ """
27
+ # 只存放訊息文字,也可以改成存整個 record 以保留更多資訊
28
+ # Only store the message text; alternatively, store the whole record for more details
29
+ log_message_queue.put(record.getMessage())
30
+ return False
@@ -0,0 +1,131 @@
1
+ import logging
2
+ import queue
3
+ from typing import Optional
4
+
5
+ from PySide6.QtCore import QTimer
6
+ from PySide6.QtGui import QIntValidator
7
+ from PySide6.QtWidgets import (
8
+ QWidget, QFormLayout, QLineEdit, QComboBox,
9
+ QPushButton, QTextEdit, QVBoxLayout, QLabel, QMessageBox
10
+ )
11
+
12
+ from je_load_density.gui.load_density_gui_thread import LoadDensityGUIThread
13
+ from je_load_density.gui.language_wrapper.multi_language_wrapper import language_wrapper
14
+ from je_load_density.gui.log_to_ui_filter import InterceptAllFilter, log_message_queue
15
+
16
+
17
+ class LoadDensityWidget(QWidget):
18
+ """
19
+ 負載測試 GUI 控制元件
20
+ Load Test GUI Widget
21
+
22
+ 提供使用者輸入測試參數並啟動負載測試,
23
+ 並將日誌訊息即時顯示在 GUI 中。
24
+ Provides input fields for test parameters, starts load tests,
25
+ and displays log messages in real-time.
26
+ """
27
+
28
+ def __init__(self, parent: Optional[QWidget] = None):
29
+ super().__init__(parent)
30
+
31
+ # === 表單區域 (Form Section) ===
32
+ form_layout = QFormLayout()
33
+
34
+ # URL 輸入框 (Target URL input)
35
+ self.url_input = QLineEdit()
36
+
37
+ # 測試時間 (Test duration, must be int)
38
+ self.test_time_input = QLineEdit()
39
+ self.test_time_input.setValidator(QIntValidator())
40
+
41
+ # 使用者數量 (User count)
42
+ self.user_count_input = QLineEdit()
43
+ self.user_count_input.setValidator(QIntValidator())
44
+
45
+ # 生成速率 (Spawn rate)
46
+ self.spawn_rate_input = QLineEdit()
47
+ self.spawn_rate_input.setValidator(QIntValidator())
48
+
49
+ # HTTP 方法選擇 (HTTP method selection)
50
+ self.method_combobox = QComboBox()
51
+ self.method_combobox.addItems([
52
+ language_wrapper.language_word_dict.get("get"),
53
+ language_wrapper.language_word_dict.get("post"),
54
+ language_wrapper.language_word_dict.get("put"),
55
+ language_wrapper.language_word_dict.get("patch"),
56
+ language_wrapper.language_word_dict.get("delete"),
57
+ language_wrapper.language_word_dict.get("head"),
58
+ language_wrapper.language_word_dict.get("options"),
59
+ ])
60
+
61
+ # 將元件加入表單 (Add widgets to form layout)
62
+ form_layout.addRow(language_wrapper.language_word_dict.get("url"), self.url_input)
63
+ form_layout.addRow(language_wrapper.language_word_dict.get("test_time"), self.test_time_input)
64
+ form_layout.addRow(language_wrapper.language_word_dict.get("user_count"), self.user_count_input)
65
+ form_layout.addRow(language_wrapper.language_word_dict.get("spawn_rate"), self.spawn_rate_input)
66
+ form_layout.addRow(language_wrapper.language_word_dict.get("test_method"), self.method_combobox)
67
+
68
+ # === 啟動按鈕 (Start button) ===
69
+ self.start_button = QPushButton(language_wrapper.language_word_dict.get("start_button"))
70
+ self.start_button.clicked.connect(self.run_load_density)
71
+
72
+ # === 日誌面板 (Log panel) ===
73
+ self.log_panel = QTextEdit()
74
+ self.log_panel.setReadOnly(True)
75
+
76
+ # === 主版面配置 (Main layout) ===
77
+ main_layout = QVBoxLayout()
78
+ main_layout.addLayout(form_layout)
79
+ main_layout.addWidget(self.start_button)
80
+ main_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("log")))
81
+ main_layout.addWidget(self.log_panel)
82
+
83
+ # === 執行緒與計時器 (Thread & Timer) ===
84
+ self.run_load_density_thread: Optional[LoadDensityGUIThread] = None
85
+ self.pull_log_timer = QTimer()
86
+ self.pull_log_timer.setInterval(50) # 稍微放大間隔,避免 UI 卡頓
87
+ self.pull_log_timer.timeout.connect(self.add_text_to_log)
88
+
89
+ self.setLayout(main_layout)
90
+
91
+ def run_load_density(self) -> None:
92
+ """
93
+ 啟動負載測試
94
+ Start the load test
95
+ """
96
+ try:
97
+ test_time = int(self.test_time_input.text())
98
+ user_count = int(self.user_count_input.text())
99
+ spawn_rate = int(self.spawn_rate_input.text())
100
+ except ValueError:
101
+ QMessageBox.warning(self, "Invalid Input", "請輸入有效的數字\nPlease enter valid numbers")
102
+ return
103
+
104
+ self.run_load_density_thread = LoadDensityGUIThread()
105
+ self.run_load_density_thread.request_url = self.url_input.text()
106
+ self.run_load_density_thread.test_duration = test_time
107
+ self.run_load_density_thread.user_count = user_count
108
+ self.run_load_density_thread.spawn_rate = spawn_rate
109
+ self.run_load_density_thread.http_method = self.method_combobox.currentText().lower()
110
+
111
+ # 設定日誌攔截器 (Attach log filter)
112
+ root_logger = logging.getLogger("root")
113
+ log_handler_list = [handler for handler in root_logger.handlers if handler.name == "log_reader"]
114
+ if log_handler_list:
115
+ log_handler = log_handler_list[0]
116
+ # 避免重複新增 Filter (Prevent duplicate filters)
117
+ if not any(isinstance(f, InterceptAllFilter) for f in log_handler.filters):
118
+ log_handler.addFilter(InterceptAllFilter())
119
+
120
+ # 啟動執行緒與計時器 (Start thread & timer)
121
+ self.run_load_density_thread.start()
122
+ self.pull_log_timer.start()
123
+ self.log_panel.clear()
124
+
125
+ def add_text_to_log(self) -> None:
126
+ """
127
+ 將日誌訊息加入到 GUI 面板
128
+ Append log messages to GUI panel
129
+ """
130
+ while not log_message_queue.empty():
131
+ self.log_panel.append(log_message_queue.get_nowait())
@@ -0,0 +1,49 @@
1
+ import sys
2
+ from typing import Optional
3
+
4
+ from PySide6.QtWidgets import QMainWindow, QApplication, QWidget
5
+ from qt_material import QtStyleTools
6
+
7
+ from je_load_density.gui.language_wrapper.multi_language_wrapper import language_wrapper
8
+ from je_load_density.gui.main_widget import LoadDensityWidget
9
+
10
+
11
+ class LoadDensityUI(QMainWindow, QtStyleTools):
12
+ """
13
+ 負載測試主視窗
14
+ Load Test Main Window
15
+
16
+ 提供 GUI 介面,整合測試控制元件與樣式設定。
17
+ Provides the main GUI window, integrating the load test widget and applying styles.
18
+ """
19
+
20
+ def __init__(self, parent: Optional[QWidget] = None):
21
+ super().__init__(parent)
22
+
23
+ # 應用程式名稱 (Application name)
24
+ self.id = language_wrapper.language_word_dict.get("application_name")
25
+
26
+ # 在 Windows 平台設定 AppUserModelID,讓工作列顯示正確的應用程式名稱
27
+ # Set AppUserModelID on Windows so the taskbar shows the correct application name
28
+ if sys.platform in ["win32", "cygwin", "msys"]:
29
+ from ctypes import windll
30
+ windll.shell32.SetCurrentProcessExplicitAppUserModelID(self.id)
31
+
32
+ # 設定字體樣式 (Set font style)
33
+ self.setStyleSheet(
34
+ "font-size: 12pt;"
35
+ "font-family: 'Lato';"
36
+ )
37
+
38
+ # 套用 qt-material 樣式 (Apply qt-material theme)
39
+ self.apply_stylesheet(self, "dark_amber.xml")
40
+
41
+ # 建立並設定主要控制元件 (Create and set main widget)
42
+ self.load_density_widget = LoadDensityWidget()
43
+ self.setCentralWidget(self.load_density_widget)
44
+
45
+ if __name__ == "__main__":
46
+ app = QApplication(sys.argv)
47
+ window = LoadDensityUI()
48
+ window.show()
49
+ sys.exit(app.exec())
@@ -0,0 +1,99 @@
1
+ import typing
2
+ from sys import stderr
3
+
4
+ from je_load_density.utils.exception.exception_tags import (
5
+ get_bad_trigger_function,
6
+ get_bad_trigger_method,
7
+ )
8
+ from je_load_density.utils.exception.exceptions import CallbackExecutorException
9
+ from je_load_density.utils.generate_report.generate_html_report import (
10
+ generate_html,
11
+ generate_html_report,
12
+ )
13
+ from je_load_density.utils.generate_report.generate_json_report import (
14
+ generate_json,
15
+ generate_json_report,
16
+ )
17
+ from je_load_density.utils.generate_report.generate_xml_report import (
18
+ generate_xml,
19
+ generate_xml_report,
20
+ )
21
+ from je_load_density.wrapper.start_wrapper.start_test import start_test
22
+
23
+
24
+ class CallbackFunctionExecutor:
25
+ """
26
+ 回呼函式執行器
27
+ Callback Function Executor
28
+
29
+ 提供事件觸發與回呼機制,先執行指定的 trigger function,
30
+ 再執行 callback function。
31
+ Provides a mechanism to trigger a function from event_dict,
32
+ then execute a callback function.
33
+ """
34
+
35
+ def __init__(self) -> None:
36
+ # 事件字典,定義可觸發的函式
37
+ # Event dictionary, defines available trigger functions
38
+ self.event_dict: dict[str, typing.Callable] = {
39
+ "user_test": start_test,
40
+ "LD_generate_html": generate_html,
41
+ "LD_generate_html_report": generate_html_report,
42
+ "LD_generate_json": generate_json,
43
+ "LD_generate_json_report": generate_json_report,
44
+ "LD_generate_xml": generate_xml,
45
+ "LD_generate_xml_report": generate_xml_report,
46
+ }
47
+
48
+ def callback_function(
49
+ self,
50
+ trigger_function_name: str,
51
+ callback_function: typing.Callable,
52
+ callback_function_param: typing.Optional[typing.Union[dict, list]] = None,
53
+ callback_param_method: str = "kwargs",
54
+ **kwargs,
55
+ ) -> typing.Any:
56
+ """
57
+ 執行事件函式並呼叫回呼函式
58
+ Execute trigger function and then call callback function
59
+
60
+ :param trigger_function_name: 事件函式名稱 (must exist in event_dict)
61
+ :param callback_function: 回呼函式 (callback function to execute)
62
+ :param callback_function_param: 回呼函式參數 (dict for kwargs, list for args)
63
+ :param callback_param_method: 參數傳遞方式 ("kwargs" or "args")
64
+ :param kwargs: 傳給事件函式的參數 (parameters for trigger function)
65
+ :return: 事件函式的回傳值 (return value of trigger function)
66
+ """
67
+ try:
68
+ # 檢查事件函式是否存在
69
+ # Validate trigger function existence
70
+ if trigger_function_name not in self.event_dict:
71
+ raise CallbackExecutorException(get_bad_trigger_function)
72
+
73
+ # 執行事件函式
74
+ # Execute trigger function
75
+ execute_return_value = self.event_dict[trigger_function_name](**kwargs)
76
+
77
+ # 執行回呼函式
78
+ # Execute callback function
79
+ if callback_function_param is not None:
80
+ if callback_param_method not in ["kwargs", "args"]:
81
+ raise CallbackExecutorException(get_bad_trigger_method)
82
+ if callback_param_method == "kwargs":
83
+ callback_function(**callback_function_param)
84
+ else:
85
+ callback_function(*callback_function_param)
86
+ else:
87
+ callback_function()
88
+
89
+ return execute_return_value
90
+
91
+ except Exception as error:
92
+ # 目前只輸出錯誤,可以改成 logging 或 raise
93
+ # Currently prints error; can be replaced with logging or re-raise
94
+ print(repr(error), file=stderr)
95
+
96
+
97
+ # 建立全域執行器實例
98
+ # Create global executor instance
99
+ callback_executor = CallbackFunctionExecutor()
@@ -0,0 +1,167 @@
1
+ import builtins
2
+ import sys
3
+ import types
4
+ from inspect import getmembers, isbuiltin
5
+ from typing import Union, Any
6
+
7
+ from je_load_density.utils.exception.exception_tags import (
8
+ executor_data_error,
9
+ add_command_exception_tag,
10
+ executor_list_error,
11
+ )
12
+ from je_load_density.utils.exception.exceptions import LoadDensityTestExecuteException
13
+ from je_load_density.utils.generate_report.generate_html_report import (
14
+ generate_html,
15
+ generate_html_report,
16
+ )
17
+ from je_load_density.utils.generate_report.generate_json_report import (
18
+ generate_json,
19
+ generate_json_report,
20
+ )
21
+ from je_load_density.utils.generate_report.generate_xml_report import (
22
+ generate_xml,
23
+ generate_xml_report,
24
+ )
25
+ from je_load_density.utils.json.json_file.json_file import read_action_json
26
+ from je_load_density.utils.package_manager.package_manager_class import package_manager
27
+ from je_load_density.wrapper.start_wrapper.start_test import start_test
28
+
29
+
30
+ class Executor:
31
+ """
32
+ 執行器 (Executor)
33
+ Event-driven executor
34
+
35
+ 提供事件字典 (event_dict),可根據動作名稱執行對應函式,
36
+ 並支援批次執行與檔案驅動。
37
+ Provides an event dictionary to execute functions by name,
38
+ supporting batch execution and file-driven execution.
39
+ """
40
+
41
+ def __init__(self) -> None:
42
+ # 初始化事件字典 (Initialize event dictionary)
43
+ self.event_dict: dict[str, Any] = {
44
+ "LD_start_test": start_test,
45
+ "LD_generate_html": generate_html,
46
+ "LD_generate_html_report": generate_html_report,
47
+ "LD_generate_json": generate_json,
48
+ "LD_generate_json_report": generate_json_report,
49
+ "LD_generate_xml": generate_xml,
50
+ "LD_generate_xml_report": generate_xml_report,
51
+ # Executor internal methods
52
+ "LD_execute_action": self.execute_action,
53
+ "LD_execute_files": self.execute_files,
54
+ "LD_add_package_to_executor": package_manager.add_package_to_executor,
55
+ }
56
+
57
+ # 將所有 Python 內建函式加入事件字典
58
+ # Add all Python built-in functions to event_dict
59
+ for name, func in getmembers(builtins, isbuiltin):
60
+ self.event_dict[name] = func
61
+
62
+ def _execute_event(self, action: list) -> Any:
63
+ """
64
+ 執行單一事件
65
+ Execute a single event
66
+
67
+ :param action: 事件結構,例如 ["function_name", {"param": value}]
68
+ :return: 事件回傳值 (return value of executed event)
69
+ """
70
+ event = self.event_dict.get(action[0])
71
+ if event is None:
72
+ raise LoadDensityTestExecuteException(executor_data_error + " " + str(action))
73
+
74
+ if len(action) == 2:
75
+ if isinstance(action[1], dict):
76
+ return event(**action[1])
77
+ else:
78
+ return event(*action[1])
79
+ elif len(action) == 1:
80
+ return event()
81
+ else:
82
+ raise LoadDensityTestExecuteException(executor_data_error + " " + str(action))
83
+
84
+ def execute_action(self, action_list: Union[list, dict]) -> dict[str, Any]:
85
+ """
86
+ 執行多個事件
87
+ Execute multiple actions
88
+
89
+ :param action_list: 事件列表,例如:
90
+ [
91
+ ["LD_start_test", {"param": value}],
92
+ ["LD_generate_json", {"param": value}]
93
+ ]
94
+ :return: 執行紀錄字典 (execution record dict)
95
+ """
96
+ if isinstance(action_list, dict):
97
+ action_list = action_list.get("load_density", None)
98
+ if action_list is None:
99
+ raise LoadDensityTestExecuteException(executor_list_error)
100
+
101
+ execute_record_dict: dict[str, Any] = {}
102
+
103
+ try:
104
+ if not isinstance(action_list, list) or len(action_list) == 0:
105
+ raise LoadDensityTestExecuteException(executor_list_error)
106
+ except Exception as error:
107
+ print(repr(error), file=sys.stderr)
108
+
109
+ for action in action_list:
110
+ try:
111
+ event_response = self._execute_event(action)
112
+ execute_record = f"execute: {action}"
113
+ execute_record_dict[execute_record] = event_response
114
+ except Exception as error:
115
+ print(repr(error), file=sys.stderr)
116
+ print(action, file=sys.stderr)
117
+ execute_record = f"execute: {action}"
118
+ execute_record_dict[execute_record] = repr(error)
119
+
120
+ # 輸出執行結果 (Print execution results)
121
+ for key, value in execute_record_dict.items():
122
+ print(key)
123
+ print(value)
124
+
125
+ return execute_record_dict
126
+
127
+ def execute_files(self, execute_files_list: list[str]) -> list[dict[str, Any]]:
128
+ """
129
+ 執行檔案中的事件
130
+ Execute actions from files
131
+
132
+ :param execute_files_list: 檔案路徑列表 (list of file paths)
133
+ :return: 每個檔案的執行結果列表 (list of execution results per file)
134
+ """
135
+ execute_detail_list: list[dict[str, Any]] = []
136
+ for file in execute_files_list:
137
+ execute_detail_list.append(self.execute_action(read_action_json(file)))
138
+ return execute_detail_list
139
+
140
+
141
+ # 建立全域執行器 (Global executor instance)
142
+ executor = Executor()
143
+ package_manager.executor = executor
144
+
145
+
146
+ def add_command_to_executor(command_dict: dict[str, Any]) -> None:
147
+ """
148
+ 新增自訂命令到執行器
149
+ Add custom commands to executor
150
+
151
+ :param command_dict: {command_name: function}
152
+ """
153
+ for command_name, command in command_dict.items():
154
+ if isinstance(command, (types.MethodType, types.FunctionType)):
155
+ executor.event_dict[command_name] = command
156
+ else:
157
+ raise LoadDensityTestExecuteException(add_command_exception_tag)
158
+
159
+
160
+ def execute_action(action_list: list) -> dict[str, Any]:
161
+ """全域執行事件 (Global execute action)"""
162
+ return executor.execute_action(action_list)
163
+
164
+
165
+ def execute_files(execute_files_list: list[str]) -> list[dict[str, Any]]:
166
+ """全域執行檔案事件 (Global execute files)"""
167
+ return executor.execute_files(execute_files_list)
@@ -0,0 +1,35 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ # 定義常數,避免硬編碼
5
+ # Define constant to avoid hard-coded string
6
+ TEMPLATE_DIR = "je_load_density/template"
7
+
8
+
9
+ def _create_dir(dir_name: str) -> Optional[Path]:
10
+ """
11
+ 建立專案目錄
12
+ Create project directory
13
+
14
+ :param dir_name: 要建立的目錄名稱 (Directory name to create)
15
+ :return: 成功建立或已存在的 Path 物件 (Path object if created/existed), None if failed
16
+ """
17
+ try:
18
+ path = Path(dir_name)
19
+ path.mkdir(parents=True, exist_ok=True)
20
+ return path
21
+ except Exception as error:
22
+ # 錯誤處理:避免因權限或路徑問題導致程式崩潰
23
+ # Error handling: prevent crash due to permission or path issues
24
+ print(f"Failed to create directory {dir_name}: {error}")
25
+ return None
26
+
27
+
28
+ def create_template_dir() -> Optional[Path]:
29
+ """
30
+ 建立模板目錄
31
+ Create template directory
32
+
33
+ :return: Path 物件或 None (Path object or None)
34
+ """
35
+ return _create_dir(TEMPLATE_DIR)
@@ -0,0 +1,28 @@
1
+ from pathlib import Path
2
+ from typing import List
3
+
4
+
5
+ def get_dir_files_as_list(
6
+ dir_path: str = str(Path.cwd()),
7
+ default_search_file_extension: str = ".json"
8
+ ) -> List[str]:
9
+ """
10
+ 取得指定目錄下所有符合副檔名的檔案清單
11
+ Get all files in a directory that end with the given extension
12
+
13
+ :param dir_path: 要搜尋的目錄路徑 (Directory path to search)
14
+ :param default_search_file_extension: 要搜尋的副檔名 (File extension to search, e.g. ".json")
15
+ :return: 檔案絕對路徑清單 (List of absolute file paths)
16
+ """
17
+ try:
18
+ path_obj = Path(dir_path)
19
+ if not path_obj.exists() or not path_obj.is_dir():
20
+ raise FileNotFoundError(f"Directory not found: {dir_path}")
21
+
22
+ return [
23
+ str(file.resolve())
24
+ for file in path_obj.rglob(f"*{default_search_file_extension.lower()}")
25
+ ]
26
+ except Exception as error:
27
+ print(f"Error while scanning directory {dir_path}: {error}")
28
+ return []