Repository navigation
Testing in CI
Two ways to get simulated instruments into automated tests: the pytest plugin (scpi-sim installed in the test environment) or the Docker image (scpi-sim as a separate service).
pip install scpi-sim registers the plugin. The scpi_bench fixture starts a
fresh bench for every test, on free ports, and stops it afterwards — tests
cannot leak instrument state into each other and can run in parallel.
import pyvisa
def test_rail_voltage(scpi_bench):
rm = pyvisa.ResourceManager("@py")
opts = dict(read_termination="\n", write_termination="\n", timeout=2000)
psu = rm.open_resource(scpi_bench.resource("psu"), **opts)
daq = rm.open_resource(scpi_bench.resource("daq"), **opts)
psu.write("INST OUT1; VOLT 12; OUTP:SEL ON;:OUTP:GEN ON")
assert abs(float(daq.query("MEAS:VOLT:DC? (@101)")) - 12) < 0.01
rm.close()pytest --scpi-bench examples/profiles/bench.yaml- Which bench:
--scpi-bench FILE, orscpi_bench = FILEinpytest.ini/[tool.pytest.ini_options]; without either a genericpsu,dmm,mux. -
scpi_bench.resource(name)→TCPIP0::127.0.0.1::<port>::SOCKET;scpi_bench.resource(name, "hislip")and"vxi11"give the LAN protocol strings — the fixture serves both (without port mapper). -
scpi_bench.<name>is the instrument object: inject faults (scpi_bench.daq.inject("offset", value=0.5)) or set inputs directly. -
scpi_bench_factory(path_or_dict)starts more benches in one test.
Most projects wrap instruments in a driver class that takes a resource string. Point it at the fixture:
from mylab.drivers import Hmp4040 # your code
def test_driver_sets_and_reads_back(scpi_bench):
psu = Hmp4040(scpi_bench.resource("psu"))
psu.set_output(channel=1, volts=12, amps=1)
assert psu.current(channel=1) == 0.5The driver does not know it is talking to a simulator.
When the tests should not import scpi-sim — other languages, a test system that only takes resource strings, or an end-to-end pipeline — run the image as a service. GitHub Actions:
jobs:
test:
runs-on: ubuntu-latest
services:
instruments:
image: ghcr.io/steitec/scpi-sim:0.7.4
ports:
- 5025:5025 # psu
- 5026:5026 # dmm
- 5027:5027 # mux
- 4880:4880 # HiSLIP
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install -r requirements.txt
- run: pytest tests/hardware
env:
PSU_RESOURCE: TCPIP0::localhost::5025::SOCKET
DMM_RESOURCE: TCPIP0::localhost::hislip_dmm::INSTRGitLab CI works the same way with services: and the alias as host name.
Your own bench file goes into the container with a volume, or build a small image on top:
FROM ghcr.io/steitec/scpi-sim:0.7.4
COPY bench.yaml /bench/bench.yamlThe image runs scpi-sim serve --hislip --vxi11 --vxi11-port 5098 --bench /bench/bench.yaml --host 0.0.0.0 and exposes 5025–5028, 5099 (control port for
faults), 4880 (HiSLIP), 5098 and 111 (VXI-11).
Pin the version (:0.7.4, not :latest) so a new scpi-sim release does
not change your pipeline unannounced.
With the service container, faults come through the control port (publish
5099 for this, only inside the CI network — it has no authentication):
pip install scpi-sim # only for the command line client
scpi-sim fault dmm no_response count=1 --control-port 5099
scpi-sim fault --listPatterns for what to test: Fault injection recipes.
-
Timeouts: keep them short in tests (
timeout=2000). The simulator answers in well under a millisecond; a long timeout only slows down the tests that check timeouts. -
Noise: generic multimeters add a little noise by default. Compare with a
tolerance, or set
noise: 0(andseed:for reproducible noise) in the bench file. -
Several instruments: every instrument has its own connection. Finish a
switching command with
*OPC?before measuring with another instrument, as on a real bench:ROUT:CLOS (@101);*OPC?.
scpi-sim · MIT licensed · instrument names are trademarks of their owners; scpi-sim is not affiliated with them.