feat: Enhance CI workflows for backend unit and e2e tests; add environment setup and skip timing-sensitive tests in CI

This commit is contained in:
David Montero Crespo 2026-04-10 23:47:25 -03:00
parent 7971743174
commit 6b161eab34
4 changed files with 189 additions and 17 deletions

128
.github/workflows/backend-e2e-tests.yml vendored Normal file
View File

@ -0,0 +1,128 @@
name: Backend E2E Tests
on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
QEMU_RELEASE_URL: https://github.com/davidmonterocrespo24/velxio/releases/download/qemu-prebuilt
QEMU_DIR: /opt/velxio-qemu
BACKEND_URL: http://localhost:8001
steps:
- name: Checkout repository
uses: actions/checkout@v4
# ── QEMU binaries + ROM files ──────────────────────────────────────────────
- name: Download QEMU binaries and ROM files
run: |
mkdir -p $QEMU_DIR
cd $QEMU_DIR
echo "Downloading QEMU shared libraries..."
curl -fSL -o libqemu-xtensa.so "$QEMU_RELEASE_URL/libqemu-xtensa-amd64.so"
curl -fSL -o libqemu-riscv32.so "$QEMU_RELEASE_URL/libqemu-riscv32-amd64.so"
echo "Downloading ROM files..."
curl -fSL -o esp32-v3-rom.bin "$QEMU_RELEASE_URL/esp32-v3-rom.bin"
curl -fSL -o esp32-v3-rom-app.bin "$QEMU_RELEASE_URL/esp32-v3-rom-app.bin"
curl -fSL -o esp32c3-rom.bin "$QEMU_RELEASE_URL/esp32c3-rom.bin"
chmod +x libqemu-xtensa.so libqemu-riscv32.so
ls -lh .
# ── arduino-cli + ESP32 core ───────────────────────────────────────────────
- name: Install arduino-cli
run: |
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh \
| BINDIR=/usr/local/bin sh
- name: Cache arduino-cli cores and libraries
uses: actions/cache@v4
with:
path: ~/.arduino15
key: arduino-cores-esp32-${{ runner.os }}-v1
- name: Install ESP32 arduino core
run: |
arduino-cli config init --overwrite
arduino-cli config add board_manager.additional_urls \
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
arduino-cli core update-index
arduino-cli core install esp32:esp32
# ── Python + backend dependencies ─────────────────────────────────────────
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ hashFiles('backend/requirements.txt') }}
- name: Install backend dependencies
run: pip install -r backend/requirements.txt
# ── Node.js ───────────────────────────────────────────────────────────────
- name: Set up Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
# ── Start backend ─────────────────────────────────────────────────────────
- name: Start backend server
run: |
cd backend
QEMU_ESP32_LIB=$QEMU_DIR/libqemu-xtensa.so \
QEMU_RISCV32_LIB=$QEMU_DIR/libqemu-riscv32.so \
uvicorn app.main:app --port 8001 &
echo "Backend PID: $!"
env:
QEMU_ESP32_LIB: /opt/velxio-qemu/libqemu-xtensa.so
QEMU_RISCV32_LIB: /opt/velxio-qemu/libqemu-riscv32.so
- name: Wait for backend to be ready
run: |
echo "Waiting for backend on port 8001..."
for i in $(seq 1 30); do
if curl -sf http://localhost:8001/docs > /dev/null 2>&1; then
echo "Backend is up after ${i}s"
break
fi
sleep 1
done
curl -sf http://localhost:8001/docs > /dev/null || (echo "Backend failed to start" && exit 1)
# ── Run e2e tests ─────────────────────────────────────────────────────────
#
# These tests compile real firmware via the backend and run full simulations
# over WebSocket. Each test has a generous timeout because compilation +
# firmware boot takes 30-60 s.
#
# NOT run here (require Windows DLLs, not .so):
# test/backend/integration/ — uses libqemu-xtensa.dll (Windows only)
#
- name: Run DHT22 e2e test
run: node test/backend/e2e/test_dht22_simulation.mjs --timeout=60
env:
BACKEND_URL: http://localhost:8001
- name: Run HC-SR04 e2e test
run: node test/backend/e2e/test_hcsr04_simulation.mjs --timeout=75
env:
BACKEND_URL: http://localhost:8001
- name: Run MPU-6050 e2e test
run: node test/backend/e2e/test_mpu6050_simulation.mjs --timeout=60
env:
BACKEND_URL: http://localhost:8001

View File

