Skip to content

Commit 1f147e1

Browse files
committed
Update dev version
* Add comments * improve code
1 parent a450f65 commit 1f147e1

35 files changed

+1234
-722
lines changed

dev_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ sphinx
33
twine
44
sphinx-rtd-theme
55
build
6+
pytest
Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,59 @@
11
from PySide6.QtCore import QThread
22
from je_load_density.wrapper.start_wrapper.start_test import start_test
33

4+
# 定義常數,避免硬編碼字串
5+
# Define constant to avoid hard-coded string
6+
DEFAULT_USER_TYPE = "fast_http_user"
7+
48

59
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+
"""
617

7-
def __init__(self):
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+
"""
834
super().__init__()
9-
self.url = None
10-
self.test_time = None
11-
self.user_count = None
12-
self.spawn_rate = None
13-
self.method = None
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
1440

1541
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+
1651
start_test(
17-
{
18-
"user": "fast_http_user",
19-
},
20-
self.user_count, self.spawn_rate, self.test_time,
21-
**{
22-
"tasks": {
23-
self.method: {"request_url": self.url},
24-
}
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}
2558
}
2659
)
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
import logging
22
import queue
33

4-
locust_log_queue = queue.Queue()
4+
# 建立一個佇列,用來存放攔截到的日誌訊息
5+
# Create a queue to store intercepted log messages
6+
log_message_queue: queue.Queue[str] = queue.Queue()
7+
58

69
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
722
8-
def filter(self, record):
9-
locust_log_queue.put(record.getMessage())
10-
return False
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

je_load_density/gui/main_widget.py

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,52 @@
11
import logging
2+
import queue
3+
from typing import Optional
24

35
from PySide6.QtCore import QTimer
46
from PySide6.QtGui import QIntValidator
5-
from PySide6.QtWidgets import QWidget, QFormLayout, QLineEdit, QComboBox, QPushButton, QTextEdit, QVBoxLayout, QLabel
7+
from PySide6.QtWidgets import (
8+
QWidget, QFormLayout, QLineEdit, QComboBox,
9+
QPushButton, QTextEdit, QVBoxLayout, QLabel, QMessageBox
10+
)
611

712
from je_load_density.gui.load_density_gui_thread import LoadDensityGUIThread
813
from je_load_density.gui.language_wrapper.multi_language_wrapper import language_wrapper
9-
from je_load_density.gui.log_to_ui_filter import InterceptAllFilter, locust_log_queue
14+
from je_load_density.gui.log_to_ui_filter import InterceptAllFilter, log_message_queue
1015

1116

