Files
uv-k5-v3-emulator/tools/run_tests.sh
mckero ae8b48c85a Run anywhere: no author paths left, and CI that proves it
tools/run_tests.sh defaults QEMU_SRC to a sibling of the checkout, which is where setup_qemu.sh puts it; the two defaults disagreed, so a fresh clone rebuilt nothing and reported a build that was not there. The interpreter list was also reading an empty $PY.

tools/test_bk4819_readback.sh was the last test with the author's paths, and the only one that could not run elsewhere. It now takes QEMU, GDB and ELF from the environment or PATH like the python tests, skips with a reason when one is missing, and says so on a platform whose QEMU cannot make the unix socket it uses.

tools/check_docs.py points UVK5_FW_DIR at a sibling and skips the file:line checks, with a message, when there is no firmware tree -- a fresh clone used to see thirteen failures it could do nothing about.

Added .github/workflows/unit.yml (the fast half of run_tests.sh on every push and PR), requirements-dev.txt for the one pip dependency, and a Dockerfile. Flask is not always installed, so test_webui now skips through setUpModule rather than erroring.
2026-10-01 15:12:45 +08:00

118 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Run every test, report what failed, and exit non-zero if anything did.
#
# This exists because the suite had grown to ten separate invocations that had to be
# remembered and pasted in the right order. That is how regressions slip through: it is
# too easy to run the two tests related to what you just changed and miss the one that
# broke.
#
# Emulator tests boot their own QEMU and take 20-30 s each, so a full run is a few
# minutes. Pass -q to run only the fast unit tests, which need no emulator at all and
# finish in about 15 s -- useful while iterating.
#
# The build is checked FIRST and a failure stops everything. ninja leaves the previous
# binary in place when it fails, so the tests would otherwise run happily against a
# stale build and report results for code that was never compiled. That has produced
# two rounds of meaningless output before now.
set -u
HERE=$(cd "$(dirname "$0")" && pwd)
SIM=$(dirname "$HERE")
# A sibling of this checkout by default, which is where tools/setup_qemu.sh puts it.
# The two defaults have to agree: they disagreed once, so a fresh clone rebuilt nothing
# and reported a build that was not there.
QEMU_SRC=${QEMU_SRC:-$SIM/../qemu-7.2}
# One interpreter name, resolved once. This script used to spell $PY on every
# line, which is a Windows problem (there is a python, not a python3) and a
# virtualenv problem alike -- and a runner that cannot start is indistinguishable
# from a runner that passes, which is the failure mode this script exists to avoid.
PY=${PYTHON:-}
if [ -z "$PY" ]; then
for candidate in python3 python; do
if command -v "$candidate" >/dev/null 2>&1; then PY=$candidate; break; fi
done
fi
[ -n "$PY" ] || { echo "no $PY or python on PATH; set PYTHON=/path/to/python" >&2; exit 1; }
QUICK=0
[ "${1:-}" = "-q" ] && QUICK=1
pass=0
fail=0
failed_names=""
run() {
local name="$1"; shift
printf '\n=== %s\n' "$name"
# Strip control characters. Some tests shell out to gdb, whose output can carry
# escape sequences and stray bytes; left alone they make the combined log a
# "binary file" as far as grep is concerned, which silently swallows the summary.
#
# PIPESTATUS, not $?, because $? here is sed's status and would report success
# for every failing test.
"$@" 2>&1 | tr -cd '\11\12\15\40-\176' | sed 's/^/ /'
if [ "${PIPESTATUS[0]}" = "0" ]; then
pass=$((pass + 1))
else
fail=$((fail + 1))
failed_names="$failed_names $name"
fi
}
# --- build ---------------------------------------------------------------
# Only when the model source differs from what the QEMU tree holds, so a plain test
# run does not pay for a rebuild it does not need.
if [ -d "$QEMU_SRC/build" ] && \
! cmp -s "$SIM/qemu/py32f071.c" "$QEMU_SRC/hw/arm/py32f071.c"; then
echo "=== build (model source changed)"
cp "$SIM/qemu/py32f071.c" "$QEMU_SRC/hw/arm/py32f071.c"
if (cd "$QEMU_SRC/build" && ninja qemu-system-arm 2>&1 | tail -20) \
| grep -qE 'FAILED|error:'; then
echo " BUILD FAILED -- stopping before any test runs"
echo " (a stale binary would otherwise be tested silently)"
exit 1
fi
echo " ok"
fi
# --- unit tests, no emulator --------------------------------------------
cd "$HERE"
# First, that this script itself reports failures. A runner that silently counts every
# test as passing is worse than no runner, because it gets trusted.
run "runner self-check" bash "$HERE/test_run_tests.sh"
run "docs match code" $PY "$HERE/check_docs.py"
run "unit: model helpers" $PY -m unittest discover -p 'test_uvk5*.py' -q
run "unit: web UI" $PY -m unittest test_webui -q
if [ "$QUICK" = "1" ]; then
printf '\n%d passed, %d failed (unit tests only)\n' "$pass" "$fail"
[ "$fail" = "0" ] || { echo "failed:$failed_names"; exit 1; }
exit 0
fi
# --- emulator tests -----------------------------------------------------
# Ordered cheapest first, so an obvious breakage surfaces without waiting for the
# whole run.
cd "$SIM"
run "keypad" $PY tools/keypad_test.py
run "BK4819 registers" $PY tools/test_bk4819.py
run "register readback" bash tools/test_bk4819_readback.sh
run "S-meter" $PY tools/test_smeter.py
run "PTT" $PY tools/test_ptt.py
run "scan" $PY tools/test_scan.py
run "audio path" $PY tools/test_audio_path.py
run "battery" $PY tools/test_battery.py
run "millis" $PY tools/test_millis.py
run "spectrum" $PY tools/test_spectrum.py
run "serial receive" $PY tools/test_serial_rx.py
run "slot over serial" $PY tools/test_slot_serial.py
run "flash persistence" $PY tools/test_flash_persist.py
run "frequency entry" $PY tools/test_freq_entry.py
printf '\n%d passed, %d failed\n' "$pass" "$fail"
if [ "$fail" != "0" ]; then
echo "failed:$failed_names"
exit 1
fi