Files
py-dvt-ate/tests/unit/test_power_supply.py
Kai Chappell 0594abe3d9 Add power supply simulator tests
Comprehensive test coverage for PowerSupplySim including VOLT, CURR,
OUTP, and MEAS commands. Tests both standalone operation and physics
engine integration.
2025-05-12 17:29:00 +00:00

353 lines
11 KiB
Python

"""Unit tests for power supply simulator."""
import pytest
from py_dvt_ate.simulation.physics.engine import PhysicsEngine
from py_dvt_ate.simulation.virtual.power_supply import PowerSupplySim
class TestPowerSupplySimBasic:
"""Tests for PowerSupplySim without physics engine."""
@pytest.fixture
def psu(self) -> PowerSupplySim:
"""Create power supply instance without physics engine."""
return PowerSupplySim()
def test_creation(self, psu: PowerSupplySim) -> None:
"""Test power supply can be created."""
assert psu is not None
assert psu.model == "PS-SIM-001"
assert psu.manufacturer == "PyDVTATE"
def test_idn_query(self, psu: PowerSupplySim) -> None:
"""Test *IDN? returns identification string."""
response = psu.process("*IDN?")
assert "PyDVTATE" in response
assert "PS-SIM-001" in response
def test_rst_command(self, psu: PowerSupplySim) -> None:
"""Test *RST resets to defaults."""
# Set non-default values
psu.process("VOLT 12.0")
psu.process("CURR 2.0")
psu.process("OUTP ON")
# Reset
response = psu.process("*RST")
assert response == ""
# Check defaults restored
assert psu.process("VOLT?") == "0.000"
assert psu.process("CURR?") == "1.000"
assert psu.process("OUTP?") == "0"
def test_opc_query(self, psu: PowerSupplySim) -> None:
"""Test *OPC? returns 1."""
response = psu.process("*OPC?")
assert response == "1"
def test_unknown_command(self, psu: PowerSupplySim) -> None:
"""Test unknown command returns error."""
response = psu.process("INVALID:CMD")
assert response.startswith("ERROR:")
assert "Unknown command" in response
class TestPowerSupplyVoltage:
"""Tests for VOLT command."""
@pytest.fixture
def psu(self) -> PowerSupplySim:
"""Create power supply instance without physics engine."""
return PowerSupplySim()
def test_volt_query_default(self, psu: PowerSupplySim) -> None:
"""Test VOLT? returns default value."""
response = psu.process("VOLT?")
assert response == "0.000"
def test_volt_set(self, psu: PowerSupplySim) -> None:
"""Test VOLT sets value."""
response = psu.process("VOLT 12.5")
assert response == ""
assert psu.process("VOLT?") == "12.500"
def test_volt_set_decimal(self, psu: PowerSupplySim) -> None:
"""Test VOLT accepts decimal values."""
psu.process("VOLT 3.3")
assert psu.process("VOLT?") == "3.300"
def test_volt_set_negative_fails(self, psu: PowerSupplySim) -> None:
"""Test VOLT rejects negative values."""
response = psu.process("VOLT -5.0")
assert response.startswith("ERROR:")
assert "negative" in response
def test_volt_set_invalid_value(self, psu: PowerSupplySim) -> None:
"""Test VOLT with invalid value returns error."""
response = psu.process("VOLT abc")
assert response.startswith("ERROR:")
assert "Invalid voltage" in response
def test_volt_set_no_argument(self, psu: PowerSupplySim) -> None:
"""Test VOLT without argument returns error."""
response = psu.process("VOLT")
assert response.startswith("ERROR:")
assert "requires a value" in response
class TestPowerSupplyCurrent:
"""Tests for CURR command."""
@pytest.fixture
def psu(self) -> PowerSupplySim:
"""Create power supply instance without physics engine."""
return PowerSupplySim()
def test_curr_query_default(self, psu: PowerSupplySim) -> None:
"""Test CURR? returns default value."""
response = psu.process("CURR?")
assert response == "1.000"
def test_curr_set(self, psu: PowerSupplySim) -> None:
"""Test CURR sets value."""
response = psu.process("CURR 0.5")
assert response == ""
assert psu.process("CURR?") == "0.500"
def test_curr_set_negative_fails(self, psu: PowerSupplySim) -> None:
"""Test CURR rejects negative values."""
response = psu.process("CURR -1.0")
assert response.startswith("ERROR:")
assert "negative" in response
def test_curr_set_invalid_value(self, psu: PowerSupplySim) -> None:
"""Test CURR with invalid value returns error."""
response = psu.process("CURR xyz")
assert response.startswith("ERROR:")
assert "Invalid current" in response
def test_curr_set_no_argument(self, psu: PowerSupplySim) -> None:
"""Test CURR without argument returns error."""
response = psu.process("CURR")
assert response.startswith("ERROR:")
assert "requires a value" in response
class TestPowerSupplyOutput:
"""Tests for OUTP command."""
@pytest.fixture
def psu(self) -> PowerSupplySim:
"""Create power supply instance without physics engine."""
return PowerSupplySim()
def test_outp_query_default(self, psu: PowerSupplySim) -> None:
"""Test OUTP? returns default value (off)."""
response = psu.process("OUTP?")
assert response == "0"
def test_outp_set_on(self, psu: PowerSupplySim) -> None:
"""Test OUTP ON enables output."""
response = psu.process("OUTP ON")
assert response == ""
assert psu.process("OUTP?") == "1"
def test_outp_set_1(self, psu: PowerSupplySim) -> None:
"""Test OUTP 1 enables output."""
psu.process("OUTP 1")
assert psu.process("OUTP?") == "1"
def test_outp_set_off(self, psu: PowerSupplySim) -> None:
"""Test OUTP OFF disables output."""
psu.process("OUTP ON")
psu.process("OUTP OFF")
assert psu.process("OUTP?") == "0"
def test_outp_set_0(self, psu: PowerSupplySim) -> None:
"""Test OUTP 0 disables output."""
psu.process("OUTP ON")
psu.process("OUTP 0")
assert psu.process("OUTP?") == "0"
def test_outp_set_invalid(self, psu: PowerSupplySim) -> None:
"""Test OUTP with invalid value returns error."""
response = psu.process("OUTP MAYBE")
assert response.startswith("ERROR:")
assert "Invalid output state" in response
def test_outp_set_no_argument(self, psu: PowerSupplySim) -> None:
"""Test OUTP without argument returns error."""
response = psu.process("OUTP")
assert response.startswith("ERROR:")
assert "requires a value" in response
class TestPowerSupplyMeasurement:
"""Tests for MEAS commands."""
@pytest.fixture
def psu(self) -> PowerSupplySim:
"""Create power supply instance without physics engine."""
return PowerSupplySim()
def test_meas_volt_when_off(self, psu: PowerSupplySim) -> None:
"""Test MEAS:VOLT? returns 0 when output is off."""
psu.process("VOLT 12.0")
response = psu.process("MEAS:VOLT?")
assert response == "0.000"
def test_meas_volt_when_on_no_engine(self, psu: PowerSupplySim) -> None:
"""Test MEAS:VOLT? returns setpoint when on without engine."""
psu.process("VOLT 12.0")
psu.process("OUTP ON")
response = psu.process("MEAS:VOLT?")
assert response == "12.000"
def test_meas_volt_as_command_fails(self, psu: PowerSupplySim) -> None:
"""Test MEAS:VOLT (without ?) returns error."""
response = psu.process("MEAS:VOLT 5.0")
assert response.startswith("ERROR:")
assert "query only" in response
def test_meas_curr_when_off(self, psu: PowerSupplySim) -> None:
"""Test MEAS:CURR? returns 0 when output is off."""
response = psu.process("MEAS:CURR?")
assert response == "0.000"
def test_meas_curr_when_on_no_engine(self, psu: PowerSupplySim) -> None:
"""Test MEAS:CURR? returns 0 when on without engine."""
psu.process("OUTP ON")
response = psu.process("MEAS:CURR?")
assert response == "0.000"
def test_meas_curr_as_command_fails(self, psu: PowerSupplySim) -> None:
"""Test MEAS:CURR (without ?) returns error."""
response = psu.process("MEAS:CURR 0.1")
assert response.startswith("ERROR:")
assert "query only" in response
class TestPowerSupplyWithPhysicsEngine:
"""Tests for PowerSupplySim with physics engine integration."""
@pytest.fixture
def engine(self) -> PhysicsEngine:
"""Create physics engine instance."""
return PhysicsEngine(update_rate_hz=100.0)
@pytest.fixture
def psu(self, engine: PhysicsEngine) -> PowerSupplySim:
"""Create power supply instance with physics engine."""
return PowerSupplySim(physics_engine=engine)
def test_outp_on_enables_engine_output(
self, psu: PowerSupplySim, engine: PhysicsEngine
) -> None:
"""Test OUTP ON enables physics engine output."""
psu.process("VOLT 5.0")
psu.process("OUTP ON")
assert engine.is_output_enabled is True
def test_outp_off_disables_engine_output(
self, psu: PowerSupplySim, engine: PhysicsEngine
) -> None:
"""Test OUTP OFF disables physics engine output."""
psu.process("OUTP ON")
psu.process("OUTP OFF")
assert engine.is_output_enabled is False
def test_volt_updates_engine_when_on(
self, psu: PowerSupplySim, engine: PhysicsEngine
) -> None:
"""Test VOLT updates engine input voltage when output is on."""
psu.process("OUTP ON")
psu.process("VOLT 5.0")
electrical = engine.get_electrical_state()
assert electrical.input_voltage == pytest.approx(5.0)
def test_volt_does_not_update_engine_when_off(
self, psu: PowerSupplySim, engine: PhysicsEngine
) -> None:
"""Test VOLT does not update engine when output is off."""
psu.process("VOLT 5.0")
electrical = engine.get_electrical_state()
assert electrical.input_voltage == pytest.approx(0.0)
def test_meas_volt_returns_engine_voltage(
self, psu: PowerSupplySim, engine: PhysicsEngine
) -> None:
"""Test MEAS:VOLT? returns physics engine voltage."""
psu.process("VOLT 5.0")
psu.process("OUTP ON")
response = psu.process("MEAS:VOLT?")
assert response == "5.000"
def test_meas_curr_returns_engine_current(
self, psu: PowerSupplySim, engine: PhysicsEngine
) -> None:
"""Test MEAS:CURR? returns total current from engine."""
psu.process("VOLT 5.0")
psu.process("OUTP ON")
engine.set_load_current(0.1)
# Step engine to allow calculations
engine.step()
response = psu.process("MEAS:CURR?")
# Should include load current + quiescent current
assert float(response) > 0.0
def test_reset_disables_engine_output(
self, psu: PowerSupplySim, engine: PhysicsEngine
) -> None:
"""Test *RST disables physics engine output."""
psu.process("VOLT 5.0")
psu.process("OUTP ON")
psu.process("*RST")
assert engine.is_output_enabled is False
def test_reset_sets_engine_voltage_zero(
self, psu: PowerSupplySim, engine: PhysicsEngine
) -> None:
"""Test *RST sets physics engine voltage to zero."""
psu.process("VOLT 5.0")
psu.process("OUTP ON")
psu.process("*RST")
electrical = engine.get_electrical_state()
assert electrical.input_voltage == pytest.approx(0.0)