Suite Configuration¶
Use this page when you are ready to define the actual hardware test logic: steps, expectations, protocols, GPIO checks, power cycles, and validation rules.
BenchCI test suites are stored in suite.yaml. A suite defines named tests and ordered steps.
Top-level structure¶
version: "1"
suite:
name: smoke
description: Optional suite description
tests:
- name: boot_ok
steps:
- expect_uart:
node: dut
transport: console
contains: "BOOT OK"
within_ms: 3000
Main sections¶
version¶
Schema version field. Use 1 or "1" in new suite files.
suite¶
Suite metadata.
suite:
name: smoke
description: First working suite
tests¶
Ordered list of test cases.
Each test has:
namesteps
Example suite¶
version: "1"
suite:
name: stm32_smoke
tests:
- name: boot_ok
steps:
- expect_uart:
node: dut
transport: console
contains: "[BOOT] OK"
within_ms: 3000
- name: ping
steps:
- send_uart:
node: dut
transport: console
data: "PING\n"
- expect_uart:
node: dut
transport: console
contains: "PONG"
within_ms: 1000
- name: irq_test
steps:
- gpio_wait_edge:
node: dut
line: irq
edge: rising
within_ms: 2000
Optional traceability metadata¶
Traceability fields are optional. Keep simple suites simple. Add these fields when a test should be connected to requirements, test cases, risks, releases, or evidence reports.
Suite-level metadata can include:
suite:
name: stm32_smoke
description: Basic real-hardware smoke test
version: "1.0.0"
release_id: "fw-0.3.5"
requirement_ids:
- REQ-BOOT-001
risk_ids:
- RISK-BOOT-001
tags:
- smoke
- hardware-ci
Each test can include:
tests:
- name: boot_ok
test_case_id: TC-BOOT-001
requirement_ids:
- REQ-BOOT-001
risk_ids:
- RISK-BOOT-001
tags:
- boot
- uart
steps:
- expect_uart:
node: dut
transport: console
contains: "BOOT OK"
within_ms: 3000
These fields are copied into results.json, evidence.json, and evidence.html so a run can show which requirements, test cases, and risks were covered.
A useful mental model is:
Risk -> Requirement -> Test case -> BenchCI run evidence
Step types¶
BenchCI currently supports these step types:
resetsleep_msflashsend_uartexpect_uartmodbus_read_holding_registersmodbus_write_single_registermodbus_read_coilsmodbus_write_single_coilgpio_setgpio_getgpio_expectgpio_wait_edgesend_canexpect_cani2c_scani2c_write_registeri2c_read_registeri2c_expectspi_transferspi_expectfuzz_uartfuzz_canfuzz_modbuspower_setpower_cyclepower_expectmeasureassert_metric
Reset step¶
- reset:
node: dut
Resets the selected node using the node’s configured reset.method.
Sleep step¶
- sleep_ms: 100
Pauses execution for the requested number of milliseconds.
Flash step¶
- flash:
node: dut
Optional step-level artifact override:
- flash:
node: dut
artifact: build/alternate.elf
Artifact resolution order is:
step artifact override
CLI
--artifactnode.flash.artifactfrombench.yaml
UART steps¶
Send text¶
- send_uart:
node: dut
transport: console
data: "PING\n"
Expect text by substring¶
- expect_uart:
node: dut
transport: console
contains: "PONG"
within_ms: 1000
Expect text by regex¶
- expect_uart:
node: dut
transport: console
regex: "FW:[0-9.]+"
within_ms: 1000
expect_uart must define exactly one of:
containsregex
Modbus steps¶
Read holding registers¶
- modbus_read_holding_registers:
node: plc
transport: fieldbus
slave: 1
address: 100
count: 2
expect: [123, 456]
Write single register¶
- modbus_write_single_register:
node: plc
transport: fieldbus
slave: 1
address: 100
value: 42
Read coils¶
- modbus_read_coils:
node: plc
transport: fieldbus
slave: 1
address: 0
count: 2
expect: [true, false]
Write single coil¶
- modbus_write_single_coil:
node: plc
transport: fieldbus
slave: 1
address: 0
value: true
GPIO steps¶
Set logical output value¶
- gpio_set:
node: dut
line: reset_n
value: false
Read logical input value¶
- gpio_get:
node: dut
line: ready
With expectation:
- gpio_get:
node: dut
line: ready
expect: true
Wait for logical value¶
- gpio_expect:
node: dut
line: ready
value: true
within_ms: 3000
Wait for edge¶
- gpio_wait_edge:
node: dut
line: irq
edge: rising
within_ms: 2000
Allowed edges are:
risingfallingboth
CAN steps¶
Send frame¶
- send_can:
node: dut
transport: bus
frame:
id: 257
extended: false
data: "01 02 0A FF"
CAN FD frames add fd: true and can use up to 64 data bytes:
- send_can:
node: dut
transport: bus
frame:
id: 0x123
extended: false
fd: true
bitrate_switch: true
data: "01 02 03 04 05 06 07 08 09 0A 0B 0C"
Expect frame¶
- expect_can:
node: dut
transport: bus
frame:
id: 513
extended: false
data: "AA BB"
within_ms: 1000
Expectations can match IDs and payload bytes with masks. With allow_extra_data: true,
the data/data_mask rule is treated as a payload prefix.
- expect_can:
node: dut
transport: bus
frame:
id: 0x520
id_mask: 0x7F0
extended: false
fd: true
data: "AA 00 BB"
data_mask: "FF 00 FF"
allow_extra_data: true
within_ms: 1000
I2C steps¶
I2C steps require a node transport with backend: i2c.
Scan addresses¶
- i2c_scan:
node: dut
transport: board_i2c
expect_addresses: [0x40, 0x68]
Write register¶
- i2c_write_register:
node: dut
transport: board_i2c
address: 0x40
register: 0x01
value: [0x12, 0x34]
Read or expect register bytes¶
- i2c_read_register:
node: dut
transport: board_i2c
address: 0x40
register: 0x02
length: 2
expect: [0x00, 0x10]
- i2c_expect:
node: dut
transport: board_i2c
address: 0x40
register: 0x03
length: 1
expect: [0x80]
SPI steps¶
SPI steps require a node transport with backend: spi. Data and expectations are hex byte strings.
- spi_transfer:
node: dut
transport: flash_spi
data: "9F 00 00 00"
- spi_expect:
node: dut
transport: flash_spi
data: "9F 00 00 00"
expect: "EF 40 18 00"
Protocol fuzzing steps¶
Fuzzing steps run bounded, reproducible robustness campaigns against real hardware. They are normal suite steps and can be mixed with flash, reset, smoke, protocol regression, power, and measurement steps.
All fuzz steps support:
seed: deterministic seed. If omitted, BenchCI generates and records one.iterations: maximum number of generated cases.max_duration_ms: wall-clock bound for the step.stop_on_failure: stop at the first failing case.inter_case_delay_ms: optional delay between cases.
Artifacts include a JSONL case log under logs/fuzz/, plus fuzz summaries in results.json, evidence.json, evidence.html, and artifact manifests.
UART fuzz¶
- fuzz_uart:
node: dut
transport: console
seed: 12648430
iterations: 200
max_duration_ms: 30000
mode: ascii
min_length: 0
max_length: 64
dictionary: ["PING", "STATUS", "HELP"]
suffix: "\n"
fail_contains: "ASSERT"
CAN fuzz¶
- fuzz_can:
node: ecu
transport: canbus
seed: 49374
iterations: 200
id_min: 0x100
id_max: 0x1ff
min_data_length: 0
max_data_length: 8
Modbus fuzz¶
- fuzz_modbus:
node: gateway
transport: api
slave: 1
seed: 195936478
iterations: 200
operations:
- read_holding_registers
- write_single_register
address_min: 0
address_max: 127
count_min: 1
count_max: 4
allow_protocol_exceptions: true
See Protocol Fuzzing for replay examples using seed, step index, and case index.
Timeout behavior¶
Steps that need a timeout can either define within_ms explicitly or inherit it from:
defaults:
timeouts:
within_ms: 1000
Validation rules¶
BenchCI cross-validates the suite against the bench before execution. For example:
referenced nodes must exist
referenced transports must exist on the selected node
transport backend must match the step type
I2C steps must use
i2ctransportsSPI steps must use
spitransportsstandard CAN IDs must be
<= 0x7FF; extended CAN IDs must be<= 0x1FFFFFFFclassic CAN payloads can contain up to 8 bytes; CAN FD payloads can contain up to 64 bytes
CAN
data_maskmust contain the same number of bytes asdatafuzz actions must use compatible UART, CAN, Modbus RTU, or Modbus TCP transports and bounded iteration/duration values
referenced GPIO logical line names must exist
flashing requires the node to define a flash backend
referenced power resources must exist
referenced measurement resources must exist
fault injection must be enabled explicitly and every target must be allow-listed within the bench safety limit
This catches many configuration mistakes before hardware execution starts.
Controlled fault injection steps¶
BenchCI supports experimental power, GPIO, and malformed-UART-byte fault steps with a required UART recovery oracle:
fault_inject_power_glitchfault_inject_gpio_glitchfault_inject_uart_noise
Power and GPIO state restoration runs in a finally path. CAN bus-off is not part of this release.
See Controlled Fault Injection for complete YAML, hard limits, evidence behavior, and the real-hardware validation gate.
Power steps¶
If your bench defines a supported power resource, suites can control outlets without exposing relay-specific commands in the test logic.
Set power state¶
- power_set:
resource: dut_power
outlet: dut
state: true
Power cycle¶
- power_cycle:
resource: dut_power
outlet: dut
off_ms: 500
on_settle_ms: 2000
Expect power state¶
- power_expect:
resource: dut_power
outlet: dut
state: true
power_expect requires backend state readback or safe state tracking. Some generic serial relays only support ON/OFF commands and cannot report state.
Power steps are useful for realistic hardware reset, boot recovery, and CI smoke tests.
Measurement and metric steps¶
Measurement steps read bench-level measurement resources and can record values as metrics. The resource can be an HTTP lab-controller reading, raw SCPI query, SCPI power-supply measurement, I2C power monitor, script output, or serial sensor; the suite syntax stays the same.
Measure a resource¶
- measure:
resource: sleep_current
record_as: sleep_current_a
unit: A
expect_less_than: 0.150
The record_as name becomes a metric that can be included in results and evidence.
Assert a metric¶
- assert_metric:
name: sleep_current_a
expect_less_than_or_equal: 0.150
Metric assertions are useful when one step records a value and a later step validates it.
Supported assertion styles include:
expect_less_than: 0.150
expect_less_than_or_equal: 0.150
expect_greater_than: 3.0
expect_greater_than_or_equal: 3.0
expect_equal: 3.3
tolerance: 0.05
Use tolerance with expect_equal for analog or physical measurements where exact equality is unrealistic.