Comprehensive test coverage for PowerSupplySim including VOLT, CURR, OUTP, and MEAS commands. Tests both standalone operation and physics engine integration.
353 lines
11 KiB
Python
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)
|