Skip to content

Commit e0ab630

Browse files
committed
Fix twister tests on local run
1 parent e773de9 commit e0ab630

7 files changed

Lines changed: 142 additions & 16 deletions

File tree

‎.github/workflows/firmware-ci.yml‎

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ env:
2828

2929
jobs:
3030
firmware-qemu:
31-
runs-on: ubuntu-24.04
31+
runs-on: ubuntu-22.04
3232
timeout-minutes: 90
3333
steps:
3434
- name: Checkout
@@ -133,55 +133,111 @@ jobs:
133133
west twister -T soc_firmware/tests -T soc_firmware/app \
134134
-p qemu_riscv64 --inline-logs -v -O twister-out
135135
136-
- name: Dump twister handler logs on failure
136+
- name: Dump twister logs on failure
137137
if: failure()
138138
run: |
139-
find twister-out -name "handler.log" -o -name "qemu.log" | while read -r f; do
140-
echo "===== $f ====="
139+
# Twister keeps the emulator's own output in qemu.stdout /
140+
# qemu.stderr inside each build directory — NOT in handler.log,
141+
# which only carries the guest's serial console. A "unexpected
142+
# eof" verdict means the `ninja run` target died before the
143+
# harness saw its end marker, and the reason is in qemu.stderr.
144+
for f in $(find twister-out \( -name "handler.log" \
145+
-o -name "qemu.stdout" \
146+
-o -name "qemu.stderr" \
147+
-o -name "device.log" \) -size +0); do
148+
echo "::group::$f"
141149
cat "$f"
150+
echo "::endgroup::"
142151
done
143152
144-
- name: QEMU smoke test on failure
145-
# Re-runs the twister-built image by hand so QEMU's own stderr
146-
# (bad option, missing lib, wrong binary) lands in the job log —
147-
# twister swallows it.
153+
# Per-configuration verdicts, so the failing scenario is named
154+
# even when the console stayed empty.
155+
if [ -f twister-out/twister.json ]; then
156+
echo "===== scenario status ====="
157+
jq -r '.testsuites[] | "\(.status)\t\(.name)\t\(.reason // "")"' \
158+
twister-out/twister.json || true
159+
fi
160+
161+
- name: Re-run a failed configuration by hand
162+
# Runs the exact `ninja run` target twister invoked, with its
163+
# output on stdout instead of in a file nobody reads. Catches the
164+
# cases twister reports only as "unexpected eof": a re-configure
165+
# error, a missing emulator binary, rejected machine arguments.
148166
if: failure()
149167
run: |
168+
echo "QEMU_BIN=$QEMU_BIN"
150169
"$QEMU_BIN" --version || true
151-
ELF=$(find twister-out -name zephyr.elf | head -1)
152-
echo "ELF: $ELF"
153-
timeout 15 "$QEMU_BIN" -machine virt -bios none -m 256 \
154-
-cpu rv64i,i=on,m=on,a=on,f=on,d=on,c=on,zicsr=on,zifencei=on,zicntr=on,pmp=on,u=on \
155-
-global virtio-mmio.force-legacy=false -nographic \
156-
-netdev user,id=n1 -device virtio-net-device,netdev=n1,bus=virtio-mmio-bus.0 \
157-
-kernel "$ELF" 2>&1 | head -40 || true
170+
171+
DIR=$(jq -r '[.testsuites[] | select(.status != "passed")][0].name // empty' \
172+
twister-out/twister.json 2>/dev/null)
173+
echo "first failing scenario: ${DIR:-<unknown>}"
174+
175+
# Map the scenario back to its build directory; fall back to any
176+
# build directory that has a zephyr.elf.
177+
BUILD=$(find twister-out -name build.ninja -printf '%h\n' | head -1)
178+
if [ -n "$DIR" ]; then
179+
CAND=$(find twister-out -type d -name "$(basename "$DIR")" | head -1)
180+
[ -n "$CAND" ] && BUILD="$CAND"
181+
fi
182+
echo "build dir: $BUILD"
183+
184+
# `run` needs a reader on the fifo or QEMU blocks; give it one.
185+
mkfifo "$BUILD/qemu-fifo.in" "$BUILD/qemu-fifo.out" 2>/dev/null || true
186+
timeout 30 ninja -C "$BUILD" run < /dev/null 2>&1 | tail -60 || true
158187
159188
- name: Upload twister artifacts
160189
# Skipped under act: no artifact service (ACTIONS_RUNTIME_TOKEN).
161190
if: ${{ always() && !env.ACT }}
162191
uses: actions/upload-artifact@v4
163192
with:
164193
name: twister-out
165-
path: twister-out/**/handler.log
194+
path: |
195+
twister-out/twister.json
196+
twister-out/twister.log
197+
twister-out/**/handler.log
198+
twister-out/**/qemu.stdout
199+
twister-out/**/qemu.stderr
200+
twister-out/**/build.log
166201
if-no-files-found: ignore
167202

