name: Coverage Check
description: "Run FastDeploy Unit Tests and Coverage"

on:
  workflow_call:
    inputs:
      DOCKER_IMAGE:
        description: "Build Images"
        required: true
        type: string
        default: "ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleqa:cuda126-py310"
      FASTDEPLOY_ARCHIVE_URL:
        description: "URL of the compressed FastDeploy code archive."
        required: true
        type: string
      FASTDEPLOY_WHEEL_URL:
        description: "URL of the FastDeploy Wheel."
        required: true
        type: string
      CACHE_DIR:
        description: "Cache Dir Use"
        required: false
        type: string
        default: ""
      MODEL_CACHE_DIR:
        description: "Cache Dir Use"
        required: false
        type: string
        default: ""
    secrets:
      github-token:
        required: true

jobs:
  check_cov_skip:
    uses: ./.github/workflows/check-bypass.yml
    secrets:
      github-token: ${{ secrets.github-token }}
    with:
      workflow-name: coverage

  run_tests_with_coverage:
    runs-on: [self-hosted, GPU-h1z1-2Cards]
    timeout-minutes: 90
    needs: check_cov_skip
    if: needs.check_cov_skip.outputs.can-skip != 'true'
    outputs:
      diff_cov_file_url: ${{ steps.cov_upload.outputs.diff_cov_file_url }}
      unittest_failed_url: ${{ steps.cov_upload.outputs.unittest_failed_url }}
      diff_cov_result_json_url: ${{ steps.cov_upload.outputs.diff_cov_result_json_url }}
    steps:
      - name: Code Prepare
        shell: bash
        env:
          docker_image: ${{ inputs.DOCKER_IMAGE }}
          fd_archive_url: ${{ inputs.FASTDEPLOY_ARCHIVE_URL }}
        run: |
            set -x
            REPO="https://github.com/${{ github.repository }}.git"
            FULL_REPO="${{ github.repository }}"
            REPO_NAME="${FULL_REPO##*/}"
            BASE_BRANCH="${{ github.base_ref }}"
            docker pull ${docker_image}
            # Clean the repository directory before starting
            docker run --rm --net=host -v $(pwd):/workspace -w /workspace \
            -e "REPO_NAME=${REPO_NAME}" \
            ${docker_image} /bin/bash -c '
              if [ -d ${REPO_NAME} ]; then
                echo "Directory ${REPO_NAME} exists, removing it..."
                rm -rf ${REPO_NAME}*
              fi
            '

            wget -q ${fd_archive_url}
            tar -xf FastDeploy.tar.gz
            rm -rf FastDeploy.tar.gz
            cd FastDeploy
            git config --global user.name "FastDeployCI"
            git config --global user.email "fastdeploy_ci@example.com"
            git log -n 3 --oneline
      - name: Run FastDeploy Unit Tests and Coverage
        shell: bash
        env:
          docker_image: ${{ inputs.DOCKER_IMAGE }}
          fd_wheel_url: ${{ inputs.FASTDEPLOY_WHEEL_URL }}
          CACHE_DIR: ${{ inputs.CACHE_DIR }}
          BASE_REF: ${{ github.event.pull_request.base.ref }}
          MODEL_CACHE_DIR: ${{ inputs.MODEL_CACHE_DIR }}
          IS_PR: ${{ github.event_name == 'pull_request' }}
        run: |
          if [[ "$IS_PR" == "true" ]]; then
            echo "Running on PR"
          else
            echo "Not a PR"
          fi
          runner_name="${{ runner.name }}"
          CARD_ID=$(echo "${runner_name}" | awk -F'-' '{print $NF}')
          DEVICES=$(echo "$CARD_ID" | fold -w1 | paste -sd,)
          DEVICE_PORT=$(echo "$DEVICES" | cut -d',' -f1)

          FLASK_PORT=$((42068 + DEVICE_PORT * 100))
          FD_API_PORT=$((42088 + DEVICE_PORT * 100))
          FD_ENGINE_QUEUE_PORT=$((42058 + DEVICE_PORT * 100))
          FD_METRICS_PORT=$((42078 + DEVICE_PORT * 100))
          FD_CACHE_QUEUE_PORT=$((42098 + DEVICE_PORT * 100))
          echo "Test ENV Parameter:"
          echo "========================================================="
          echo "FLASK_PORT=${FLASK_PORT}"
          echo "FD_API_PORT=${FD_API_PORT}"
          echo "FD_ENGINE_QUEUE_PORT=${FD_ENGINE_QUEUE_PORT}"
          echo "FD_METRICS_PORT=${FD_METRICS_PORT}"
          echo "FD_CACHE_QUEUE_PORT=${FD_CACHE_QUEUE_PORT}"
          echo "DEVICES=${DEVICES}"
          echo "========================================================="

          CACHE_DIR="${CACHE_DIR:-$(dirname "$(dirname "${{ github.workspace }}")")}"
          echo "CACHE_DIR is set to ${CACHE_DIR}"
          if [ ! -f "${CACHE_DIR}/gitconfig" ]; then
            touch "${CACHE_DIR}/gitconfig"
          fi

          PORTS=($FLASK_PORT $FD_API_PORT $FD_ENGINE_QUEUE_PORT $FD_METRICS_PORT $FD_CACHE_QUEUE_PORT)
          LOG_FILE="./port_cleanup_$(date +%Y%m%d_%H%M%S).log"
          echo "==== LOG_FILE is ${LOG_FILE} ===="

          echo "==== PORT CLEAN BEFORE TASK RUN ====" | tee -a $LOG_FILE

          for port in "${PORTS[@]}"; do
              PIDS=$(lsof -t -i :$port || true)
              if [ -n "$PIDS" ]; then
                  echo "Port $port is occupied by PID(s): $PIDS" | tee -a $LOG_FILE
                  echo "$PIDS" | xargs -r kill -9
                  echo "Port $port cleared" | tee -a $LOG_FILE
              else
                  echo "Port $port is free" | tee -a $LOG_FILE
              fi
          done

          echo "==== PORT CLEAN COMPLETE ====" | tee -a $LOG_FILE

          echo "========================================================="
          echo "Ensuring no stale container named ${runner_name} ..."
          if [ "$(docker ps -a -q -f name=${runner_name})" ]; then
            echo "Removing stale container: ${runner_name}"
            docker rm -f ${runner_name} || true
          fi

          docker run --rm --net=host \
          --name ${runner_name} \
          --cap-add=SYS_PTRACE --shm-size=64G \
          -v $(pwd):/workspace -w /workspace \
          -v "${CACHE_DIR}/gitconfig:/etc/gitconfig:ro" \
          -v "${CACHE_DIR}/.cache:/root/.cache" \
          -v "${CACHE_DIR}/ConfigDir:/root/.config" \
          -v "${MODEL_CACHE_DIR}:/ModelData:ro" \
          -e "MODEL_PATH=/ModelData" \
          -e "FD_API_PORT=${FD_API_PORT}" \
          -e "FD_ENGINE_QUEUE_PORT=${FD_ENGINE_QUEUE_PORT}" \
          -e "FD_METRICS_PORT=${FD_METRICS_PORT}" \
          -e "FLASK_PORT=${FLASK_PORT}" \
          -e "FD_CACHE_QUEUE_PORT=${FD_CACHE_QUEUE_PORT}" \
          -e TZ="Asia/Shanghai" \
          -e "fd_wheel_url=${fd_wheel_url}" \
          -e "BASE_REF=${BASE_REF}" \
          -e "IS_PR=${IS_PR}" \
          --gpus "\"device=${DEVICES}\"" ${docker_image} /bin/bash -c '

          git config --global --add safe.directory /workspace/FastDeploy
          cd FastDeploy
          git diff origin/${BASE_REF}..HEAD --unified=0 > diff.txt
          python -m pip install --pre paddlepaddle-gpu -i https://www.paddlepaddle.org.cn/packages/nightly/cu126/
          pip config set global.extra-index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple

          python -m pip install -r scripts/unittest_requirement.txt
          python -m pip install ${fd_wheel_url}
          rm -rf fastdeploy
          # coverage subprocess use
          python -m pip install ${fd_wheel_url} --no-deps --target=/workspace/FastDeploy
          export PYTHONPATH=/workspace/FastDeploy/
          if [ -d "tests/plugins" ]; then
              cd tests/plugins
              python setup.py install
              cd ../..
          else
              echo "Warning: tests/plugins directory not found, skipping setup.py install"
          fi
          export COVERAGE_FILE=/workspace/FastDeploy/coveragedata/.coverage
          export COVERAGE_RCFILE=/workspace/FastDeploy/scripts/.coveragerc
          TEST_EXIT_CODE=0
          bash scripts/coverage_run.sh || TEST_EXIT_CODE=8
          echo "TEST_EXIT_CODE=${TEST_EXIT_CODE}" >> exit_code.env
          coverage combine coveragedata/ || echo "No data to combine"
          coverage report
          coverage xml -o python_coverage_all.xml
          COVERAGE_EXIT_CODE=0
          if [[ "$IS_PR" == "true" ]]; then
            diff-cover python_coverage_all.xml --diff-file=diff.txt --fail-under=80 --json-report diff_coverage.json || COVERAGE_EXIT_CODE=9
            python scripts/generate_diff_coverage_xml.py diff.txt python_coverage_all.xml
          else
            echo "Not a PR, skipping diff-cover"
          fi
          echo "COVERAGE_EXIT_CODE=${COVERAGE_EXIT_CODE}" >> exit_code.env
          '
          if [ -f FastDeploy/exit_code.env ]; then
            cat FastDeploy/exit_code.env >> $GITHUB_ENV
          fi
      - name: Upload unit resule and diff coverage to bos
        id: cov_upload
        shell: bash
        run: |
          cd FastDeploy
          commit_id=${{ github.event.pull_request.head.sha }}
          pr_num=${{ github.event.pull_request.number }}
          target_path=paddle-github-action/PR/FastDeploy/${pr_num}/${commit_id}/SM${compile_arch//,/_}
          wget  -q --no-proxy --no-check-certificate https://paddle-qa.bj.bcebos.com/CodeSync/develop/PaddlePaddle/PaddleTest/tools/bos_tools.py -O bos_tools.py
          push_file=$(realpath bos_tools.py)
          python -m pip install bce-python-sdk==0.9.29
          diff_cov_file="diff_coverage.xml"
          if [ -f ${diff_cov_file} ];then
            python ${push_file} ${diff_cov_file} ${target_path}/CoverageData
            target_path_stripped="${target_path#paddle-github-action/}"
            DIFF_COV_FILE_URL=https://paddle-github-action.bj.bcebos.com/${target_path_stripped}/CoverageData/${diff_cov_file}
            echo "diff_cov_file_url=${DIFF_COV_FILE_URL}" >> $GITHUB_OUTPUT
            echo "diff_cov_file_url=${DIFF_COV_FILE_URL}" >> $GITHUB_ENV
          fi
          diff_cov_result_json="diff_coverage.json"
          if [ -f ${diff_cov_result_json} ];then
            python ${push_file} ${diff_cov_result_json} ${target_path}/CoverageData
            target_path_stripped="${target_path#paddle-github-action/}"
            DIFF_COV_JSON_URL=https://paddle-github-action.bj.bcebos.com/${target_path_stripped}/CoverageData/${diff_cov_result_json}
            echo "diff_cov_result_json_url=${DIFF_COV_JSON_URL}" >> $GITHUB_OUTPUT
            echo "diff_cov_result_json_url=${DIFF_COV_JSON_URL}" >> $GITHUB_ENV
          fi
          unittest_result="failed_tests.log"
          if [ -s ${unittest_result} ];then
            python ${push_file} ${unittest_result} ${target_path}/UnitTestResult
            target_path_stripped="${target_path#paddle-github-action/}"
            UNIT_TEST_RESULT_URL=https://paddle-github-action.bj.bcebos.com/${target_path_stripped}/UnitTestResult/${unittest_result}
            echo "unittest_failed_url=${UNIT_TEST_RESULT_URL}" >> $GITHUB_OUTPUT
            echo "unittest_failed_url=${UNIT_TEST_RESULT_URL}" >> $GITHUB_ENV
          fi
      - name: Check Unit Test Success
        shell: bash
        run: |
          cd FastDeploy
          if [ "$TEST_EXIT_CODE" -eq 8 ]; then
            filename=$(basename "$unittest_failed_url")
            if [ -z "${unittest_failed_url}" ]; then
              echo "No diff unit failed file URL provided."
            else
              rm -rf "${filename}"
              wget -O ${filename} ${unittest_failed_url} || echo "Download unittest file failed, but continuing..."
            fi
            echo "Unit tests failed (exit code 8)"
            if [ -f "${filename}" ];then
              echo "Failed test cases:"
              cat "${filename}"
            fi
            exit "$TEST_EXIT_CODE"
          fi
          echo "All tests passed"

      - name: Verify Code Coverage Threshold (80%)
        if: ${{ github.event_name == 'pull_request' }}
        shell: bash
        run: |
          cd FastDeploy
          if [ "$COVERAGE_EXIT_CODE" -eq 9 ]; then
            echo "Coverage generation failed (exit code 9)"
            filename=$(basename "$diff_cov_result_json_url")
            if [ -z "${diff_cov_result_json_url}" ]; then
              echo "No diff cov result file URL provided."
            else
              rm -rf "${filename}"
              wget -O ${filename} ${diff_cov_result_json_url} || echo "Download cov json file failed, but continuing..."
            fi
            if [ -f "${filename}" ];then
              echo "Failed test cases:"
              if command -v jq >/dev/null 2>&1; then
                  jq . "${filename}"
              else
                  cat "${filename}"
              fi
            fi
            exit "$COVERAGE_EXIT_CODE"
          fi
          echo "coverage passed"
          exit 0

  diff_coverage_report:
    needs: run_tests_with_coverage
    if: always()
    runs-on: ubuntu-latest
    env:
      fd_archive_url: ${{ inputs.FASTDEPLOY_ARCHIVE_URL }}
    steps:
      - name: coverage diff file download
        shell: bash
        env:
          diff_cov_file_url: ${{ needs.run_tests_with_coverage.outputs.diff_cov_file_url }}
        run: |
          wget ${fd_archive_url}
          tar -xf FastDeploy.tar.gz
          cd FastDeploy
          if [ -z "${diff_cov_file_url}" ]; then
            echo "No diff coverage file URL provided."
            exit 0
          fi
          wget "${diff_cov_file_url}" -O ./diff_coverage.xml || echo "Download cov file failed, but continuing..."
      - name: Upload diff coverage report
        if: ${{ needs.run_tests_with_coverage.outputs.diff_cov_file_url != null && needs.run_tests_with_coverage.outputs.diff_cov_file_url != '' }}
        uses: codecov/codecov-action@v5
        with:
          files: ./FastDeploy/diff_coverage.xml
          name: python diff coverage
          verbose: true
          disable_search: true
          commit_parent: false
          flags: diff
