Comprehensive test suite for SCPI command parsing: - SCPICommand dataclass tests (creation, keyword property) - Parser tests for queries, commands, arguments - IEEE 488.2 common command tests (*IDN?, *RST, etc.) - Edge cases (whitespace, empty strings) - Instrument-specific command tests Also fixed bug where is_query was determined from command string ending rather than header ending (handles queries with arguments).
204 lines
6.8 KiB
Python
204 lines
6.8 KiB
Python
"""Unit tests for SCPI command parsing."""
|
|
|
|
import pytest
|
|
|
|
from py_dvt_ate.instruments.scpi import SCPICommand, SCPIParser
|
|
|
|
|
|
class TestSCPICommand:
|
|
"""Tests for the SCPICommand dataclass."""
|
|
|
|
def test_creation(self) -> None:
|
|
"""Test SCPICommand can be created with valid values."""
|
|
cmd = SCPICommand(
|
|
header="VOLT",
|
|
arguments=["3.3"],
|
|
is_query=False,
|
|
)
|
|
|
|
assert cmd.header == "VOLT"
|
|
assert cmd.arguments == ["3.3"]
|
|
assert cmd.is_query is False
|
|
|
|
def test_keyword_for_command(self) -> None:
|
|
"""Test keyword property for regular command."""
|
|
cmd = SCPICommand(header="VOLT", arguments=["3.3"], is_query=False)
|
|
|
|
assert cmd.keyword == "VOLT"
|
|
|
|
def test_keyword_for_query(self) -> None:
|
|
"""Test keyword property strips '?' from query."""
|
|
cmd = SCPICommand(header="VOLT?", arguments=[], is_query=True)
|
|
|
|
assert cmd.keyword == "VOLT"
|
|
|
|
def test_keyword_for_nested_command(self) -> None:
|
|
"""Test keyword property for nested SCPI command."""
|
|
cmd = SCPICommand(header="TEMP:SETPOINT?", arguments=[], is_query=True)
|
|
|
|
assert cmd.keyword == "TEMP:SETPOINT"
|
|
|
|
|
|
class TestSCPIParser:
|
|
"""Tests for the SCPIParser class."""
|
|
|
|
@pytest.fixture
|
|
def parser(self) -> SCPIParser:
|
|
"""Create parser instance for tests."""
|
|
return SCPIParser()
|
|
|
|
def test_parse_simple_query(self, parser: SCPIParser) -> None:
|
|
"""Test parsing simple query command."""
|
|
cmd = parser.parse("*IDN?")
|
|
|
|
assert cmd.header == "*IDN?"
|
|
assert cmd.arguments == []
|
|
assert cmd.is_query is True
|
|
assert cmd.keyword == "*IDN"
|
|
|
|
def test_parse_simple_command(self, parser: SCPIParser) -> None:
|
|
"""Test parsing simple command without arguments."""
|
|
cmd = parser.parse("*RST")
|
|
|
|
assert cmd.header == "*RST"
|
|
assert cmd.arguments == []
|
|
assert cmd.is_query is False
|
|
assert cmd.keyword == "*RST"
|
|
|
|
def test_parse_command_with_single_argument(self, parser: SCPIParser) -> None:
|
|
"""Test parsing command with single numeric argument."""
|
|
cmd = parser.parse("VOLT 3.3")
|
|
|
|
assert cmd.header == "VOLT"
|
|
assert cmd.arguments == ["3.3"]
|
|
assert cmd.is_query is False
|
|
|
|
def test_parse_command_with_multiple_arguments(self, parser: SCPIParser) -> None:
|
|
"""Test parsing command with comma-separated arguments."""
|
|
cmd = parser.parse("CONF:VOLT:DC 10,0.001")
|
|
|
|
assert cmd.header == "CONF:VOLT:DC"
|
|
assert cmd.arguments == ["10", "0.001"]
|
|
assert cmd.is_query is False
|
|
|
|
def test_parse_nested_scpi_command(self, parser: SCPIParser) -> None:
|
|
"""Test parsing nested SCPI command hierarchy."""
|
|
cmd = parser.parse("TEMP:SETPOINT 85.0")
|
|
|
|
assert cmd.header == "TEMP:SETPOINT"
|
|
assert cmd.arguments == ["85.0"]
|
|
assert cmd.is_query is False
|
|
assert cmd.keyword == "TEMP:SETPOINT"
|
|
|
|
def test_parse_nested_scpi_query(self, parser: SCPIParser) -> None:
|
|
"""Test parsing nested SCPI query."""
|
|
cmd = parser.parse("TEMP:SETPOINT?")
|
|
|
|
assert cmd.header == "TEMP:SETPOINT?"
|
|
assert cmd.arguments == []
|
|
assert cmd.is_query is True
|
|
|
|
def test_parse_ieee_common_commands(self, parser: SCPIParser) -> None:
|
|
"""Test parsing IEEE 488.2 common commands."""
|
|
# Identity query
|
|
cmd = parser.parse("*IDN?")
|
|
assert cmd.is_query is True
|
|
assert cmd.keyword == "*IDN"
|
|
|
|
# Reset
|
|
cmd = parser.parse("*RST")
|
|
assert cmd.is_query is False
|
|
assert cmd.keyword == "*RST"
|
|
|
|
# Clear status
|
|
cmd = parser.parse("*CLS")
|
|
assert cmd.is_query is False
|
|
assert cmd.keyword == "*CLS"
|
|
|
|
# Operation complete query
|
|
cmd = parser.parse("*OPC?")
|
|
assert cmd.is_query is True
|
|
assert cmd.keyword == "*OPC"
|
|
|
|
def test_parse_strips_whitespace(self, parser: SCPIParser) -> None:
|
|
"""Test parser strips leading and trailing whitespace."""
|
|
cmd = parser.parse(" VOLT 3.3 ")
|
|
|
|
assert cmd.header == "VOLT"
|
|
assert cmd.arguments == ["3.3"]
|
|
|
|
def test_parse_strips_argument_whitespace(self, parser: SCPIParser) -> None:
|
|
"""Test parser strips whitespace from arguments."""
|
|
cmd = parser.parse("CONF:VOLT:DC 10 , 0.001 ")
|
|
|
|
assert cmd.arguments == ["10", "0.001"]
|
|
|
|
def test_parse_empty_string(self, parser: SCPIParser) -> None:
|
|
"""Test parsing empty string returns empty command."""
|
|
cmd = parser.parse("")
|
|
|
|
assert cmd.header == ""
|
|
assert cmd.arguments == []
|
|
assert cmd.is_query is False
|
|
|
|
def test_parse_whitespace_only(self, parser: SCPIParser) -> None:
|
|
"""Test parsing whitespace-only string returns empty command."""
|
|
cmd = parser.parse(" ")
|
|
|
|
assert cmd.header == ""
|
|
assert cmd.arguments == []
|
|
assert cmd.is_query is False
|
|
|
|
def test_parse_output_on_off(self, parser: SCPIParser) -> None:
|
|
"""Test parsing output enable/disable commands."""
|
|
cmd_on = parser.parse("OUTP ON")
|
|
assert cmd_on.arguments == ["ON"]
|
|
|
|
cmd_off = parser.parse("OUTP OFF")
|
|
assert cmd_off.arguments == ["OFF"]
|
|
|
|
cmd_1 = parser.parse("OUTP 1")
|
|
assert cmd_1.arguments == ["1"]
|
|
|
|
cmd_0 = parser.parse("OUTP 0")
|
|
assert cmd_0.arguments == ["0"]
|
|
|
|
def test_parse_channel_select(self, parser: SCPIParser) -> None:
|
|
"""Test parsing channel selection commands."""
|
|
cmd = parser.parse("INST:SEL CH1")
|
|
|
|
assert cmd.header == "INST:SEL"
|
|
assert cmd.arguments == ["CH1"]
|
|
|
|
def test_parse_measurement_query(self, parser: SCPIParser) -> None:
|
|
"""Test parsing measurement query commands."""
|
|
cmd = parser.parse("MEAS:VOLT:DC?")
|
|
|
|
assert cmd.header == "MEAS:VOLT:DC?"
|
|
assert cmd.is_query is True
|
|
assert cmd.keyword == "MEAS:VOLT:DC"
|
|
|
|
def test_parse_measurement_with_range(self, parser: SCPIParser) -> None:
|
|
"""Test parsing measurement query with range argument."""
|
|
cmd = parser.parse("MEAS:VOLT:DC? AUTO")
|
|
|
|
assert cmd.header == "MEAS:VOLT:DC?"
|
|
assert cmd.arguments == ["AUTO"]
|
|
assert cmd.is_query is True
|
|
|
|
def test_parse_system_error_query(self, parser: SCPIParser) -> None:
|
|
"""Test parsing system error query."""
|
|
cmd = parser.parse("SYST:ERR?")
|
|
|
|
assert cmd.header == "SYST:ERR?"
|
|
assert cmd.is_query is True
|
|
assert cmd.keyword == "SYST:ERR"
|
|
|
|
def test_parse_nplc_setting(self, parser: SCPIParser) -> None:
|
|
"""Test parsing NPLC (integration time) command."""
|
|
cmd = parser.parse("SENS:VOLT:DC:NPLC 10")
|
|
|
|
assert cmd.header == "SENS:VOLT:DC:NPLC"
|
|
assert cmd.arguments == ["10"]
|
|
assert cmd.is_query is False
|