""" EU-Utility Integration Test - Platform Compatibility ===================================================== Tests cross-platform compatibility: - Windows-specific features - Linux compatibility - Path handling differences - File locking mechanisms - Process management Author: Integration Tester Version: 1.0.0 """ import sys import os import platform import subprocess import threading import tempfile from pathlib import Path from typing import Dict, Any, List, Tuple, Optional from dataclasses import dataclass, asdict from datetime import datetime from plugins.base_plugin import BasePlugin from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTextEdit, QTableWidget, QTableWidgetItem, QHeaderView, QGroupBox, QProgressBar, QTabWidget ) from PyQt6.QtCore import Qt @dataclass class PlatformTest: """Platform compatibility test case.""" name: str category: str # 'path', 'file', 'process', 'system' description: str windows_only: bool = False linux_only: bool = False class PlatformCompatibilityTester(BasePlugin): """Plugin for testing cross-platform compatibility.""" name = "Platform Compatibility Tester" version = "1.0.0" author = "Integration Tester" description = "Test cross-platform compatibility and platform-specific features" # Platform info IS_WINDOWS = sys.platform == 'win32' IS_LINUX = sys.platform == 'linux' IS_MAC = sys.platform == 'darwin' # Test cases TEST_CASES = [ PlatformTest("Path Separator", "path", "Test path separator handling"), PlatformTest("Path Resolution", "path", "Test path resolution"), PlatformTest("Unicode Paths", "path", "Test Unicode in paths"), PlatformTest("Long Paths", "path", "Test long path handling (Windows)", windows_only=True), PlatformTest("UNC Paths", "path", "Test UNC path handling (Windows)", windows_only=True), PlatformTest("File Locking - fcntl", "file", "Test fcntl file locking (Linux/Mac)", linux_only=True), PlatformTest("File Locking - portalocker", "file", "Test portalocker file locking (Windows)"), PlatformTest("File Permissions", "file", "Test file permission handling"), PlatformTest("Temp Directory", "file", "Test temp directory access"), PlatformTest("Process List", "process", "Test process enumeration"), PlatformTest("Window Enumeration", "process", "Test window enumeration (Windows)", windows_only=True), PlatformTest("CPU Info", "system", "Test CPU information retrieval"), PlatformTest("Memory Info", "system", "Test memory information retrieval"), PlatformTest("Environment Variables", "system", "Test environment variable handling"), ] def initialize(self): """Initialize the tester.""" self.log_info("Platform Compatibility Tester initialized") self._test_results: List[Dict] = [] def get_ui(self) -> QWidget: """Create the plugin UI.""" widget = QWidget() layout = QVBoxLayout(widget) # Title title = QLabel("Platform Compatibility Tester") title.setStyleSheet("font-size: 16px; font-weight: bold;") layout.addWidget(title) # Platform info banner self._create_platform_banner(layout) # Tabs tabs = QTabWidget() # Overview tab tabs.addTab(self._create_overview_tab(), "Overview") # Path Tests tab tabs.addTab(self._create_path_tab(), "Path Tests") # File Tests tab tabs.addTab(self._create_file_tab(), "File Tests") # System Tests tab tabs.addTab(self._create_system_tab(), "System Tests") # Test Results tab tabs.addTab(self._create_results_tab(), "Test Results") layout.addWidget(tabs) return widget def _create_platform_banner(self, parent_layout): """Create the platform info banner.""" banner = QGroupBox("Current Platform") banner_layout = QHBoxLayout(banner) # Platform icon/color if self.IS_WINDOWS: platform_text = "๐ŸชŸ Windows" color = "#0078D4" elif self.IS_LINUX: platform_text = "๐Ÿง Linux" color = "#FCC624" elif self.IS_MAC: platform_text = "๐ŸŽ macOS" color = "#999999" else: platform_text = f"โ“ {sys.platform}" color = "#888888" # Platform label platform_label = QLabel(f"Platform: {platform_text}") platform_label.setStyleSheet(f"color: {color}; font-weight: bold;") banner_layout.addWidget(platform_label) # Python version python_label = QLabel(f"Python: {platform.python_version()}") banner_layout.addWidget(python_label) # Architecture arch_label = QLabel(f"Arch: {platform.machine()}") banner_layout.addWidget(arch_label) # Processor info proc_label = QLabel(f"Processor: {platform.processor() or 'Unknown'}") banner_layout.addWidget(proc_label) banner_layout.addStretch() parent_layout.addWidget(banner) def _create_overview_tab(self) -> QWidget: """Create the overview tab.""" widget = QWidget() layout = QVBoxLayout(widget) # Run all tests button run_btn = QPushButton("Run All Compatibility Tests") run_btn.setStyleSheet("font-size: 14px; padding: 10px;") run_btn.clicked.connect(self._run_all_tests) layout.addWidget(run_btn) # Platform details details = QTextEdit() details.setReadOnly(True) details.setHtml(self._get_platform_details_html()) layout.addWidget(details) # Feature support matrix layout.addWidget(QLabel("Feature Support Matrix:")) matrix = QTextEdit() matrix.setReadOnly(True) matrix.setMaximumHeight(200) matrix.setHtml(self._get_feature_matrix_html()) layout.addWidget(matrix) return widget def _create_path_tab(self) -> QWidget: """Create the path tests tab.""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("Path Handling Tests")) # Path test results self.path_results = QTextEdit() self.path_results.setReadOnly(True) layout.addWidget(self.path_results) # Test buttons btn_layout = QHBoxLayout() test_sep_btn = QPushButton("Test Separators") test_sep_btn.clicked.connect(self._test_path_separators) btn_layout.addWidget(test_sep_btn) test_resolve_btn = QPushButton("Test Resolution") test_resolve_btn.clicked.connect(self._test_path_resolution) btn_layout.addWidget(test_resolve_btn) test_unicode_btn = QPushButton("Test Unicode") test_unicode_btn.clicked.connect(self._test_unicode_paths) btn_layout.addWidget(test_unicode_btn) if self.IS_WINDOWS: test_long_btn = QPushButton("Test Long Paths") test_long_btn.clicked.connect(self._test_long_paths) btn_layout.addWidget(test_long_btn) layout.addLayout(btn_layout) return widget def _create_file_tab(self) -> QWidget: """Create the file tests tab.""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("File Operations Tests")) # File locking section locking_group = QGroupBox("File Locking Tests") locking_layout = QVBoxLayout(locking_group) self.locking_results = QTextEdit() self.locking_results.setReadOnly(True) self.locking_results.setMaximumHeight(150) locking_layout.addWidget(self.locking_results) lock_btn_layout = QHBoxLayout() test_fcntl_btn = QPushButton("Test fcntl") test_fcntl_btn.clicked.connect(self._test_fcntl_locking) test_fcntl_btn.setEnabled(self.IS_LINUX or self.IS_MAC) lock_btn_layout.addWidget(test_fcntl_btn) test_portalocker_btn = QPushButton("Test portalocker") test_portalocker_btn.clicked.connect(self._test_portalocker_locking) lock_btn_layout.addWidget(test_portalocker_btn) test_threading_btn = QPushButton("Test Threading Lock") test_threading_btn.clicked.connect(self._test_threading_lock) lock_btn_layout.addWidget(test_threading_btn) locking_layout.addLayout(lock_btn_layout) layout.addWidget(locking_group) # Permissions section perm_group = QGroupBox("Permission Tests") perm_layout = QVBoxLayout(perm_group) self.perm_results = QTextEdit() self.perm_results.setReadOnly(True) self.perm_results.setMaximumHeight(100) perm_layout.addWidget(self.perm_results) test_perm_btn = QPushButton("Test File Permissions") test_perm_btn.clicked.connect(self._test_file_permissions) perm_layout.addWidget(test_perm_btn) layout.addWidget(perm_group) layout.addStretch() return widget def _create_system_tab(self) -> QWidget: """Create the system tests tab.""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("System Information Tests")) # System info display self.system_info = QTextEdit() self.system_info.setReadOnly(True) layout.addWidget(self.system_info) # Test buttons btn_layout = QHBoxLayout() cpu_btn = QPushButton("Get CPU Info") cpu_btn.clicked.connect(self._test_cpu_info) btn_layout.addWidget(cpu_btn) mem_btn = QPushButton("Get Memory Info") mem_btn.clicked.connect(self._test_memory_info) btn_layout.addWidget(mem_btn) env_btn = QPushButton("Test Environment") env_btn.clicked.connect(self._test_environment) btn_layout.addWidget(env_btn) if self.IS_WINDOWS: window_btn = QPushButton("Test Window Enum") window_btn.clicked.connect(self._test_window_enumeration) btn_layout.addWidget(window_btn) layout.addLayout(btn_layout) return widget def _create_results_tab(self) -> QWidget: """Create the results tab.""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("Test Results")) # Results table self.results_table = QTableWidget() self.results_table.setColumnCount(5) self.results_table.setHorizontalHeaderLabels([ "Test Name", "Category", "Status", "Details", "Platform" ]) self.results_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch) layout.addWidget(self.results_table) # Summary self.results_summary = QLabel("No tests run yet") layout.addWidget(self.results_summary) # Export button export_btn = QPushButton("Export Results") export_btn.clicked.connect(self._export_results) layout.addWidget(export_btn) return widget def _get_platform_details_html(self) -> str: """Get platform details as HTML.""" details = [] details.append("