168203
- name: Build QEMU image (TAP networking)
169204
run: west build -b qemu_riscv64 -d soc_firmware/app/build_qemu soc_firmware/app
170205

206+
# ---- NMOS conformance: needs a host TAP device ----
207+
# Creating "zeth" requires NET_ADMIN, which act's container does not
208+
# have by default ("Failed to send flush request: Operation not
209+
# permitted"). The steps below are therefore skipped under act; to
210+
# run them locally anyway, give the container the capability:
211+
#
212+
# act --container-options "--cap-add=NET_ADMIN --device=/dev/net/tun"
213+
#
214+
# Everything up to and including twister runs under act unchanged.
171215
- name: TAP interface for QEMU
216+
if: ${{ !env.ACT }}
172217
run: sudo scripts/ci/qemu-net-setup.sh
173218

174219
- name: Fetch nmos-testing
220+
if: ${{ !env.ACT }}
175221
run: git clone --depth 1 https://github.com/AMWA-TV/nmos-testing "$RUNNER_TEMP/nmos-testing"
176222

177223
- name: NMOS conformance (IS-04 / IS-05 / IS-08)
224+
if: ${{ !env.ACT }}
178225
run: |
179226
NMOS_TESTING="$RUNNER_TEMP/nmos-testing" \
180227
BUILD_DIR="$PWD/soc_firmware/app/build_qemu" \
181228
RESULTS_DIR="$PWD/nmos-results" \
182229
QEMU="$QEMU_BIN" \
183230
scripts/ci/run-nmos-conformance.sh
184231
232+
- name: Note skipped steps (act)
233+
# So a green local run is not mistaken for full coverage.
234+
if: ${{ env.ACT }}
235+
run: |
236+
echo "Skipped under act: TAP setup + NMOS conformance (no NET_ADMIN)."
237+
echo "Built and ran: twister unit suites + QEMU boot test."
238+
echo "To include NMOS locally, re-run act with:"
239+
echo " --container-options \"--cap-add=NET_ADMIN --device=/dev/net/tun\""
240+
185241
- name: Upload NMOS results
186242
# Skipped under act: no artifact service (ACTIONS_RUNTIME_TOKEN).
187243
if: ${{ always() && !env.ACT }}

‎scripts/ci/qemu-net-setup.sh‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ if [ "${1:-up}" = "down" ]; then
2626
exit 0
2727
fi
2828

29+
# Creating and configuring a TAP device needs CAP_NET_ADMIN. Containers
30+
# (act, plain docker) usually lack it, and the failure mode further down
31+
# is a bare "Operation not permitted" from ip(8) — say so up front.
32+
ip link del "${IFACE}-probe" 2>/dev/null || true
33+
if ! ip tuntap add "${IFACE}-probe" mode tap 2>/dev/null; then
34+
echo "error: cannot create TAP devices (CAP_NET_ADMIN missing?)." >&2
35+
echo " In a container, add: --cap-add=NET_ADMIN --device=/dev/net/tun" >&2
36+
exit 1
37+
fi
38+
ip link del "${IFACE}-probe"
39+
2940
if ! ip link show "$IFACE" >/dev/null 2>&1; then
3041
ip tuntap add "$IFACE" mode tap user "$TAP_USER"
3142
fi

‎soc_firmware/tests/README.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,25 @@ are assembled at runtime with a variable number of resources, which a
6565
descriptor-based encoder cannot express. `nmos_json.c` likewise keeps
6666
its own DOM — see its header comment.
6767