1217
class LoadDensityWidget(QWidget):
18+
"""
19+
負載測試 GUI 控制元件
20+
Load Test GUI Widget
1321
14-
def __init__(self, parent=None):
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):
1529
super().__init__(parent)
16-
# from
30+
31+
# === 表單區域 (Form Section) ===
1732
form_layout = QFormLayout()
33+
34+
# URL 輸入框 (Target URL input)
1835
self.url_input = QLineEdit()
36+
37+
# 測試時間 (Test duration, must be int)
1938
self.test_time_input = QLineEdit()
2039
self.test_time_input.setValidator(QIntValidator())
40+
41+
# 使用者數量 (User count)
2142
self.user_count_input = QLineEdit()
2243
self.user_count_input.setValidator(QIntValidator())
44+
45+
# 生成速率 (Spawn rate)
2346
self.spawn_rate_input = QLineEdit()
2447
self.spawn_rate_input.setValidator(QIntValidator())
48+
49+
# HTTP 方法選擇 (HTTP method selection)
2550
self.method_combobox = QComboBox()
2651
self.method_combobox.addItems([
2752
language_wrapper.language_word_dict.get("get"),
@@ -32,50 +57,75 @@ def __init__(self, parent=None):
3257
language_wrapper.language_word_dict.get("head"),
3358
language_wrapper.language_word_dict.get("options"),
3459
])
60+
61+
# 將元件加入表單 (Add widgets to form layout)
3562
form_layout.addRow(language_wrapper.language_word_dict.get("url"), self.url_input)
3663
form_layout.addRow(language_wrapper.language_word_dict.get("test_time"), self.test_time_input)
3764
form_layout.addRow(language_wrapper.language_word_dict.get("user_count"), self.user_count_input)
3865
form_layout.addRow(language_wrapper.language_word_dict.get("spawn_rate"), self.spawn_rate_input)
3966
form_layout.addRow(language_wrapper.language_word_dict.get("test_method"), self.method_combobox)
4067

68+
# === 啟動按鈕 (Start button) ===
4169
self.start_button = QPushButton(language_wrapper.language_word_dict.get("start_button"))
4270
self.start_button.clicked.connect(self.run_load_density)
4371

44-
# Log panel
72+
# === 日誌面板 (Log panel) ===
4573
self.log_panel = QTextEdit()
4674
self.log_panel.setReadOnly(True)
4775

48-
# Add widget to vertical layout
76+
# === 主版面配置 (Main layout) ===
4977
main_layout = QVBoxLayout()
5078
main_layout.addLayout(form_layout)
5179
main_layout.addWidget(self.start_button)
5280
main_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("log")))
5381
main_layout.addWidget(self.log_panel)
5482

55-
# Param
56-
self.run_load_density_thread = None
83+
# === 執行緒與計時器 (Thread & Timer) ===
84+
self.run_load_density_thread: Optional[LoadDensityGUIThread] = None
5785
self.pull_log_timer = QTimer()
58-
self.pull_log_timer.setInterval(20)
86+
self.pull_log_timer.setInterval(50) # 稍微放大間隔,避免 UI 卡頓
5987
self.pull_log_timer.timeout.connect(self.add_text_to_log)
6088

6189
self.setLayout(main_layout)
6290

63-
def run_load_density(self):
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+
64104
self.run_load_density_thread = LoadDensityGUIThread()
65-
self.run_load_density_thread.url = self.url_input.text()
66-
self.run_load_density_thread.test_time = int(self.test_time_input.text())
67-
self.run_load_density_thread.user_count = int(self.user_count_input.text())
68-
self.run_load_density_thread.spawn_rate = int(self.spawn_rate_input.text())
69-
self.run_load_density_thread.method = self.method_combobox.currentText().lower()
70-
log_handler_list = [handler for handler in logging.getLogger("root").handlers if handler.name == "log_reader"]
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"]
71114
if log_handler_list:
72115
log_handler = log_handler_list[0]
73-
log_handler.addFilter(InterceptAllFilter())
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)
74121
self.run_load_density_thread.start()
75-
self.pull_log_timer.stop()
76122
self.pull_log_timer.start()
77123
self.log_panel.clear()
78124

79-
def add_text_to_log(self):
80-
if not locust_log_queue.empty():
81-
self.log_panel.append(locust_log_queue.get_nowait())
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())

je_load_density/gui/main_window.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,49 @@
11
import sys
2+
from typing import Optional
23

3-
from PySide6.QtWidgets import QMainWindow, QApplication
4+
from PySide6.QtWidgets import QMainWindow, QApplication, QWidget
45
from qt_material import QtStyleTools
56

67
from je_load_density.gui.language_wrapper.multi_language_wrapper import language_wrapper
78
from je_load_density.gui.main_widget import LoadDensityWidget
89

910

1011
class LoadDensityUI(QMainWindow, QtStyleTools):
12+
"""
13+
負載測試主視窗
14+
Load Test Main Window
1115
12-
def __init__(self):
13-
super().__init__()
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)
1424
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
1528
if sys.platform in ["win32", "cygwin", "msys"]:
1629
from ctypes import windll
1730
windll.shell32.SetCurrentProcessExplicitAppUserModelID(self.id)
31+
32+
# 設定字體樣式 (Set font style)
1833
self.setStyleSheet(
19-
f"font-size: 12pt;"
20-
f"font-family: 'Lato';"
34+
"font-size: 12pt;"
35+
"font-family: 'Lato';"
2136
)
37+
38+
# 套用 qt-material 樣式 (Apply qt-material theme)
2239
self.apply_stylesheet(self, "dark_amber.xml")
40+
41+
# 建立並設定主要控制元件 (Create and set main widget)
2342
self.load_density_widget = LoadDensityWidget()
2443
self.setCentralWidget(self.load_density_widget)
2544

45+
if __name__ == "__main__":
46+
app = QApplication(sys.argv)
47+
window = LoadDensityUI()
48+
window.show()
49+
sys.exit(app.exec())

0 commit comments

Comments
 (0)