Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3435cec
feat(ci): GHA hardware-test workflow alongside JenkinsfileHW
tfcollins May 14, 2026
3c3aec2
fix(test/hw): satisfy black 19.10b0 line-split in conftest
tfcollins May 14, 2026
e45fe52
fix(hw): wire smoke tests to real coordinator places
tfcollins May 14, 2026
135faaf
fix(hw): use venv labgrid-client binary, fail instead of skip
tfcollins May 14, 2026
e0d811d
fix(hw/conftest): parse Python-repr 'address' format from labgrid show
tfcollins May 14, 2026
d2eabbf
feat(hw-ci): migrate to hw-matrix.yml@v2 (discovery-driven)
tfcollins May 14, 2026
ea33ead
ci: retrigger hardware-test on labgrid-plugins runner-label fix
tfcollins May 14, 2026
904b87d
ci: retrigger after labgrid-plugins REST→CLI fallback fix
tfcollins May 14, 2026
8594575
ci: retrigger after marker-harvest plugin-autoload fix
tfcollins May 14, 2026
5f4d8e0
ci: retrigger after AST marker harvest
tfcollins May 14, 2026
a142f06
ci: retrigger after boot-strategy tag added to mini2/bq/nemo
tfcollins May 14, 2026
9ae4fd2
fix(hw-ci): pytest uses --lg-env, not --lg-config
tfcollins May 14, 2026
fc4fbea
ci: retrigger after HW_DAUGHTER/HW_CARRIER narrowing fix
tfcollins May 14, 2026
128d641
ci: retrigger after v2 acquire+boot+release added
tfcollins May 14, 2026
c809f08
fix(hw-ci): install adi-labgrid-plugins[kuiper] for pytsk3
tfcollins May 14, 2026
26a2068
ci: retrigger after power-driver tag + wait-for-iiod
tfcollins May 14, 2026
c1f85bb
ci: retrigger after HomeAssistantPowerDriver class-name fix
tfcollins May 14, 2026
465988e
ci: retrigger after bq/nemo retagged to BootFPGASoCSSH
tfcollins May 14, 2026
9b5fe09
ci: retrigger after BootFPGASoCSSH SSHDriver wiring fix
tfcollins May 14, 2026
03301b8
ci: retrigger after serial-based IP self-resolve
tfcollins May 14, 2026
768e612
fix(hw-ci): pass DUT URI explicitly to pytest; drop conftest auto-dis…
tfcollins May 14, 2026
98946a0
ci: retrigger after nemo ser2net install + UART connected
tfcollins May 14, 2026
ed4497e
ci: retrigger after dropping strategy-driven boot for nemo+bq
tfcollins May 14, 2026
15808a6
ci: retrigger after microcom installed on bq+nemo runners
tfcollins May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/hardware-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Hardware Tests (GHA)

# Discovery-driven HW pipeline (v2). Lab coordinator advertises live
# places via `tags:` (carrier, daughter-board, boot-strategy); this
# repo's tests carry `@pytest.mark.iio_hardware([…])` markers; the
# reusable workflow intersects the two and builds the matrix.
#
# No `.github/hw-nodes.json`, no `test/hw/env/*.yaml`. The env yaml is
# rendered per-shard from the place tags by adi-labgrid-plugins.
#
# Triggered by:
# * workflow_dispatch (manual)
# * PRs labeled `hw-test` — keeps every push from burning lab time
# * Nightly cron at 08:00 UTC

permissions:
contents: read
checks: write
pull-requests: write

on:
workflow_dispatch:
pull_request:
types: [labeled, opened, synchronize, reopened]
schedule:
- cron: "0 8 * * *"

