EU-Utility/plugins/test_suite/widget_stress_test/plugin.py

399 lines
14 KiB
Python

"""
Widget Stress Test Plugin
Tests widget system under load by:
- Creating multiple widgets rapidly
- Testing layout arrangements
- Simulating user interactions
- Measuring performance
This plugin helps identify memory leaks, performance bottlenecks,
and stability issues in the widget system.
"""
import time
import random
from datetime import datetime
from typing import List, Dict
from dataclasses import dataclass
from core.base_plugin import BasePlugin
from core.api.widget_api import get_widget_api, WidgetType, WidgetConfig
@dataclass
class StressTestResult:
"""Result of a stress test operation."""
operation: str
count: int
duration_ms: float
success: bool
error: str = None
class WidgetStressTestPlugin(BasePlugin):
"""
Stress test suite for the WidgetAPI.
Tests include:
- Bulk widget creation
- Rapid show/hide cycles
- Layout stress testing
- Memory pressure simulation
"""
def __init__(self):
super().__init__()
self.widget_api = None
self.results: List[StressTestResult] = []
self.created_widgets: List[str] = []
self.main_widget = None
def initialize(self):
"""Initialize and run stress tests."""
self.widget_api = get_widget_api()
self._create_control_widget()
self._run_stress_tests()
def _create_control_widget(self):
"""Create main control widget for results."""
self.main_widget = self.widget_api.create_widget(
name="widget_stress_control",
title="🔥 Widget Stress Test Control",
size=(700, 500),
position=(100, 100),
widget_type=WidgetType.CONTROL
)
self.main_widget.show()
self._update_control_display()
def _update_control_display(self):
"""Update control widget with current results."""
try:
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QTextBrowser, QProgressBar, QGroupBox
)
from PyQt6.QtCore import Qt
container = QWidget()
main_layout = QVBoxLayout(container)
# Title
title = QLabel("🔥 Widget Stress Test Suite")
title.setStyleSheet("font-size: 18px; font-weight: bold; color: #ff8c42;")
main_layout.addWidget(title)
# Results display
self.results_display = QTextBrowser()
self.results_display.setHtml(self._generate_results_html())
main_layout.addWidget(self.results_display)
# Control buttons
btn_layout = QHBoxLayout()
self.btn_create = QPushButton("Create 10 Widgets")
self.btn_create.clicked.connect(lambda: self._run_test_create_batch(10))
btn_layout.addWidget(self.btn_create)
self.btn_stress = QPushButton("Run Full Stress Test")
self.btn_stress.clicked.connect(self._run_stress_tests)
btn_layout.addWidget(self.btn_stress)
self.btn_cleanup = QPushButton("Cleanup All")
self.btn_cleanup.clicked.connect(self._cleanup_all)
btn_layout.addWidget(self.btn_cleanup)
main_layout.addLayout(btn_layout)
# Status bar
self.status_label = QLabel("Ready")
main_layout.addWidget(self.status_label)
self.main_widget.set_content(container)
except ImportError as e:
print(f"Widget creation error: {e}")
def _generate_results_html(self) -> str:
"""Generate HTML results display."""
html = """
<style>
body { font-family: monospace; background: #1a1a2e; color: #eee; padding: 10px; }
.header { color: #ff8c42; font-size: 16px; font-weight: bold; margin-bottom: 10px; }
.success { color: #4ecca3; }
.error { color: #ff6b6b; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th { background: #2d3748; padding: 8px; text-align: left; color: #ff8c42; }
td { padding: 6px; border-bottom: 1px solid #444; }
.metric { display: inline-block; margin: 5px 15px; padding: 10px; background: #2d3748; border-radius: 4px; }
.metric-value { font-size: 20px; font-weight: bold; color: #ff8c42; }
</style>
<div class="header">Stress Test Results</div>
"""
if not self.results:
html += "<p>No tests run yet. Click 'Run Full Stress Test' to begin.</p>"
else:
# Summary metrics
total_ops = len(self.results)
total_widgets = sum(r.count for r in self.results if r.operation == "create")
avg_time = sum(r.duration_ms for r in self.results) / len(self.results)
errors = sum(1 for r in self.results if not r.success)
html += f"""
<div>
<span class="metric"><span class="metric-value">{total_ops}</span> Operations</span>
<span class="metric"><span class="metric-value">{total_widgets}</span> Widgets Created</span>
<span class="metric"><span class="metric-value">{avg_time:.1f}ms</span> Avg Time</span>
<span class="metric"><span class="metric-value" style="color: {'#ff6b6b' if errors else '#4ecca3'};">{errors}</span> Errors</span>
</div>
<table>
<tr><th>Operation</th><th>Count</th><th>Duration</th><th>Result</th></tr>
"""
for result in self.results:
status_class = "success" if result.success else "error"
status_text = "" if result.success else ""
html += f"""
<tr>
<td>{result.operation}</td>
<td>{result.count}</td>
<td>{result.duration_ms:.2f}ms</td>
<td class="{status_class}">{status_text}</td>
</tr>
"""
html += "</table>"
return html
def _record_result(self, operation: str, count: int, duration_ms: float,
success: bool, error: str = None):
"""Record a test result."""
result = StressTestResult(
operation=operation,
count=count,
duration_ms=duration_ms,
success=success,
error=error
)
self.results.append(result)
self._update_control_display()
def _run_stress_tests(self):
"""Execute complete stress test suite."""
self.results.clear()
self._update_control_display()
# Test 1: Rapid creation
self._run_test_create_batch(10)
self._run_test_create_batch(20)
# Test 2: Layout stress
self._run_test_layout_stress()
# Test 3: Show/hide cycles
self._run_test_visibility_cycles()
# Test 4: Property modifications
self._run_test_property_modifications()
# Test 5: Concurrent operations
self._run_test_concurrent_operations()
# Final cleanup
self._cleanup_all()
if self.status_label:
self.status_label.setText("✅ All stress tests completed")
def _run_test_create_batch(self, count: int):
"""Test creating multiple widgets rapidly."""
start = time.time()
created = 0
error = None
try:
for i in range(count):
widget_name = f"stress_widget_{int(time.time()*1000)}_{i}"
widget = self.widget_api.create_widget(
name=widget_name,
title=f"Stress Test {i+1}",
size=(200, 150),
position=(random.randint(50, 800), random.randint(50, 600)),
widget_type=random.choice([WidgetType.MINI, WidgetType.CONTROL, WidgetType.CHART])
)
self.created_widgets.append(widget_name)
created += 1
except Exception as e:
error = str(e)
duration = (time.time() - start) * 1000
self._record_result("create", created, duration, error is None, error)
def _run_test_layout_stress(self):
"""Test layout arrangements under load."""
start = time.time()
error = None
try:
# Create some widgets first
for i in range(15):
widget = self.widget_api.create_widget(
name=f"layout_stress_{i}",
title=f"Layout {i}",
size=(180, 120)
)
self.created_widgets.append(f"layout_stress_{i}")
widget.show()
# Test different layouts rapidly
layouts = ["grid", "horizontal", "vertical", "cascade"]
for layout in layouts:
self.widget_api.arrange_widgets(layout=layout, spacing=random.randint(5, 20))
time.sleep(0.1)
# Test grid snapping
self.widget_api.snap_to_grid(grid_size=20)
except Exception as e:
error = str(e)
duration = (time.time() - start) * 1000
self._record_result("layout_stress", 15, duration, error is None, error)
def _run_test_visibility_cycles(self):
"""Test rapid show/hide cycles."""
start = time.time()
error = None
cycles = 0
try:
# Create test widgets
for i in range(5):
name = f"vis_cycle_{i}"
if not self.widget_api.widget_exists(name):
widget = self.widget_api.create_widget(
name=name,
title=f"Cycle {i}",
size=(150, 100)
)
self.created_widgets.append(name)
# Rapid show/hide cycles
for _ in range(10):
self.widget_api.hide_all_widgets()
time.sleep(0.05)
self.widget_api.show_all_widgets()
time.sleep(0.05)
cycles += 1
except Exception as e:
error = str(e)
duration = (time.time() - start) * 1000
self._record_result("visibility_cycles", cycles, duration, error is None, error)
def _run_test_property_modifications(self):
"""Test rapid property changes."""
start = time.time()
error = None
changes = 0
try:
# Create test widget
widget = self.widget_api.create_widget(
name="prop_test",
title="Property Test",
size=(250, 200)
)
self.created_widgets.append("prop_test")
widget.show()
# Rapid property changes
for i in range(20):
widget.set_opacity(random.uniform(0.3, 1.0))
widget.move(random.randint(100, 500), random.randint(100, 400))
widget.resize(random.randint(200, 400), random.randint(150, 300))
widget.set_locked(random.choice([True, False]))
widget.set_title(f"Property Test {i}")
changes += 5
except Exception as e:
error = str(e)
duration = (time.time() - start) * 1000
self._record_result("property_modifications", changes, duration, error is None, error)
def _run_test_concurrent_operations(self):
"""Test multiple operations in quick succession."""
start = time.time()
error = None
ops = 0
try:
# Create widgets while modifying others
for i in range(10):
# Create new
name = f"concurrent_{i}"
widget = self.widget_api.create_widget(
name=name,
title=f"Concurrent {i}",
size=(150, 100)
)
self.created_widgets.append(name)
widget.show()
ops += 1
# Modify existing
if self.widget_api.widget_exists("prop_test"):
w = self.widget_api.get_widget("prop_test")
w.set_opacity(random.uniform(0.5, 1.0))
ops += 1
# Get all widgets
all_widgets = self.widget_api.get_all_widgets()
ops += 1
except Exception as e:
error = str(e)
duration = (time.time() - start) * 1000
self._record_result("concurrent_operations", ops, duration, error is None, error)
def _cleanup_all(self):
"""Clean up all created widgets."""
start = time.time()
error = None
try:
# Close all widgets we created
for name in self.created_widgets:
try:
if self.widget_api.widget_exists(name):
self.widget_api.close_widget(name)
except:
pass
self.created_widgets.clear()
except Exception as e:
error = str(e)
duration = (time.time() - start) * 1000
self._record_result("cleanup", 0, duration, error is None, error)
if self.status_label:
self.status_label.setText(f"🧹 Cleanup completed in {duration:.1f}ms")
def shutdown(self):
"""Clean up on shutdown."""
self._cleanup_all()
if self.main_widget:
self.main_widget.close()
# Plugin entry point
plugin_class = WidgetStressTestPlugin