Platform Details

") details.append("") info = [ ("System", platform.system()), ("Release", platform.release()), ("Version", platform.version()), ("Machine", platform.machine()), ("Processor", platform.processor() or 'Unknown'), ("Platform", platform.platform()), ("Python Version", platform.python_version()), ("Python Implementation", platform.python_implementation()), ("Python Compiler", platform.python_compiler()), ("Default Encoding", sys.getdefaultencoding()), ("Filesystem Encoding", sys.getfilesystemencoding()), ("Max Unicode", f"U+{sys.maxunicode:X}"), ("Executable", sys.executable), ("Prefix", sys.prefix), ] for key, value in info: details.append(f"") details.append("
{key}{value}
") return "\n".join(details) def _get_feature_matrix_html(self) -> str: """Get feature support matrix as HTML.""" matrix = [] matrix.append("") matrix.append("") features = [ ("Window Manager", "โœ…", "โŒ", "โŒ"), ("Native Hotkeys", "โœ…", "โš ๏ธ Limited", "โš ๏ธ Limited"), ("Global Hotkeys", "โœ…", "โœ…", "โœ…"), ("File Locking (fcntl)", "โŒ", "โœ…", "โœ…"), ("File Locking (portalocker)", "โœ…", "โš ๏ธ Optional", "โš ๏ธ Optional"), ("OCR (EasyOCR)", "โœ…", "โœ…", "โœ…"), ("OCR (Tesseract)", "โœ…", "โœ…", "โœ…"), ("Discord Webhooks", "โœ…", "โœ…", "โœ…"), ("MQTT", "โœ…", "โœ…", "โœ…"), ("WebSocket", "โœ…", "โœ…", "โœ…"), ("Long Paths (>260)", "โœ…*", "โœ…", "โœ…"), ] for feature, win, lin, mac in features: matrix.append(f"") matrix.append("
FeatureWindowsLinuxmacOS
{feature}{win}{lin}{mac}
") matrix.append("

* Requires Windows 10 1607+ with long path support enabled

") return "\n".join(matrix) def _run_all_tests(self): """Run all compatibility tests.""" self._test_results.clear() # Clear and setup results table self.results_table.setRowCount(0) for test in self.TEST_CASES: # Skip platform-specific tests that don't apply if test.windows_only and not self.IS_WINDOWS: continue if test.linux_only and not self.IS_LINUX: continue result = self._run_single_test(test) self._test_results.append(result) self._add_result_to_table(result) self._update_results_summary() def _run_single_test(self, test: PlatformTest) -> Dict: """Run a single test.""" result = { "name": test.name, "category": test.category, "description": test.description, "success": False, "details": "", "platform": "Windows" if test.windows_only else ("Linux" if test.linux_only else "All") } try: if test.category == "path": result.update(self._run_path_test(test)) elif test.category == "file": result.update(self._run_file_test(test)) elif test.category == "process": result.update(self._run_process_test(test)) elif test.category == "system": result.update(self._run_system_test(test)) except Exception as e: result["details"] = f"Error: {str(e)}" return result def _run_path_test(self, test: PlatformTest) -> Dict: """Run path-related tests.""" result = {"success": True, "details": ""} if test.name == "Path Separator": sep = os.sep alt_sep = os.altsep result["details"] = f"Primary: '{sep}', Alt: {repr(alt_sep)}" elif test.name == "Path Resolution": test_path = Path("~").expanduser() result["details"] = f"Home resolved to: {test_path}" elif test.name == "Unicode Paths": # Test creating a path with Unicode test_dir = Path(tempfile.gettempdir()) / "EU_Utility_ๆต‹่ฏ•" result["details"] = f"Unicode path: {test_dir}" elif test.name == "Long Paths": if self.IS_WINDOWS: # Windows long path test result["details"] = "Long path support varies by Windows version" return result def _run_file_test(self, test: PlatformTest) -> Dict: """Run file-related tests.""" result = {"success": True, "details": ""} if test.name == "File Locking - fcntl": if self.IS_LINUX or self.IS_MAC: try: import fcntl result["details"] = "fcntl module available" except ImportError: result["success"] = False result["details"] = "fcntl not available" else: result["success"] = False result["details"] = "Not applicable on Windows" elif test.name == "File Locking - portalocker": try: import portalocker result["details"] = "portalocker available" except ImportError: result["success"] = False result["details"] = "portalocker not installed" elif test.name == "File Permissions": test_file = Path(tempfile.mktemp()) test_file.touch() try: mode = test_file.stat().st_mode result["details"] = f"File mode: {oct(mode)}" finally: test_file.unlink() return result def _run_process_test(self, test: PlatformTest) -> Dict: """Run process-related tests.""" result = {"success": True, "details": ""} if test.name == "Process List": # Simple process list test result["details"] = f"Current PID: {os.getpid()}" elif test.name == "Window Enumeration": if self.IS_WINDOWS: try: import ctypes result["details"] = "ctypes available for window enum" except ImportError: result["success"] = False result["details"] = "ctypes not available" else: result["success"] = False result["details"] = "Not applicable on this platform" return result def _run_system_test(self, test: PlatformTest) -> Dict: """Run system-related tests.""" result = {"success": True, "details": ""} if test.name == "CPU Info": result["details"] = f"Processor: {platform.processor() or 'Unknown'}" elif test.name == "Memory Info": result["details"] = "Memory info requires psutil or platform-specific APIs" elif test.name == "Environment Variables": path_var = os.environ.get('PATH', '')[:100] result["details"] = f"PATH starts with: {path_var}..." return result def _add_result_to_table(self, result: Dict): """Add a result to the results table.""" row = self.results_table.rowCount() self.results_table.insertRow(row) self.results_table.setItem(row, 0, QTableWidgetItem(result["name"])) self.results_table.setItem(row, 1, QTableWidgetItem(result["category"])) status = "โœ… PASS" if result["success"] else "โŒ FAIL" status_item = QTableWidgetItem(status) self.results_table.setItem(row, 2, status_item) self.results_table.setItem(row, 3, QTableWidgetItem(result["details"])) self.results_table.setItem(row, 4, QTableWidgetItem(result["platform"])) def _update_results_summary(self): """Update results summary.""" passed = sum(1 for r in self._test_results if r["success"]) total = len(self._test_results) self.results_summary.setText(f"Results: {passed}/{total} tests passed") def _test_path_separators(self): """Test path separators.""" results = [] results.append(f"os.sep = '{os.sep}'") results.append(f"os.altsep = {repr(os.altsep)}") results.append(f"os.pathsep = '{os.pathsep}'") results.append(f"os.linesep = {repr(os.linesep)}") results.append("") results.append("Pathlib tests:") results.append(f"Path('/foo/bar').parts = {Path('/foo/bar').parts}") self.path_results.setText("\n".join(results)) def _test_path_resolution(self): """Test path resolution.""" results = [] results.append(f"Path.home() = {Path.home()}") results.append(f"Path.cwd() = {Path.cwd()}") results.append(f"Path('~').expanduser() = {Path('~').expanduser()}") # Test absolute/relative test_path = Path("relative/path") results.append(f"Path('relative/path').is_absolute() = {test_path.is_absolute()}") results.append(f"Path('relative/path').resolve() = {test_path.resolve()}") self.path_results.setText("\n".join(results)) def _test_unicode_paths(self): """Test Unicode path handling.""" results = [] test_names = [ "test_ๆ–‡ไปถ", "test_๐ŸŽฎ", "test_รฑรกรฉรญรณรบ", "test_ู…ุฑุญุจุง", ] for name in test_names: try: test_path = Path(tempfile.gettempdir()) / name test_path.mkdir(exist_ok=True) exists = test_path.exists() test_path.rmdir() results.append(f"โœ… {name}: OK") except Exception as e: results.append(f"โŒ {name}: {str(e)}") self.path_results.setText("\n".join(results)) def _test_long_paths(self): """Test long path handling.""" results = [] results.append("Long path support:") results.append(f"Windows version: {platform.version()}") results.append("") results.append("Note: Windows 10 1607+ supports long paths") results.append("when registry key is set and manifest declares support.") self.path_results.setText("\n".join(results)) def _test_fcntl_locking(self): """Test fcntl file locking.""" results = [] try: import fcntl results.append("โœ… fcntl module available") # Create test file test_file = tempfile.NamedTemporaryFile(delete=False) test_path = test_file.name test_file.close() try: with open(test_path, 'w') as f: # Try to acquire lock fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) results.append("โœ… Exclusive lock acquired") fcntl.flock(f.fileno(), fcntl.LOCK_UN) results.append("โœ… Lock released") finally: os.unlink(test_path) except ImportError: results.append("โŒ fcntl not available (expected on Windows)") except Exception as e: results.append(f"โŒ Error: {str(e)}") self.locking_results.setText("\n".join(results)) def _test_portalocker_locking(self): """Test portalocker file locking.""" results = [] try: import portalocker results.append("โœ… portalocker available") results.append(f"Version: {getattr(portalocker, '__version__', 'unknown')}") except ImportError: results.append("โŒ portalocker not installed") results.append("Install with: pip install portalocker") self.locking_results.setText("\n".join(results)) def _test_threading_lock(self): """Test threading lock as fallback.""" results = [] lock = threading.RLock() results.append("Testing threading.RLock:") results.append(f"โœ… Lock created: {lock}") with lock: results.append("โœ… Lock acquired in main thread") results.append("โœ… Lock released") results.append("") results.append("Threading locks work on all platforms") results.append("but only within a single process.") self.locking_results.setText("\n".join(results)) def _test_file_permissions(self): """Test file permissions.""" results = [] test_file = Path(tempfile.mktemp()) test_file.write_text("test") try: stat = test_file.stat() results.append(f"File: {test_file}") results.append(f"Mode: {oct(stat.st_mode)}") results.append(f"UID: {stat.st_uid}") results.append(f"GID: {stat.st_gid}") results.append(f"Size: {stat.st_size} bytes") if self.IS_WINDOWS: results.append("") results.append("Note: Windows uses ACLs, not Unix permissions") finally: test_file.unlink() self.perm_results.setText("\n".join(results)) def _test_cpu_info(self): """Test CPU information retrieval.""" results = [] results.append(f"Platform: {platform.platform()}") results.append(f"Processor: {platform.processor() or 'Unknown'}") results.append(f"Machine: {platform.machine()}") results.append(f"CPU Count: {os.cpu_count()}") if self.IS_LINUX: try: with open('/proc/cpuinfo', 'r') as f: for line in f: if 'model name' in line: results.append(f"CPU: {line.split(':')[1].strip()}") break except: pass self.system_info.setText("\n".join(results)) def _test_memory_info(self): """Test memory information retrieval.""" results = [] try: import psutil mem = psutil.virtual_memory() results.append(f"Total: {mem.total / (1024**3):.2f} GB") results.append(f"Available: {mem.available / (1024**3):.2f} GB") results.append(f"Used: {mem.used / (1024**3):.2f} GB") results.append(f"Percent: {mem.percent}%") except ImportError: results.append("psutil not installed") results.append("Install with: pip install psutil") if self.IS_LINUX: try: with open('/proc/meminfo', 'r') as f: results.append("") results.append("From /proc/meminfo:") for _ in range(3): results.append(f.read(100)) except: pass self.system_info.setText("\n".join(results)) def _test_environment(self): """Test environment variables.""" results = [] important_vars = [ 'PATH', 'HOME', 'USERPROFILE', 'APPDATA', 'TEMP', 'TMP', 'PYTHONPATH', 'PYTHONHOME', 'QT_QPA_PLATFORM', ] results.append("Environment Variables:") results.append("") for var in important_vars: value = os.environ.get(var) if value: # Truncate long values if len(value) > 80: value = value[:77] + "..." results.append(f"{var}={value}") else: results.append(f"{var}=(not set)") self.system_info.setText("\n".join(results)) def _test_window_enumeration(self): """Test window enumeration (Windows).""" results = [] if not self.IS_WINDOWS: results.append("โŒ Window enumeration only available on Windows") self.system_info.setText("\n".join(results)) return try: import ctypes from ctypes import wintypes results.append("โœ… ctypes available") # Try to enumerate windows user32 = ctypes.windll.user32 def callback(hwnd, extra): if user32.IsWindowVisible(hwnd): text = ctypes.create_unicode_buffer(256) user32.GetWindowTextW(hwnd, text, 256) if text.value: extra.append(text.value) return True EnumWindowsProc = ctypes.WINFUNCTYPE( wintypes.BOOL, wintypes.HWND, wintypes.LPARAM ) windows = [] proc = EnumWindowsProc(callback) user32.EnumWindows(proc, 0) results.append(f"โœ… Found {len(windows)} visible windows") results.append("") results.append("Sample windows:") for title in windows[:10]: results.append(f" - {title[:50]}") except Exception as e: results.append(f"โŒ Error: {str(e)}") self.system_info.setText("\n".join(results)) def _export_results(self): """Export test results.""" if not self._test_results: self.notify_warning("No Results", "No test results to export") return filename = f"platform_compat_{sys.platform}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" export_data = { "platform": { "system": platform.system(), "release": platform.release(), "version": platform.version(), "machine": platform.machine(), "processor": platform.processor(), "python": platform.python_version(), }, "timestamp": datetime.now().isoformat(), "results": self._test_results } with open(filename, 'w') as f: json.dump(export_data, f, indent=2) self.notify_success("Exported", f"Results saved to {filename}") plugin_class = PlatformCompatibilityTester