name: Code Coverage with codecov

on:
  schedule:
    - cron: '25 06,18 * * 1-5'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }}
  cancel-in-progress: true

jobs:
  codecov:
    if: github.repository_owner == 'zephyrproject-rtos'
    runs-on:
      group: zephyr-runner-v2-linux-x64-4xlarge
    container:
      image: ghcr.io/zephyrproject-rtos/ci-repo-cache:v0.26.11.20240324
      options: '--entrypoint /bin/bash'
    strategy:
      fail-fast: false
      matrix:
        platform: ["mps2/an385", "native_sim", "qemu_x86", "unit_testing"]
        include:
          - platform: 'mps2/an385'
            normalized: 'mps2_an385'
          - platform: 'native_sim'
            normalized: 'native_sim'
          - platform: 'qemu_x86'
            normalized: 'qemu_x86'
          - platform: 'unit_testing'
            normalized: 'unit_testing'
    env:
      CCACHE_DIR: /node-cache/ccache-zephyr
      CCACHE_REMOTE_STORAGE: "redis://cache-*.keydb-cache.svc.cluster.local|shards=1,2,3"
      CCACHE_REMOTE_ONLY: "true"
      # `--specs` is ignored because ccache is unable to resovle the toolchain specs file path.
      CCACHE_IGNOREOPTIONS: '--specs=*'
    steps:
      - name: Apply container owner mismatch workaround
        run: |
          # FIXME: The owner UID of the GITHUB_WORKSPACE directory may not
          #        match the container user UID because of the way GitHub
          #        Actions runner is implemented. Remove this workaround when
          #        GitHub comes up with a fundamental fix for this problem.
          git config --global --add safe.directory ${GITHUB_WORKSPACE}

      - name: Print cloud service information
        run: |
          echo "ZEPHYR_RUNNER_CLOUD_PROVIDER = ${ZEPHYR_RUNNER_CLOUD_PROVIDER}"
          echo "ZEPHYR_RUNNER_CLOUD_NODE = ${ZEPHYR_RUNNER_CLOUD_NODE}"
          echo "ZEPHYR_RUNNER_CLOUD_POD = ${ZEPHYR_RUNNER_CLOUD_POD}"

      - name: Update PATH for west
        run: |
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Clone cached Zephyr repository
        continue-on-error: true
        run: |
          git clone --shared /repo-cache/zephyrproject/zephyr .
          git remote set-url origin ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}

      - name: checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: west setup
        run: |
          west init -l . || true
          west update 1> west.update.log || west update 1> west.update-2.log

      - name: Environment Setup
        run: |
          cmake --version
          gcc --version
          ls -la

          echo "ZEPHYR_SDK_INSTALL_DIR=/opt/toolchains/zephyr-sdk-$( cat SDK_VERSION )" >> $GITHUB_ENV

      - name: Set up ccache
        run: |
          mkdir -p ${CCACHE_DIR}
          ccache -M 10G
          ccache -p
          ccache -z -s -vv

      - name: Update BabbleSim to manifest revision
        run: |
          export BSIM_VERSION=$( west list bsim -f {revision} )
          echo "Manifest points to bsim sha $BSIM_VERSION"
          cd /opt/bsim_west/bsim
          git fetch -n origin ${BSIM_VERSION}
          git -c advice.detachedHead=false checkout ${BSIM_VERSION}
          west update
          make everything -s -j 8

      - name: Run Tests with Twister (Push)
        continue-on-error: true
        run: |
          export ZEPHYR_BASE=${PWD}
          export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
          mkdir -p coverage/reports
          pip3 install gcovr==6.0
          ./scripts/twister -E ${{matrix.normalized}}-testplan.json
          ls -la
          ./scripts/twister \
            -i --force-color -N -v --filter runnable -p ${{ matrix.platform }} --coverage \
            -T tests --coverage-tool gcovr -xCONFIG_TEST_EXTRA_STACK_SIZE=4096 -e nano \
            --timeout-multiplier 2

      - name: Print ccache stats
        if: always()
        run: |
          ccache -s -vv

      - name: Rename coverage files
        if: always()
        run: |
          mv twister-out/coverage.json coverage/reports/${{matrix.normalized}}.json

      - name: Upload Coverage Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: Coverage Data (Subset ${{ matrix.normalized }})
          path: |
            coverage/reports/${{ matrix.normalized }}.json
            ${{ matrix.normalized }}-testplan.json

  codecov-results:
    name: "Publish Coverage Results"
    needs: codecov
    runs-on: ubuntu-22.04
    # the codecov job might be skipped, we don't need to run this job then
    if: success() || failure()

    steps:
      - name: checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Download Artifacts
        uses: actions/download-artifact@v4
        with:
          path: coverage/reports

      - name: Move coverage files
        run: |
          ls -lRt  ./coverage/reports
          mv ./coverage/reports/*/*testplan.json .
          mv ./coverage/reports/*/coverage/reports/*.json ./coverage/reports
          ls -la ./coverage/reports

      - name: Generate list of coverage files
        id: get-coverage-files
        shell: cmake -P {0}
        run: |
          file(GLOB INPUT_FILES_LIST  "coverage/reports/*.json")
          set(MERGELIST "")
          set(FILELIST "")
          foreach(ITEM ${INPUT_FILES_LIST})
            get_filename_component(f ${ITEM} NAME)
            if(FILELIST STREQUAL "")
              set(FILELIST "${f}")
            else()
              set(FILELIST "${FILELIST},${f}")
            endif()
          endforeach()
          foreach(ITEM ${INPUT_FILES_LIST})
            get_filename_component(f ${ITEM} NAME)
            if(MERGELIST STREQUAL "")
              set(MERGELIST "--add-tracefile ${f}")
            else()
              set(MERGELIST "${MERGELIST} -a ${f}")
            endif()
          endforeach()
          file(APPEND $ENV{GITHUB_OUTPUT} "mergefiles=${MERGELIST}\n")
          file(APPEND $ENV{GITHUB_OUTPUT} "covfiles=${FILELIST}\n")

      - name: Merge coverage files
        run: |
          pushd ./coverage/reports
          pip3 install gcovr==6.0
          gcovr ${{ steps.get-coverage-files.outputs.mergefiles }}  --merge-mode-functions=separate --json merged.json
          gcovr ${{ steps.get-coverage-files.outputs.mergefiles }} --merge-mode-functions=separate --cobertura merged.xml
          popd

      - name: Get current date
        id: run_date
        run: |
            echo "run_date=$(date --iso-8601=minutes)" >> "$GITHUB_OUTPUT"
            echo "run_date_short=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT"
            echo "run_date_year=$(date +'%Y')" >> "$GITHUB_OUTPUT"
            echo "run_date_month=$(date +'%m')" >> "$GITHUB_OUTPUT"

      - name: Generate Coverage Report
        if: always()
        run: |
          pip install xlsxwriter ijson
          python3 ./scripts/ci/coverage/coverage_analysis.py \
            -t native_sim-testplan.json \
            -m MAINTAINERS.yml \
            -c coverage/reports/merged.json \
            -o coverage-report-${{ steps.run_date.outputs.run_date_short }} \
            -f all
          cp coverage-report-* coverage/reports/

      - name: Upload Merged Coverage Results and Report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: Coverage Data and report
          path: |
            coverage/reports/merged.json
            coverage/reports/merged.xml
            coverage/reports/coverage-report-${{ steps.run_date.outputs.run_date_short }}.json
            coverage/reports/coverage-report-${{ steps.run_date.outputs.run_date_short }}.xlsx

      - name: Upload coverage to Codecov
        if: always()
        uses: codecov/codecov-action@v3
        with:
          directory: ./coverage/reports
          env_vars: OS,PYTHON
          fail_ci_if_error: false
          verbose: true
          files: merged.xml
