name: Firmware QEMU Tests (ADR-061) on: push: paths: - 'firmware/**' - 'scripts/qemu-esp32s3-test.sh' - 'scripts/validate_qemu_output.py' - 'scripts/generate_nvs_matrix.py' - '.github/workflows/firmware-qemu.yml' pull_request: paths: - 'firmware/**' - 'scripts/qemu-esp32s3-test.sh' - 'scripts/validate_qemu_output.py' - 'scripts/generate_nvs_matrix.py' - '.github/workflows/firmware-qemu.yml' env: IDF_VERSION: "v5.4" QEMU_REPO: "https://github.com/espressif/qemu.git" QEMU_BRANCH: "esp-develop" jobs: build-qemu: name: Build Espressif QEMU runs-on: ubuntu-latest steps: - name: Cache QEMU build id: cache-qemu uses: actions/cache@v4 with: path: /opt/qemu-esp32 key: qemu-esp32s3-${{ env.QEMU_BRANCH }}-v2 - name: Install QEMU build dependencies if: steps.cache-qemu.outputs.cache-hit != 'true' run: | sudo apt-get update sudo apt-get install -y \ git build-essential ninja-build pkg-config \ libglib2.0-dev libpixman-1-dev libslirp-dev \ python3 python3-venv - name: Clone and build Espressif QEMU if: steps.cache-qemu.outputs.cache-hit != 'true' run: | git clone --depth 1 -b "$QEMU_BRANCH" "$QEMU_REPO" /tmp/qemu-esp cd /tmp/qemu-esp mkdir build && cd build ../configure \ --target-list=xtensa-softmmu \ --prefix=/opt/qemu-esp32 \ --enable-slirp \ --disable-werror ninja -j$(nproc) ninja install - name: Verify QEMU binary run: | /opt/qemu-esp32/bin/qemu-system-xtensa --version echo "QEMU binary size: $(stat -c%s /opt/qemu-esp32/bin/qemu-system-xtensa) bytes" - name: Upload QEMU artifact uses: actions/upload-artifact@v4 with: name: qemu-esp32 path: /opt/qemu-esp32/ retention-days: 7 qemu-test: name: QEMU Test (${{ matrix.nvs_config }}) needs: build-qemu runs-on: ubuntu-latest container: image: espressif/idf:${{ env.IDF_VERSION }} strategy: fail-fast: false matrix: nvs_config: - default - full-adr060 - edge-tier0 - tdm-3node steps: - uses: actions/checkout@v4 - name: Download QEMU artifact uses: actions/download-artifact@v4 with: name: qemu-esp32 path: /opt/qemu-esp32 - name: Make QEMU executable run: chmod +x /opt/qemu-esp32/bin/qemu-system-xtensa - name: Verify QEMU works run: /opt/qemu-esp32/bin/qemu-system-xtensa --version - name: Install Python dependencies run: pip install esptool esp-idf-nvs-partition-gen - name: Set target ESP32-S3 working-directory: firmware/esp32-csi-node run: | . $IDF_PATH/export.sh idf.py set-target esp32s3 - name: Build firmware (mock CSI mode) working-directory: firmware/esp32-csi-node run: | . $IDF_PATH/export.sh idf.py \ -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.qemu" \ build - name: Generate NVS matrix run: | python3 scripts/generate_nvs_matrix.py \ --output-dir firmware/esp32-csi-node/build/nvs_matrix \ --only ${{ matrix.nvs_config }} - name: Create merged flash image working-directory: firmware/esp32-csi-node run: | . $IDF_PATH/export.sh # Determine merge_bin arguments OTA_ARGS="" if [ -f build/ota_data_initial.bin ]; then OTA_ARGS="0xf000 build/ota_data_initial.bin" fi python3 -m esptool --chip esp32s3 merge_bin \ -o build/qemu_flash.bin \ --flash_mode dio --flash_freq 80m --flash_size 8MB \ 0x0 build/bootloader/bootloader.bin \ 0x8000 build/partition_table/partition-table.bin \ $OTA_ARGS \ 0x20000 build/esp32-csi-node.bin echo "Flash image size: $(stat -c%s build/qemu_flash.bin) bytes" - name: Inject NVS partition if: matrix.nvs_config != 'default' working-directory: firmware/esp32-csi-node run: | NVS_BIN="build/nvs_matrix/nvs_${{ matrix.nvs_config }}.bin" if [ -f "$NVS_BIN" ]; then echo "Injecting NVS: $NVS_BIN ($(stat -c%s "$NVS_BIN") bytes)" dd if="$NVS_BIN" of=build/qemu_flash.bin \ bs=1 seek=$((0x9000)) conv=notrunc 2>/dev/null else echo "WARNING: NVS binary not found: $NVS_BIN" fi - name: Run QEMU smoke test env: QEMU_PATH: /opt/qemu-esp32/bin/qemu-system-xtensa QEMU_TIMEOUT: "60" run: | # Run QEMU with timeout; capture output echo "Starting QEMU (timeout: ${QEMU_TIMEOUT}s)..." timeout "$QEMU_TIMEOUT" "$QEMU_PATH" \ -machine esp32s3 \ -nographic \ -drive file=firmware/esp32-csi-node/build/qemu_flash.bin,if=mtd,format=raw \ -serial mon:stdio \ -no-reboot \ 2>&1 | tee firmware/esp32-csi-node/build/qemu_output.log || true echo "QEMU finished. Log size: $(wc -l < firmware/esp32-csi-node/build/qemu_output.log) lines" - name: Validate QEMU output run: | python3 scripts/validate_qemu_output.py \ firmware/esp32-csi-node/build/qemu_output.log - name: Upload test logs if: always() uses: actions/upload-artifact@v4 with: name: qemu-logs-${{ matrix.nvs_config }} path: | firmware/esp32-csi-node/build/qemu_output.log firmware/esp32-csi-node/build/nvs_matrix/ retention-days: 14