mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
🐛 修復桌面應用測試啟動問題
This commit is contained in:
parent
0e04186805
commit
d5ba76e248
@ -91,7 +91,18 @@ def run_tests(args):
|
|||||||
|
|
||||||
# 在 Windows 上抑制 asyncio 警告
|
# 在 Windows 上抑制 asyncio 警告
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
os.environ["PYTHONWARNINGS"] = "ignore::ResourceWarning"
|
import warnings
|
||||||
|
|
||||||
|
# 設置更全面的警告抑制
|
||||||
|
os.environ["PYTHONWARNINGS"] = (
|
||||||
|
"ignore::ResourceWarning,ignore::DeprecationWarning"
|
||||||
|
)
|
||||||
|
warnings.filterwarnings("ignore", category=ResourceWarning)
|
||||||
|
warnings.filterwarnings("ignore", message=".*unclosed transport.*")
|
||||||
|
warnings.filterwarnings("ignore", message=".*I/O operation on closed pipe.*")
|
||||||
|
warnings.filterwarnings("ignore", message=".*unclosed.*")
|
||||||
|
# 抑制 asyncio 相關的所有警告
|
||||||
|
warnings.filterwarnings("ignore", module="asyncio.*")
|
||||||
|
|
||||||
if args.web:
|
if args.web:
|
||||||
print("🧪 執行 Web UI 測試...")
|
print("🧪 執行 Web UI 測試...")
|
||||||
@ -216,7 +227,9 @@ def test_desktop_app():
|
|||||||
|
|
||||||
print("✅ 桌面環境檢查通過")
|
print("✅ 桌面環境檢查通過")
|
||||||
|
|
||||||
# 設置桌面模式
|
# 設置測試環境變數,避免端口衝突和權限問題
|
||||||
|
os.environ["MCP_TEST_MODE"] = "true"
|
||||||
|
os.environ["MCP_WEB_PORT"] = "9767" # 使用不同端口避免與 Web 測試衝突
|
||||||
os.environ["MCP_FEEDBACK_MODE"] = "desktop"
|
os.environ["MCP_FEEDBACK_MODE"] = "desktop"
|
||||||
|
|
||||||
print("🔧 創建 Electron 管理器...")
|
print("🔧 創建 Electron 管理器...")
|
||||||
@ -254,6 +267,11 @@ def test_desktop_app():
|
|||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
|
finally:
|
||||||
|
# 清理測試環境變數
|
||||||
|
os.environ.pop("MCP_TEST_MODE", None)
|
||||||
|
os.environ.pop("MCP_WEB_PORT", None)
|
||||||
|
os.environ.pop("MCP_FEEDBACK_MODE", None)
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_process(process):
|
async def wait_for_process(process):
|
||||||
|
@ -120,19 +120,33 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
|
|||||||
|
|
||||||
if desktop_success:
|
if desktop_success:
|
||||||
debug_log("桌面應用啟動成功,等待用戶回饋...")
|
debug_log("桌面應用啟動成功,等待用戶回饋...")
|
||||||
# 等待用戶回饋
|
try:
|
||||||
result = await session.wait_for_feedback(timeout)
|
# 等待用戶回饋
|
||||||
debug_log("收到桌面應用用戶回饋")
|
result = await session.wait_for_feedback(timeout)
|
||||||
return result
|
debug_log("收到桌面應用用戶回饋")
|
||||||
debug_log("桌面應用啟動失敗,回退到 Web 模式")
|
return result
|
||||||
# 回退到 Web 模式
|
finally:
|
||||||
from ..web import launch_web_feedback_ui
|
# 確保 Electron 進程被正確清理
|
||||||
|
debug_log("清理 Electron 進程...")
|
||||||
|
await manager.cleanup_async()
|
||||||
|
debug_log("Electron 進程清理完成")
|
||||||
|
else:
|
||||||
|
debug_log("桌面應用啟動失敗,回退到 Web 模式")
|
||||||
|
# 回退到 Web 模式
|
||||||
|
from ..web import launch_web_feedback_ui
|
||||||
|
|
||||||
return await launch_web_feedback_ui(project_dir, summary, timeout)
|
return await launch_web_feedback_ui(project_dir, summary, timeout)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_log(f"桌面應用啟動過程中出錯: {e}")
|
debug_log(f"桌面應用啟動過程中出錯: {e}")
|
||||||
debug_log("回退到 Web 模式")
|
debug_log("回退到 Web 模式")
|
||||||
|
# 確保清理 Electron 進程
|
||||||
|
try:
|
||||||
|
if "manager" in locals():
|
||||||
|
await manager.cleanup_async()
|
||||||
|
except Exception as cleanup_error:
|
||||||
|
debug_log(f"清理 Electron 進程時出錯: {cleanup_error}")
|
||||||
|
|
||||||
# 回退到 Web 模式
|
# 回退到 Web 模式
|
||||||
from ..web import launch_web_feedback_ui
|
from ..web import launch_web_feedback_ui
|
||||||
|
|
||||||
|
@ -16,24 +16,46 @@ Electron 管理器
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..debug import web_debug_log as debug_log
|
from ..debug import web_debug_log as debug_log
|
||||||
from ..utils.error_handler import ErrorHandler, ErrorType
|
from ..utils.error_handler import ErrorHandler, ErrorType
|
||||||
|
|
||||||
|
|
||||||
|
def suppress_windows_asyncio_warnings():
|
||||||
|
"""抑制 Windows 上的 asyncio 相關警告"""
|
||||||
|
if platform.system().lower() == "windows":
|
||||||
|
# 抑制 ResourceWarning 和 asyncio 相關警告
|
||||||
|
warnings.filterwarnings("ignore", category=ResourceWarning)
|
||||||
|
warnings.filterwarnings("ignore", message=".*unclosed transport.*")
|
||||||
|
warnings.filterwarnings("ignore", message=".*I/O operation on closed pipe.*")
|
||||||
|
# 設置環境變數抑制 asyncio 警告
|
||||||
|
os.environ.setdefault("PYTHONWARNINGS", "ignore::ResourceWarning")
|
||||||
|
debug_log("已設置 Windows asyncio 警告抑制")
|
||||||
|
|
||||||
|
|
||||||
class ElectronManager:
|
class ElectronManager:
|
||||||
"""Electron 進程管理器"""
|
"""Electron 進程管理器 - 跨平台支持"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""初始化 Electron 管理器"""
|
"""初始化 Electron 管理器"""
|
||||||
self.electron_process: asyncio.subprocess.Process | None = None
|
self.electron_process: asyncio.subprocess.Process | None = None
|
||||||
self.desktop_dir = Path(__file__).parent
|
self.desktop_dir = Path(__file__).parent
|
||||||
self.web_server_port: int | None = None
|
self.web_server_port: int | None = None
|
||||||
|
self.platform = platform.system().lower()
|
||||||
|
self._cleanup_in_progress = False
|
||||||
|
|
||||||
|
# 設置平台特定的警告抑制
|
||||||
|
suppress_windows_asyncio_warnings()
|
||||||
|
|
||||||
debug_log("ElectronManager 初始化完成")
|
debug_log("ElectronManager 初始化完成")
|
||||||
debug_log(f"桌面模組目錄: {self.desktop_dir}")
|
debug_log(f"桌面模組目錄: {self.desktop_dir}")
|
||||||
|
debug_log(f"檢測到平台: {self.platform}")
|
||||||
|
|
||||||
async def launch_desktop_app(self, summary: str, project_dir: str) -> bool:
|
async def launch_desktop_app(self, summary: str, project_dir: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -142,50 +164,221 @@ class ElectronManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""清理資源"""
|
"""同步清理資源(向後兼容)"""
|
||||||
if self.electron_process:
|
try:
|
||||||
try:
|
# 在事件循環中運行異步清理
|
||||||
# 檢查進程是否還在運行
|
loop = asyncio.get_event_loop()
|
||||||
if self.electron_process.returncode is None:
|
if loop.is_running():
|
||||||
self.electron_process.terminate()
|
# 如果事件循環正在運行,創建任務
|
||||||
debug_log("Electron 進程已終止")
|
asyncio.create_task(self.cleanup_async())
|
||||||
else:
|
else:
|
||||||
debug_log("Electron 進程已自然結束")
|
# 如果沒有事件循環,運行異步清理
|
||||||
except Exception as e:
|
asyncio.run(self.cleanup_async())
|
||||||
debug_log(f"終止 Electron 進程時出錯: {e}")
|
except Exception as e:
|
||||||
try:
|
debug_log(f"同步清理失敗,嘗試基本清理: {e}")
|
||||||
if self.electron_process.returncode is None:
|
self._basic_cleanup()
|
||||||
self.electron_process.kill()
|
|
||||||
debug_log("強制終止 Electron 進程")
|
|
||||||
except Exception as kill_error:
|
|
||||||
debug_log(f"強制終止 Electron 進程失敗: {kill_error}")
|
|
||||||
finally:
|
|
||||||
# 關閉管道以避免 ResourceWarning
|
|
||||||
try:
|
|
||||||
# 對於 asyncio 子進程,需要特殊處理
|
|
||||||
if (
|
|
||||||
hasattr(self.electron_process, "stdout")
|
|
||||||
and self.electron_process.stdout
|
|
||||||
):
|
|
||||||
if hasattr(self.electron_process.stdout, "close"):
|
|
||||||
self.electron_process.stdout.close()
|
|
||||||
if (
|
|
||||||
hasattr(self.electron_process, "stderr")
|
|
||||||
and self.electron_process.stderr
|
|
||||||
):
|
|
||||||
if hasattr(self.electron_process.stderr, "close"):
|
|
||||||
self.electron_process.stderr.close()
|
|
||||||
if (
|
|
||||||
hasattr(self.electron_process, "stdin")
|
|
||||||
and self.electron_process.stdin
|
|
||||||
):
|
|
||||||
if hasattr(self.electron_process.stdin, "close"):
|
|
||||||
self.electron_process.stdin.close()
|
|
||||||
except Exception:
|
|
||||||
# 忽略管道關閉錯誤,這些通常是無害的
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
async def cleanup_async(self):
|
||||||
|
"""異步清理資源 - 跨平台支持"""
|
||||||
|
if self._cleanup_in_progress or not self.electron_process:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._cleanup_in_progress = True
|
||||||
|
debug_log(f"開始清理 Electron 進程 (平台: {self.platform})")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 檢查進程是否還在運行
|
||||||
|
if self.electron_process.returncode is None:
|
||||||
|
await self._terminate_process_cross_platform()
|
||||||
|
else:
|
||||||
|
debug_log("Electron 進程已自然結束")
|
||||||
|
|
||||||
|
# 等待進程完全結束並清理管道
|
||||||
|
await self._wait_and_cleanup_pipes()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"清理 Electron 進程時出錯: {e}")
|
||||||
|
# 嘗試強制清理
|
||||||
|
await self._force_cleanup()
|
||||||
|
finally:
|
||||||
|
self.electron_process = None
|
||||||
|
self._cleanup_in_progress = False
|
||||||
|
debug_log("Electron 進程清理完成")
|
||||||
|
|
||||||
|
async def _terminate_process_cross_platform(self):
|
||||||
|
"""跨平台進程終止"""
|
||||||
|
if self.platform == "windows":
|
||||||
|
await self._terminate_windows()
|
||||||
|
else:
|
||||||
|
await self._terminate_unix()
|
||||||
|
|
||||||
|
async def _terminate_windows(self):
|
||||||
|
"""Windows 平台進程終止"""
|
||||||
|
debug_log("使用 Windows 進程終止策略")
|
||||||
|
if not self.electron_process:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Windows: 使用 terminate() 然後等待
|
||||||
|
self.electron_process.terminate()
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(self.electron_process.wait(), timeout=5.0)
|
||||||
|
debug_log("Electron 進程已優雅終止")
|
||||||
|
except TimeoutError:
|
||||||
|
debug_log("優雅終止超時,強制終止")
|
||||||
|
self.electron_process.kill()
|
||||||
|
await asyncio.wait_for(self.electron_process.wait(), timeout=3.0)
|
||||||
|
debug_log("Electron 進程已強制終止")
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"Windows 進程終止失敗: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def _terminate_unix(self):
|
||||||
|
"""Unix 系統進程終止"""
|
||||||
|
debug_log("使用 Unix 進程終止策略")
|
||||||
|
if not self.electron_process:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Unix: 發送 SIGTERM 然後等待
|
||||||
|
self.electron_process.send_signal(signal.SIGTERM)
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(self.electron_process.wait(), timeout=5.0)
|
||||||
|
debug_log("Electron 進程已優雅終止")
|
||||||
|
except TimeoutError:
|
||||||
|
debug_log("優雅終止超時,發送 SIGKILL")
|
||||||
|
# 使用 getattr 來處理可能不存在的 SIGKILL
|
||||||
|
sigkill = getattr(signal, "SIGKILL", signal.SIGTERM)
|
||||||
|
self.electron_process.send_signal(sigkill)
|
||||||
|
await asyncio.wait_for(self.electron_process.wait(), timeout=3.0)
|
||||||
|
debug_log("Electron 進程已強制終止")
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"Unix 進程終止失敗: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def _wait_and_cleanup_pipes(self):
|
||||||
|
"""等待進程結束並清理管道"""
|
||||||
|
if not self.electron_process:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 確保進程已經結束
|
||||||
|
if self.electron_process.returncode is None:
|
||||||
|
await self.electron_process.wait()
|
||||||
|
|
||||||
|
# 平台特定的管道清理
|
||||||
|
if self.platform == "windows":
|
||||||
|
await self._cleanup_pipes_windows()
|
||||||
|
else:
|
||||||
|
await self._cleanup_pipes_unix()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"管道清理失敗: {e}")
|
||||||
|
|
||||||
|
async def _cleanup_pipes_windows(self):
|
||||||
|
"""Windows 平台管道清理"""
|
||||||
|
debug_log("清理 Windows 管道")
|
||||||
|
if not self.electron_process:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Windows: 需要特殊處理 asyncio 管道
|
||||||
|
pipes = [
|
||||||
|
("stdout", self.electron_process.stdout),
|
||||||
|
("stderr", self.electron_process.stderr),
|
||||||
|
("stdin", self.electron_process.stdin),
|
||||||
|
]
|
||||||
|
|
||||||
|
for pipe_name, pipe in pipes:
|
||||||
|
if pipe and hasattr(pipe, "close"):
|
||||||
|
try:
|
||||||
|
# 檢查是否有 is_closing 方法(某些管道類型可能沒有)
|
||||||
|
if not hasattr(pipe, "is_closing") or not pipe.is_closing():
|
||||||
|
pipe.close()
|
||||||
|
# 等待管道關閉
|
||||||
|
if hasattr(pipe, "wait_closed"):
|
||||||
|
await pipe.wait_closed()
|
||||||
|
debug_log(f"已關閉 {pipe_name} 管道")
|
||||||
|
except Exception as e:
|
||||||
|
# Windows 上的管道關閉錯誤通常是無害的
|
||||||
|
debug_log(f"關閉 {pipe_name} 管道時出現預期錯誤: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"Windows 管道清理失敗: {e}")
|
||||||
|
|
||||||
|
async def _cleanup_pipes_unix(self):
|
||||||
|
"""Unix 系統管道清理"""
|
||||||
|
debug_log("清理 Unix 管道")
|
||||||
|
if not self.electron_process:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Unix: 直接關閉管道
|
||||||
|
pipes = [
|
||||||
|
("stdout", self.electron_process.stdout),
|
||||||
|
("stderr", self.electron_process.stderr),
|
||||||
|
("stdin", self.electron_process.stdin),
|
||||||
|
]
|
||||||
|
|
||||||
|
for pipe_name, pipe in pipes:
|
||||||
|
if pipe and hasattr(pipe, "close"):
|
||||||
|
try:
|
||||||
|
pipe.close()
|
||||||
|
if hasattr(pipe, "wait_closed"):
|
||||||
|
await pipe.wait_closed()
|
||||||
|
debug_log(f"已關閉 {pipe_name} 管道")
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"關閉 {pipe_name} 管道失敗: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"Unix 管道清理失敗: {e}")
|
||||||
|
|
||||||
|
async def _force_cleanup(self):
|
||||||
|
"""強制清理(最後手段)"""
|
||||||
|
debug_log("執行強制清理")
|
||||||
|
try:
|
||||||
|
if self.electron_process and self.electron_process.returncode is None:
|
||||||
|
# 嘗試使用 psutil 強制終止
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
process = psutil.Process(self.electron_process.pid)
|
||||||
|
process.kill()
|
||||||
|
debug_log("使用 psutil 強制終止進程")
|
||||||
|
except ImportError:
|
||||||
|
debug_log("psutil 不可用,使用系統調用")
|
||||||
|
if self.electron_process:
|
||||||
|
if self.platform == "windows":
|
||||||
|
self.electron_process.kill()
|
||||||
|
else:
|
||||||
|
# 使用 getattr 來處理可能不存在的 SIGKILL
|
||||||
|
sigkill = getattr(signal, "SIGKILL", signal.SIGTERM)
|
||||||
|
self.electron_process.send_signal(sigkill)
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"強制終止失敗: {e}")
|
||||||
|
|
||||||
|
# 基本清理
|
||||||
|
self._basic_cleanup()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"強制清理失敗: {e}")
|
||||||
|
|
||||||
|
def _basic_cleanup(self):
|
||||||
|
"""基本清理(同步)"""
|
||||||
|
debug_log("執行基本清理")
|
||||||
|
try:
|
||||||
|
if self.electron_process:
|
||||||
|
# 嘗試基本的管道關閉
|
||||||
|
for pipe_name in ["stdout", "stderr", "stdin"]:
|
||||||
|
pipe = getattr(self.electron_process, pipe_name, None)
|
||||||
|
if pipe and hasattr(pipe, "close"):
|
||||||
|
try:
|
||||||
|
pipe.close()
|
||||||
|
except Exception:
|
||||||
|
pass # 忽略錯誤
|
||||||
self.electron_process = None
|
self.electron_process = None
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"基本清理失敗: {e}")
|
||||||
|
|
||||||
async def _create_package_json(self):
|
async def _create_package_json(self):
|
||||||
"""創建 package.json 文件"""
|
"""創建 package.json 文件"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user