Repository navigation
Testing in CI
Two ways to get simulated devices into automated tests: the pytest plugin, or hart-sim serve as a background
process for any language and test runner.
pip install hart-sim registers two fixtures. They start a new bench on free ports for every test and stop it
afterwards, so tests cannot leak device state into each other.
import pytest
def test_reads_the_transmitter(hart_bench):
with hart_bench.client() as client: # a connected HartIPClient
variables = client.handle(0).read_variables()
assert variables.pv_unit == 12 # kPa
assert 3.8 <= variables.current_ma <= 20.5
def test_alarm_current(hart_bench):
hart_bench.devices["PT-101"].faults.inject("sensor_failure")
with hart_bench.client() as client:
assert client.handle(0).read_variables().current_ma == pytest.approx(21.0)hart_bench is one pressure device at polling address 0 with tag PT-101, unless you choose a bench with
--hart-bench FILE or the ini key hart_bench. For more devices, use the factory:
def test_two_devices(hart_bench_factory):
bench = hart_bench_factory({"devices": [
{"profile": "pressure", "polling_address": 1, "device_id": 1, "tag": "PT-101"},
{"profile": "temperature", "polling_address": 2, "device_id": 2, "tag": "TT-201"},
]})
with bench.client() as client:
assert client.find("TT-201").read_variables().pv_unit == 32 # degCThe factory takes a bench file, a dict or a bundled profile name. bench.client(transport="udp", timeout=0.5)
sets the transport and the timeout.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
- run: pip install hart-sim pytest
- run: pytestFor a job that tests something other than Python, run the published image next to it. GitHub Actions waits until the
image reports healthy, so no sleep is needed. Pin the version.
jobs:
test:
runs-on: ubuntu-latest
services:
hart:
image: ghcr.io/steitec/hart-sim:0.1.2
ports:
- 5094:5094 # HART-IP over TCP
- 5094:5094/udp # HART-IP over UDP
- 127.0.0.1:5095:5095 # control channel, no authentication
steps:
- uses: actions/checkout@v7
- run: ./run-my-system-tests # connects to 127.0.0.1:5094The default bench has PT-101 at polling address 1 and TT-201 at polling address 2. If the job itself runs in a
container, leave out ports and connect to the host name hart instead. Service containers start before
actions/checkout, so they cannot mount a bench file from your repository; to use your own, start the image with
docker run -d -v "$PWD/bench.yaml:/bench/bench.yaml" ... in a step after the checkout.
For tests in another language, or a system test against a built application:
pip install hart-sim
hart-sim serve --bench plant.yaml &
sleep 1
./run-my-system-tests # connects to 127.0.0.1:5094
hart-sim inject PT-101 sensor_failure # from a script, or the JSON belowThe control channel takes one JSON object per line on port 5095, so any language can inject faults:
echo '{"device": "PT-101", "inject": "sensor_failure", "params": {}}' | nc -q1 127.0.0.1 5095{"ok": true}Neither HART-IP nor the control channel has authentication. Both listen on loopback by default.
hart-sim · MIT licensed · HART is a trademark of the FieldComm Group; hart-sim is not affiliated with it.