68+
## QEMU networking
69+
70+
A suite that sets `CONFIG_NETWORKING=y` **must** also set
71+
`CONFIG_NET_QEMU_USER=y`. The `NET_QEMU_NETWORKING` choice has no "none"
72+
option, and on `qemu_riscv64` neither SLIP nor PPP is available, so it
73+
falls through to `NET_QEMU_ETHERNET` — which launches QEMU with
74+
`-netdev tap,...,ifname=zeth` and a hard dependency on a host TAP
75+
interface. Any machine without `zeth` (every CI runner: the workflow
76+
only creates it later, for the NMOS step) makes QEMU exit within
77+
milliseconds, and twister reports the run as `unexpected eof` with no
78+
further explanation. `NET_QEMU_USER` selects QEMU's built-in SLIRP NAT
79+
instead, which needs nothing from the host.
80+
81+
Two traps make this easy to miss: a developer machine that has run the
82+
QEMU image by hand has a leftover `zeth` and passes, and the reason QEMU
83+
died is written to `qemu.stderr` in the build directory, which is *not*
84+
`handler.log` (that one only carries the guest's serial console). The CI
85+
workflow dumps both on failure.
86+
6887
## Notes for new suites
6988

7089
* Add a directory with `CMakeLists.txt`, `prj.conf`, `testcase.yaml` and

‎soc_firmware/tests/config/prj.conf‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,13 @@ CONFIG_LOG_MODE_IMMEDIATE=y
2323
# Zephyr JSON library: config persistence and the REST request bodies
2424
# are described as descriptor tables (config_json.c, webapi/).
2525
CONFIG_JSON_LIBRARY=y
26+
27+
# QEMU networking backend: SLIRP (user-mode NAT), not a host TAP device.
28+
# The NET_QEMU_NETWORKING choice has no "none" option, and on this board
29+
# it falls through to NET_QEMU_ETHERNET, which wires QEMU to a host TAP
30+
# named "zeth". On any machine without that interface — every CI runner,
31+
# since the TAP is only set up later for the NMOS step — QEMU exits
32+
# within milliseconds and twister reports the run as "unexpected eof"
33+
# with no further explanation. These suites never bring an interface up;
34+
# they only need the networking types and the address helpers.
35+
CONFIG_NET_QEMU_USER=y

‎soc_firmware/tests/fpga_hal/prj.conf‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,13 @@ CONFIG_FPGA_HAL_MOCK_RX_CHANNELS=16
2222

2323
CONFIG_MAIN_STACK_SIZE=4096
2424
CONFIG_ZTEST_STACK_SIZE=4096
25+
26+
# QEMU networking backend: SLIRP (user-mode NAT), not a host TAP device.
27+
# The NET_QEMU_NETWORKING choice has no "none" option, and on this board
28+
# it falls through to NET_QEMU_ETHERNET, which wires QEMU to a host TAP
29+
# named "zeth". On any machine without that interface — every CI runner,
30+
# since the TAP is only set up later for the NMOS step — QEMU exits
31+
# within milliseconds and twister reports the run as "unexpected eof"
32+
# with no further explanation. These suites never bring an interface up;
33+
# they only need the networking types and the address helpers.
34+
CONFIG_NET_QEMU_USER=y

‎soc_firmware/tests/parsers/prj.conf‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ CONFIG_ZTEST_STACK_SIZE=4096
1818

1919
# Request bodies are decoded with Zephyr's JSON library.
2020
CONFIG_JSON_LIBRARY=y
21+
22+
# QEMU networking backend: SLIRP (user-mode NAT), not a host TAP device.
23+
# The NET_QEMU_NETWORKING choice has no "none" option, and on this board
24+
# it falls through to NET_QEMU_ETHERNET, which wires QEMU to a host TAP
25+
# named "zeth". On any machine without that interface — every CI runner,
26+
# since the TAP is only set up later for the NMOS step — QEMU exits
27+
# within milliseconds and twister reports the run as "unexpected eof"
28+
# with no further explanation. These suites never bring an interface up;
29+
# they only need the networking types and the address helpers.
30+
CONFIG_NET_QEMU_USER=y

‎soc_firmware/tests/ptp_ctrl/prj.conf‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ CONFIG_MAIN_STACK_SIZE=4096
1818
CONFIG_ZTEST_STACK_SIZE=4096
1919
CONFIG_LOG=y
2020
CONFIG_LOG_MODE_IMMEDIATE=y
21+
22+
# QEMU networking backend: SLIRP (user-mode NAT), not a host TAP device.
23+
# The NET_QEMU_NETWORKING choice has no "none" option, and on this board
24+
# it falls through to NET_QEMU_ETHERNET, which wires QEMU to a host TAP
25+
# named "zeth". On any machine without that interface — every CI runner,
26+
# since the TAP is only set up later for the NMOS step — QEMU exits
27+
# within milliseconds and twister reports the run as "unexpected eof"
28+
# with no further explanation. These suites never bring an interface up;
29+
# they only need the networking types and the address helpers.
30+
CONFIG_NET_QEMU_USER=y

0 commit comments

Comments
 (0)