name: Run Tests on Native platform on: workflow_call: workflow_dispatch: permissions: {} env: LCOV_CAPTURE_FLAGS: --quiet --capture --include "${PWD}/src/*" --exclude '*/src/mesh/generated/*' --directory .pio/build/coverage/src --base-directory "${PWD}" jobs: simulator-tests: name: Native Simulator Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Setup native build id: base uses: ./.github/actions/setup-native - name: Install simulator dependencies run: pip install -U dotmap # We now run integration test before other build steps (to quickly see runtime failures) - name: Build for native/coverage run: platformio run -e coverage - name: Capture initial coverage information shell: bash run: | sudo apt-get install -y lcov lcov ${{ env.LCOV_CAPTURE_FLAGS }} --initial --output-file coverage_base.info sed -i -e "s#${PWD}#.#" coverage_base.info # Make paths relative. - name: Integration test # Cap the whole step: if the simulator ever fails to exit (e.g. the # exit_simulator admin path regresses again) the job must fail fast, # not run to GitHub's 6-hour limit. timeout-minutes: 5 run: | .pio/build/coverage/meshtasticd -s & PID=$! trap 'kill "$PID" 2>/dev/null || true' EXIT timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done" echo "Simulator started, launching python test..." python3 -c 'from meshtastic.test import testSimulator; testSimulator()' # The Python harness sends exit_simulator and exits; the simulator is # expected to terminate on its own. Give it a moment, then verify. # If it is still alive the exit handshake is broken — fail loudly and # do NOT fall through to `wait`, which would otherwise block until the # job's hard timeout. for i in $(seq 1 10); do kill -0 "$PID" 2>/dev/null || break sleep 1 done if kill -0 "$PID" 2>/dev/null; then echo "::error title=Simulator did not exit::meshtasticd ignored exit_simulator and is still running after the integration test. The exit_simulator admin path is broken (see AdminModule::handleReceivedProtobuf, ARCH_PORTDUINO bypass). Killing it to avoid a 6-hour CI overrun." kill -9 "$PID" 2>/dev/null || true wait "$PID" 2>/dev/null || true exit 1 fi wait "$PID" 2>/dev/null || true - name: Capture coverage information if: always() # run this step even if previous step failed run: | lcov ${{ env.LCOV_CAPTURE_FLAGS }} --test-name integration --output-file coverage_integration.info sed -i -e "s#${PWD}#.#" coverage_integration.info # Make paths relative. - name: Get release version string if: always() # run this step even if previous step failed run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Save coverage information uses: actions/upload-artifact@v7 if: always() # run this step even if previous step failed with: name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }} overwrite: true path: ./coverage_*.info platformio-tests: name: Native PlatformIO Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Setup native build id: base uses: ./.github/actions/setup-native - name: Get release version string run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version # Disable (comment-out) BUILD_EPOCH. It causes a full rebuild between tests and resets the # coverage information each time. - name: Disable BUILD_EPOCH run: sed -i 's/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini - name: PlatformIO Tests run: | set -o pipefail # Filter out SKIPPED summary rows for hardware variants that can't run on the # native host. They flood the log and make it harder to spot real failures. # The JUnit XML is written directly to testreport.xml before the pipe, so # the test artifact is unaffected. platformio test -e coverage -v --junit-output-path testreport.xml 2>&1 | grep -v "[[:space:]]SKIPPED$" - name: Save test results if: always() # run this step even if previous step failed uses: actions/upload-artifact@v7 with: name: platformio-test-report-${{ steps.version.outputs.long }} overwrite: true path: ./testreport.xml - name: Capture coverage information if: always() # run this step even if previous step failed run: | sudo apt-get install -y lcov lcov ${{ env.LCOV_CAPTURE_FLAGS }} --test-name tests --output-file coverage_tests.info sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative. - name: Save coverage information uses: actions/upload-artifact@v7 if: always() # run this step even if previous step failed with: name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }} overwrite: true path: ./coverage_*.info generate-reports: name: Generate Test Reports runs-on: ubuntu-latest permissions: # Needed for dorny/test-reporter. contents: read actions: read checks: write needs: - simulator-tests - platformio-tests if: always() steps: - uses: actions/checkout@v6 - name: Get release version string run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Download test artifacts uses: actions/download-artifact@v8 with: name: platformio-test-report-${{ steps.version.outputs.long }} merge-multiple: true - name: Drop no-status testsuites from the report # PlatformIO emits a self-closing row for every test_* dir # crossed with every hardware variant it cannot run on the native host (~4900 rows). # They carry no pass/fail/skip status and bury the suites that actually ran. Strip # them so the Test Report lists only suites with a real status. Only the copy the # reporter renders is trimmed; the uploaded artifact keeps the full XML. run: sed -i -E 's#]*tests="0"[^>]*/>##g' testreport.xml - name: Test Report uses: dorny/test-reporter@v3.0.0 with: name: PlatformIO Tests path: testreport.xml reporter: java-junit - name: Download coverage artifacts uses: actions/download-artifact@v8 with: pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }} path: code-coverage-report merge-multiple: true - name: Generate Code Coverage Report run: | sudo apt-get install -y lcov lcov --quiet --add-tracefile code-coverage-report/coverage_base.info --add-tracefile code-coverage-report/coverage_integration.info --add-tracefile code-coverage-report/coverage_tests.info --output-file code-coverage-report/coverage_src.info genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report - name: Save Code Coverage Report uses: actions/upload-artifact@v7 with: name: code-coverage-report-${{ steps.version.outputs.long }} path: code-coverage-report