Skip to content

Replace bespoke MATLAB HW-CI with pyadi-iio mirror #4

Replace bespoke MATLAB HW-CI with pyadi-iio mirror

Replace bespoke MATLAB HW-CI with pyadi-iio mirror #4

Workflow file for this run

name: Hardware Tests
# Modelled on pyadi-iio's hardware-test.yml (same labs, same coordinator,
# same conventions). The shape is intentional: preflight probes the
# coordinator for places, intersects against `test/hw/env/*.yaml`
# committed in this repo, and emits a matrix; per-place shards acquire
# the place + run pytest + release. The bridge tests in test/hw/
# subprocess MATLAB once per supported board reference name.
permissions:
contents: read
# dorny/test-reporter creates Check Runs for the per-leg JUnit reports.
checks: write
pull-requests: write
on:
push:
branches: [master]
pull_request:
# Gated below by the `hw-test` label so PRs don't auto-grab the
# self-hosted runners.
types: [labeled, opened, synchronize, reopened]
workflow_dispatch:
env:
COORDINATOR: ${{ vars.ADI_LG_COORDINATOR }}
concurrency:
group: hw-${{ github.ref }}
cancel-in-progress: false
jobs:
preflight:
# Gate: on PRs only when the `hw-test` label is applied. Push/dispatch
# always run.
if: >-
github.event_name != 'pull_request' ||
contains(github.event.pull_request.labels.*.name, 'hw-test')
runs-on: [self-hosted, hw-coordinator]
timeout-minutes: 3
outputs:
available_nodes: ${{ steps.probe.outputs.available_nodes }}
steps:
- uses: actions/checkout@v4
- name: Bootstrap uv
run: bash .github/scripts/bootstrap-uv.sh
- id: probe
timeout-minutes: 2
env:
LABGRID_PIP: 'labgrid @ git+https://github.com/tfcollins/labgrid.git@tfcollins/plugin-support'
run: |
set -euo pipefail
HOST="${COORDINATOR%%:*}"
PORT="${COORDINATOR##*:}"
if ! timeout 10 bash -c "</dev/tcp/$HOST/$PORT" 2>/dev/null; then
echo "Coordinator $COORDINATOR unreachable — marking all nodes unavailable." >&2
echo 'available_nodes=[]' >> "$GITHUB_OUTPUT"
exit 0
fi
export PATH="$HOME/.local/bin:$PATH"
VENV="$HOME/.cache/trx-hw-ci/labgrid-venv"
if [[ ! -x "$VENV/bin/labgrid-client" ]]; then
echo "Creating labgrid venv at $VENV" >&2
uv venv --quiet "$VENV"
uv pip install --quiet --python "$VENV/bin/python" "$LABGRID_PIP"
fi
LGCLIENT="$VENV/bin/labgrid-client"
echo "Using labgrid-client: $LGCLIENT" >&2
echo "--- places -v output ---" >&2
PLACES_OUT=$(timeout 30 "$LGCLIENT" -x "$COORDINATOR" -v places 2>&1 || true)
echo "$PLACES_OUT" >&2
echo "--- end places -v output ---" >&2
python3 - "$PLACES_OUT" >> "$GITHUB_OUTPUT" <<'PY'
import json, os, re, sys
# Discovery-driven matrix (same pattern pyadi-iio uses):
# 1. Coordinator publishes places, each tagged with
# carrier=<X> / daughter-board=<Y>.
# 2. Per-place env yamls live at test/hw/env/<place>.yaml.
# A place is auto-included when its yaml exists in the
# repo.
# 3. We also emit the place's `daughter-board` tag so the
# shard can set $HW_DAUGHTER for the adi_lg_plugins
# pytest plugin's iio_hardware-marker filter.
place_re = re.compile(r"^Place '(?P<name>[^']+)':")
tags_re = re.compile(r"^\s+tags:\s+(?P<tags>.*)$")
places = {}
current = None
for line in sys.argv[1].splitlines():
m = place_re.match(line)
if m:
current = m.group("name")
places[current] = {}
continue
if current is None:
continue
m = tags_re.match(line)
if m:
for kv in m.group("tags").split(","):
kv = kv.strip()
if "=" in kv:
k, v = kv.split("=", 1)
places[current][k.strip()] = v.strip()
repo_root = os.environ.get("GITHUB_WORKSPACE", ".")
avail = []
for name, tags in sorted(places.items()):
env_yaml = f"test/hw/env/{name}.yaml"
if os.path.exists(os.path.join(repo_root, env_yaml)):
avail.append({
"place": name,
"env_remote": env_yaml,
"daughter_board": tags.get("daughter-board", ""),
"carrier": tags.get("carrier", ""),
})
else:
print(
f"skip: place {name!r} (tags={tags}) — {env_yaml} not in repo",
file=sys.stderr,
)
print("available_nodes=" + json.dumps(avail))
PY
hw-coord:
needs: preflight
if: needs.preflight.outputs.available_nodes != '[]'
strategy:
fail-fast: false
matrix:
node: ${{ fromJSON(needs.preflight.outputs.available_nodes) }}
name: hw-coord (${{ matrix.node.place }})
# Per-place runner. `hw-<place>` is the convention from
# adi-labgrid-plugins; the host running the runner must have MATLAB,
# ssh access to lab hosts as needed, and reach the coordinator.
runs-on: [self-hosted, "hw-${{ matrix.node.place }}"]
timeout-minutes: 40
concurrency:
group: hw-coord-${{ matrix.node.place }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
with:
# +adi/+common is a git submodule (ToolboxCommon, shared
# across ADI MATLAB toolboxes); MATLAB can't resolve
# adi.common.RxTx without it.
submodules: recursive
- name: Bootstrap uv
run: bash .github/scripts/bootstrap-uv.sh
- name: Install trx HW venv (adi-labgrid-plugins[kuiper] + pytest)
run: bash .github/scripts/install-trx-hw-venv.sh
- name: Acquire coordinator place
env:
LG_COORDINATOR: ${{ env.COORDINATOR }}
run: |
set -euo pipefail
LGCLIENT="$HOME/.cache/trx-hw-ci/venv/bin/labgrid-client"
"$LGCLIENT" -x "$LG_COORDINATOR" -p "${{ matrix.node.place }}" acquire
- name: Run coordinator-mode tests
env:
LG_COORDINATOR: ${{ env.COORDINATOR }}
LG_ENV: ${{ github.workspace }}/${{ matrix.node.env_remote }}
# HW_DAUGHTER drives adi_lg_plugins.pytest_plugin's
# iio_hardware-marker filter: tests whose
# @pytest.mark.iio_hardware([...]) doesn't include this chip
# are auto-skipped.
HW_DAUGHTER: ${{ matrix.node.daughter_board }}
HW_CARRIER: ${{ matrix.node.carrier }}
# MATLAB binary; override per-lab via vars.MATLAB_BIN.
MATLAB_BIN: ${{ vars.MATLAB_BIN || 'matlab' }}
run: |
set -euo pipefail
# The bridge tests in test/hw/test_*.py subprocess MATLAB.
# Bridge skips when iio_uri unresolved (conftest no-op);
# marker filter skips tests whose chip != $HW_DAUGHTER.
"$HOME/.cache/trx-hw-ci/venv/bin/pytest" \
-v -s -p no:cacheprovider \
test/hw/ \
--junitxml=junit-hw-coord-${{ matrix.node.place }}.xml
- name: Release coordinator place
if: always()
env:
LG_COORDINATOR: ${{ env.COORDINATOR }}
run: |
set -euo pipefail
LGCLIENT="$HOME/.cache/trx-hw-ci/venv/bin/labgrid-client"
"$LGCLIENT" -x "$LG_COORDINATOR" -p "${{ matrix.node.place }}" release || true
- name: Publish hw-coord test results to PR
if: always()
uses: dorny/test-reporter@v1
with:
name: Tests (hw-coord ${{ matrix.node.place }})
path: junit-hw-coord-${{ matrix.node.place }}.xml
reporter: java-junit
fail-on-error: false
- name: Upload JUnit + MATLAB artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: junit-hw-coord-${{ matrix.node.place }}
path: |
junit-hw-coord-${{ matrix.node.place }}.xml
${{ matrix.node.daughter_board && format('*{0}*_HWTestResults.xml', matrix.node.daughter_board) || '*_HWTestResults.xml' }}
failures.txt
retention-days: 14
if-no-files-found: ignore
publish-pr-test-summary:
name: PR Test Summary
needs: [hw-coord]
if: always() && github.event_name == 'pull_request' && needs.hw-coord.result != 'skipped'
runs-on: [self-hosted, hw-coordinator]
steps:
- uses: actions/download-artifact@v4
with:
pattern: junit-hw-*
path: junit
merge-multiple: true
- uses: EnricoMi/publish-unit-test-result-action@v2
with:
files: 'junit/**/*.xml'
check_name: 'Hardware Test Results'
comment_mode: always
report_individual_runs: true