@ -32,7 +32,16 @@ jobs:
- name: Install test dependencies
run: pip install pytest pytest-asyncio
# Run only unit tests (test/backend/unit/) — no QEMU or arduino-cli needed.
# Integration tests live in test/backend/integration/ and must be run manually.
# Run only UNIT tests (test/backend/unit/).
# These tests have zero external dependencies — no QEMU binary, no arduino-cli,
# no running backend server.
#
# NOT included here (must be run manually on Windows with QEMU DLLs):
# test/backend/integration/ — needs arduino-cli + QEMU binary
# test/backend/e2e/ — JS tests, need running backend + QEMU + arduino-cli
# Run e2e tests locally: scripts\run-e2e-tests.bat
- name: Run backend unit tests
# CI=true is set automatically by GitHub Actions.
# Tests decorated with @unittest.skipIf(CI=='true', ...) are skipped here
# (e.g. timing-sensitive tests that use busy-wait).
run: pytest test/backend/unit/ -v --tb=short

View File

@ -3,27 +3,58 @@
These tests compile real firmware via the backend API and run the full
simulation through a WebSocket, verifying sensor readings in the serial output.
## Prerequisites
## CI status
- Backend running on `http://localhost:8001`
- Node.js 18+
These tests **run in GitHub Actions** via `.github/workflows/backend-e2e-tests.yml`.
## Run
The CI workflow:
1. Downloads `libqemu-xtensa-amd64.so` and `libqemu-riscv32-amd64.so` from the
[qemu-prebuilt](https://github.com/davidmonterocrespo24/velxio/releases/tag/qemu-prebuilt) release.
2. Downloads ROM files: `esp32-v3-rom.bin`, `esp32-v3-rom-app.bin`, `esp32c3-rom.bin`.
3. Installs arduino-cli + `esp32:esp32` core.
4. Starts `uvicorn app.main:app --port 8001` with `QEMU_ESP32_LIB` and `QEMU_RISCV32_LIB` set.
5. Runs all three JS tests.
## Local prerequisites
1. Backend running with QEMU libs in PATH:
```bash
cd backend
QEMU_ESP32_LIB=/path/to/libqemu-xtensa.so \
QEMU_RISCV32_LIB=/path/to/libqemu-riscv32.so \
uvicorn app.main:app --reload --port 8001
```
On Windows the `.dll` files live in `backend/app/services/`.
2. Node.js 18+ installed.
## Run all e2e tests
Use the convenience script from the repo root:
```bash
# From repo root
node test/backend/e2e/test_dht22_simulation.mjs [--timeout=45]
node test/backend/e2e/test_hcsr04_simulation.mjs [--timeout=60]
node test/backend/e2e/test_mpu6050_simulation.mjs [--timeout=40]
# Windows
scripts\run-e2e-tests.bat
# Custom backend URL
node test/backend/e2e/test_dht22_simulation.mjs --backend=http://localhost:8001
# Or individually (from repo root):
node test/backend/e2e/test_dht22_simulation.mjs --timeout=60
node test/backend/e2e/test_hcsr04_simulation.mjs --timeout=75
node test/backend/e2e/test_mpu6050_simulation.mjs --timeout=60
```
## Tests
| File | Sensor | What it verifies |
|------|--------|-----------------|
| `test_dht22_simulation.mjs` | DHT22 | Temperature & humidity, sensor_update changes values |
| `test_hcsr04_simulation.mjs` | HC-SR04 | Distance at 10/40/100/200 cm, sensor_update changes distance |
| `test_mpu6050_simulation.mjs` | MPU-6050 | I2C accelerometer/gyroscope readings |
| File | Sensor | What it verifies | Typical runtime |
|------|--------|-----------------|-----------------|
| `test_dht22_simulation.mjs` | DHT22 (GPIO4) | Temperature & humidity readings, `sensor_update` changes values | ~50 s |
| `test_hcsr04_simulation.mjs` | HC-SR04 (GPIO18/19) | Distance at 10/40/100/200 cm, `sensor_update` changes distance | ~60 s |
| `test_mpu6050_simulation.mjs` | MPU-6050 (I2C) | Accelerometer/gyroscope I2C readings | ~40 s |
## Pass criteria
| Test | Passes when |
|------|-------------|
| DHT22 | First reading received + values change after `sensor_update` |
| HC-SR04 | ≥3 correct readings, ≥2 distances, miss rate ≤30% |
| MPU-6050 | I2C communication established, sensor data in serial output |

View File

@ -387,6 +387,10 @@ class TestDHT22ResponseWaveform(unittest.TestCase):
print(' Waveform sequence: CORRECT ✓')
@unittest.skipIf(
__import__('os').environ.get('CI') == 'true',
'Timing-sensitive test (uses busy-wait + sleep) — skipped in CI'
)
def test_response_data_matches_payload(self):
"""Verify that the HIGH pulse durations encode the correct bits."""
harness = DHT22SimulatorHarness()