Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """ | |
| Comprehensive Pytest configuration file for MMORPG test suite. | |
| Provides shared fixtures, test configuration, and testing utilities. | |
| """ | |
| import pytest | |
| import sys | |
| import os | |
| import tempfile | |
| import shutil | |
| from typing import Generator, Dict, Any, List | |
| from unittest.mock import Mock, patch | |
| import threading | |
| import time | |
| # Add project root to path | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) | |
| # Import project components | |
| from src.core.game_engine import GameEngine | |
| from src.facades.game_facade import GameFacade | |
| # Avoid importing UI components to prevent Gradio instantiation | |
| # from src.ui.interface_manager import InterfaceManager | |
| # from src.ui.huggingface_ui import HuggingFaceUI | |
| from src.core.player import Player | |
| from src.core.world import GameWorld | |
| class TestGameEngine(GameEngine): | |
| """Test version of GameEngine that can be reset between tests.""" | |
| def __new__(cls): | |
| # Use a different instance variable for test engine to avoid conflicts | |
| if not hasattr(cls, '_test_instance') or cls._test_instance is None: | |
| with cls._lock: | |
| if not hasattr(cls, '_test_instance') or cls._test_instance is None: | |
| cls._test_instance = object.__new__(cls) | |
| return cls._test_instance | |
| def __init__(self): | |
| if hasattr(self, '_test_initialized'): | |
| return | |
| with self._lock: | |
| if hasattr(self, '_test_initialized'): | |
| return | |
| self._test_initialized = True | |
| self._running = False | |
| self._services: Dict[str, Any] = {} | |
| # Initialize core components | |
| from src.core.world import GameWorld | |
| self._game_world = GameWorld() | |
| # Initialize services but disable threading | |
| self._initialize_services_no_threads() | |
| def _initialize_services_no_threads(self): | |
| """Initialize services without background threads for testing.""" | |
| from src.services.player_service import PlayerService | |
| from src.services.chat_service import ChatService | |
| from src.services.npc_service import NPCService | |
| from src.services.mcp_service import MCPService | |
| from src.services.plugin_service import PluginService | |
| # Initialize services | |
| try: | |
| self._services['player'] = PlayerService(self._game_world) | |
| self._services['plugin'] = PluginService() | |
| self._services['chat'] = ChatService(self._game_world, plugin_service=self._services['plugin']) | |
| # Initialize NPC service but prevent movement system from starting | |
| npc_service = NPCService(self._game_world) | |
| # Override the start method to prevent thread creation | |
| npc_service.start_movement_system = lambda: None | |
| self._services['npc'] = npc_service | |
| self._services['mcp'] = MCPService( | |
| player_service=self._services['player'], | |
| npc_service=self._services['npc'], | |
| chat_service=self._services['chat'], | |
| game_world=self._game_world | |
| ) | |
| except Exception as e: | |
| print(f"Error initializing test services: {e}") | |
| def reset_instance(cls): | |
| """Reset the singleton instance for testing.""" | |
| with cls._lock: | |
| if hasattr(cls, '_test_instance') and cls._test_instance and hasattr(cls._test_instance, '_services'): | |
| # Stop all background threads before resetting | |
| for service_name, service in cls._test_instance._services.items(): | |
| if hasattr(service, 'stop_movement_system'): | |
| try: | |
| service.stop_movement_system() | |
| except Exception as e: | |
| print(f"Warning: Error stopping {service_name} movement system: {e}") | |
| cls._test_instance = None | |
| def setup_test_environment(): | |
| """Set up the test environment and clean up after all tests.""" | |
| # Create temporary directory for test files | |
| test_dir = tempfile.mkdtemp(prefix="mmorpg_test_") | |
| os.environ['MMORPG_TEST_DIR'] = test_dir | |
| yield test_dir | |
| # Cleanup | |
| if os.path.exists(test_dir): | |
| shutil.rmtree(test_dir) | |
| def game_engine() -> Generator[GameEngine, None, None]: | |
| """Provide a fresh game engine instance for each test.""" | |
| # Reset singleton before test | |
| TestGameEngine.reset_instance() | |
| # Patch the original GameEngine with our test version | |
| with patch('src.core.game_engine.GameEngine', TestGameEngine): | |
| engine = TestGameEngine() | |
| yield engine | |
| # Stop engine and reset after test | |
| engine.stop() | |
| TestGameEngine.reset_instance() | |
| def game_facade(game_engine) -> Generator[GameFacade, None, None]: | |
| """Provide a game facade instance for tests.""" | |
| facade = GameFacade() | |
| yield facade | |
| def game_world() -> Generator[GameWorld, None, None]: | |
| """Provide a fresh game world instance for tests.""" | |
| world = GameWorld() | |
| yield world | |
| def ui_components(game_facade) -> Generator[tuple, None, None]: | |
| """Provide UI components for tests.""" | |
| from unittest.mock import Mock, MagicMock | |
| # Create mock UI components to avoid Gradio instantiation during tests | |
| ui = Mock() # Remove spec to avoid importing HuggingFaceUI | |
| ui.game_facade = game_facade | |
| ui.get_keyboard_script = Mock(return_value=""" | |
| document.addEventListener('keydown', function(event) { | |
| if (event.key === 'w' || event.key === 'W') { | |
| event.preventDefault(); | |
| } | |
| if (event.key === 's' || event.key === 'S') { | |
| event.preventDefault(); | |
| } | |
| if (event.key === 'a' || event.key === 'A') { | |
| event.preventDefault(); | |
| } | |
| if (event.key === 'd' || event.key === 'D') { | |
| event.preventDefault(); | |
| } | |
| }); | |
| """) | |
| ui.generate_world_html = Mock(return_value="<div>Mock World HTML</div>") | |
| ui.create_interface = Mock(return_value=MagicMock()) | |
| interface_manager = Mock() # Remove spec to avoid importing InterfaceManager | |
| interface_manager.game_facade = game_facade | |
| interface_manager.ui = ui | |
| interface_manager.handle_movement = Mock(return_value={"success": True}) | |
| interface_manager.handle_chat = Mock(return_value={"success": True}) | |
| interface_manager.setup_event_handlers = Mock() | |
| yield ui, interface_manager | |
| def test_player(game_facade) -> Generator[str, None, None]: | |
| """Create a test player and clean up after test.""" | |
| player_id = game_facade.join_game("TestPlayer") | |
| yield player_id | |
| # Cleanup - remove player if still exists | |
| try: | |
| game_facade.leave_game(player_id) | |
| except: | |
| pass # Player might already be removed | |
| def multiple_test_players(game_facade) -> Generator[List[str], None, None]: | |
| """Create multiple test players for multiplayer testing.""" | |
| player_ids = [] | |
| for i in range(3): | |
| player_id = game_facade.join_game(f"TestPlayer{i+1}") | |
| player_ids.append(player_id) | |
| yield player_ids | |
| # Cleanup | |
| for player_id in player_ids: | |
| try: | |
| game_facade.leave_game(player_id) | |
| except: | |
| pass | |
| def sample_player_data() -> Dict[str, Any]: | |
| """Provide sample player data for testing.""" | |
| return { | |
| "name": "TestPlayer", | |
| "x": 100, | |
| "y": 150, | |
| "health": 100, | |
| "level": 1, | |
| "experience": 0, | |
| "gold": 50, | |
| "type": "human" | |
| } | |
| def sample_npc_data() -> List[Dict[str, Any]]: | |
| """Provide sample NPC data for testing.""" | |
| return [ | |
| { | |
| "id": "test_npc_1", | |
| "name": "Test Merchant", | |
| "x": 200, | |
| "y": 200, | |
| "char": "🏪", | |
| "personality": "friendly" | |
| }, | |
| { | |
| "id": "test_npc_2", | |
| "name": "Test Guard", | |
| "x": 300, | |
| "y": 100, | |
| "char": "🛡️", | |
| "personality": "stern" | |
| } | |
| ] | |
| def sample_chat_messages() -> List[Dict[str, Any]]: | |
| """Provide sample chat messages for testing.""" | |
| return [ | |
| { | |
| "sender": "TestPlayer1", | |
| "message": "Hello everyone!", | |
| "type": "public", | |
| "timestamp": "2024-01-01 10:00:00" | |
| }, | |
| { | |
| "sender": "TestPlayer2", | |
| "message": "Hi there!", | |
| "type": "public", | |
| "timestamp": "2024-01-01 10:01:00" | |
| }, | |
| { | |
| "sender": "TestPlayer1", | |
| "message": "Secret message", | |
| "type": "private", | |
| "recipient": "TestPlayer2", | |
| "timestamp": "2024-01-01 10:02:00" | |
| } | |
| ] | |
| def mock_gradio_components(): | |
| """Provide mock Gradio components for UI testing.""" | |
| mock_components = {} | |
| # Mock common Gradio components | |
| mock_components['textbox'] = Mock() | |
| mock_components['button'] = Mock() | |
| mock_components['html'] = Mock() | |
| mock_components['json'] = Mock() | |
| mock_components['dataframe'] = Mock() | |
| mock_components['chatbot'] = Mock() | |
| mock_components['dropdown'] = Mock() | |
| mock_components['checkbox'] = Mock() | |
| return mock_components | |
| def mock_file_system(): | |
| """Provide a mock file system for testing.""" | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| # Create test directory structure | |
| plugins_dir = os.path.join(temp_dir, "plugins") | |
| os.makedirs(plugins_dir, exist_ok=True) | |
| # Create a sample plugin file | |
| sample_plugin = os.path.join(plugins_dir, "test_plugin.py") | |
| with open(sample_plugin, 'w') as f: | |
| f.write(""" | |
| class TestPlugin: | |
| def __init__(self): | |
| self.name = "test_plugin" | |
| def activate(self): | |
| return True | |
| def deactivate(self): | |
| return True | |
| """) | |
| yield temp_dir | |
| def mock_mcp_client(): | |
| """Provide a mock MCP client for testing.""" | |
| mock_client = Mock() | |
| mock_client.connect.return_value = True | |
| mock_client.disconnect.return_value = True | |
| mock_client.send_message.return_value = {"status": "success"} | |
| mock_client.receive_message.return_value = {"type": "response", "data": {}} | |
| return mock_client | |
| def performance_timer(): | |
| """Provide a performance timer for benchmarking tests.""" | |
| class PerformanceTimer: | |
| def __init__(self): | |
| self.start_time = None | |
| self.end_time = None | |
| def start(self): | |
| self.start_time = time.time() | |
| def stop(self): | |
| self.end_time = time.time() | |
| return self.end_time - self.start_time | |
| def elapsed(self): | |
| if self.start_time and self.end_time: | |
| return self.end_time - self.start_time | |
| return None | |
| return PerformanceTimer() | |
| def thread_pool(): | |
| """Provide a thread pool for concurrency testing.""" | |
| threads = [] | |
| def create_thread(target, *args, **kwargs): | |
| thread = threading.Thread(target=target, args=args, kwargs=kwargs) | |
| threads.append(thread) | |
| return thread | |
| yield create_thread | |
| # Cleanup - wait for all threads to complete | |
| for thread in threads: | |
| if thread.is_alive(): | |
| thread.join(timeout=5.0) | |
| # Test markers | |
| def pytest_configure(config): | |
| """Configure pytest markers.""" | |
| config.addinivalue_line("markers", "unit: Unit tests") | |
| config.addinivalue_line("markers", "integration: Integration tests") | |
| config.addinivalue_line("markers", "e2e: End-to-end tests") | |
| config.addinivalue_line("markers", "performance: Performance tests") | |
| config.addinivalue_line("markers", "security: Security tests") | |
| config.addinivalue_line("markers", "smoke: Smoke tests") | |
| config.addinivalue_line("markers", "slow: Slow running tests") | |
| # Test collection customization | |
| def pytest_collection_modifyitems(config, items): | |
| """Modify test collection to add automatic markers.""" | |
| for item in items: | |
| # Automatically mark tests based on their location | |
| if "unit" in str(item.fspath): | |
| item.add_marker(pytest.mark.unit) | |
| elif "integration" in str(item.fspath): | |
| item.add_marker(pytest.mark.integration) | |
| elif "e2e" in str(item.fspath): | |
| item.add_marker(pytest.mark.e2e) | |
| elif "performance" in str(item.fspath): | |
| item.add_marker(pytest.mark.performance) | |
| item.add_marker(pytest.mark.slow) | |
| elif "security" in str(item.fspath): | |
| item.add_marker(pytest.mark.security) | |
| elif "smoke" in str(item.fspath): | |
| item.add_marker(pytest.mark.smoke) | |
| # Utility functions for tests | |
| class TestUtilities: | |
| """Utility functions for testing.""" | |
| def wait_for_condition(condition_func, timeout=5.0, interval=0.1): | |
| """Wait for a condition to become true.""" | |
| start_time = time.time() | |
| while time.time() - start_time < timeout: | |
| if condition_func(): | |
| return True | |
| time.sleep(interval) | |
| return False | |
| def assert_eventually(condition_func, timeout=5.0, message="Condition not met"): | |
| """Assert that a condition eventually becomes true.""" | |
| if not TestUtilities.wait_for_condition(condition_func, timeout): | |
| raise AssertionError(message) | |
| def test_utils(): | |
| """Provide test utilities.""" | |
| return TestUtilities | |
| # Performance benchmarking | |
| def benchmark_tracker(): | |
| """Provide benchmark tracking for performance tests.""" | |
| class BenchmarkTracker: | |
| def __init__(self): | |
| self.metrics = {} | |
| def track(self, name, value, unit="ms"): | |
| self.metrics[name] = {"value": value, "unit": unit} | |
| def get_metrics(self): | |
| return self.metrics | |
| def assert_performance(self, name, max_value, message=None): | |
| if name not in self.metrics: | |
| raise AssertionError(f"Metric '{name}' not tracked") | |
| actual = self.metrics[name]["value"] | |
| if actual > max_value: | |
| msg = message or f"Performance regression: {name} = {actual}, expected <= {max_value}" | |
| raise AssertionError(msg) | |
| return BenchmarkTracker() | |
| def npc_service_no_movement(game_engine) -> Generator[Any, None, None]: | |
| """Provide an NPC service without movement system for tests.""" | |
| from src.services.npc_service import NPCService | |
| # Get the NPC service from the engine | |
| npc_service = game_engine.get_service('npc') | |
| # Stop movement system if it's running | |
| if hasattr(npc_service, 'stop_movement_system'): | |
| npc_service.stop_movement_system() | |
| # Override the start_movement_system to do nothing during tests | |
| original_start = npc_service.start_movement_system | |
| npc_service.start_movement_system = lambda: None | |
| yield npc_service | |
| # Restore original method | |
| npc_service.start_movement_system = original_start | |
| # Test configuration | |
| def pytest_configure(config): | |
| """Configure pytest with custom markers.""" | |
| config.addinivalue_line("markers", "unit: Unit tests") | |
| config.addinivalue_line("markers", "integration: Integration tests") | |
| config.addinivalue_line("markers", "e2e: End-to-end tests") | |
| config.addinivalue_line("markers", "performance: Performance tests") | |
| config.addinivalue_line("markers", "smoke: Smoke tests") | |
| config.addinivalue_line("markers", "refactoring: Refactoring completion tests") | |