347 lines
14 KiB
Python
347 lines
14 KiB
Python
"""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)
|