"""Unit tests for instrument drivers. Tests SCPI command formatting and driver functionality using mock transports. """ from unittest.mock import MagicMock import pytest from py_dvt_ate.instruments.drivers.base import BaseDriver from py_dvt_ate.instruments.drivers.chamber import ThermalChamberDriver from py_dvt_ate.instruments.drivers.multimeter import MultimeterDriver from py_dvt_ate.instruments.drivers.power_supply import PowerSupplyDriver @pytest.fixture def mock_transport(): """Create a mock transport for testing.""" transport = MagicMock() transport.is_connected = True return transport class TestBaseDriver: """Tests for BaseDriver base class.""" def test_connect(self, mock_transport): """Test connection establishment.""" driver = BaseDriver(mock_transport) driver.connect() mock_transport.connect.assert_called_once() def test_disconnect(self, mock_transport): """Test disconnection.""" driver = BaseDriver(mock_transport) driver.disconnect() mock_transport.disconnect.assert_called_once() def test_is_connected(self, mock_transport): """Test connection status check.""" driver = BaseDriver(mock_transport) assert driver.is_connected is True def test_write(self, mock_transport): """Test SCPI command write.""" driver = BaseDriver(mock_transport) driver.write("VOLT 3.3") mock_transport.write.assert_called_once_with("VOLT 3.3") def test_query(self, mock_transport): """Test SCPI query.""" mock_transport.query.return_value = "3.300" driver = BaseDriver(mock_transport) result = driver.query("VOLT?") assert result == "3.300" mock_transport.query.assert_called_once_with("VOLT?", None) def test_query_float(self, mock_transport): """Test SCPI query with float parsing.""" mock_transport.query.return_value = "3.300" driver = BaseDriver(mock_transport) result = driver.query_float("VOLT?") assert result == 3.3 assert isinstance(result, float) def test_query_float_invalid(self, mock_transport): """Test SCPI query with invalid float response.""" mock_transport.query.return_value = "INVALID" driver = BaseDriver(mock_transport) with pytest.raises(ValueError, match="Cannot parse 'INVALID' as float"): driver.query_float("VOLT?") def test_query_int(self, mock_transport): """Test SCPI query with integer parsing.""" mock_transport.query.return_value = "42" driver = BaseDriver(mock_transport) result = driver.query_int("COUNT?") assert result == 42 assert isinstance(result, int) def test_query_int_invalid(self, mock_transport): """Test SCPI query with invalid integer response.""" mock_transport.query.return_value = "3.14" driver = BaseDriver(mock_transport) with pytest.raises(ValueError, match="Cannot parse '3.14' as int"): driver.query_int("COUNT?") def test_query_bool_true_variants(self, mock_transport): """Test SCPI query with boolean parsing - true variants.""" driver = BaseDriver(mock_transport) for value in ["1", "ON", "TRUE", "on", "true"]: mock_transport.query.return_value = value result = driver.query_bool("OUTP?") assert result is True def test_query_bool_false_variants(self, mock_transport): """Test SCPI query with boolean parsing - false variants.""" driver = BaseDriver(mock_transport) for value in ["0", "OFF", "FALSE", "off", "false"]: mock_transport.query.return_value = value result = driver.query_bool("OUTP?") assert result is False def test_query_bool_invalid(self, mock_transport): """Test SCPI query with invalid boolean response.""" mock_transport.query.return_value = "MAYBE" driver = BaseDriver(mock_transport) with pytest.raises(ValueError, match="Cannot parse 'MAYBE' as bool"): driver.query_bool("OUTP?") def test_identify(self, mock_transport): """Test instrument identification query.""" mock_transport.query.return_value = "Manufacturer,Model,SN123,1.0.0" driver = BaseDriver(mock_transport) result = driver.identify() assert result == "Manufacturer,Model,SN123,1.0.0" mock_transport.query.assert_called_once_with("*IDN?", None) def test_reset(self, mock_transport): """Test instrument reset command.""" driver = BaseDriver(mock_transport) driver.reset() mock_transport.write.assert_called_once_with("*RST") def test_clear_status(self, mock_transport): """Test clear status command.""" driver = BaseDriver(mock_transport) driver.clear_status() mock_transport.write.assert_called_once_with("*CLS") def test_operation_complete(self, mock_transport): """Test operation complete query.""" mock_transport.query.return_value = "1" driver = BaseDriver(mock_transport) result = driver.operation_complete() assert result is True mock_transport.query.assert_called_once_with("*OPC?", None) class TestThermalChamberDriver: """Tests for ThermalChamberDriver.""" def test_set_temperature(self, mock_transport): """Test temperature setpoint command.""" driver = ThermalChamberDriver(mock_transport) driver.set_temperature(85.0) mock_transport.write.assert_called_once_with("TEMP:SETPOINT 85.00") def test_get_temperature(self, mock_transport): """Test temperature measurement query.""" mock_transport.query.return_value = "25.50" driver = ThermalChamberDriver(mock_transport) temp = driver.get_temperature() assert temp == 25.5 mock_transport.query.assert_called_once_with("TEMP:ACTUAL?", None) def test_get_setpoint(self, mock_transport): """Test setpoint query.""" mock_transport.query.return_value = "85.00" driver = ThermalChamberDriver(mock_transport) setpoint = driver.get_setpoint() assert setpoint == 85.0 mock_transport.query.assert_called_once_with("TEMP:SETPOINT?", None) def test_is_stable_true(self, mock_transport): """Test stability check - stable.""" mock_transport.query.return_value = "1" driver = ThermalChamberDriver(mock_transport) assert driver.is_stable() is True def test_is_stable_false(self, mock_transport): """Test stability check - not stable.""" mock_transport.query.return_value = "0" driver = ThermalChamberDriver(mock_transport) assert driver.is_stable() is False def test_wait_until_stable_immediate(self, mock_transport): """Test wait for stability - already stable.""" mock_transport.query.return_value = "1" driver = ThermalChamberDriver(mock_transport) result = driver.wait_until_stable(timeout=5.0, poll_interval=0.1) assert result is True def test_wait_until_stable_timeout(self, mock_transport): """Test wait for stability - timeout.""" mock_transport.query.return_value = "0" # Never becomes stable driver = ThermalChamberDriver(mock_transport) result = driver.wait_until_stable(timeout=0.2, poll_interval=0.1) assert result is False def test_wait_until_stable_invalid_timeout(self, mock_transport): """Test wait with negative timeout.""" driver = ThermalChamberDriver(mock_transport) with pytest.raises(ValueError, match="Timeout must be non-negative"): driver.wait_until_stable(timeout=-1.0) def test_wait_until_stable_invalid_interval(self, mock_transport): """Test wait with non-positive poll interval.""" driver = ThermalChamberDriver(mock_transport) with pytest.raises(ValueError, match="Poll interval must be positive"): driver.wait_until_stable(poll_interval=0.0) def test_set_ramp_rate(self, mock_transport): """Test ramp rate command.""" driver = ThermalChamberDriver(mock_transport) driver.set_ramp_rate(5.0) mock_transport.write.assert_called_once_with("TEMP:RAMP 5.00") def test_get_ramp_rate(self, mock_transport): """Test ramp rate query.""" mock_transport.query.return_value = "5.00" driver = ThermalChamberDriver(mock_transport) rate = driver.get_ramp_rate() assert rate == 5.0 class TestPowerSupplyDriver: """Tests for PowerSupplyDriver.""" def test_set_voltage(self, mock_transport): """Test voltage setpoint command.""" driver = PowerSupplyDriver(mock_transport) driver.set_voltage(1, 3.3) mock_transport.write.assert_called_once_with("VOLT 3.300") def test_get_voltage(self, mock_transport): """Test voltage setpoint query.""" mock_transport.query.return_value = "3.300" driver = PowerSupplyDriver(mock_transport) voltage = driver.get_voltage(1) assert voltage == 3.3 def test_set_current_limit(self, mock_transport): """Test current limit command.""" driver = PowerSupplyDriver(mock_transport) driver.set_current_limit(1, 0.5) mock_transport.write.assert_called_once_with("CURR 0.500") def test_get_current_limit(self, mock_transport): """Test current limit query.""" mock_transport.query.return_value = "0.500" driver = PowerSupplyDriver(mock_transport) current = driver.get_current_limit(1) assert current == 0.5 def test_measure_voltage(self, mock_transport): """Test voltage measurement.""" mock_transport.query.return_value = "3.305" driver = PowerSupplyDriver(mock_transport) voltage = driver.measure_voltage(1) assert voltage == 3.305 mock_transport.query.assert_called_once_with("MEAS:VOLT?", None) def test_measure_current(self, mock_transport): """Test current measurement.""" mock_transport.query.return_value = "0.125" driver = PowerSupplyDriver(mock_transport) current = driver.measure_current(1) assert current == 0.125 mock_transport.query.assert_called_once_with("MEAS:CURR?", None) def test_enable_output_on(self, mock_transport): """Test enable output command.""" driver = PowerSupplyDriver(mock_transport) driver.enable_output(1, True) mock_transport.write.assert_called_once_with("OUTP ON") def test_enable_output_off(self, mock_transport): """Test disable output command.""" driver = PowerSupplyDriver(mock_transport) driver.enable_output(1, False) mock_transport.write.assert_called_once_with("OUTP OFF") def test_is_output_enabled_true(self, mock_transport): """Test output enabled query - enabled.""" mock_transport.query.return_value = "1" driver = PowerSupplyDriver(mock_transport) assert driver.is_output_enabled(1) is True def test_is_output_enabled_false(self, mock_transport): """Test output enabled query - disabled.""" mock_transport.query.return_value = "0" driver = PowerSupplyDriver(mock_transport) assert driver.is_output_enabled(1) is False class TestMultimeterDriver: """Tests for MultimeterDriver.""" def test_measure_dc_voltage(self, mock_transport): """Test DC voltage measurement.""" mock_transport.query.return_value = "3.300000" driver = MultimeterDriver(mock_transport) voltage = driver.measure_dc_voltage() assert voltage == 3.3 mock_transport.query.assert_called_once_with("MEAS:VOLT:DC?", None) def test_measure_dc_current(self, mock_transport): """Test DC current measurement.""" mock_transport.query.return_value = "0.125000" driver = MultimeterDriver(mock_transport) current = driver.measure_dc_current() assert current == 0.125 mock_transport.query.assert_called_once_with("MEAS:CURR:DC?", None) def test_measure_resistance_not_implemented(self, mock_transport): """Test resistance measurement raises NotImplementedError.""" driver = MultimeterDriver(mock_transport) with pytest.raises(NotImplementedError, match="Resistance measurement"): driver.measure_resistance() def test_set_integration_time_not_implemented(self, mock_transport): """Test integration time setting raises NotImplementedError.""" driver = MultimeterDriver(mock_transport) with pytest.raises(NotImplementedError, match="Integration time"): driver.set_integration_time(1.0) def test_configure_dc_voltage(self, mock_transport): """Test configure for DC voltage.""" driver = MultimeterDriver(mock_transport) driver.configure_dc_voltage() mock_transport.write.assert_called_once_with("CONF:VOLT:DC") def test_configure_dc_current(self, mock_transport): """Test configure for DC current.""" driver = MultimeterDriver(mock_transport) driver.configure_dc_current() mock_transport.write.assert_called_once_with("CONF:CURR:DC") def test_get_configuration(self, mock_transport): """Test get current configuration.""" mock_transport.query.return_value = '"VOLT:DC"' driver = MultimeterDriver(mock_transport) config = driver.get_configuration() assert config == "VOLT:DC" mock_transport.query.assert_called_once_with("CONF?", None) def test_read(self, mock_transport): """Test read measurement with current configuration.""" mock_transport.query.return_value = "3.300000" driver = MultimeterDriver(mock_transport) value = driver.read() assert value == 3.3 mock_transport.query.assert_called_once_with("READ?", None)