import time import epics import pytest from epics.device import Device from epics.motor import MotorException import sys import os sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from slic.utils.hastyepics import * from unittest.mock import patch, PropertyMock def test_motor_instantiation_fast_vs_epics(): fast_times = [] slow_times = [] # Mock to simulate a real EPICS moteur with patch('epics.Motor.get') as mock_get: mock_get.return_value = 'motor' # Mean of 10 runs for _ in range(10): t0 = time.perf_counter() m_fast = Motor("TEST:MOTOR_t1") t1 = time.perf_counter() t2 = time.perf_counter() m_slow = epics.Motor("TEST:MOTOR_t2") t3 = time.perf_counter() fast_times.append(t1 - t0) slow_times.append(t3 - t2) avg_fast = sum(fast_times) / len(fast_times) avg_slow = sum(slow_times) / len(slow_times) assert avg_fast < avg_slow, f"Fast version ({avg_fast:.6f}s) should be faster than EPICS ({avg_slow:.6f}s)" def test_motor_without_suffix_and_dot(): # Suffix .VAL ... dot should be stripped from name m1 = Motor("TEST:MOTOR_1.VAL") assert m1._prefix == "TEST:MOTOR_1" m2 = Motor("TEST:MOTOR_2.") assert m2._prefix == "TEST:MOTOR_2" m3 = Motor("TEST:MOTOR") assert m3._prefix == "TEST:MOTOR" def test_motor_invalid_name_raises(): # Should raise MotorException if name is none with pytest.raises(MotorException): Motor(None) def test_motor_init_list_has_no_disabled(): assert "disabled" not in Motor._init_list assert "disabled" not in Motor._extras def test_motor_initializes_device_attrs(): # Check that Motor correctly initializes all Device PVs from _init_list m = Motor("TEST:MOTOR") print("_init_list contents:", m._init_list) print("All attributes on motor:", dir(m)) for attr in m._init_list: print(f"\nChecking attribute: {attr}") assert hasattr(m, attr), f"Motor is missing attribute: {attr}" pv = getattr(m, attr) print(f"PV object: {pv}") print("Type:", type(pv)) if pv is not None: print(f"PV name: {pv.pvname}") print(f"PV connected: {pv.connected}") assert isinstance(pv, epics.pv.PV), f"{attr} is not a valid PV object" assert not pv.connected, f"{attr} should not be connected yet" def test_motor_pv_connection_skipped_by_default(): # PVs should not be connected right after Motor creation m = Motor("TEST:MOTOR") for key in m._extras: pv = m.PV(key) # Explicitly check that no connection was forced during init assert not pv.connected, f"PV {key} was connected unexpectedly" def test_motor_adds_all_extras_pvs(): # Given a test motor name m = Motor("TEST:MOTOR") # For each key in _extras, the motor should have an attribute with that name for attr_name, suffix in m._extras.items(): # The attribute must exist assert hasattr(m, attr_name), f"Missing attribute: {attr_name}" # The attribute must be a PV pv = getattr(m, attr_name) assert isinstance(pv, PV), f"{attr_name} is not a PV" # The PV's name should match the expected pattern expected_name = f"TEST:MOTOR{suffix}" assert pv.pvname == expected_name, f"PV name mismatch for {attr_name}" assert not pv.connected, f"{attr_name} PV should not be connected immediately" def test_motor_PV_method_exists_and_works(): # Motor.PV(): should call super().PV and return a PV object m = Motor("TEST:MOTOR") pv = m.PV("RBV", connect=False) assert pv is not None assert isinstance(pv, epics.pv.PV)