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: 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
