From 6b161eab34593a9e88c39aaa0d9b84202fdfff19 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Fri, 10 Apr 2026 23:47:25 -0300 Subject: [PATCH] feat: Enhance CI workflows for backend unit and e2e tests; add environment setup and skip timing-sensitive tests in CI --- .github/workflows/backend-e2e-tests.yml | 128 +++++++++++++++++++++++ .github/workflows/backend-unit-tests.yml | 13 ++- test/backend/e2e/README.md | 61 ++++++++--- test/backend/unit/test_dht22_worker.py | 4 + 4 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/backend-e2e-tests.yml diff --git a/.github/workflows/backend-e2e-tests.yml b/.github/workflows/backend-e2e-tests.yml new file mode 100644 index 00000000..50d482fa --- /dev/null +++ b/.github/workflows/backend-e2e-tests.yml @@ -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 diff --git a/.github/workflows/backend-unit-tests.yml b/.github/workflows/backend-unit-tests.yml index a279ddfa..764e63ae 100644 --- a/.github/workflows/backend-unit-tests.yml +++ b/.github/workflows/backend-unit-tests.yml @@ -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 diff --git a/test/backend/e2e/README.md b/test/backend/e2e/README.md index 19e2ca8c..cdc0a940 100644 --- a/test/backend/e2e/README.md +++ b/test/backend/e2e/README.md @@ -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 | diff --git a/test/backend/unit/test_dht22_worker.py b/test/backend/unit/test_dht22_worker.py index 0dd4471e..29505ca1 100644 --- a/test/backend/unit/test_dht22_worker.py +++ b/test/backend/unit/test_dht22_worker.py @@ -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()