ci: offline-тесты на Gitea runner (smoke/guardrails/gftest/cross-build)

Четыре workflow по образу и подобию runner-tests/examples, уровень C:
- 01-smoke — runner жив, какие инструменты/либы есть в образе
- 02-guardrails — grep-страж жёстких правил CLAUDE.md (rate 1920000,
  header_len=12 на TX и RX, только local:, static/ARM-флаги, нет тяжёлых
  зависимостей); логика в scripts/ci_guardrails.sh, гоняется и локально
- 03-gftest — make gftest: нативная (не кросс-) сборка group_fec.c
  с -DGF_SELFTEST, 774 400 кейсов GF(256)
- 04-cross-build — make check-env && make all под armhf; приёмка
  ARM-тулчейна проверяется реальной компиляцией + разбором e_machine
  из ELF-заголовка (od), а не command -v — на Alpine/musl наличие
  файла ничего не доказывает, если это glibc-бинарь без ld-linux

E2E по радио и деплой на платы сознательно вне CI — нужны две живые
Pluto-платы, это остаётся ручным чек-листом (scripts/test_file_link.sh).
This commit is contained in:
Maxim
2026-07-15 10:32:38 +03:00
parent 4bf1a6df7b
commit a4eab6296e
5 changed files with 334 additions and 0 deletions

65
scripts/ci_guardrails.sh Normal file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# ci_guardrails.sh — статические grep-проверки жёстких правил CLAUDE.md.
# Ловит регрессии до кросс-сборки и до платы: без тулчейна, без $XARM.
# Запуск: scripts/ci_guardrails.sh (локально или из .gitea/workflows/02-guardrails.yml)
set -euo pipefail
cd "$(dirname "$0")/.."
pass=0
fail=0
check() {
description="$1"
if eval "$2" >/dev/null 2>&1; then
printf ' [OK] %s\n' "$description"
pass=$((pass + 1))
else
printf ' [FAIL] %s\n' "$description"
fail=$((fail + 1))
fi
}
echo "== Правило #1: язык C, без Python/GNU Radio в тракте =="
check "нет .py в src/" '[ -z "$(find src -name "*.py")" ]'
echo ""
echo "== Правило #2: libiio только local: в тракте =="
check "нет буквального \"ip: в src/*.c" '! grep -rn "\"ip:" src/*.c'
check "TX_URI_DEFAULT = local:" 'grep -q "TX_URI_DEFAULT.*\"local:\"" src/common.h'
check "RX_URI_DEFAULT = local:" 'grep -q "RX_URI_DEFAULT.*\"local:\"" src/common.h'
echo ""
echo "== Правило #3: статическая линковка, ARM-флаги (makefile) =="
check "-static" 'grep -q -- "-static" makefile'
check "-mcpu=cortex-a9" 'grep -q -- "-mcpu=cortex-a9" makefile'
check "-mfpu=neon" 'grep -q -- "-mfpu=neon" makefile'
check "-mfloat-abi=hard" 'grep -q -- "-mfloat-abi=hard" makefile'
check "-ffast-math" 'grep -q -- "-ffast-math" makefile'
echo ""
echo "== Правило #4: нет тяжёлых зависимостей =="
check "нет cjson/curl/ffmpeg/libav в src/" \
'! grep -rniE "cjson|curl/curl\.h|libav|ffmpeg" src/*.c src/*.h'
echo ""
echo "== Правило #5: sample rate 1920000, никогда 3840000 =="
check "DEFAULT_RATE = 1920000 в common.h" 'grep -q "DEFAULT_RATE.*1920000" src/common.h'
check "нет 3840000 нигде в src/" '! grep -rn "3840000" src/'
echo ""
echo "== Формат кадра: header_len=12 на обеих сторонах (техдолг #1) =="
check "HDR_SIZE = 12 в common.h" 'grep -q "define HDR_SIZE *12" src/common.h'
check "ofdmflexframegen_set_header_len(fg, HDR_SIZE) в transmitter.c" \
'grep -q "ofdmflexframegen_set_header_len(fg, HDR_SIZE)" src/transmitter.c'
check "ofdmflexframesync_set_header_len(fs, HDR_SIZE) в receiver.c" \
'grep -q "ofdmflexframesync_set_header_len(fs, HDR_SIZE)" src/receiver.c'
check "сигнатура заголовка 0xF0/0xAA в common.h" \
'grep -q "HDR_SIGNATURE_0 *0xF0" src/common.h'
check "сигнатура заголовка 0xF0/0xAA (байт 1) в common.h" \
'grep -q "HDR_SIGNATURE_1 *0xAA" src/common.h'
echo ""
echo "== Итог =="
printf 'Пройдено: %d, Провалено: %d\n' "$pass" "$fail"
[ "$fail" -eq 0 ]