jobs:
hw:
if: >-
github.event_name != 'pull_request' ||
contains(github.event.pull_request.labels.*.name, 'hw-test')
# TODO: change ref to @v2 after tfcollins/labgrid-plugins#16 merges
# and the v2 tag is cut. Pinned to the feature branch for now so
# the discovery-driven flow can be exercised end-to-end before tag.
uses: tfcollins/labgrid-plugins/.github/workflows/hw-matrix-v2.yml@feature/hw-matrix-v2-discovery
with:
marker_filter: iio_hardware
test_root: test/hw
venv_install_cmd: |
uv pip install --quiet --python "$VENV_DIR/bin/python" \
-r requirements_dev.txt
uv pip install --quiet --python "$VENV_DIR/bin/python" -e .
uv pip install --quiet --python "$VENV_DIR/bin/python" \
"labgrid @ git+https://github.com/tfcollins/labgrid.git@81c35eda6436af63d3acf7f8b98893abae0bbbca"
# adi-labgrid-plugins supplies BOTH the iio_hardware/iio_carrier
# marker plugin (auto-registered via pytest11 entry point) AND
# the adi-lg-hw-ci CLI the reusable workflow shells out to.
# `[kuiper]` pulls in pytsk3, required by KuiperDLDriver
# for extracting boot files at strategy init time.
uv pip install --quiet --python "$VENV_DIR/bin/python" \
"adi-labgrid-plugins[kuiper] @ git+https://github.com/tfcollins/labgrid-plugins.git@feature/hw-matrix-v2-discovery"
pytest_cmd_template: >-
"$VENV_DIR/bin/pytest" -v test/hw
-m "$MARKER_FILTER"
--lg-env "$LG_ENV"
--iio-uri-override "$IIO_URI_OVERRIDE"
--junitxml="$JUNIT"
artifact_glob: |
junit-hw-*.xml
**/*.log
prism_project: pyadi-iio
secrets: inherit
89 changes: 89 additions & 0 deletions test/hw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# HW smoke tests (`test/hw/`)

Minimal pyadi-iio tests that run against real boards held by a labgrid
coordinator. Wired into CI by `.github/workflows/hardware-test.yml`,
which calls the shared **discovery-driven** `hw-matrix-v2.yml` reusable
workflow in `tfcollins/labgrid-plugins`.

This suite is **independent** of the historic `test/test_*.py` files
under `test/` (which use the `pytest-libiio` plugin and a host_map). It
exists so the new GH Actions HW pipeline has something to run while the
existing suite stays on Jenkins during the migration.

## How the matrix is built (v2 model)

The reusable workflow doesn't read any per-repo manifest. Instead, on
every run it:

1. Queries the coordinator for live places (each tagged with `carrier`,
`daughter-board`, `boot-strategy`).
2. Runs `pytest --collect-only -m iio_hardware` against this repo and
harvests every test's `@pytest.mark.iio_hardware([...])` and optional
`@pytest.mark.iio_carrier([...])` markers.
3. Builds one matrix shard per (place, daughter-board) intersection —
per-shard pytest runs `pytest -m "iio_hardware and <daughter>"`.

If a place isn't live, its shard doesn't appear. If we mark a test for
hardware that no live place advertises, the workflow emits a clear
"no overlap right now" annotation instead of failing.

## Layout

```
test/hw/
├── conftest.py # iio_uri fixture: labgrid place → ip:URI
├── test_ad9081_smoke.py # @pytest.mark.iio_hardware(["ad9081"])
├── test_ad9371_smoke.py # @pytest.mark.iio_hardware(["adrv9371", "ad9371"])
└── test_adrv9009_smoke.py # @pytest.mark.iio_hardware(["adrv9009"])
```

No per-place env yamls. The reusable workflow renders the labgrid env
yaml on the fly from each place's `boot-strategy` tag.

## How a test resolves a URI

`conftest.py::iio_uri` (session-scoped) requires the URI to be passed
**explicitly** via `--iio-uri-override <uri>` (the `IIO_URI_OVERRIDE`
env var is honoured as a default). The conftest deliberately refuses
to discover a URI from labgrid or the coordinator — making the URI
explicit means CI can never accidentally talk to a stray board that
happens to be on the lab LAN.

The CI workflow handles URI resolution itself: it acquires the place,
boots the board, captures the live serial console, parses eth0's
DHCP-assigned address, and passes `--iio-uri-override ip:<real-ip>`
to pytest. Tests skip cleanly when no URI is provided.

## Running locally

```bash
# Without labgrid (point at an iiod yourself)
pytest -v test/hw/test_ad9081_smoke.py --iio-uri-override ip:10.0.0.23

# Against the lab coordinator (acquires + shows the place)
LG_COORDINATOR=10.0.0.41:20408 LG_PLACE=mini2 \
pytest -v test/hw/test_ad9081_smoke.py
```

## Adding a new board family

1. Write `test/hw/test_<board>_smoke.py`. Take the `iio_uri` fixture,
instantiate the right `adi.<class>(uri=iio_uri)`, exercise one or
two attributes plus a buffer.
2. Decorate each test with `@pytest.mark.iio_hardware([<board>])`. Use
the same name the coordinator's `daughter-board` tag uses (or a list
that includes aliases).
3. (Optional) Add `@pytest.mark.iio_carrier(["zcu102"])` if the test
only works on a specific carrier.
4. (Lab admin) Ensure the live coordinator has at least one place
tagged `daughter-board=<board>` and `boot-strategy=<one of the
adi_lg_plugins.strategies class names>`.

No workflow edit needed. The discover step picks the new tests up on
the next run.

## Running alongside Jenkins

The existing `JenkinsfileHW` is unchanged. Both pipelines run on every
PR for at least 2 weeks; if results diverge, open a tracking issue
before disabling either one.
55 changes: 55 additions & 0 deletions test/hw/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""HW-test conftest.

Resolves a libiio URI for tests under ``test/hw/``. The URI is **always**
supplied explicitly via ``--iio-uri-override``; there is no implicit
labgrid/coordinator lookup. The caller (CI workflow or local user) is
responsible for naming the exact board to talk to — this avoids any
chance of pytest connecting to a stray board that happens to be on the
network.

Used by the GH Actions hardware-test workflow, which acquires the
place, boots it, reads eth0's IP from the live serial console, and
passes ``--iio-uri-override ip:<real-ip>`` to pytest.

Sibling ``test/conftest.py`` (the historic libiio-plugin-driven suite
under ``test/``) is untouched; this conftest only applies to tests
collected under ``test/hw/``.
"""

from __future__ import annotations

import os

import pytest


def pytest_addoption(parser):
g = parser.getgroup("hw")
g.addoption(
"--iio-uri-override",
default=os.environ.get("IIO_URI_OVERRIDE", ""),
help=(
"libiio URI for the DUT (e.g. ip:10.0.0.211). Required for tests "
"under test/hw/. Defaults to the IIO_URI_OVERRIDE env var when "
"unset on the CLI."
),
)


@pytest.fixture(scope="session")
def iio_uri(request) -> str:
"""Return the explicitly-named DUT URI.

Tests are skipped if no URI was passed — the conftest deliberately
refuses to auto-discover, so an unrelated board on the LAN can't be
poked by accident.
"""
uri = (request.config.getoption("--iio-uri-override") or "").strip()
if not uri:
pytest.skip(
"no DUT URI provided. Pass --iio-uri-override ip:<host> on the "
"pytest CLI (the reusable hw-matrix-v2 workflow does this "
"automatically after reading eth0 from the booted board's "
"serial console)."
)
return uri
38 changes: 38 additions & 0 deletions test/hw/test_ad9081_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Smoke test: open an AD9081 over IIO and read its context.

Discovered by labgrid-plugins hw-matrix.yml@v2 — any coordinator place
tagged ``daughter-board=ad9081`` will fan a shard of this file out.

Local manual run:
pytest -v test/hw/test_ad9081_smoke.py --iio-uri-override ip:10.0.0.23
"""

from __future__ import annotations

import adi
import pytest


@pytest.mark.iio_hardware(["ad9081"])
def test_context_attrs(iio_uri):
"""The AD9081 driver enumerates context attrs (hw_model, etc.)."""
sdr = adi.ad9081(uri=iio_uri)
try:
assert sdr.ctx is not None
assert sdr.ctx.attrs, f"empty context attrs at {iio_uri}"
finally:
del sdr


@pytest.mark.iio_hardware(["ad9081"])
def test_rx_buffer(iio_uri):
"""Capture one small RX buffer end-to-end."""
sdr = adi.ad9081(uri=iio_uri)
try:
sdr.rx_buffer_size = 1024
data = sdr.rx()
assert data is not None
# ad9081 RX is multi-channel: list of np arrays. At minimum non-empty.
assert len(data) > 0
finally:
del sdr
38 changes: 38 additions & 0 deletions test/hw/test_ad9371_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Smoke test: open an AD9371 / ADRV9371 over IIO.

Discovered by labgrid-plugins hw-matrix.yml@v2 — any coordinator place
tagged ``daughter-board=adrv9371`` (or the legacy ``ad9371`` alias)
will fan a shard of this file out.

Local manual run:
pytest -v test/hw/test_ad9371_smoke.py --iio-uri-override ip:<bq-dut-ip>
"""

from __future__ import annotations

import adi
import pytest


@pytest.mark.iio_hardware(["adrv9371", "ad9371"])
def test_context_attrs(iio_uri):
"""The AD9371 driver enumerates context attrs."""
sdr = adi.ad9371(uri=iio_uri)
try:
assert sdr.ctx is not None
assert sdr.ctx.attrs, f"empty context attrs at {iio_uri}"
finally:
del sdr


@pytest.mark.iio_hardware(["adrv9371", "ad9371"])
def test_rx_buffer(iio_uri):
"""Capture one small RX buffer end-to-end."""
sdr = adi.ad9371(uri=iio_uri)
try:
sdr.rx_buffer_size = 1024
data = sdr.rx()
assert data is not None
assert len(data) > 0
finally:
del sdr
37 changes: 37 additions & 0 deletions test/hw/test_adrv9009_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Smoke test: open an ADRV9009 over IIO.

Discovered by labgrid-plugins hw-matrix.yml@v2 — any coordinator place
tagged ``daughter-board=adrv9009`` will fan a shard of this file out.

Local manual run:
pytest -v test/hw/test_adrv9009_smoke.py --iio-uri-override ip:<nemo-dut-ip>
"""

from __future__ import annotations

import adi
import pytest


@pytest.mark.iio_hardware(["adrv9009"])
def test_context_attrs(iio_uri):
"""The ADRV9009 driver enumerates context attrs."""
sdr = adi.adrv9009(uri=iio_uri)
try:
assert sdr.ctx is not None
assert sdr.ctx.attrs, f"empty context attrs at {iio_uri}"
finally:
del sdr


@pytest.mark.iio_hardware(["adrv9009"])
def test_rx_buffer(iio_uri):
"""Capture one small RX buffer end-to-end."""
sdr = adi.adrv9009(uri=iio_uri)
try:
sdr.rx_buffer_size = 1024
data = sdr.rx()
assert data is not None
assert len(data) > 0
finally:
del sdr
Loading