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.
This commit is contained in:
352
tests/unit/test_power_supply.py
Normal file
352
tests/unit/test_power_supply.py
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
"""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)
|
||||||
Reference in New Issue
Block a user