| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- """
- Integration test fixtures - Hardware detection and setup.
- These fixtures help determine if real hardware is available
- and provide setup/teardown for hardware-dependent tests.
- """
- import pytest
- import serial.tools.list_ports
- def pytest_addoption(parser):
- """Add --run-hardware option to pytest CLI."""
- parser.addoption(
- "--run-hardware",
- action="store_true",
- default=False,
- help="Run tests that require real hardware connection"
- )
- @pytest.fixture
- def run_hardware(request):
- """Check if hardware tests should run."""
- return request.config.getoption("--run-hardware")
- @pytest.fixture
- def available_serial_ports():
- """Return list of available serial ports on this machine.
- Filters out known non-hardware ports like debug consoles.
- """
- IGNORE_PORTS = ['/dev/cu.debug-console', '/dev/cu.Bluetooth-Incoming-Port']
- ports = serial.tools.list_ports.comports()
- return [port.device for port in ports if port.device not in IGNORE_PORTS]
- @pytest.fixture
- def hardware_port(available_serial_ports, run_hardware):
- """Get a hardware port for testing, or skip if not available.
- This fixture:
- 1. Checks if --run-hardware flag was passed
- 2. Checks if any serial ports are available
- 3. Returns the first available port or skips the test
- """
- if not run_hardware:
- pytest.skip("Hardware tests disabled (use --run-hardware to enable)")
- if not available_serial_ports:
- pytest.skip("No serial ports available for hardware testing")
- # Prefer USB ports over built-in ports
- usb_ports = [p for p in available_serial_ports if 'usb' in p.lower() or 'USB' in p]
- if usb_ports:
- return usb_ports[0]
- return available_serial_ports[0]
- @pytest.fixture
- def serial_connection(hardware_port):
- """Create a real serial connection for testing.
- This fixture establishes an actual serial connection to the hardware.
- The connection is automatically closed after the test.
- """
- import serial
- conn = serial.Serial(hardware_port, baudrate=115200, timeout=2)
- yield conn
- conn.close()
- @pytest.fixture(autouse=True)
- def fast_test_speed(run_hardware):
- """Set speed to 500 for faster integration tests.
- This fixture runs automatically for all integration tests.
- Restores original speed after the test.
- """
- if not run_hardware:
- yield
- return
- from modules.core.state import state
- original_speed = state.speed
- state.speed = 500 # Fast speed for tests
- yield
- state.speed = original_speed # Restore original speed
- @pytest.fixture(autouse=True)
- def reset_asyncio_events(run_hardware):
- """Reset global asyncio primitives before each test.
- The pattern_manager uses global asyncio objects (Lock, Event) that are
- bound to the event loop where they were created. When TestClient creates
- its own event loop, these become incompatible.
- This fixture resets them to None so they get recreated in the current loop.
- Also ensures pause/stop state is cleared so tests start fresh.
- """
- if not run_hardware:
- yield
- return
- import modules.core.pattern_manager as pm
- from modules.core.state import state
- # Reset pattern_manager's global async primitives
- pm.pause_event = None
- pm.pattern_lock = None # Will be recreated via get_pattern_lock()
- # Reset state's event loop tracking so events get recreated in new loop
- state._event_loop = None
- state._stop_event = None
- state._skip_event = None
- # Clear any lingering pause/stop state from previous tests
- state._pause_requested = False
- state._stop_requested = False
- state._skip_requested = False
- # Clear playback state
- state.current_playing_file = None
- state.current_playlist = None
- state.playlist_mode = None
- state.current_playlist_index = None
- yield
- # Clean up after test
- pm.pause_event = None
- pm.pattern_lock = None
- state._event_loop = None
- state._stop_event = None
- state._skip_event = None
- state._pause_requested = False
- state._stop_requested = False
- state._skip_requested = False
|