Add SCPI parser tests
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).
This commit is contained in:
@@ -67,8 +67,6 @@ class SCPIParser:
|
||||
if not command_string:
|
||||
return SCPICommand(header="", arguments=[], is_query=False)
|
||||
|
||||
is_query = command_string.endswith("?")
|
||||
|
||||
# Split into header and arguments on first whitespace
|
||||
parts = command_string.split(None, 1)
|
||||
header = parts[0]
|
||||
@@ -79,6 +77,9 @@ class SCPIParser:
|
||||
arg_string = parts[1]
|
||||
arguments = [arg.strip() for arg in arg_string.split(",")]
|
||||
|
||||
# Query is determined by whether the header ends with '?'
|
||||
is_query = header.endswith("?")
|
||||
|
||||
return SCPICommand(
|
||||
header=header,
|
||||
arguments=arguments,
|
||||
|
||||
203
tests/unit/test_scpi_parser.py
Normal file
203
tests/unit/test_scpi_parser.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user