External Test Bridge Workflow¶
If your team already has hardware tests — a pytest suite, a Robot Framework run, a labgrid nightly, a custom test harness — you do not need to rewrite them to get structured evidence, dashboards, and release traceability from BenchCI.
Any test runner that can emit a JUnit XML or CTRF JSON file works. BenchCI ingests the results, captures DUT identity and firmware provenance, and produces the same evidence trail as a native BenchCI run.
How it works¶
Your existing test runner → JUnit XML / CTRF JSON
↓
benchci runs create-external
↓
BenchCI run record (DUT identity, firmware SHA-256, results)
↓
Release bundle + sign-off workflow
No bench agent required. No changes to your existing tests.
Step 1: Run your tests and produce a JUnit XML¶
Most test frameworks support JUnit XML output out of the box:
# pytest
pytest --junitxml=results.xml tests/
# Robot Framework
robot --xunit results.xml tests/
# Unity (via test runner script)
./run_tests.sh --junit results.xml
# any other runner that supports JUnit output
your-test-runner --junit results.xml
Step 2: Create an external BenchCI run¶
benchci runs create-external \
--name "hardware suite — rev-b" \
--junit-xml results.xml \
--framework pytest \
--source your-test-runner \
--artifacts artifacts/ \
--firmware-sha256 "$(sha256sum firmware.elf | cut -d' ' -f1)" \
--firmware-filename firmware.elf \
--dut-hardware-revision rev-b \
--dut-serial-number "$DUT_SERIAL" \
--dut-asset-id "$DUT_ASSET_ID"
--source is a free-form label. Use whatever identifies your test runner: pytest, labgrid, robot, unity, ctest, etc.
By default, the run is created in the active workspace for the current CLI session. If a user or CI token can access multiple workspaces, pass --workspace-id <WORKSPACE_ID> to create the external run in a specific workspace.
This returns a run_id. The run is immediately in terminal state (done or failed based on your JUnit results) and has full DUT identity captured.
Attaching coverage¶
If your run also produces LCOV coverage data (via gcovr, lcov, OpenCppCoverage, or similar):
benchci coverage upload coverage.info --run-id $RUN_ID
Step 3: Attach the run to a release bundle¶
benchci releases create "v1.2.0-rc1" --runs $RUN_ID
Step 4: Submit for review¶
Submit the bundle for review from the BenchCI dashboard. A workspace admin can then approve or reject it. The approval creates a tamper-evident hash chain entry linking your test evidence to the sign-off decision.
GitHub Actions example¶
jobs:
hardware-ci:
runs-on: [self-hosted, hardware]
steps:
- uses: actions/checkout@v4
- name: Run hardware tests
run: |
pytest --junitxml=results.xml tests/
mkdir -p artifacts && cp results.xml artifacts/
continue-on-error: true
- name: Report to BenchCI
if: always()
env:
BENCHCI_TOKEN: ${{ secrets.BENCHCI_TOKEN }}
run: |
RUN_JSON=$(benchci runs create-external \
--name "hw-suite ${{ github.ref_name }}" \
--junit-xml results.xml \
--framework pytest \
--source pytest \
--artifacts artifacts/ \
--workspace-id "${{ vars.BENCHCI_WORKSPACE_ID }}" \
--firmware-sha256 "${{ needs.build.outputs.firmware_sha256 }}" \
--firmware-filename firmware.elf \
--dut-hardware-revision "${{ vars.DUT_HW_REV }}" \
--json)
RUN_ID=$(printf '%s' "$RUN_JSON" | jq -r .run_id)
echo "BENCHCI_RUN_ID=$RUN_ID" >> $GITHUB_ENV
- name: Create release bundle on main
if: github.ref == 'refs/heads/main'
env:
BENCHCI_TOKEN: ${{ secrets.BENCHCI_TOKEN }}
run: |
benchci releases create "v$(cat VERSION)" --runs $BENCHCI_RUN_ID
Tips¶
Keep your test runner unchanged. BenchCI only reads the JUnit XML output — it does not interact with your test runner’s configuration or fixtures.
Multiple DUT configurations. Run
create-externalonce per DUT and add all resulting run IDs to the same release bundle for a multi-board evidence package.Artifact downloads. Local files and directories passed with
--artifactor--artifactsare uploaded with SHA256 metadata and can be downloaded from the BenchCI dashboard. URL artifacts stay as external links.Requirement traceability. BenchCI reads
requirement_idsandtest_case_idfrom<property>elements inside a JUnit<testcase>. Add them to your JUnit output (via a pytest hook, a post-processing script, or your runner’s property API) and BenchCI will show requirement coverage in the release bundle:<testcase name="test_boot"> <properties> <property name="requirement_ids" value="REQ-BOOT,REQ-PWR"/> <property name="test_case_id" value="TC-001"/> </properties> </testcase>
Mixing with native runs. A release bundle can contain both native BenchCI agent runs and external runs. The coverage matrix and requirement coverage view span all of them.