Quickstart¶
Run your first real hardware test with BenchCI.
This guide uses the simplest useful path:
firmware artifact
↓
benchci run
↓
flash board
↓
read UART output
↓
write structured results
By the end, you will have a local BenchCI run that flashes firmware, validates UART output, and stores logs under benchci-results/.
What you need¶
Python 3.11+
BenchCI installed
a board connected to your machine, for example an STM32 NUCLEO
a supported flashing tool, for example OpenOCD
firmware that prints expected UART output
a serial connection to the board
Install BenchCI first:
pip install benchci
Log in:
benchci login
Check your active account and workspace:
benchci whoami
Step 1 — Create starter YAML¶
Start with a preset instead of writing both YAML files from scratch:
benchci init --list-presets
benchci init --preset flash-uart
benchci init writes bench.yaml and suite.yaml, validates them, and prints the next commands to run. It refuses to overwrite existing files unless you pass --force.
Use --yes in scripts or CI bootstrap flows to skip interactive prompts and accept the preset defaults:
benchci init --preset flash-uart --yes --bench-output bench.yaml --suite-output suite.yaml
You can also create the same starter files from the dashboard Config Builder, then copy or download them into your repository.
Step 2 — Inspect your machine¶
Run doctor so you know which ports, USB devices, tools, and GPIO chips BenchCI can see:
benchci doctor
After creating bench.yaml, cross-check it against the current machine:
benchci doctor --bench bench.yaml
This is especially useful for finding the correct UART port, checking whether OpenOCD/J-Link/esptool is installed, and confirming GPIO device paths such as /dev/gpiochip0.
Step 3 — Review bench.yaml¶
bench.yaml describes the physical hardware.
Example:
version: "1"
bench:
name: nucleo_uart_demo
description: Simple single-node UART bench
defaults:
node: dut
timeouts:
within_ms: 1000
nodes:
dut:
kind: mcu
role: target
flash:
backend: openocd
interface_cfg: interface/stlink.cfg
target_cfg: target/stm32f4x.cfg
reset:
method: openocd
transports:
console:
backend: uart
port: /dev/ttyUSB0
baud: 115200
timeout_ms: 100
artifacts:
root_dir: benchci-results
per_node_dirs: true
This defines:
one node named
dutOpenOCD flashing
OpenOCD reset
one UART transport named
consoleartifact output under
benchci-results/
Adjust these fields for your board:
target_cfgUART
portUART
baudoptional probe serials or extra flash arguments
Step 4 — Review suite.yaml¶
suite.yaml defines the test logic.
Example:
version: "1"
suite:
name: firmware_smoke
description: Flash firmware and validate boot logs
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
This suite checks that:
the firmware prints
[BOOT] OKthe device responds to
PINGwithPONG
Optional: add power control¶
After your first UART-only run works, you can make the suite closer to real CI by adding a bench-level power resource.
In bench.yaml:
resources:
dut_power:
kind: power_controller
driver:
type: gpio_power
chip: /dev/gpiochip0
outlets:
main: 17
active_high: true
initial_state: false
In suite.yaml:
- power_cycle:
resource: dut_power
outlet: main
off_ms: 1000
on_settle_ms: 2000
This keeps the suite focused on intent while the bench file describes whether power is GPIO-backed, HTTP-backed, or driven by a serial relay command map.
Optional: add a measurement¶
Measurement resources let you record physical behavior such as current or voltage.
For example, a lab controller can expose a measured value over HTTP:
resources:
sleep_current:
kind: measurement
driver:
type: http_measurement
quantity: current
url: "http://192.168.1.60/measurements/sleep_current"
value_field: value
unit_field: unit
For a real SCPI-capable power supply or meter, use a SCPI measurement resource. TCP/IP example:
resources:
supply_current:
kind: measurement
driver:
type: scpi_power_supply_measurement
address: tcp://127.0.0.1:5025
quantity: current
timeout_ms: 1000
Serial/RS232 example:
resources:
supply_current:
kind: measurement
driver:
type: scpi_power_supply_measurement
preset: owon_sp
address: serial:///dev/ttyUSB0
quantity: current
timeout_ms: 1000
baudrate: 9600
USB/VISA-style example:
resources:
supply_current:
kind: measurement
driver:
type: scpi_measurement
address: USB0::0x1234::0x5678::INSTR
query: "MEAS:CURR?"
quantity: current
unit: A
timeout_ms: 1000
Use benchci measure to debug one measurement resource before running a full suite:
benchci measure --bench bench.yaml supply_current
In suite.yaml:
- measure:
resource: supply_current
record_as: sleep_current_a
unit: A
expect_less_than: 0.150
The measured value can appear in results, evidence reports, CLI summaries, and dashboard run detail where supported.
Optional: add bounded protocol fuzzing¶
After your smoke/regression checks are stable, add a short fuzz step to exercise parser robustness on real hardware.
UART example:
- fuzz_uart:
node: dut
transport: console
seed: 12648430
iterations: 50
max_duration_ms: 10000
mode: ascii
min_length: 0
max_length: 64
suffix: "\n"
fail_contains: "ASSERT"
BenchCI records the seed, step index, first failing case, and a JSONL case log in logs/fuzz/, so failures can be replayed. See Protocol Fuzzing before adding fuzzing to CI gates.
Optional: add traceability¶
For a first run, a simple suite is enough. When you want evidence reports to show requirement or risk coverage, add optional traceability fields:
suite:
name: firmware_smoke
description: Flash firmware and validate boot logs
requirement_ids:
- REQ-BOOT-001
risk_ids:
- RISK-BOOT-001
tags:
- smoke
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
Traceability fields are optional. Use them when you want the run to support release or QA evidence workflows.
Step 5 — Validate configuration¶
Validate without touching hardware:
benchci validate --bench bench.yaml --suite suite.yaml
If validation fails, fix the config before running on the device.
Step 6 — Run locally¶
benchci run \
--bench bench.yaml \
--suite suite.yaml \
--artifact build/fw.elf \
--verbose
BenchCI will:
load and validate
bench.yamlload and validate
suite.yamlflash the artifact unless
--skip-flashis usedstart only the required transports and GPIO resources
execute the suite steps
write logs and structured results
Step 7 — Inspect results¶
BenchCI writes artifacts into benchci-results/ by default. Use --results-dir to choose a different result root for a run, or set artifacts.root_dir in bench.yaml for the local-run default.
Typical structure:
benchci-results/
└── 20260328-142200/
├── results.json
├── evidence.json
├── evidence.html
├── manifest.json
├── metadata.json
├── inputs/
│ ├── bench.yaml
│ └── suite.yaml
└── logs/
├── nodes/
│ └── dut/
│ ├── flash.log
│ └── transport-console.log
└── fuzz/
└── uart_protocol_fuzz-step1-uart.jsonl
results.json contains the high-level outcome.
Per-node logs contain flash, transport, GPIO, power, or protocol logs depending on the bench and suite. Fuzz case logs are stored under logs/fuzz/ when a suite uses fuzz_uart, fuzz_can, or fuzz_modbus.
Common first-run problems¶
UART port is wrong¶
Check available ports:
ls /dev/ttyUSB* /dev/ttyACM*
Update bench.yaml.
Permission denied on serial port¶
On Linux, add your user to the serial group used by your distribution, often dialout:
sudo usermod -aG dialout $USER
Then log out and back in.
Flash tool not found¶
Install the required flash tool, for example:
sudo apt-get install -y openocd
Boot message not found¶
Increase timeout or verify the firmware really prints the expected text:
within_ms: 5000
Next step: connect CI¶
Once local execution works, move to the end-to-end CI flow:
Evidence outputs¶
BenchCI writes both machine-readable and human-readable evidence:
results.json— execution summary, test results, structured failures, and per-test traceabilityevidence.json— firmware hash, Git/CI metadata, bench/suite hashes, traceability, fuzz summaries, and artifact listevidence.html— human-readable evidence reportmetadata.json— supporting run metadatainputs/bench.yamlandinputs/suite.yaml— snapshots of the exact inputs used
Open evidence.html after the run when you want a report that is easier to share with a teammate, QA reviewer, or release owner.
Recommended pre-run checks¶
Before the first real run, use the readiness workflow:
benchci validate --bench bench.yaml --suite suite.yaml
benchci benches self-test --bench bench.yaml --open-hardware --log-dir bench-health
benchci run --bench bench.yaml --suite suite.yaml --artifact build/fw.elf --dry-run-plan
Then run the actual suite:
benchci run --bench bench.yaml --suite suite.yaml --artifact build/fw.elf
Use this order to separate problems:
validate -> YAML or suite/bench compatibility issue
self-test -> bench infrastructure issue
dry-run plan -> execution plan surprise
run -> firmware, test logic, or hardware behavior issue
See Validation, Self-Test, and Dry-Run Planning for the full workflow.