import asyncio import sys import os from pathlib import Path # Set UTF-8 encoding for Windows console if os.name == 'nt': if hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(encoding='utf-8') if hasattr(sys.stderr, 'reconfigure'): sys.stderr.reconfigure(encoding='utf-8') sys.path.insert(0, str(Path(__file__).parent)) from app.services.qemu_manager import qemu_manager def test_qemu_manager_starts_stops(): """Test that the QemuManager can register and unregister instances.""" print("\n=== Test 1: QemuManager Instance tracking ===") async def dummy_callback(event, data): pass # Directly set state to avoid asyncio.create_task outside a running event loop qemu_manager.running_instances["test-client"] = { "board": "raspberry-pi-3", "status": "running", "pins": {}, "process": None, } qemu_manager.callbacks["test-client"] = dummy_callback assert "test-client" in qemu_manager.running_instances assert qemu_manager.running_instances["test-client"]["status"] == "running" print("✓ QEMU instance tracked correctly upon start") qemu_manager.stop_instance("test-client") assert "test-client" not in qemu_manager.running_instances print("✓ QEMU instance removed correctly upon stop") async def _test_websocket_flow_async(): """Async core of the WebSocket simulation flow test.""" print("\n=== Test 2: WebSocket Communication Flow ===") received_events: list[dict] = [] async def ws_callback(event_type: str, data: dict): received_events.append({"type": event_type, "data": data}) print(f" [WS Event] type={event_type}, data={data}") # --- start_pi simulation --- qemu_manager.running_instances["test-ws-client"] = { "board": "raspberry-pi-3", "status": "running", "pins": {}, "process": None, } qemu_manager.callbacks["test-ws-client"] = ws_callback assert "test-ws-client" in qemu_manager.running_instances print("✓ start_pi: QemuManager instance registered") # --- send a system 'booted' event via the callback --- await qemu_manager.send_event_to_frontend("test-ws-client", "system", {"event": "booted"}) assert any(e["type"] == "system" for e in received_events) print("✓ send_event_to_frontend: booted event delivered to callback") # --- pin_change --- qemu_manager.set_pin_state("test-ws-client", "18", 1) assert qemu_manager.running_instances["test-ws-client"]["pins"].get("18") == 1 print("✓ pin_change: Pin=18 set to HIGH (1)") qemu_manager.set_pin_state("test-ws-client", "18", 0) assert qemu_manager.running_instances["test-ws-client"]["pins"].get("18") == 0 print("✓ pin_change: Pin=18 set to LOW (0)") # --- stop_pi --- qemu_manager.stop_instance("test-ws-client") assert "test-ws-client" not in qemu_manager.running_instances print("✓ stop_pi: instance removed from manager") def test_websocket_simulation_flow(): asyncio.run(_test_websocket_flow_async()) def test_qemu_files_exist(): """Verify that the required QEMU boot files are present.""" print("\n=== Test 3: QEMU Boot Files ===") files = { "kernel": qemu_manager.kernel_path, "dtb": qemu_manager.dtb_path, "sd img": qemu_manager.sd_path, } all_ok = True for label, path in files.items(): exists = os.path.exists(path) status = "✓" if exists else "✗ MISSING" print(f" {status} {label}: {path}") if not exists: all_ok = False if all_ok: print("✓ All QEMU boot files found") else: print("⚠ Some files are missing — QEMU will not launch until they are present") # Not a hard failure: missing files are expected during early development return all_ok def run_all_tests(): print("=" * 60) print("SIMULATION BACKEND TEST SUITE") print("=" * 60) try: test_qemu_manager_starts_stops() test_websocket_simulation_flow() test_qemu_files_exist() print("\n✅ All WebSocket & Simulation tests passed!") return True except AssertionError as e: print(f"\n❌ Test assertion failed: {e}") return False except Exception as e: import traceback print(f"\n❌ Unexpected error: {e}") traceback.print_exc() return False if __name__ == "__main__": success = run_all_tests() sys.exit(0 if success else 1)