-
Notifications
You must be signed in to change notification settings - Fork 54
[ci] add coverage reporting workflow #271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,266 @@ | ||
| name: coverage | ||
| permissions: | ||
| contents: read | ||
| actions: write | ||
| on: | ||
| pull_request: | ||
| branches: [ "main" ] | ||
| workflow_dispatch: | ||
| inputs: | ||
| runs-on: | ||
| description: "Runner type" | ||
| type: choice | ||
| options: | ||
| - ubuntu-24.04-arm | ||
| - ubuntu-latest | ||
| - aliyun-ecs-x64 | ||
| default: ubuntu-latest | ||
| rebuildDiskCache: | ||
| description: "Rebuild disk cache" | ||
| type: boolean | ||
| default: false | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ inputs.runs-on || vars.COVERAGE_RUNS_ON || 'ubuntu-latest' }} | ||
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | ||
| jobs: | ||
| coverage-github: | ||
| outputs: | ||
| coverage-artifact-url: ${{ steps.upload_coverage_artifacts.outputs.artifact-url }} | ||
| if: ${{ (inputs.runs-on || vars.COVERAGE_RUNS_ON || 'ubuntu-latest') == 'ubuntu-latest' || (inputs.runs-on || vars.COVERAGE_RUNS_ON || 'ubuntu-latest') == 'ubuntu-24.04-arm' }} | ||
| name: coverage | ||
| runs-on: ${{ inputs.runs-on || vars.COVERAGE_RUNS_ON || 'ubuntu-latest' }} | ||
| container: | ||
| image: ghcr.io/alibaba/tair-kvcache-kvcm-dev:2026_02_13_12_03_24230b1 | ||
| volumes: | ||
| - /:/host_root/ | ||
| options: --privileged | ||
| steps: | ||
| # https://github.com/actions/runner-images/issues/2840 | ||
| - name: Free up disk space | ||
| run: | | ||
| echo "Disk space before cleanup:" | ||
| df -h | ||
| rm -rf /host_root/usr/share/dotnet | ||
| rm -rf /host_root/usr/local/lib/android | ||
| rm -rf /host_root/opt/ghc | ||
| echo "Disk space after cleanup:" | ||
| df -h | ||
|
|
||
| - &checkout_step | ||
| name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - &setup_bazel_step | ||
| name: setup_bazel | ||
| uses: bazel-contrib/setup-bazel@0.18.0 | ||
| with: | ||
| bazelisk-cache: true | ||
| disk-cache: ${{ runner.os }}-${{ github.workflow }} | ||
| repository-cache: true | ||
| cache-save: ${{ inputs.rebuildDiskCache || false }} | ||
|
|
||
| - &clean_disk_cache_step | ||
| name: clean_disk_cache | ||
| if: ${{ inputs.rebuildDiskCache || false }} | ||
| run: | | ||
| rm -rf ~/.cache/bazel-disk | ||
|
|
||
| - &install_coverage_tools_step | ||
| name: install_coverage_tools | ||
| run: | | ||
| set -e | ||
| set -x | ||
| if ! command -v genhtml >/dev/null 2>&1; then | ||
| if command -v apt-get >/dev/null 2>&1; then | ||
| apt-get update -qq | ||
| DEBIAN_FRONTEND=noninteractive apt-get install -y -qq lcov | ||
| elif command -v yum >/dev/null 2>&1; then | ||
| yum install -y -q lcov | ||
| else | ||
| echo "No supported package manager found for installing lcov/genhtml" >&2 | ||
| exit 1 | ||
| fi | ||
| fi | ||
| genhtml --version | ||
|
|
||
| - &bazel_coverage_step | ||
| name: bazel_coverage | ||
| env: | ||
| COVERAGE_BASE_REF: ${{ github.event.pull_request.base.sha || 'origin/main' }} | ||
| COVERAGE_XTRACE: "1" | ||
| run: | | ||
| set -e | ||
| tools/coverage/run_coverage.sh \ | ||
| --base-ref "${COVERAGE_BASE_REF:-origin/main}" \ | ||
| --head-ref HEAD \ | ||
| --output-dir coverage \ | ||
| --include-prefix kv_cache_manager/ \ | ||
| --jobs 8 \ | ||
| --local-test-jobs 8 \ | ||
| --test-timeout 900 \ | ||
| --test-output errors \ | ||
| --fetch-main \ | ||
| -- \ | ||
| //kv_cache_manager/... \ | ||
| //integration_test/... | ||
|
|
||
| - &delete_old_disk_cache_step | ||
| name: delete_old_disk_cache | ||
| if: ${{ inputs.rebuildDiskCache || false }} | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -x | ||
| ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') | ||
| curl -sLo /usr/local/bin/jq "https://github.com/jqlang/jq/releases/download/jq-1.8.1/jq-linux-${ARCH}" && chmod +x /usr/local/bin/jq | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Downloading Consider either:
🤖 Generated by Qoder |
||
| GH_VER=2.89.0 | ||
| curl -sL "https://github.com/cli/cli/releases/download/v${GH_VER}/gh_${GH_VER}_linux_${ARCH}.tar.gz" | tar xz --strip-components=1 -C /usr/local | ||
| RUNNER_ARCH_LOWER=$(echo "${{ runner.arch }}" | tr '[:upper:]' '[:lower:]') | ||
| DISK_CACHE_NAME="disk-${{ runner.os }}-${{ github.workflow }}" | ||
| gh cache list --repo ${{ github.repository }} --ref "${{ github.ref }}" --json id,key --limit 100 | \ | ||
| jq -r --arg arch "$RUNNER_ARCH_LOWER" --arg name "$DISK_CACHE_NAME" \ | ||
| '.[] | select((.key | contains($arch)) and (.key | contains($name))) | .id' | \ | ||
| xargs -I {} gh cache delete {} --repo ${{ github.repository }} || true | ||
|
|
||
| - &append_coverage_summary_step | ||
| name: Append coverage summary | ||
| if: ${{ always() && hashFiles('coverage/coverage-summary.md') != '' }} | ||
| run: | | ||
| cat coverage/coverage-summary.md >> "${GITHUB_STEP_SUMMARY}" | ||
| if [ -f coverage/html/index.html ]; then | ||
| { | ||
| echo "" | ||
| echo "HTML coverage report: \`coverage/html/index.html\` in the uploaded artifact." | ||
| } >> "${GITHUB_STEP_SUMMARY}" | ||
| fi | ||
|
|
||
| - &upload_coverage_artifacts_step | ||
| name: Upload coverage artifacts | ||
| id: upload_coverage_artifacts | ||
| uses: actions/upload-artifact@v6 | ||
|
wangxiyu191 marked this conversation as resolved.
|
||
| if: always() | ||
| with: | ||
| name: coverage-report | ||
| path: | | ||
| coverage/** | ||
| bazel-testlogs/**/*.xml | ||
| if-no-files-found: warn | ||
| overwrite: true | ||
|
|
||
| coverage-aliyun: | ||
| outputs: | ||
| coverage-artifact-url: ${{ steps.upload_coverage_artifacts.outputs.artifact-url }} | ||
| if: ${{ (inputs.runs-on || vars.COVERAGE_RUNS_ON || 'ubuntu-latest') == 'aliyun-ecs-x64' }} | ||
| name: coverage | ||
| runs-on: aliyun-ecs-x64 | ||
| container: | ||
| image: ghcr.io/alibaba/tair-kvcache-kvcm-dev:2026_02_13_12_03_24230b1 | ||
| options: --privileged | ||
| steps: | ||
| - *checkout_step | ||
| - *setup_bazel_step | ||
| - *clean_disk_cache_step | ||
| - *install_coverage_tools_step | ||
| - *bazel_coverage_step | ||
| - *delete_old_disk_cache_step | ||
| - *append_coverage_summary_step | ||
| - *upload_coverage_artifacts_step | ||
|
|
||
| comment-pr-coverage: | ||
| if: ${{ always() && github.event_name == 'pull_request' && (needs.coverage-github.outputs.coverage-artifact-url != '' || needs.coverage-aliyun.outputs.coverage-artifact-url != '') }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
🤖 Generated by Qoder |
||
| needs: | ||
| - coverage-github | ||
| - coverage-aliyun | ||
| name: comment_pr_coverage | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| actions: read | ||
| issues: write | ||
| steps: | ||
| - name: Download coverage artifact | ||
| uses: actions/download-artifact@v6 | ||
|
wangxiyu191 marked this conversation as resolved.
|
||
| continue-on-error: true | ||
| with: | ||
| name: coverage-report | ||
| path: coverage-artifact | ||
|
|
||
| - name: Comment PR coverage | ||
| uses: actions/github-script@v8 | ||
| env: | ||
| COVERAGE_ARTIFACT_URL: ${{ needs.coverage-github.outputs.coverage-artifact-url || needs.coverage-aliyun.outputs.coverage-artifact-url }} | ||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
|
|
||
| const marker = '<!-- tair-kvcache-coverage-report -->'; | ||
| const summaryPath = 'coverage-artifact/coverage/coverage-summary.md'; | ||
| if (!fs.existsSync(summaryPath)) { | ||
| core.warning(`Coverage summary not found at ${summaryPath}; skip coverage comment.`); | ||
| return; | ||
| } | ||
|
|
||
| let summary = fs.readFileSync(summaryPath, 'utf8').trim(); | ||
| const maxSummaryLength = 60000; | ||
| if (summary.length > maxSummaryLength) { | ||
| summary = `${summary.slice(0, maxSummaryLength)}\n\n_Comment truncated; see the uploaded artifact for the full report._`; | ||
| } | ||
|
|
||
| const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | ||
| const artifactUrl = process.env.COVERAGE_ARTIFACT_URL || ''; | ||
| const links = artifactUrl | ||
| ? `HTML report and raw LCOV: [coverage-report](${artifactUrl})` | ||
| : `Workflow run: [${context.runId}](${runUrl})`; | ||
| const body = [ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When 🤖 Generated by Qoder |
||
| marker, | ||
| summary, | ||
| '', | ||
| links, | ||
| `Workflow run: [${context.runId}](${runUrl})`, | ||
| ].join('\n'); | ||
|
|
||
| const issue_number = context.payload.pull_request?.number; | ||
| if (!issue_number) { | ||
| core.warning('No pull request number found; skip coverage comment.'); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const { owner, repo } = context.repo; | ||
| const comments = await github.paginate(github.rest.issues.listComments, { | ||
| owner, | ||
| repo, | ||
| issue_number, | ||
| per_page: 100, | ||
| }); | ||
| const existing = comments.find((comment) => comment.body?.includes(marker)); | ||
|
|
||
| if (existing) { | ||
| await github.rest.issues.updateComment({ | ||
| owner, | ||
| repo, | ||
| comment_id: existing.id, | ||
| body, | ||
| }); | ||
| } else { | ||
| await github.rest.issues.createComment({ | ||
| owner, | ||
| repo, | ||
| issue_number, | ||
| body, | ||
| }); | ||
| } | ||
| } catch (error) { | ||
| core.warning(`Failed to write PR coverage comment: ${error.message}`); | ||
| } | ||
|
|
||
| validate-runner-selection: | ||
| if: ${{ (inputs.runs-on || vars.COVERAGE_RUNS_ON || 'ubuntu-latest') != 'ubuntu-latest' && (inputs.runs-on || vars.COVERAGE_RUNS_ON || 'ubuntu-latest') != 'ubuntu-24.04-arm' && (inputs.runs-on || vars.COVERAGE_RUNS_ON || 'ubuntu-latest') != 'aliyun-ecs-x64' }} | ||
| name: validate_runner_selection | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: fail_invalid_runner_selection | ||
| run: | | ||
| echo "::error::Invalid runner selection. Set runs-on or COVERAGE_RUNS_ON to one of: ubuntu-latest, ubuntu-24.04-arm, aliyun-ecs-x64." | ||
| exit 1 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mounting the host root (
/:/host_root/) with--privilegedis necessary for the disk-space cleanup step, but it significantly widens the blast radius if any step in the job is compromised. Consider scoping the mount to only the directories actually cleaned (/usr/share/dotnet,/usr/local/lib/android,/opt/ghc) or moving the cleanup to a separate pre-job step that doesn't need it, so the main coverage steps run with reduced privileges.🤖 Generated by Qoder